using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using Codice.Client.Common; using Codice.Client.Common.EventTracking; using Codice.Client.Common.WebApi; using Codice.CM.Common; using PlasticGui; using PlasticGui.CloudDrive; using PlasticGui.CloudDrive.Workspaces; using PlasticGui.WorkspaceWindow.Items; using Unity.PlasticSCM.Editor.AssetUtils; using Unity.PlasticSCM.Editor.UI; using Unity.PlasticSCM.Editor.CloudDrive.CreateWorkspace; using Unity.PlasticSCM.Editor.CloudDrive.Workspaces.DirectoryContent; using Unity.PlasticSCM.Editor.CloudDrive.Workspaces.DragAndDrop; using Unity.PlasticSCM.Editor.CloudDrive.Workspaces.Tree; using Unity.PlasticSCM.Editor.UI.Errors; using Unity.PlasticSCM.Editor.UI.Progress; #if !UNITY_6000_3_OR_NEWER using SplitterGUILayout = Unity.PlasticSCM.Editor.UnityInternals.UnityEditor.SplitterGUILayout; using SplitterState = Unity.PlasticSCM.Editor.UnityInternals.UnityEditor.SplitterState; #endif namespace Unity.PlasticSCM.Editor.CloudDrive.Workspaces { internal interface IDragAndDrop { List GetDragSourcePaths(); void ClearDragTargetPath(); void SetDragTargetPath(string path); void ExecuteDropAction(string[] paths); } internal class CloudWorkspacesView : IRefreshableView, INotifyUpdatePaths, FillOrganizationsAndProjects.INotify, ICloudWorkspacesOperations, IDragAndDrop { internal const float MIN_WIDTH = TREE_VIEW_MIN_SIZE + SPLITTER_WIDTH + DIRECTORY_CONTENT_MIN_SIZE; internal CloudWorkspacesView( string proposedCloudServer, string proposedProject, WorkspaceInfo workspaceToSelect, IPlasticWebRestApi restApi, IPlasticAPI plasticApi, IProgressControls progressControls, EditorWindow parentWindow) { mRestApi = restApi; mPlasticApi = plasticApi; mProgressControls = progressControls; mParentWindow = parentWindow; mFillCloudWorkspacesView = new FillCloudWorkspaces(); InitializeProposedOrganizationProject(proposedCloudServer, proposedProject); BuildComponents(workspaceToSelect, parentWindow); mSplitterState = new SplitterState( SplitterSettings.Load( UnityConstants.CLOUD_WORKSPACES_SPLITTER_SETTINGS_NAME, new float[] { 0.25f, 0.75f }), new int[] { TREE_VIEW_MIN_SIZE, DIRECTORY_CONTENT_MIN_SIZE }, new int[] { 100000, 100000 } ); #if UNITY_6000_3_OR_NEWER UnityEditor.DragAndDrop.AddDropHandlerV2(ProjectBrowserDropHandler); #else UnityEditor.DragAndDrop.AddDropHandler(ProjectBrowserDropHandler); #endif Refresh(); } internal void SelectWorkspaceAndCopyPaths( string organization, string project, WorkspaceInfo wkInfo, string[] assetPaths, string dstRelativePath) { OnOrganizationSelected(organization); OnProjectSelected(project); mCloudWorkspacesTreeView.WorkspaceToSelect = wkInfo; mCloudWorkspacesTreeView.Reload(); TrackFeatureUseEvent.For( PlasticGui.Plastic.API.GetRepositorySpec(wkInfo), TrackFeatureUseEvent.Features.UnityPackage.AddAssetsToCloudDrive); CopyPaths( assetPaths, AssetsPath.GetFullPath.ForPath(string.Concat(wkInfo.ClientPath, dstRelativePath)), mCloudWorkspacesTreeView, wkInfo, mParentWindow, this, beforeStartingOperationDelegate: null, endOperationDelegate: null, shouldRefreshView: true); } internal bool IsOperationRunning() { return mCloudWorkspacesTreeView.IsOperationRunning(); } internal void AutoRefresh() { Refresh(); } internal void OnGUI(Rect rect, bool hasFocus) { Event e = Event.current; if (ProcessTabKeyActionIfNeeded(e, mCloudWorkspacesTreeView)) e.Use(); if (ProcessF5KeyActionIfNeeded(e, this)) e.Use(); UpdateDirectoryContentFocusIfNeeded( e, mCloudWorkspacesTreeView, mDirectoryContentPanel.Rect); DoContentArea( mCloudWorkspacesTreeView, mDirectoryContentPanel, mSplitterState, rect.width, rect.height - EditorStyles.toolbar.fixedHeight, hasFocus); DoToolbar( GetToolbarRect(rect), mCloudWorkspacesTreeView.Width, mOrganizations, mProjects, this); } internal void OnEnable() { #if UNITY_6000_3_OR_NEWER UnityEditor.DragAndDrop.AddDropHandlerV2(ProjectBrowserDropHandler); #else UnityEditor.DragAndDrop.AddDropHandler(ProjectBrowserDropHandler); #endif } internal void OnDisable() { #if UNITY_6000_3_OR_NEWER UnityEditor.DragAndDrop.RemoveDropHandlerV2(ProjectBrowserDropHandler); #else UnityEditor.DragAndDrop.RemoveDropHandler(ProjectBrowserDropHandler); #endif SplitterSettings.Save( mSplitterState, UnityConstants.CLOUD_WORKSPACES_SPLITTER_SETTINGS_NAME); } internal void Update(EditorWindow parentWindow) { mCloudWorkspacesTreeView.Update(parentWindow); } void IRefreshableView.Refresh() { Refresh(); } void INotifyUpdatePaths.RefreshViewSilently() { mFillCloudWorkspacesView.LoadExpandedTrees( mSelectedOrganization, mSelectedProject, mCloudWorkspacesTreeView, progressControls: null); } void INotifyUpdatePaths.ShowErrors(string wkName, List errors) { ErrorsDialog.ShowDialog( PlasticLocalization.Name.CopyFileErrorsTitle.GetString(), PlasticLocalization.Name.CopyFileErrorsMessage.GetString(wkName), errors, mParentWindow); } void FillOrganizationsAndProjects.INotify.OrganizationsRetrieved(List organizations) { mOrganizations = organizations; mSelectedOrganization = GetDefaultValue(mProposedOrganization, mOrganizations); if (mSelectedOrganization == null) return; OnOrganizationSelected(mSelectedOrganization); } void FillOrganizationsAndProjects.INotify.ProjectsRetrieved(List projects) { mProjects = projects; mProjects.Insert(0, PlasticLocalization.Name.AllProjects.GetString()); mProjects.Insert(1, string.Empty); mSelectedProject = GetDefaultValue(mProposedProject, mProjects); if (mSelectedProject == null) return; OnProjectSelected(mSelectedProject); } List IDragAndDrop.GetDragSourcePaths() { return mDirectoryContentPanel.ItemsGridView.GetSelectedItemsPaths(); } void IDragAndDrop.ClearDragTargetPath() { mDragTargetPath = null; } void IDragAndDrop.SetDragTargetPath(string path) { mDragTargetPath = path; } void IDragAndDrop.ExecuteDropAction(string[] paths) { WorkspaceInfo wkInfo = mCloudWorkspacesTreeView.GetSelectedWorkspaceInfo(); TrackFeatureUseEvent.For( PlasticGui.Plastic.API.GetRepositorySpec(wkInfo), TrackFeatureUseEvent.Features.UnityPackage.CloudDriveDragToWindow); CopyPaths( paths, mDragTargetPath, mCloudWorkspacesTreeView, wkInfo, mParentWindow, this, beforeStartingOperationDelegate: null, endOperationDelegate: null, shouldRefreshView: true); } void ICloudWorkspacesOperations.DeleteItems(List paths) { WorkspaceInfo wkInfo = mCloudWorkspacesTreeView.GetSelectedWorkspaceInfo(); ProgressControlsForViews workspaceProgressControls = mCloudWorkspacesTreeView.GetWorkspaceProgressControls(wkInfo); if (workspaceProgressControls.IsOperationRunning()) { GuiMessage.ShowInformation(PlasticLocalization.Name.OperationInProgress.GetString()); return; } DeletePathsOperation.DeleteRecursively( paths.ToArray(), Path.GetFileName(wkInfo.ClientPath), this, workspaceProgressControls); } void ICloudWorkspacesOperations.ImportInProject(string[] paths, string projectPath) { WorkspaceInfo wkInfo = mCloudWorkspacesTreeView.GetSelectedWorkspaceInfo(); TrackFeatureUseEvent.For( PlasticGui.Plastic.API.GetRepositorySpec(wkInfo), TrackFeatureUseEvent.Features.UnityPackage.CloudDriveImportToProject); CopyPaths( paths, AssetsPath.GetFullPath.ForPath(string.Join(ProjectPath.Get(), projectPath)), mCloudWorkspacesTreeView, wkInfo, mParentWindow, this, BeforeCopyPathsForProjectBrowserDropAction, AfterCopyPathsForProjectBrowserDropAction, shouldRefreshView: false); } void Refresh() { FillOrganizationsAndProjects.LoadOrganizations( mRestApi, mPlasticApi, mProgressControls, this); RefreshTree(); } void RefreshTree() { mFillCloudWorkspacesView.LoadExpandedTrees( mSelectedOrganization, mSelectedProject, mCloudWorkspacesTreeView, mProgressControls); } void OnTreeViewSelectionChanged() { List selectedNodes = CloudWorkspacesSelection. GetSelectedNodes(mCloudWorkspacesTreeView); if (selectedNodes.Count != 1) { mDirectoryContentPanel.CleanItems(); return; } mDirectoryContentPanel.UpdateItemsForDirectory(selectedNodes[0]); } DragAndDropVisualMode ProjectBrowserDropHandler( #if UNITY_6000_3_OR_NEWER EntityId dragEntityId, string dropUponPath, bool perform) #else int dragInstanceId, string dropUponPath, bool perform) #endif { if (perform == false) return DragAndDropVisualMode.None; if (UnityEditor.DragAndDrop.objectReferences.Length == 0 || UnityEditor.DragAndDrop.objectReferences[0] != mParentWindow) return DragAndDropVisualMode.None; WorkspaceInfo wkInfo = mCloudWorkspacesTreeView.GetSelectedWorkspaceInfo(); TrackFeatureUseEvent.For( PlasticGui.Plastic.API.GetRepositorySpec(wkInfo), TrackFeatureUseEvent.Features.UnityPackage.CloudDriveDragToProjectView); CopyPaths( UnityEditor.DragAndDrop.paths, AssetsPath.GetFullPath.ForPath(dropUponPath), mCloudWorkspacesTreeView, wkInfo, mParentWindow, this, BeforeCopyPathsForProjectBrowserDropAction, AfterCopyPathsForProjectBrowserDropAction, shouldRefreshView: false); return DragAndDropVisualMode.Copy; } void BeforeCopyPathsForProjectBrowserDropAction() { if (mCopyPathsForProjectBrowserDropCount == 0) RefreshAsset.BeforeLongAssetOperation(); mCopyPathsForProjectBrowserDropCount++; } void AfterCopyPathsForProjectBrowserDropAction() { if (mCopyPathsForProjectBrowserDropCount == 1) RefreshAsset.AfterLongAssetOperation(); mCopyPathsForProjectBrowserDropCount--; } void OnOrganizationSelected(object organization) { mSelectedOrganization = organization.ToString(); mProposedOrganization = mSelectedOrganization; mSelectedProject = null; PlasticGuiConfig.Get().Configuration.LastUsedCloudDriveServer = mSelectedOrganization; PlasticGuiConfig.Get().Save(); mProjects.Clear(); if (!CloudServer.IsUnityOrganization(mSelectedOrganization)) { OnProjectSelected(string.Empty); return; } FillOrganizationsAndProjects.LoadProjects( mSelectedOrganization, mPlasticApi, mProgressControls, this); } void OnProjectSelected(object project) { mSelectedProject = project.ToString(); mProposedProject = mSelectedProject; PlasticGuiConfig.Get().Configuration.LastUsedCloudDriveProject = mSelectedProject; PlasticGuiConfig.Get().Save(); RefreshTree(); } static void CopyPaths( string[] paths, string targetPath, CloudWorkspacesTreeView cloudWorkspacesTreeView, WorkspaceInfo wkInfo, EditorWindow parentWindow, INotifyUpdatePaths notify, Action beforeStartingOperationDelegate, Action endOperationDelegate, bool shouldRefreshView) { ProgressControlsForViews workspaceProgressControls = cloudWorkspacesTreeView.GetWorkspaceProgressControls(wkInfo); if (workspaceProgressControls.IsOperationRunning()) { GuiMessage.ShowInformation(PlasticLocalization.Name.OperationInProgress.GetString()); return; } CopyPathsOperation.CopyRecursivelyTo( paths, targetPath, Path.GetFileName(wkInfo.ClientPath), new AskUserForExistingFile(parentWindow), notify, workspaceProgressControls, beforeStartingOperationDelegate, endOperationDelegate, shouldRefreshView); } static bool ProcessTabKeyActionIfNeeded( Event e, CloudWorkspacesTreeView treeView) { if (!Keyboard.IsTabPressed(e)) return false; if (!treeView.HasFocus()) { treeView.SetFocus(); return true; } GUI.FocusControl(null); return true; } static bool ProcessF5KeyActionIfNeeded( Event e, IRefreshableView view) { if (!Keyboard.IsKeyPressed(e, KeyCode.F5)) return false; view.Refresh(); return true; } static void UpdateDirectoryContentFocusIfNeeded( Event e, CloudWorkspacesTreeView treeView, Rect directoryContentRect) { if (e.type != EventType.MouseDown) return; if (!directoryContentRect.Contains(e.mousePosition)) return; if (!treeView.HasFocus()) return; GUI.FocusControl(null); } void DoContentArea( CloudWorkspacesTreeView cloudWorkspacesTreeView, DirectoryContentPanel directoryContentPanel, SplitterState splitterState, float width, float height, bool hasFocus) { SplitterGUILayout.BeginHorizontalSplit(splitterState); Rect treeAreaRect = GetTreeRect(width, height); Rect directoryContentPanelRect = GetDirectoryContentPanelRect(width, height); SplitterGUILayout.EndHorizontalSplit(); DoTreeArea(cloudWorkspacesTreeView, treeAreaRect); directoryContentPanel.OnGUI( directoryContentPanelRect, hasFocus && !cloudWorkspacesTreeView.HasFocus()); EditorGUI.DrawRect( GetSplitterRect(directoryContentPanel.Rect), UnityStyles.Colors.BarBorder); } void DoTreeArea( CloudWorkspacesTreeView cloudWorkspacesTreeView, Rect treeAreaRect) { Rect newDriveButtonRect = new Rect( treeAreaRect.x + NEW_DRIVE_BUTTON_MARGIN, treeAreaRect.y + NEW_DRIVE_BUTTON_MARGIN, treeAreaRect.width - 2 * NEW_DRIVE_BUTTON_MARGIN, EditorStyles.miniButton.fixedHeight + NEW_DRIVE_BUTTON_MARGIN); if (GUI.Button( newDriveButtonRect, PlasticLocalization.Name.NewDriveButton.GetString(), EditorStyles.miniButton)) { WorkspaceCreationData wkCreationData = CreateWorkspaceDialog.CreateWorkspace( mSelectedOrganization, mSelectedProject, mRestApi, mPlasticApi, mParentWindow); CreateWorkspaceOperation.CreateWorkspace( wkCreationData, mPlasticApi, mProgressControls, (createdWorkspace) => { TrackFeatureUseEvent.For( PlasticGui.Plastic.API.GetRepositorySpec(createdWorkspace), TrackFeatureUseEvent.Features.UnityPackage.CloudDriveCreateWorkspaceFromNewDriveButton); OnOrganizationSelected(wkCreationData.CloudServer); mProposedProject = CloudProjectRepository.GetProjectName( wkCreationData.WorkspaceName); mCloudWorkspacesTreeView.WorkspaceToSelect = createdWorkspace; mCloudWorkspacesTreeView.Reload(); }); } Rect treeRect = new Rect( treeAreaRect.x, treeAreaRect.y + EditorStyles.miniButton.fixedHeight + 2 * NEW_DRIVE_BUTTON_MARGIN, treeAreaRect.width, treeAreaRect.height - EditorStyles.miniButton.fixedHeight - 2 * NEW_DRIVE_BUTTON_MARGIN); cloudWorkspacesTreeView.OnGUI(treeRect); } void DoToolbar( Rect rect, float combosAreaWidth, List organizations, List projects, IRefreshableView view) { GUILayout.BeginArea(rect); EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); DoCombosArea(combosAreaWidth, organizations, projects); GUILayout.FlexibleSpace(); DoRefreshButton(view); EditorGUILayout.EndHorizontal(); GUILayout.EndArea(); } void DoCombosArea( float combosAreaWidth, List organizations, List projects) { EditorGUILayout.BeginHorizontal(GUILayout.Width(combosAreaWidth)); DoOrganizationsCombo(organizations); DoProjectsCombo(projects); EditorGUILayout.EndHorizontal(); } void DoOrganizationsCombo(List organizations) { GUIContent content = new GUIContent( PlasticLocalization.Name.OrganizationsComboBoxLabel.GetString( mSelectedOrganization)); Rect rect = GUILayoutUtility.GetRect(content, EditorStyles.toolbarPopup); if (GUI.Button(rect, content, EditorStyles.toolbarPopup)) { GenericMenu menu = new GenericMenu(); foreach (string option in organizations) { menu.AddItem( new GUIContent(UnityMenuItem.EscapedText(option)), false, OnOrganizationSelected, option); } menu.DropDown(rect); } } void DoProjectsCombo(List projects) { if (projects.Count == 0) return; GUIContent content = new GUIContent( PlasticLocalization.Name.ProjectsComboBoxLabel.GetString(mSelectedProject)); Rect rect = GUILayoutUtility.GetRect(content, EditorStyles.toolbarPopup); if (GUI.Button(rect, content, EditorStyles.toolbarPopup)) { GenericMenu menu = new GenericMenu(); foreach (string option in projects) { menu.AddItem( new GUIContent(UnityMenuItem.EscapedText(option)), false, OnProjectSelected, option); } menu.DropDown(rect); } } static void DoRefreshButton(IRefreshableView view) { if (DrawToolbarButton( Images.GetRefreshIcon(), PlasticLocalization.Name.RefreshButton.GetString())) view.Refresh(); } static bool DrawToolbarButton(Texture icon, string tooltip) { return GUILayout.Button( new GUIContent(icon, tooltip), EditorStyles.toolbarButton, GUILayout.Width(26)); } static Rect GetToolbarRect(Rect rect) { return new Rect( rect.x, rect.y, rect.width, EditorStyles.toolbar.fixedHeight); } static Rect GetSplitterRect(Rect directoryContentRect) { return new Rect( directoryContentRect.x, directoryContentRect.y, SPLITTER_WIDTH, directoryContentRect.height); } static Rect GetTreeRect(float width, float height) { Rect result = GUILayoutUtility.GetRect( width, height, GUILayout.ExpandWidth(true)); result.y += EditorStyles.toolbar.fixedHeight; return result; } static Rect GetDirectoryContentPanelRect(float width, float height) { Rect result = GUILayoutUtility.GetRect( width, height, GUILayout.ExpandWidth(true)); result.y += EditorStyles.toolbar.fixedHeight; return result; } static string GetDefaultValue(string proposedValue, List values) { if (values.Count == 0) return null; if (!string.IsNullOrEmpty(proposedValue) && values.Contains(proposedValue)) return proposedValue; return values[0]; } void BuildComponents(WorkspaceInfo workspaceToSelect, EditorWindow parentWindow) { mCloudWorkspacesTreeView = new CloudWorkspacesTreeView( OnTreeViewSelectionChanged, mProgressControls, parentWindow); mCloudWorkspacesTreeView.WorkspaceToSelect = workspaceToSelect; mCloudWorkspacesTreeView.Reload(); mDirectoryContentPanel = new DirectoryContentPanel( mCloudWorkspacesTreeView, this, this, parentWindow); } void InitializeProposedOrganizationProject( string proposedCloudServer, string proposedProject) { if (!string.IsNullOrEmpty(proposedCloudServer) && !string.IsNullOrEmpty(proposedProject)) { mProposedOrganization = proposedCloudServer; mProposedProject = proposedProject; return; } string lastUsedServer = PlasticGuiConfig.Get().Configuration.LastUsedCloudDriveServer; string lastUsedProject = PlasticGuiConfig.Get().Configuration.LastUsedCloudDriveProject; if (!string.IsNullOrEmpty(lastUsedServer) && !string.IsNullOrEmpty(lastUsedProject)) { mProposedOrganization = lastUsedServer; mProposedProject = lastUsedProject; return; } GetProposedOrganizationProject.Values proposedOrganizationProject = GetProposedOrganizationProject.FromCloudProjectSettings(); mProposedOrganization = proposedOrganizationProject != null ? proposedOrganizationProject.Organization : GetDefaultServer.FromConfig(mRestApi); mProposedProject = proposedOrganizationProject != null ? proposedOrganizationProject.Project : Application.productName; } int mCopyPathsForProjectBrowserDropCount = 0; string mDragTargetPath; CloudWorkspacesTreeView mCloudWorkspacesTreeView; DirectoryContentPanel mDirectoryContentPanel; List mOrganizations = new List(); List mProjects = new List(); string mProposedOrganization; string mProposedProject; string mSelectedOrganization; string mSelectedProject; readonly SplitterState mSplitterState; readonly FillCloudWorkspaces mFillCloudWorkspacesView; readonly IPlasticWebRestApi mRestApi; readonly IPlasticAPI mPlasticApi; readonly IProgressControls mProgressControls; readonly EditorWindow mParentWindow; const float SPLITTER_WIDTH = 1; const float NEW_DRIVE_BUTTON_MARGIN = 5; const int TREE_VIEW_MIN_SIZE = 139; const int DIRECTORY_CONTENT_MIN_SIZE = 2 * (int)DrawItemsGridView.GRID_AREA_MARGIN + (int)DrawExpandedTreeNode.ICON_SIZE; } }