diff --git a/CHANGELOG.md b/CHANGELOG.md
index 240ed49..dc0e496 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [1.2.7] - 2018-08-07
+Toolbar drop-down will no longer show up when package is uninstalled.
+
## [1.2.6] - 2018-06-15
Fixed an issue where Collab's History window wouldn't load properly.
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
new file mode 100644
index 0000000..57808d5
--- /dev/null
+++ b/DEPENDENCIES.md
@@ -0,0 +1,9 @@
+
+
+
+ Unity.CollabProxy.Dependencies
+ 1.1.0-experimental
+ Rohit Garg
+ Dependencies for the CollabProxy package
+
+
diff --git a/DEPENDENCIES.md.meta b/DEPENDENCIES.md.meta
new file mode 100644
index 0000000..24e45c2
--- /dev/null
+++ b/DEPENDENCIES.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 470530e667ad4475786b28fa3187ce95
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/AssemblyInfo.cs b/Editor/AssemblyInfo.cs
new file mode 100644
index 0000000..d7266b6
--- /dev/null
+++ b/Editor/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using System.Runtime.CompilerServices;
+using UnityEngine;
+
+[assembly: InternalsVisibleTo("Unity.CollabProxy.EditorTests")]
diff --git a/Editor/AssemblyInfo.cs.meta b/Editor/AssemblyInfo.cs.meta
new file mode 100644
index 0000000..e384b31
--- /dev/null
+++ b/Editor/AssemblyInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d4ef26aa386b44923b61c9c4b505a67c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/bin.meta b/Editor/Collab.meta
similarity index 77%
rename from Editor/bin.meta
rename to Editor/Collab.meta
index faed551..694fc4e 100644
--- a/Editor/bin.meta
+++ b/Editor/Collab.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: d616cfe637ed94a66b96479ea905849b
+guid: c18cb9388313e4287ad5895ee735c47d
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Editor/Collab/Bootstrap.cs b/Editor/Collab/Bootstrap.cs
new file mode 100644
index 0000000..029ce1c
--- /dev/null
+++ b/Editor/Collab/Bootstrap.cs
@@ -0,0 +1,24 @@
+using UnityEditor;
+using UnityEditor.Collaboration;
+using UnityEngine;
+
+namespace CollabProxy.UI
+{
+ [InitializeOnLoad]
+ public class Bootstrap
+ {
+ private const float kCollabToolbarButtonWidth = 78.0f;
+
+ static Bootstrap()
+ {
+ Collab.ShowHistoryWindow = CollabHistoryWindow.ShowHistoryWindow;
+ Collab.ShowToolbarAtPosition = CollabToolbarWindow.ShowCenteredAtPosition;
+ Collab.IsToolbarVisible = CollabToolbarWindow.IsVisible;
+ Collab.CloseToolbar = CollabToolbarWindow.CloseToolbar;
+ Toolbar.AddSubToolbar(new CollabToolbarButton
+ {
+ Width = kCollabToolbarButtonWidth
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/Collab/Bootstrap.cs.meta b/Editor/Collab/Bootstrap.cs.meta
new file mode 100644
index 0000000..641d54b
--- /dev/null
+++ b/Editor/Collab/Bootstrap.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8aa8171e088f94069bbd1978a053f7dd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/CollabAnalytics.cs b/Editor/Collab/CollabAnalytics.cs
new file mode 100644
index 0000000..c7f90aa
--- /dev/null
+++ b/Editor/Collab/CollabAnalytics.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace UnityEditor.Collaboration
+{
+ internal static class CollabAnalytics
+ {
+ [Serializable]
+ private struct CollabUserActionAnalyticsEvent
+ {
+ public string category;
+ public string action;
+ }
+
+ public static void SendUserAction(string category, string action)
+ {
+ EditorAnalytics.SendCollabUserAction(new CollabUserActionAnalyticsEvent() { category = category, action = action });
+ }
+
+ public static readonly string historyCategoryString = "History";
+ };
+}
diff --git a/Editor/Collab/CollabAnalytics.cs.meta b/Editor/Collab/CollabAnalytics.cs.meta
new file mode 100644
index 0000000..2f46e9b
--- /dev/null
+++ b/Editor/Collab/CollabAnalytics.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f944311c8fff2479fa3ba741f6039fc8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/CollabHistoryWindow.cs b/Editor/Collab/CollabHistoryWindow.cs
new file mode 100644
index 0000000..ccc3e39
--- /dev/null
+++ b/Editor/Collab/CollabHistoryWindow.cs
@@ -0,0 +1,302 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEditor.Collaboration;
+using UnityEditor.Experimental.UIElements;
+using UnityEngine.Experimental.UIElements;
+using UnityEngine.Experimental.UIElements.StyleEnums;
+using UnityEngine;
+using UnityEditor.Connect;
+
+namespace UnityEditor
+{
+ internal class CollabHistoryWindow : EditorWindow, ICollabHistoryWindow
+ {
+ const string kWindowTitle = "Collab History";
+ const string kServiceUrl = "developer.cloud.unity3d.com";
+
+ [MenuItem("Window/Asset Management/Collab History", false, 1)]
+ public static void ShowHistoryWindow()
+ {
+ EditorWindow.GetWindow(kWindowTitle);
+ }
+
+ [MenuItem("Window/Asset Management/Collab History", true)]
+ public static bool ValidateShowHistoryWindow()
+ {
+ return Collab.instance.IsCollabEnabledForCurrentProject();
+ }
+
+ CollabHistoryPresenter m_Presenter;
+ Dictionary m_Views;
+ List m_HistoryItems = new List();
+ HistoryState m_State;
+ VisualElement m_Container;
+ PagedListView m_Pager;
+ ScrollView m_HistoryView;
+ int m_ItemsPerPage = 5;
+ string m_InProgressRev;
+ bool m_RevisionActionsEnabled;
+
+ public CollabHistoryWindow()
+ {
+ minSize = new Vector2(275, 50);
+ }
+
+ public void OnEnable()
+ {
+ SetupGUI();
+ name = "CollabHistory";
+
+ if (m_Presenter == null)
+ {
+ m_Presenter = new CollabHistoryPresenter(this, new CollabHistoryItemFactory(), new RevisionsService(Collab.instance, UnityConnect.instance));
+ }
+ m_Presenter.OnWindowEnabled();
+ }
+
+ public void OnDisable()
+ {
+ m_Presenter.OnWindowDisabled();
+ }
+
+ public bool revisionActionsEnabled
+ {
+ get { return m_RevisionActionsEnabled; }
+ set
+ {
+ if (m_RevisionActionsEnabled == value)
+ return;
+
+ m_RevisionActionsEnabled = value;
+ foreach (var historyItem in m_HistoryItems)
+ {
+ historyItem.RevisionActionsEnabled = value;
+ }
+ }
+ }
+
+ public void SetupGUI()
+ {
+ var root = this.GetRootVisualContainer();
+ root.AddStyleSheetPath("StyleSheets/CollabHistoryCommon.uss");
+ if (EditorGUIUtility.isProSkin)
+ {
+ root.AddStyleSheetPath("StyleSheets/CollabHistoryDark.uss");
+ }
+ else
+ {
+ root.AddStyleSheetPath("StyleSheets/CollabHistoryLight.uss");
+ }
+
+ m_Container = new VisualElement();
+ m_Container.StretchToParentSize();
+ root.Add(m_Container);
+
+ m_Pager = new PagedListView()
+ {
+ name = "PagedElement",
+ pageSize = m_ItemsPerPage
+ };
+
+ var errorView = new StatusView()
+ {
+ message = "An Error Occurred",
+ icon = EditorGUIUtility.LoadIconRequired("Collab.Warning") as Texture,
+ };
+
+ var noInternetView = new StatusView()
+ {
+ message = "No Internet Connection",
+ icon = EditorGUIUtility.LoadIconRequired("Collab.NoInternet") as Texture,
+ };
+
+ var maintenanceView = new StatusView()
+ {
+ message = "Maintenance",
+ };
+
+ var loginView = new StatusView()
+ {
+ message = "Sign in to access Collaborate",
+ buttonText = "Sign in...",
+ callback = SignInClick,
+ };
+
+ var noSeatView = new StatusView()
+ {
+ message = "Ask your project owner for access to Unity Teams",
+ buttonText = "Learn More",
+ callback = NoSeatClick,
+ };
+
+ var waitingView = new StatusView()
+ {
+ message = "Updating...",
+ };
+
+ m_HistoryView = new ScrollView() { name = "HistoryContainer", showHorizontal = false};
+ m_HistoryView.contentContainer.StretchToParentWidth();
+ m_HistoryView.Add(m_Pager);
+
+ m_Views = new Dictionary()
+ {
+ {HistoryState.Error, errorView},
+ {HistoryState.Offline, noInternetView},
+ {HistoryState.Maintenance, maintenanceView},
+ {HistoryState.LoggedOut, loginView},
+ {HistoryState.NoSeat, noSeatView},
+ {HistoryState.Waiting, waitingView},
+ {HistoryState.Ready, m_HistoryView}
+ };
+ }
+
+ public void UpdateState(HistoryState state, bool force)
+ {
+ if (state == m_State && !force)
+ return;
+
+ m_State = state;
+ switch (state)
+ {
+ case HistoryState.Ready:
+ UpdateHistoryView(m_Pager);
+ break;
+ case HistoryState.Disabled:
+ Close();
+ return;
+ }
+
+ m_Container.Clear();
+ m_Container.Add(m_Views[m_State]);
+ }
+
+ public void UpdateRevisions(IEnumerable datas, string tip, int totalRevisions, int currentPage)
+ {
+ var elements = new List();
+ var isFullDateObtained = false; // Has everything from this date been obtained?
+ m_HistoryItems.Clear();
+
+ if (datas != null)
+ {
+ DateTime currentDate = DateTime.MinValue;
+ foreach (var data in datas)
+ {
+ if (data.timeStamp.Date != currentDate.Date)
+ {
+ elements.Add(new CollabHistoryRevisionLine(data.timeStamp, isFullDateObtained));
+ currentDate = data.timeStamp;
+ }
+
+ var item = new CollabHistoryItem(data);
+ m_HistoryItems.Add(item);
+
+ var container = new VisualContainer();
+ container.style.flexDirection = FlexDirection.Row;
+ if (data.current)
+ {
+ isFullDateObtained = true;
+ container.AddToClassList("currentRevision");
+ container.AddToClassList("obtainedRevision");
+ }
+ else if (data.obtained)
+ {
+ container.AddToClassList("obtainedRevision");
+ }
+ else
+ {
+ container.AddToClassList("absentRevision");
+ }
+ // If we use the index as-is, the latest commit will become #1, but we want it to be last
+ container.Add(new CollabHistoryRevisionLine(data.index));
+ container.Add(item);
+ elements.Add(container);
+ }
+ }
+
+ m_HistoryView.scrollOffset = new Vector2(0, 0);
+ m_Pager.totalItems = totalRevisions;
+ m_Pager.curPage = currentPage;
+ m_Pager.items = elements;
+ }
+
+ public string inProgressRevision
+ {
+ get { return m_InProgressRev; }
+ set
+ {
+ m_InProgressRev = value;
+ foreach (var historyItem in m_HistoryItems)
+ {
+ historyItem.SetInProgressStatus(value);
+ }
+ }
+ }
+
+ public int itemsPerPage
+ {
+ set
+ {
+ if (m_ItemsPerPage == value)
+ return;
+ m_Pager.pageSize = m_ItemsPerPage;
+ }
+ }
+
+ public PageChangeAction OnPageChangeAction
+ {
+ set { m_Pager.OnPageChanged = value; }
+ }
+
+ public RevisionAction OnGoBackAction
+ {
+ set { CollabHistoryItem.s_OnGoBack = value; }
+ }
+
+ public RevisionAction OnUpdateAction
+ {
+ set { CollabHistoryItem.s_OnUpdate = value; }
+ }
+
+ public RevisionAction OnRestoreAction
+ {
+ set { CollabHistoryItem.s_OnRestore = value; }
+ }
+
+ public ShowBuildAction OnShowBuildAction
+ {
+ set { CollabHistoryItem.s_OnShowBuild = value; }
+ }
+
+ public Action OnShowServicesAction
+ {
+ set { CollabHistoryItem.s_OnShowServices = value; }
+ }
+
+ void UpdateHistoryView(VisualElement history)
+ {
+ }
+
+ void NoSeatClick()
+ {
+ var connection = UnityConnect.instance;
+ var env = connection.GetEnvironment();
+ // Map environment to url - prod is special
+ if (env == "production")
+ env = "";
+ else
+ env += "-";
+
+ var url = "https://" + env + kServiceUrl
+ + "/orgs/" + connection.GetOrganizationId()
+ + "/projects/" + connection.GetProjectName()
+ + "/unity-teams/";
+ Application.OpenURL(url);
+ }
+
+ void SignInClick()
+ {
+ UnityConnect.instance.ShowLogin();
+ }
+ }
+}
diff --git a/Editor/Collab/CollabHistoryWindow.cs.meta b/Editor/Collab/CollabHistoryWindow.cs.meta
new file mode 100644
index 0000000..74358d4
--- /dev/null
+++ b/Editor/Collab/CollabHistoryWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fed9dda667cab45d398d06402bba03f4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/CollabToolbarButton.cs b/Editor/Collab/CollabToolbarButton.cs
new file mode 100644
index 0000000..4422735
--- /dev/null
+++ b/Editor/Collab/CollabToolbarButton.cs
@@ -0,0 +1,254 @@
+using System;
+using UnityEditor.Collaboration;
+using UnityEditor.Connect;
+using UnityEditor.Web;
+using UnityEngine;
+
+namespace UnityEditor
+{
+ internal class CollabToolbarButton : SubToolbar, IDisposable
+ {
+ // Must match s_CollabIcon array
+ enum CollabToolbarState
+ {
+ NeedToEnableCollab,
+ UpToDate,
+ Conflict,
+ OperationError,
+ ServerHasChanges,
+ FilesToPush,
+ InProgress,
+ Disabled,
+ Offline
+ }
+
+ CollabToolbarState m_CollabToolbarState = CollabToolbarState.UpToDate;
+ static GUIContent[] s_CollabIcons;
+ const float kCollabButtonWidth = 78.0f;
+ ButtonWithAnimatedIconRotation m_CollabButton;
+ string m_DynamicTooltip;
+ static bool m_ShowCollabTooltip = false;
+
+ GUIContent currentCollabContent
+ {
+ get
+ {
+ GUIContent content = new GUIContent(s_CollabIcons[(int)m_CollabToolbarState]);
+ if (!m_ShowCollabTooltip)
+ {
+ content.tooltip = null;
+ }
+ else if (m_DynamicTooltip != "")
+ {
+ content.tooltip = m_DynamicTooltip;
+ }
+
+ if (Collab.instance.AreTestsRunning())
+ {
+ content.text = "CTF";
+ }
+
+ return content;
+ }
+ }
+
+ void InitializeToolIcons()
+ {
+ // Must match enum CollabToolbarState
+ s_CollabIcons = new GUIContent[]
+ {
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " You need to enable collab.", "CollabNew"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " You are up to date.", "Collab"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " Please fix your conflicts prior to publishing.", "CollabConflict"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " Last operation failed. Please retry later.", "CollabError"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " Please update, there are server changes.", "CollabPull"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " You have files to publish.", "CollabPush"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " Operation in progress.", "CollabProgress"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " Collab is disabled.", "CollabNew"),
+ EditorGUIUtility.TrTextContentWithIcon("Collab", " Please check your network connection.", "CollabNew")
+ };
+ }
+
+ public CollabToolbarButton()
+ {
+ InitializeToolIcons();
+ Collab.instance.StateChanged += OnCollabStateChanged;
+ UnityConnect.instance.StateChanged += OnUnityConnectStateChanged;
+ UnityConnect.instance.UserStateChanged += OnUnityConnectUserStateChanged;
+
+ if (m_CollabButton == null)
+ {
+ const int repaintsPerSecond = 20;
+ const float animSpeed = 500f;
+ const bool mouseDownButton = true;
+ m_CollabButton = new ButtonWithAnimatedIconRotation(() => (float)EditorApplication.timeSinceStartup * animSpeed, Toolbar.RepaintToolbar, repaintsPerSecond, mouseDownButton);
+ }
+ }
+
+ private void OnUnityConnectUserStateChanged(UserInfo state)
+ {
+ UpdateCollabToolbarState();
+ }
+
+ private void OnUnityConnectStateChanged(ConnectInfo state)
+ {
+ UpdateCollabToolbarState();
+ }
+
+ public override void OnGUI(Rect rect)
+ {
+ DoCollabDropDown(rect);
+ }
+
+ Rect GUIToScreenRect(Rect guiRect)
+ {
+ Vector2 screenPoint = GUIUtility.GUIToScreenPoint(new Vector2(guiRect.x, guiRect.y));
+ guiRect.x = screenPoint.x;
+ guiRect.y = screenPoint.y;
+ return guiRect;
+ }
+
+ void ShowPopup(Rect rect)
+ {
+ // window should be centered on the button
+ ReserveRight(kCollabButtonWidth / 2, ref rect);
+ ReserveBottom(5, ref rect);
+ // calculate screen rect before saving assets since it might open the AssetSaveDialog window
+ var screenRect = GUIToScreenRect(rect);
+ // save all the assets
+ AssetDatabase.SaveAssets();
+ if (Collab.ShowToolbarAtPosition != null && Collab.ShowToolbarAtPosition(screenRect))
+ {
+ GUIUtility.ExitGUI();
+ }
+ }
+
+ void DoCollabDropDown(Rect rect)
+ {
+ UpdateCollabToolbarState();
+ GUIStyle collabButtonStyle = "OffsetDropDown";
+ bool showPopup = Toolbar.requestShowCollabToolbar;
+ Toolbar.requestShowCollabToolbar = false;
+
+ bool enable = !EditorApplication.isPlaying;
+
+ using (new EditorGUI.DisabledScope(!enable))
+ {
+ bool animate = m_CollabToolbarState == CollabToolbarState.InProgress;
+
+ EditorGUIUtility.SetIconSize(new Vector2(12, 12));
+ if (m_CollabButton.OnGUI(rect, currentCollabContent, animate, collabButtonStyle))
+ {
+ showPopup = true;
+ }
+ EditorGUIUtility.SetIconSize(Vector2.zero);
+ }
+
+ if (m_CollabToolbarState == CollabToolbarState.Disabled)
+ return;
+
+ if (showPopup)
+ {
+ ShowPopup(rect);
+ }
+ }
+
+ public void OnCollabStateChanged(CollabInfo info)
+ {
+ UpdateCollabToolbarState();
+ }
+
+ public void UpdateCollabToolbarState()
+ {
+ var currentCollabState = CollabToolbarState.UpToDate;
+ bool networkAvailable = UnityConnect.instance.connectInfo.online && UnityConnect.instance.connectInfo.loggedIn;
+ m_DynamicTooltip = "";
+
+ if (UnityConnect.instance.isDisableCollabWindow)
+ {
+ currentCollabState = CollabToolbarState.Disabled;
+ }
+ else if (networkAvailable)
+ {
+ Collab collab = Collab.instance;
+ CollabInfo currentInfo = collab.collabInfo;
+ UnityErrorInfo errInfo;
+ bool error = false;
+ if (collab.GetError((UnityConnect.UnityErrorFilter.ByContext | UnityConnect.UnityErrorFilter.ByChild), out errInfo))
+ {
+ error = (errInfo.priority <= (int)UnityConnect.UnityErrorPriority.Error);
+ m_DynamicTooltip = errInfo.shortMsg;
+ }
+
+ if (!currentInfo.ready)
+ {
+ currentCollabState = CollabToolbarState.InProgress;
+ }
+ else if (error)
+ {
+ currentCollabState = CollabToolbarState.OperationError;
+ }
+ else if (currentInfo.inProgress)
+ {
+ currentCollabState = CollabToolbarState.InProgress;
+ }
+ else
+ {
+ bool collabEnable = Collab.instance.IsCollabEnabledForCurrentProject();
+
+ if (UnityConnect.instance.projectInfo.projectBound == false || !collabEnable)
+ {
+ currentCollabState = CollabToolbarState.NeedToEnableCollab;
+ }
+ else if (currentInfo.update)
+ {
+ currentCollabState = CollabToolbarState.ServerHasChanges;
+ }
+ else if (currentInfo.conflict)
+ {
+ currentCollabState = CollabToolbarState.Conflict;
+ }
+ else if (currentInfo.publish)
+ {
+ currentCollabState = CollabToolbarState.FilesToPush;
+ }
+ }
+ }
+ else
+ {
+ currentCollabState = CollabToolbarState.Offline;
+ }
+
+ if (Collab.IsToolbarVisible != null)
+ {
+ if (currentCollabState != m_CollabToolbarState ||
+ Collab.IsToolbarVisible() == m_ShowCollabTooltip)
+ {
+ m_CollabToolbarState = currentCollabState;
+ m_ShowCollabTooltip = !Collab.IsToolbarVisible();
+ Toolbar.RepaintToolbar();
+ }
+ }
+ }
+
+ void ReserveRight(float width, ref Rect pos)
+ {
+ pos.x += width;
+ }
+
+ void ReserveBottom(float height, ref Rect pos)
+ {
+ pos.y += height;
+ }
+
+ public void Dispose()
+ {
+ Collab.instance.StateChanged -= OnCollabStateChanged;
+ UnityConnect.instance.StateChanged -= OnUnityConnectStateChanged;
+ UnityConnect.instance.UserStateChanged -= OnUnityConnectUserStateChanged;
+
+ if (m_CollabButton != null)
+ m_CollabButton.Clear();
+ }
+ }
+} // namespace
\ No newline at end of file
diff --git a/Editor/Collab/CollabToolbarButton.cs.meta b/Editor/Collab/CollabToolbarButton.cs.meta
new file mode 100644
index 0000000..949d8db
--- /dev/null
+++ b/Editor/Collab/CollabToolbarButton.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 882f1a4147a284f028899b9c018e63eb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/CollabToolbarWindow.cs b/Editor/Collab/CollabToolbarWindow.cs
new file mode 100644
index 0000000..2793875
--- /dev/null
+++ b/Editor/Collab/CollabToolbarWindow.cs
@@ -0,0 +1,137 @@
+using UnityEngine;
+using UnityEditor.Collaboration;
+using UnityEditor.Web;
+using UnityEditor.Connect;
+
+namespace UnityEditor
+{
+ [InitializeOnLoad]
+ internal class WebViewStatic : ScriptableSingleton
+ {
+ [SerializeField]
+ WebView m_WebView;
+
+ static public WebView GetWebView()
+ {
+ return instance.m_WebView;
+ }
+
+ static public void SetWebView(WebView webView)
+ {
+ instance.m_WebView = webView;
+ }
+ }
+
+ [InitializeOnLoad]
+ internal class CollabToolbarWindow : WebViewEditorStaticWindow, IHasCustomMenu
+ {
+ internal override WebView webView
+ {
+ get {return WebViewStatic.GetWebView(); }
+ set {WebViewStatic.SetWebView(value); }
+ }
+
+ private const string kWindowName = "Unity Collab Toolbar";
+
+ private static long s_LastClosedTime;
+ private static CollabToolbarWindow s_CollabToolbarWindow;
+
+ public static bool s_ToolbarIsVisible = false;
+
+ const int kWindowWidth = 320;
+ const int kWindowHeight = 350;
+
+ public static void CloseToolbar()
+ {
+ foreach (CollabToolbarWindow window in Resources.FindObjectsOfTypeAll())
+ window.Close();
+ }
+
+ [MenuItem("Window/Asset Management/Collab Toolbar", false /*IsValidateFunction*/, 2, true /* IsInternalMenu */)]
+ public static CollabToolbarWindow ShowToolbarWindow()
+ {
+ //Create a new window if it does not exist
+ if (s_CollabToolbarWindow == null)
+ {
+ s_CollabToolbarWindow = GetWindow(false, kWindowName) as CollabToolbarWindow;
+ }
+
+ return s_CollabToolbarWindow;
+ }
+
+ [MenuItem("Window/Asset Management/Collab Toolbar", true /*IsValidateFunction*/)]
+ public static bool ValidateShowToolbarWindow()
+ {
+ return true;
+ }
+
+ public static bool IsVisible()
+ {
+ return s_ToolbarIsVisible;
+ }
+
+ public static bool ShowCenteredAtPosition(Rect buttonRect)
+ {
+ buttonRect.x -= kWindowWidth / 2;
+ // We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting playmode, we assume an increasing time when comparing time.
+ long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond;
+ bool justClosed = nowMilliSeconds < s_LastClosedTime + 50;
+ if (!justClosed)
+ {
+ // Method may have been triggered programmatically, without a user event to consume.
+ if (Event.current.type != EventType.Layout)
+ {
+ Event.current.Use();
+ }
+ if (s_CollabToolbarWindow == null)
+ s_CollabToolbarWindow = CreateInstance() as CollabToolbarWindow;
+ var windowSize = new Vector2(kWindowWidth, kWindowHeight);
+ s_CollabToolbarWindow.initialOpenUrl = "file:///" + EditorApplication.userJavascriptPackagesPath + "unityeditor-collab-toolbar/dist/index.html";
+ s_CollabToolbarWindow.Init();
+ s_CollabToolbarWindow.ShowAsDropDown(buttonRect, windowSize);
+ s_CollabToolbarWindow.OnFocus();
+ return true;
+ }
+ return false;
+ }
+
+ // Receives HTML title
+ public void OnReceiveTitle(string title)
+ {
+ titleContent.text = title;
+ }
+
+ public new void OnInitScripting()
+ {
+ base.OnInitScripting();
+ }
+
+ public override void OnEnable()
+ {
+ minSize = new Vector2(kWindowWidth, kWindowHeight);
+ maxSize = new Vector2(kWindowWidth, kWindowHeight);
+ initialOpenUrl = "file:///" + EditorApplication.userJavascriptPackagesPath + "unityeditor-collab-toolbar/dist/index.html";
+ base.OnEnable();
+ s_ToolbarIsVisible = true;
+ }
+
+ internal new void OnDisable()
+ {
+ s_LastClosedTime = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond;
+ if (s_CollabToolbarWindow)
+ {
+ s_ToolbarIsVisible = false;
+ NotifyVisibility(s_ToolbarIsVisible);
+ }
+ s_CollabToolbarWindow = null;
+
+ base.OnDisable();
+ }
+
+ public new void OnDestroy()
+ {
+ OnLostFocus();
+ base.OnDestroy();
+ }
+ }
+}
diff --git a/Editor/Collab/CollabToolbarWindow.cs.meta b/Editor/Collab/CollabToolbarWindow.cs.meta
new file mode 100644
index 0000000..b08bf2a
--- /dev/null
+++ b/Editor/Collab/CollabToolbarWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6f516f1ec21a54a59a92bf99db2d9535
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Presenters.meta b/Editor/Collab/Presenters.meta
new file mode 100644
index 0000000..9133153
--- /dev/null
+++ b/Editor/Collab/Presenters.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d437fe60bb34f45728664a5d930c1635
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Presenters/CollabHistoryPresenter.cs b/Editor/Collab/Presenters/CollabHistoryPresenter.cs
new file mode 100644
index 0000000..91d500b
--- /dev/null
+++ b/Editor/Collab/Presenters/CollabHistoryPresenter.cs
@@ -0,0 +1,228 @@
+using System.Collections.Generic;
+using UnityEditor.Connect;
+using UnityEditor.Web;
+
+namespace UnityEditor.Collaboration
+{
+ internal class CollabHistoryPresenter
+ {
+ public const int ItemsPerPage = 5;
+ ICollabHistoryWindow m_Window;
+ ICollabHistoryItemFactory m_Factory;
+ IRevisionsService m_Service;
+ ConnectInfo m_ConnectState;
+ CollabInfo m_CollabState;
+ bool m_IsCollabError;
+ int m_TotalRevisions;
+ int m_CurrentPage;
+ int m_RequestedPage;
+ bool m_FetchInProgress;
+
+ BuildAccess m_BuildAccess;
+ string m_ProgressRevision;
+ public bool BuildServiceEnabled {get; set; }
+
+ public CollabHistoryPresenter(ICollabHistoryWindow window, ICollabHistoryItemFactory factory, IRevisionsService service)
+ {
+ m_Window = window;
+ m_Factory = factory;
+ m_Service = service;
+ m_CurrentPage = 0;
+ m_BuildAccess = new BuildAccess();
+ m_Service.FetchRevisionsCallback += OnFetchRevisions;
+ }
+
+ public void OnWindowEnabled()
+ {
+ UnityConnect.instance.StateChanged += OnConnectStateChanged;
+ Collab.instance.StateChanged += OnCollabStateChanged;
+ Collab.instance.RevisionUpdated += OnCollabRevisionUpdated;
+ Collab.instance.JobsCompleted += OnCollabJobsCompleted;
+ Collab.instance.ErrorOccurred += OnCollabError;
+ Collab.instance.ErrorCleared += OnCollabErrorCleared;
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+ m_ConnectState = UnityConnect.instance.GetConnectInfo();
+ m_CollabState = Collab.instance.GetCollabInfo();
+
+ m_Window.revisionActionsEnabled = !EditorApplication.isPlayingOrWillChangePlaymode;
+
+ // Setup window callbacks
+ m_Window.OnPageChangeAction = OnUpdatePage;
+ m_Window.OnUpdateAction = OnUpdate;
+ m_Window.OnRestoreAction = OnRestore;
+ m_Window.OnGoBackAction = OnGoBack;
+ m_Window.OnShowBuildAction = ShowBuildForCommit;
+ m_Window.OnShowServicesAction = ShowServicePage;
+ m_Window.itemsPerPage = ItemsPerPage;
+
+ // Initialize data
+ UpdateBuildServiceStatus();
+ var state = RecalculateState();
+ // Only try to load the page if we're ready
+ if (state == HistoryState.Ready)
+ OnUpdatePage(m_CurrentPage);
+ m_Window.UpdateState(state, true);
+ }
+
+ public void OnWindowDisabled()
+ {
+ UnityConnect.instance.StateChanged -= OnConnectStateChanged;
+ Collab.instance.StateChanged -= OnCollabStateChanged;
+ Collab.instance.RevisionUpdated -= OnCollabRevisionUpdated;
+ Collab.instance.JobsCompleted -= OnCollabJobsCompleted;
+ EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
+ }
+
+ private void OnConnectStateChanged(ConnectInfo state)
+ {
+ m_ConnectState = state;
+
+ m_Window.UpdateState(RecalculateState(), false);
+ }
+
+ private void OnCollabStateChanged(CollabInfo state)
+ {
+ // Sometimes a collab state change will trigger even though everything is the same
+ if (m_CollabState.Equals(state))
+ return;
+
+ if (m_CollabState.tip != state.tip)
+ OnUpdatePage(m_CurrentPage);
+
+ m_CollabState = state;
+ m_Window.UpdateState(RecalculateState(), false);
+ if (state.inProgress)
+ {
+ m_Window.inProgressRevision = m_ProgressRevision;
+ }
+ else
+ {
+ m_Window.inProgressRevision = null;
+ }
+ }
+
+ private void OnCollabRevisionUpdated(CollabInfo state)
+ {
+ OnUpdatePage(m_CurrentPage);
+ }
+
+ private void OnCollabJobsCompleted(CollabInfo state)
+ {
+ m_ProgressRevision = null;
+ }
+
+ private void OnCollabError()
+ {
+ m_IsCollabError = true;
+ m_Window.UpdateState(RecalculateState(), false);
+ }
+
+ private void OnCollabErrorCleared()
+ {
+ m_IsCollabError = false;
+ m_FetchInProgress = true;
+ m_Service.GetRevisions(m_CurrentPage * ItemsPerPage, ItemsPerPage);
+ m_Window.UpdateState(RecalculateState(), false);
+ }
+
+ private void OnPlayModeStateChanged(PlayModeStateChange stateChange)
+ {
+ // If entering play mode, disable
+ if (stateChange == PlayModeStateChange.ExitingEditMode ||
+ stateChange == PlayModeStateChange.EnteredPlayMode)
+ {
+ m_Window.revisionActionsEnabled = false;
+ }
+ // If exiting play mode, enable!
+ else if (stateChange == PlayModeStateChange.EnteredEditMode ||
+ stateChange == PlayModeStateChange.ExitingPlayMode)
+ {
+ m_Window.revisionActionsEnabled = true;
+ }
+ }
+
+ private HistoryState RecalculateState()
+ {
+ if (!m_ConnectState.online)
+ return HistoryState.Offline;
+ if (m_ConnectState.maintenance || m_CollabState.maintenance)
+ return HistoryState.Maintenance;
+ if (!m_ConnectState.loggedIn)
+ return HistoryState.LoggedOut;
+ if (!m_CollabState.seat)
+ return HistoryState.NoSeat;
+ if (!Collab.instance.IsCollabEnabledForCurrentProject())
+ return HistoryState.Disabled;
+ if (!Collab.instance.IsConnected() || !m_CollabState.ready || m_FetchInProgress)
+ return HistoryState.Waiting;
+ if (m_ConnectState.error || m_IsCollabError)
+ return HistoryState.Error;
+
+ return HistoryState.Ready;
+ }
+
+ // TODO: Eventually this can be a listener on the build service status
+ public void UpdateBuildServiceStatus()
+ {
+ foreach (var service in UnityConnectServiceCollection.instance.GetAllServiceInfos())
+ {
+ if (service.name.Equals("Build"))
+ {
+ BuildServiceEnabled = service.enabled;
+ }
+ }
+ }
+
+ public void ShowBuildForCommit(string revisionID)
+ {
+ m_BuildAccess.ShowBuildForCommit(revisionID);
+ }
+
+ public void ShowServicePage()
+ {
+ m_BuildAccess.ShowServicePage();
+ }
+
+ public void OnUpdatePage(int page)
+ {
+ m_FetchInProgress = true;
+ m_Service.GetRevisions(page * ItemsPerPage, ItemsPerPage);
+ m_Window.UpdateState(RecalculateState(), false);
+ m_RequestedPage = page;
+ }
+
+ private void OnFetchRevisions(RevisionsResult data)
+ {
+ m_FetchInProgress = false;
+ IEnumerable items = null;
+ if (data != null)
+ {
+ m_CurrentPage = m_RequestedPage;
+ m_TotalRevisions = data.RevisionsInRepo;
+ items = m_Factory.GenerateElements(data.Revisions, m_TotalRevisions, m_CurrentPage * ItemsPerPage, m_Service.tipRevision, m_Window.inProgressRevision, m_Window.revisionActionsEnabled, BuildServiceEnabled, m_Service.currentUser);
+ }
+
+ // State must be recalculated prior to inserting items
+ m_Window.UpdateState(RecalculateState(), false);
+ m_Window.UpdateRevisions(items, m_Service.tipRevision, m_TotalRevisions, m_CurrentPage);
+ }
+
+ private void OnRestore(string revisionId, bool updatetorevision)
+ {
+ m_ProgressRevision = revisionId;
+ Collab.instance.ResyncToRevision(revisionId);
+ }
+
+ private void OnGoBack(string revisionId, bool updatetorevision)
+ {
+ m_ProgressRevision = revisionId;
+ Collab.instance.GoBackToRevision(revisionId, false);
+ }
+
+ private void OnUpdate(string revisionId, bool updatetorevision)
+ {
+ m_ProgressRevision = revisionId;
+ Collab.instance.Update(revisionId, updatetorevision);
+ }
+ }
+}
diff --git a/Editor/Collab/Presenters/CollabHistoryPresenter.cs.meta b/Editor/Collab/Presenters/CollabHistoryPresenter.cs.meta
new file mode 100644
index 0000000..9c37ecd
--- /dev/null
+++ b/Editor/Collab/Presenters/CollabHistoryPresenter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a7c91a123806d41a0873fcdcb629b1c4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views.meta b/Editor/Collab/Views.meta
new file mode 100644
index 0000000..f62ac6b
--- /dev/null
+++ b/Editor/Collab/Views.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fd0a39b4d296d4d509b4f1dbd08d0630
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/BuildStatusButton.cs b/Editor/Collab/Views/BuildStatusButton.cs
new file mode 100644
index 0000000..29a8149
--- /dev/null
+++ b/Editor/Collab/Views/BuildStatusButton.cs
@@ -0,0 +1,48 @@
+using System;
+using UnityEditor;
+using UnityEditor.Collaboration;
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+
+namespace UnityEditor.Collaboration
+{
+ internal class BuildStatusButton : Button
+ {
+ private readonly string iconPrefix = "Icons/Collab.Build";
+ private readonly string iconSuffix = ".png";
+ Label labelElement = new Label();
+ Image iconElement = new Image() {name = "BuildIcon"};
+
+ public BuildStatusButton(Action clickEvent) : base(clickEvent)
+ {
+ iconElement.image = EditorGUIUtility.Load(iconPrefix + iconSuffix) as Texture;
+ labelElement.text = "Build Now";
+ Add(iconElement);
+ Add(labelElement);
+ }
+
+ public BuildStatusButton(Action clickEvent, BuildState state, int failures) : base(clickEvent)
+ {
+ switch (state)
+ {
+ case BuildState.InProgress:
+ iconElement.image = EditorGUIUtility.Load(iconPrefix + iconSuffix) as Texture;
+ labelElement.text = "In progress";
+ break;
+
+ case BuildState.Failed:
+ iconElement.image = EditorGUIUtility.Load(iconPrefix + "Failed" + iconSuffix) as Texture;
+ labelElement.text = failures + ((failures == 1) ? " failure" : " failures");
+ break;
+
+ case BuildState.Success:
+ iconElement.image = EditorGUIUtility.Load(iconPrefix + "Succeeded" + iconSuffix) as Texture;
+ labelElement.text = "success";
+ break;
+ }
+
+ Add(iconElement);
+ Add(labelElement);
+ }
+ }
+}
diff --git a/Editor/Collab/Views/BuildStatusButton.cs.meta b/Editor/Collab/Views/BuildStatusButton.cs.meta
new file mode 100644
index 0000000..d74a58a
--- /dev/null
+++ b/Editor/Collab/Views/BuildStatusButton.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0217a80286f79419daa202f69409f19b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/CollabHistoryDropDown.cs b/Editor/Collab/Views/CollabHistoryDropDown.cs
new file mode 100644
index 0000000..0ef7ba8
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryDropDown.cs
@@ -0,0 +1,72 @@
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+using System.Collections.Generic;
+using UnityEditor.Connect;
+
+namespace UnityEditor.Collaboration
+{
+ internal class CollabHistoryDropDown : VisualElement
+ {
+ private readonly VisualElement m_FilesContainer;
+ private readonly Label m_ToggleLabel;
+ private int m_ChangesTotal;
+ private string m_RevisionId;
+
+ public CollabHistoryDropDown(ICollection changes, int changesTotal, bool changesTruncated, string revisionId)
+ {
+ m_FilesContainer = new VisualElement();
+ m_ChangesTotal = changesTotal;
+ m_RevisionId = revisionId;
+
+ m_ToggleLabel = new Label(ToggleText(false));
+ m_ToggleLabel.AddManipulator(new Clickable(ToggleDropdown));
+ Add(m_ToggleLabel);
+
+ foreach (ChangeData change in changes)
+ {
+ m_FilesContainer.Add(new CollabHistoryDropDownItem(change.path, change.action));
+ }
+
+ if (changesTruncated)
+ {
+ m_FilesContainer.Add(new Button(ShowAllClick)
+ {
+ text = "Show all on dashboard"
+ });
+ }
+ }
+
+ private void ToggleDropdown()
+ {
+ if (Contains(m_FilesContainer))
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "CollapseAssets");
+ Remove(m_FilesContainer);
+ m_ToggleLabel.text = ToggleText(false);
+ }
+ else
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "ExpandAssets");
+ Add(m_FilesContainer);
+ m_ToggleLabel.text = ToggleText(true);
+ }
+ }
+
+ private string ToggleText(bool open)
+ {
+ var icon = open ? "\u25bc" : "\u25b6";
+ var change = m_ChangesTotal == 1 ? "Change" : "Changes";
+ return string.Format("{0} {1} Asset {2}", icon, m_ChangesTotal, change);
+ }
+
+ private void ShowAllClick()
+ {
+ var host = UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudServicesDashboard);
+ var org = UnityConnect.instance.GetOrganizationId();
+ var proj = UnityConnect.instance.GetProjectGUID();
+ var url = string.Format("{0}/collab/orgs/{1}/projects/{2}/commits?commit={3}", host, org, proj, m_RevisionId);
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "ShowAllOnDashboard");
+ Application.OpenURL(url);
+ }
+ }
+}
diff --git a/Editor/Collab/Views/CollabHistoryDropDown.cs.meta b/Editor/Collab/Views/CollabHistoryDropDown.cs.meta
new file mode 100644
index 0000000..513b66b
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryDropDown.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a483595b0257945278dc75c5ff7d82ee
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/CollabHistoryDropDownItem.cs b/Editor/Collab/Views/CollabHistoryDropDownItem.cs
new file mode 100644
index 0000000..8982113
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryDropDownItem.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+
+namespace UnityEditor.Collaboration
+{
+ internal class CollabHistoryDropDownItem : VisualElement
+ {
+ public CollabHistoryDropDownItem(string path, string action)
+ {
+ var fileName = Path.GetFileName(path);
+ var isFolder = Path.GetFileNameWithoutExtension(path).Equals(fileName);
+ var fileIcon = GetIconElement(action, fileName, isFolder);
+ var metaContainer = new VisualElement();
+ var fileNameLabel = new Label
+ {
+ name = "FileName",
+ text = fileName
+ };
+ var filePathLabel = new Label
+ {
+ name = "FilePath",
+ text = path
+ };
+ metaContainer.Add(fileNameLabel);
+ metaContainer.Add(filePathLabel);
+ Add(fileIcon);
+ Add(metaContainer);
+ }
+
+ private Image GetIconElement(string action, string fileName, bool isFolder)
+ {
+ var prefix = isFolder ? "Folder" : "File";
+ var actionName = action.First().ToString().ToUpper() + action.Substring(1);
+ // Use the same icon for renamed and moved files
+ actionName = actionName.Equals("Renamed") ? "Moved" : actionName;
+ var iconElement = new Image
+ {
+ name = "FileIcon",
+ image = EditorGUIUtility.LoadIcon("Icons/Collab." + prefix + actionName + ".png")
+ };
+ return iconElement;
+ }
+ }
+}
diff --git a/Editor/Collab/Views/CollabHistoryDropDownItem.cs.meta b/Editor/Collab/Views/CollabHistoryDropDownItem.cs.meta
new file mode 100644
index 0000000..10bf40e
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryDropDownItem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d912d4873af534bd4a9d44bf1b52f14e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/CollabHistoryItem.cs b/Editor/Collab/Views/CollabHistoryItem.cs
new file mode 100644
index 0000000..fd6fd33
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryItem.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using UnityEditor.Connect;
+using UnityEditor.Web;
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+using UnityEngine.Experimental.UIElements.StyleEnums;
+
+namespace UnityEditor.Collaboration
+{
+ internal class CollabHistoryItem : VisualContainer
+ {
+ public static RevisionAction s_OnRestore;
+ public static RevisionAction s_OnGoBack;
+ public static RevisionAction s_OnUpdate;
+ public static ShowBuildAction s_OnShowBuild;
+ public static Action s_OnShowServices;
+
+ private readonly string m_RevisionId;
+ private readonly string m_FullDescription;
+ private readonly DateTime m_TimeStamp;
+ private readonly Button m_Button;
+ private readonly HistoryProgressSpinner m_ProgressSpinner;
+ private VisualContainer m_ActionsTray;
+ private VisualElement m_Details;
+ private Label m_Description;
+ private Label m_TimeAgo;
+ private readonly Button m_ExpandCollapseButton;
+ private bool m_Expanded;
+
+ private const int kMaxDescriptionChars = 500;
+
+ public bool RevisionActionsEnabled
+ {
+ set
+ {
+ m_Button.SetEnabled(value);
+ }
+ }
+
+ public DateTime timeStamp
+ {
+ get { return m_TimeStamp; }
+ }
+
+ public CollabHistoryItem(RevisionData data)
+ {
+ m_RevisionId = data.id;
+ m_TimeStamp = data.timeStamp;
+ name = "HistoryItem";
+ m_ActionsTray = new VisualContainer {name = "HistoryItemActionsTray"};
+ m_ProgressSpinner = new HistoryProgressSpinner();
+ m_Details = new VisualElement {name = "HistoryDetail"};
+ var author = new Label(data.authorName) {name = "Author"};
+ m_TimeAgo = new Label(TimeAgo.GetString(m_TimeStamp));
+ m_FullDescription = data.comment;
+ var shouldTruncate = ShouldTruncateDescription(m_FullDescription);
+ if (shouldTruncate)
+ {
+ m_Description = new Label(GetTruncatedDescription(m_FullDescription));
+ }
+ else
+ {
+ m_Description = new Label(m_FullDescription);
+ }
+ m_Description.name = "RevisionDescription";
+ var dropdown = new CollabHistoryDropDown(data.changes, data.changesTotal, data.changesTruncated, data.id);
+ if (data.current)
+ {
+ m_Button = new Button(Restore) {name = "ActionButton", text = "Restore"};
+ }
+ else if (data.obtained)
+ {
+ m_Button = new Button(GoBackTo) {name = "ActionButton", text = "Go back to..."};
+ }
+ else
+ {
+ m_Button = new Button(UpdateTo) {name = "ActionButton", text = "Update"};
+ }
+ m_Button.SetEnabled(data.enabled);
+ m_ProgressSpinner.ProgressEnabled = data.inProgress;
+
+ m_ActionsTray.Add(m_ProgressSpinner);
+ m_ActionsTray.Add(m_Button);
+
+ m_Details.Add(author);
+ m_Details.Add(m_TimeAgo);
+ m_Details.Add(m_Description);
+
+ if (shouldTruncate)
+ {
+ m_ExpandCollapseButton = new Button(ToggleDescription) { name = "ToggleDescription", text = "Show More" };
+ m_Details.Add(m_ExpandCollapseButton);
+ }
+
+ if (data.buildState != BuildState.None)
+ {
+ BuildStatusButton buildButton;
+ if (data.buildState == BuildState.Configure)
+ buildButton = new BuildStatusButton(ShowServicePage);
+ else
+ buildButton = new BuildStatusButton(ShowBuildForCommit, data.buildState, data.buildFailures);
+
+ m_Details.Add(buildButton);
+ }
+
+ m_Details.Add(m_ActionsTray);
+ m_Details.Add(dropdown);
+
+ Add(m_Details);
+
+ this.schedule.Execute(UpdateTimeAgo).Every(1000 * 20);
+ }
+
+ public static void SetUpCallbacks(RevisionAction Restore, RevisionAction GoBack, RevisionAction Update)
+ {
+ s_OnRestore = Restore;
+ s_OnGoBack = GoBack;
+ s_OnUpdate = Update;
+ }
+
+ public void SetInProgressStatus(string revisionIdInProgress)
+ {
+ if (String.IsNullOrEmpty(revisionIdInProgress))
+ {
+ m_Button.SetEnabled(true);
+ m_ProgressSpinner.ProgressEnabled = false;
+ }
+ else
+ {
+ m_Button.SetEnabled(false);
+ if (m_RevisionId.Equals(revisionIdInProgress))
+ {
+ m_ProgressSpinner.ProgressEnabled = true;
+ }
+ }
+ }
+
+ void ShowBuildForCommit()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "ShowBuild");
+ if (s_OnShowBuild != null)
+ {
+ s_OnShowBuild(m_RevisionId);
+ }
+ }
+
+ void ShowServicePage()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "ShowServices");
+ if (s_OnShowServices != null)
+ {
+ s_OnShowServices();
+ }
+ }
+
+ void Restore()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "Restore");
+ if (s_OnRestore != null)
+ {
+ s_OnRestore(m_RevisionId, false);
+ }
+ }
+
+ void GoBackTo()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "GoBackTo");
+ if (s_OnGoBack != null)
+ {
+ s_OnGoBack(m_RevisionId, false);
+ }
+ }
+
+ void UpdateTo()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "Update");
+ if (s_OnUpdate != null)
+ {
+ s_OnUpdate(m_RevisionId, true);
+ }
+ }
+
+ void UpdateTimeAgo()
+ {
+ m_TimeAgo.text = TimeAgo.GetString(m_TimeStamp);
+ }
+
+ bool ShouldTruncateDescription(string description)
+ {
+ return description.Contains(Environment.NewLine) || description.Length > kMaxDescriptionChars;
+ }
+
+ string GetTruncatedDescription(string description)
+ {
+ string result = description.Contains(Environment.NewLine) ?
+ description.Substring(0, description.IndexOf(Environment.NewLine)) : description;
+ if (result.Length > kMaxDescriptionChars)
+ {
+ result = result.Substring(0, kMaxDescriptionChars) + "...";
+ }
+ return result;
+ }
+
+ void ToggleDescription()
+ {
+ if (m_Expanded)
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "CollapseDescription");
+ m_Expanded = false;
+ m_ExpandCollapseButton.text = "Show More";
+ m_Description.text = GetTruncatedDescription(m_FullDescription);
+ }
+ else
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "ExpandDescription");
+ m_Expanded = true;
+ m_ExpandCollapseButton.text = "Show Less";
+ m_Description.text = m_FullDescription;
+ }
+ }
+ }
+}
diff --git a/Editor/Collab/Views/CollabHistoryItem.cs.meta b/Editor/Collab/Views/CollabHistoryItem.cs.meta
new file mode 100644
index 0000000..290bd28
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryItem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c4c1445ee948a4124bfa9fb818a17e36
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/CollabHistoryItemFactory.cs b/Editor/Collab/Views/CollabHistoryItemFactory.cs
new file mode 100644
index 0000000..d3d32ce
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryItemFactory.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor.Collaboration;
+using UnityEngine.Experimental.UIElements;
+using UnityEngine.Experimental.UIElements.StyleEnums;
+using UnityEngine;
+
+namespace UnityEditor.Collaboration
+{
+ internal class CollabHistoryItemFactory : ICollabHistoryItemFactory
+ {
+ const int k_MaxChangesPerRevision = 10;
+
+ public IEnumerable GenerateElements(IEnumerable revisions, int totalRevisions, int startIndex, string tipRev, string inProgressRevision, bool revisionActionsEnabled, bool buildServiceEnabled, string currentUser)
+ {
+ int index = startIndex;
+
+ foreach (var rev in revisions)
+ {
+ index++;
+ var current = rev.revisionID == tipRev;
+
+ // Calculate build status
+ BuildState buildState = BuildState.None;
+ int buildFailures = 0;
+ if (rev.buildStatuses != null && rev.buildStatuses.Length > 0)
+ {
+ bool inProgress = false;
+ foreach (CloudBuildStatus buildStatus in rev.buildStatuses)
+ {
+ if (buildStatus.complete)
+ {
+ if (!buildStatus.success)
+ {
+ buildFailures++;
+ }
+ }
+ else
+ {
+ inProgress = true;
+ break;
+ }
+ }
+
+ if (inProgress)
+ {
+ buildState = BuildState.InProgress;
+ }
+ else if (buildFailures > 0)
+ {
+ buildState = BuildState.Failed;
+ }
+ else
+ {
+ buildState = BuildState.Success;
+ }
+ }
+ else if (current && !buildServiceEnabled)
+ {
+ buildState = BuildState.Configure;
+ }
+
+ // Calculate the number of changes performed on files and folders (not meta files)
+ var paths = new Dictionary();
+ foreach (ChangeAction change in rev.entries)
+ {
+ if (change.path.EndsWith(".meta"))
+ {
+ var path = change.path.Substring(0, change.path.Length - 5);
+ // Actions taken on meta files are secondary to any actions taken on the main file
+ if (!paths.ContainsKey(path))
+ paths[path] = new ChangeData() {path = path, action = change.action};
+ }
+ else
+ {
+ paths[change.path] = new ChangeData() {path = change.path, action = change.action};
+ }
+ }
+
+ var displayName = (rev.author != currentUser) ? rev.authorName : "You";
+
+ var item = new RevisionData
+ {
+ id = rev.revisionID,
+ index = totalRevisions - index + 1,
+ timeStamp = TimeStampToDateTime(rev.timeStamp),
+ authorName = displayName,
+ comment = rev.comment,
+
+ obtained = rev.isObtained,
+ current = current,
+ inProgress = (rev.revisionID == inProgressRevision),
+ enabled = revisionActionsEnabled,
+
+ buildState = buildState,
+ buildFailures = buildFailures,
+
+ changes = paths.Values.Take(k_MaxChangesPerRevision).ToList(),
+ changesTotal = paths.Values.Count,
+ changesTruncated = paths.Values.Count > k_MaxChangesPerRevision,
+ };
+
+ yield return item;
+ }
+ }
+
+ private static DateTime TimeStampToDateTime(double timeStamp)
+ {
+ DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+ dateTime = dateTime.AddSeconds(timeStamp).ToLocalTime();
+ return dateTime;
+ }
+ }
+}
diff --git a/Editor/Collab/Views/CollabHistoryItemFactory.cs.meta b/Editor/Collab/Views/CollabHistoryItemFactory.cs.meta
new file mode 100644
index 0000000..3250d96
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryItemFactory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fc46f91ea1e8e4ca2ab693fef9156dbe
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/CollabHistoryRevisionLine.cs b/Editor/Collab/Views/CollabHistoryRevisionLine.cs
new file mode 100644
index 0000000..7fb8212
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryRevisionLine.cs
@@ -0,0 +1,89 @@
+using System;
+using UnityEditor;
+using UnityEditor.Collaboration;
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+
+namespace UnityEditor.Collaboration
+{
+ internal class CollabHistoryRevisionLine : VisualElement
+ {
+ public CollabHistoryRevisionLine(int number)
+ {
+ AddNumber(number);
+ AddLine("topLine");
+ AddLine("bottomLine");
+ AddIndicator();
+ }
+
+ public CollabHistoryRevisionLine(DateTime date, bool isFullDateObtained)
+ {
+ AddLine(isFullDateObtained ? "obtainedDateLine" : "absentDateLine");
+ AddHeader(GetFormattedHeader(date));
+ AddToClassList("revisionLineHeader");
+ }
+
+ private void AddHeader(string content)
+ {
+ Add(new Label
+ {
+ text = content
+ });
+ }
+
+ private void AddIndicator()
+ {
+ Add(new VisualElement
+ {
+ name = "RevisionIndicator"
+ });
+ }
+
+ private void AddLine(string className = null)
+ {
+ var line = new VisualElement
+ {
+ name = "RevisionLine"
+ };
+ if (!String.IsNullOrEmpty(className))
+ {
+ line.AddToClassList(className);
+ }
+ Add(line);
+ }
+
+ private void AddNumber(int number)
+ {
+ Add(new Label
+ {
+ text = number.ToString(),
+ name = "RevisionIndex"
+ });
+ }
+
+ private string GetFormattedHeader(DateTime date)
+ {
+ string result = "Commits on " + date.ToString("MMM d");
+ switch (date.Day)
+ {
+ case 1:
+ case 21:
+ case 31:
+ result += "st";
+ break;
+ case 2:
+ case 22:
+ result += "nd";
+ break;
+ case 3:
+ case 23:
+ result += "rd";
+ break;
+ default:
+ result += "th";
+ break;
+ }
+ return result;
+ }
+ }
+}
diff --git a/Editor/Collab/Views/CollabHistoryRevisionLine.cs.meta b/Editor/Collab/Views/CollabHistoryRevisionLine.cs.meta
new file mode 100644
index 0000000..2659a3c
--- /dev/null
+++ b/Editor/Collab/Views/CollabHistoryRevisionLine.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3c737f7a9d78541d1ab25f28f045dd32
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/HistoryProgressSpinner.cs b/Editor/Collab/Views/HistoryProgressSpinner.cs
new file mode 100644
index 0000000..d58ba0e
--- /dev/null
+++ b/Editor/Collab/Views/HistoryProgressSpinner.cs
@@ -0,0 +1,64 @@
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+
+namespace UnityEditor.Collaboration
+{
+ internal class HistoryProgressSpinner : Image
+ {
+ private readonly Texture2D[] m_StatusWheelTextures;
+ private bool m_ProgressEnabled;
+ private IVisualElementScheduledItem m_Animation;
+
+ public bool ProgressEnabled
+ {
+ set
+ {
+ if (m_ProgressEnabled == value)
+ return;
+
+ m_ProgressEnabled = value;
+ visible = value;
+
+
+ if (value)
+ {
+ if (m_Animation == null)
+ {
+ m_Animation = this.schedule.Execute(AnimateProgress).Every(33);
+ }
+ else
+ {
+ m_Animation.Resume();
+ }
+ }
+ else
+ {
+ if (m_Animation != null)
+ {
+ m_Animation.Pause();
+ }
+ }
+ }
+ }
+
+ public HistoryProgressSpinner()
+ {
+ m_StatusWheelTextures = new Texture2D[12];
+ for (int i = 0; i < 12; i++)
+ {
+ m_StatusWheelTextures[i] = EditorGUIUtility.LoadIcon("WaitSpin" + i.ToString("00"));
+ }
+ image = m_StatusWheelTextures[0];
+ style.width = m_StatusWheelTextures[0].width;
+ style.height = m_StatusWheelTextures[0].height;
+ visible = false;
+ }
+
+ private void AnimateProgress(TimerState obj)
+ {
+ int frame = (int)Mathf.Repeat(Time.realtimeSinceStartup * 10, 11.99f);
+ image = m_StatusWheelTextures[frame];
+ MarkDirtyRepaint();
+ }
+ }
+}
diff --git a/Editor/Collab/Views/HistoryProgressSpinner.cs.meta b/Editor/Collab/Views/HistoryProgressSpinner.cs.meta
new file mode 100644
index 0000000..0ded4e8
--- /dev/null
+++ b/Editor/Collab/Views/HistoryProgressSpinner.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cf6aca931950a4a6a886e214e9e649c4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/ICollabHistoryItemFactory.cs b/Editor/Collab/Views/ICollabHistoryItemFactory.cs
new file mode 100644
index 0000000..a677dea
--- /dev/null
+++ b/Editor/Collab/Views/ICollabHistoryItemFactory.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using UnityEditor.Collaboration;
+using UnityEngine.Experimental.UIElements;
+
+namespace UnityEditor.Collaboration
+{
+ internal interface ICollabHistoryItemFactory
+ {
+ IEnumerable GenerateElements(IEnumerable revsRevisions, int mTotalRevisions, int startIndex, string tipRev, string inProgressRevision, bool revisionActionsEnabled, bool buildServiceEnabled, string currentUser);
+ }
+}
diff --git a/Editor/Collab/Views/ICollabHistoryItemFactory.cs.meta b/Editor/Collab/Views/ICollabHistoryItemFactory.cs.meta
new file mode 100644
index 0000000..08e9085
--- /dev/null
+++ b/Editor/Collab/Views/ICollabHistoryItemFactory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 821f5482c5a3f4389885f4432433f56f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/PagedListView.cs b/Editor/Collab/Views/PagedListView.cs
new file mode 100644
index 0000000..2d66426
--- /dev/null
+++ b/Editor/Collab/Views/PagedListView.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine.Experimental.UIElements;
+using UnityEngine.Experimental.UIElements.StyleEnums;
+
+namespace UnityEditor.Collaboration
+{
+ internal interface IPagerData
+ {
+ int curPage { get; }
+ int totalPages { get; }
+ PageChangeAction OnPageChanged { get; }
+ }
+
+ internal class PagerElement : VisualElement
+ {
+ IPagerData m_Data;
+ readonly Label m_PageText;
+ readonly Button m_DownButton;
+ readonly Button m_UpButton;
+
+ public PagerElement(IPagerData dataSource)
+ {
+ m_Data = dataSource;
+
+ this.style.flexDirection = FlexDirection.Row;
+ this.style.alignSelf = Align.Center;
+
+ Add(m_DownButton = new Button(OnPageDownClicked) {text = "\u25c5 Newer"});
+ m_DownButton.AddToClassList("PagerDown");
+
+ m_PageText = new Label();
+ m_PageText.AddToClassList("PagerLabel");
+ Add(m_PageText);
+
+ Add(m_UpButton = new Button(OnPageUpClicked) {text = "Older \u25bb"});
+ m_UpButton.AddToClassList("PagerUp");
+
+ UpdateControls();
+ }
+
+ void OnPageDownClicked()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "NewerPage");
+ m_Data.OnPageChanged(m_Data.curPage - 1);
+ }
+
+ void OnPageUpClicked()
+ {
+ CollabAnalytics.SendUserAction(CollabAnalytics.historyCategoryString, "OlderPage");
+ m_Data.OnPageChanged(m_Data.curPage + 1);
+ }
+
+ public void Refresh()
+ {
+ UpdateControls();
+ }
+
+ void UpdateControls()
+ {
+ var curPage = m_Data.curPage;
+ var totalPages = m_Data.totalPages;
+
+ m_PageText.text = (curPage + 1) + " / " + totalPages;
+ m_DownButton.SetEnabled(curPage > 0);
+ m_UpButton.SetEnabled(curPage < totalPages - 1);
+ }
+ }
+
+ internal enum PagerLocation
+ {
+ Top,
+ Bottom,
+ }
+
+ internal class PagedListView : VisualElement, IPagerData
+ {
+ public const int DefaultItemsPerPage = 10;
+
+ readonly VisualElement m_ItemContainer;
+ readonly PagerElement m_PagerTop, m_PagerBottom;
+ int m_PageSize = DefaultItemsPerPage;
+ IEnumerable m_Items;
+ int m_TotalItems;
+ int m_CurPage;
+
+ public int pageSize
+ {
+ set { m_PageSize = value; }
+ }
+
+ public IEnumerable items
+ {
+ set
+ {
+ m_Items = value;
+ LayoutItems();
+ }
+ }
+
+ public int totalItems
+ {
+ set
+ {
+ if (m_TotalItems == value)
+ return;
+
+ m_TotalItems = value;
+ UpdatePager();
+ }
+ }
+
+ public PageChangeAction OnPageChanged { get; set; }
+
+ public PagedListView()
+ {
+ m_PagerTop = new PagerElement(this);
+
+ m_ItemContainer = new VisualElement()
+ {
+ name = "PagerItems",
+ };
+ Add(m_ItemContainer);
+ m_Items = new List();
+
+ m_PagerBottom = new PagerElement(this);
+ }
+
+ void LayoutItems()
+ {
+ m_ItemContainer.Clear();
+ foreach (var item in m_Items)
+ {
+ m_ItemContainer.Add(item);
+ }
+ }
+
+ void UpdatePager()
+ {
+ if (m_PagerTop.parent != this && totalPages > 1 && curPage > 0)
+ Insert(0, m_PagerTop);
+ if (m_PagerTop.parent == this && (totalPages <= 1 || curPage == 0))
+ Remove(m_PagerTop);
+
+ if (m_PagerBottom.parent != this && totalPages > 1)
+ Add(m_PagerBottom);
+ if (m_PagerBottom.parent == this && totalPages <= 1)
+ Remove(m_PagerBottom);
+
+ m_PagerTop.Refresh();
+ m_PagerBottom.Refresh();
+ }
+
+ int pageCount
+ {
+ get
+ {
+ var pages = m_TotalItems / m_PageSize;
+ if (m_TotalItems % m_PageSize > 0)
+ pages++;
+
+ return pages;
+ }
+ }
+
+ public int curPage
+ {
+ get { return m_CurPage; }
+ set
+ {
+ m_CurPage = value;
+ UpdatePager();
+ }
+ }
+
+ public int totalPages
+ {
+ get
+ {
+ var extraPage = 0;
+ if (m_TotalItems % m_PageSize > 0)
+ extraPage = 1;
+ return m_TotalItems / m_PageSize + extraPage;
+ }
+ }
+ }
+}
diff --git a/Editor/Collab/Views/PagedListView.cs.meta b/Editor/Collab/Views/PagedListView.cs.meta
new file mode 100644
index 0000000..565f7a2
--- /dev/null
+++ b/Editor/Collab/Views/PagedListView.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 50de529b6a28f4a7093045e08810a5df
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Collab/Views/StatusView.cs b/Editor/Collab/Views/StatusView.cs
new file mode 100644
index 0000000..a27bfb3
--- /dev/null
+++ b/Editor/Collab/Views/StatusView.cs
@@ -0,0 +1,83 @@
+using System;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.Experimental.UIElements;
+using UnityEngine.Experimental.UIElements.StyleEnums;
+
+namespace UnityEditor.Collaboration
+{
+ internal class StatusView : VisualContainer
+ {
+ Image m_Image;
+ Label m_Message;
+ Button m_Button;
+ Action m_Callback;
+
+ public Texture icon
+ {
+ get { return m_Image.image; }
+ set
+ {
+ m_Image.image = value;
+ m_Image.visible = value != null;
+ // Until "display: hidden" is added, this is the only way to hide an element
+ m_Image.style.height = value != null ? 150 : 0;
+ }
+ }
+
+ public string message
+ {
+ get { return m_Message.text; }
+ set
+ {
+ m_Message.text = value;
+ m_Message.visible = value != null;
+ }
+ }
+
+ public string buttonText
+ {
+ get { return m_Button.text; }
+ set
+ {
+ m_Button.text = value;
+ UpdateButton();
+ }
+ }
+
+ public Action callback
+ {
+ get { return m_Callback; }
+ set
+ {
+ m_Callback = value;
+ UpdateButton();
+ }
+ }
+
+ public StatusView()
+ {
+ name = "StatusView";
+
+ this.StretchToParentSize();
+
+ m_Image = new Image() { name = "StatusIcon", visible = false, style = { height = 0 }};
+ m_Message = new Label() { name = "StatusMessage", visible = false};
+ m_Button = new Button(InternalCallaback) { name = "StatusButton", visible = false};
+
+ Add(m_Image);
+ Add(m_Message);
+ Add(m_Button);
+ }
+
+ private void UpdateButton()
+ {
+ m_Button.visible = m_Button.text != null && m_Callback != null;
+ }
+
+ private void InternalCallaback()
+ {
+ m_Callback();
+ }
+ }
+}
diff --git a/Editor/Collab/Views/StatusView.cs.meta b/Editor/Collab/Views/StatusView.cs.meta
new file mode 100644
index 0000000..bb634b1
--- /dev/null
+++ b/Editor/Collab/Views/StatusView.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 08e9894bdf0834710b22d3c0aa245ac0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Unity.CollabProxy.Editor.asmdef b/Editor/Unity.CollabProxy.Editor.asmdef
new file mode 100644
index 0000000..66511e1
--- /dev/null
+++ b/Editor/Unity.CollabProxy.Editor.asmdef
@@ -0,0 +1,7 @@
+{
+ "name": "Unity.CollabProxy.Editor",
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": []
+}
diff --git a/Editor/Unity.CollabProxy.Editor.asmdef.meta b/Editor/Unity.CollabProxy.Editor.asmdef.meta
new file mode 100644
index 0000000..03ebeca
--- /dev/null
+++ b/Editor/Unity.CollabProxy.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 645165c8169474bfbbeb8fb0bcfd26f5
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/bin/Unity.CollabProxy.Editor.dll b/Editor/bin/Unity.CollabProxy.Editor.dll
deleted file mode 100644
index 899c98a..0000000
Binary files a/Editor/bin/Unity.CollabProxy.Editor.dll and /dev/null differ
diff --git a/Editor/bin/Unity.CollabProxy.Editor.dll.meta b/Editor/bin/Unity.CollabProxy.Editor.dll.meta
deleted file mode 100644
index 0fe2110..0000000
--- a/Editor/bin/Unity.CollabProxy.Editor.dll.meta
+++ /dev/null
@@ -1,30 +0,0 @@
-fileFormatVersion: 2
-guid: dc0f15c4dc13b4206be2460e7baa642a
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- isPreloaded: 0
- isOverridable: 1
- platformData:
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 1
- settings:
- DefaultValueInitialized: true
- - first:
- Windows Store Apps: WindowsStoreApps
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Tests.meta b/Tests.meta
new file mode 100644
index 0000000..f43ddd3
--- /dev/null
+++ b/Tests.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1369382d2c5e64dc5b2ec0b6b0a94531
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Tests/Editor.meta b/Tests/Editor.meta
new file mode 100644
index 0000000..b80cefd
--- /dev/null
+++ b/Tests/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4506ac79f5b274cb1b249ed7f4abfb9a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Tests/Editor/HistoryTests.cs b/Tests/Editor/HistoryTests.cs
new file mode 100644
index 0000000..00a063c
--- /dev/null
+++ b/Tests/Editor/HistoryTests.cs
@@ -0,0 +1,600 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEditor.Collaboration;
+using UnityEngine.TestTools;
+using NUnit.Framework;
+
+namespace UnityEditor.Collaboration.Tests
+{
+ [TestFixture]
+ internal class HistoryTests
+ {
+ private TestHistoryWindow _window;
+ private TestRevisionsService _service;
+ private CollabHistoryPresenter _presenter;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _window = new TestHistoryWindow();
+ _service = new TestRevisionsService();
+ _presenter = new CollabHistoryPresenter(_window, new CollabHistoryItemFactory(), _service);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__PropagatesRevisionResult()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(authorName: "authorName", comment: "comment", revisionID: "revisionID"),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual("revisionID", item.id);
+ Assert.AreEqual("authorName", item.authorName);
+ Assert.AreEqual("comment", item.comment);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__DateCalculatedCorrectly()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(timeStamp: 1506978483),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(new DateTime(2017, 10, 02, 14, 08, 03), item.timeStamp);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__RevisionNumberingIsInOrder()
+ {
+ _service.result = new RevisionsResult()
+ {
+ RevisionsInRepo = 4,
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ new Revision(revisionID: "1"),
+ new Revision(revisionID: "2"),
+ new Revision(revisionID: "3"),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var items = _window.items.ToArray();
+
+ Assert.AreEqual(4, items[0].index);
+ Assert.AreEqual(3, items[1].index);
+ Assert.AreEqual(2, items[2].index);
+ Assert.AreEqual(1, items[3].index);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__RevisionNumberingChangesForMorePages()
+ {
+ _service.result = new RevisionsResult()
+ {
+ RevisionsInRepo = 12,
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ new Revision(revisionID: "1"),
+ new Revision(revisionID: "2"),
+ new Revision(revisionID: "3"),
+ new Revision(revisionID: "4"),
+ }
+ };
+
+ _presenter.OnUpdatePage(1);
+ var items = _window.items.ToArray();
+
+ Assert.AreEqual(12, items[0].index);
+ Assert.AreEqual(11, items[1].index);
+ Assert.AreEqual(10, items[2].index);
+ Assert.AreEqual(9, items[3].index);
+ Assert.AreEqual(8, items[4].index);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__ObtainedIsCalculated()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(isObtained: false),
+ new Revision(isObtained: true),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var items = _window.items.ToArray();
+
+ Assert.IsFalse(items[0].obtained);
+ Assert.IsTrue(items[1].obtained);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__CurrentIsCalculated()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "1"),
+ new Revision(revisionID: "2"),
+ new Revision(revisionID: "3"),
+ }
+ };
+ _service.tipRevision = "2";
+
+ _presenter.OnUpdatePage(0);
+ var items = _window.items.ToArray();
+
+ Assert.AreEqual(false, items[0].current);
+ Assert.AreEqual(true, items[1].current);
+ Assert.AreEqual(false, items[2].current);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__InProgressIsCalculated()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "1"),
+ new Revision(revisionID: "2"),
+ new Revision(revisionID: "3"),
+ }
+ };
+ _window.inProgressRevision = "2";
+
+ _presenter.OnUpdatePage(0);
+ var items = _window.items.ToArray();
+
+ Assert.IsFalse(items[0].inProgress);
+ Assert.IsTrue(items[1].inProgress);
+ Assert.IsFalse(items[2].inProgress);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__EnabledIsCalculated()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ }
+ };
+ _window.revisionActionsEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(true, item.enabled);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__DisabledIsCalculated()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ }
+ };
+ _window.revisionActionsEnabled = false;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(false, item.enabled);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasNoneWhenNotTip()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "1"),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = false;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.None, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateTipHasNoneWhenEnabled()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.None, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasConfigureWhenTip()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = false;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.Configure, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasConfigureWhenZeroBuildStatus()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = false;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.Configure, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasNoneWhenZeroBuildStatuses()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0"),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.None, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasSuccessWhenCompleteAndSucceeded()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision
+ (
+ revisionID: "0",
+ buildStatuses: new CloudBuildStatus[1]
+ {
+ new CloudBuildStatus(complete: true, success: true),
+ }
+ ),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.Success, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasInProgress()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision
+ (
+ revisionID: "0",
+ buildStatuses: new CloudBuildStatus[1]
+ {
+ new CloudBuildStatus(complete: false),
+ }
+ ),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.InProgress, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasFailure()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision
+ (
+ revisionID: "0",
+ buildStatuses: new CloudBuildStatus[1]
+ {
+ new CloudBuildStatus(complete: true, success: false),
+ }
+ ),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.Failed, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__BuildStateHasFailureWhenAnyBuildsFail()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision
+ (
+ revisionID: "0",
+ buildStatuses: new CloudBuildStatus[3]
+ {
+ new CloudBuildStatus(complete: true, success: false),
+ new CloudBuildStatus(complete: true, success: false),
+ new CloudBuildStatus(complete: true, success: true),
+ }
+ ),
+ }
+ };
+ _service.tipRevision = "0";
+ _presenter.BuildServiceEnabled = true;
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(BuildState.Failed, item.buildState);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__ChangesPropagateThrough()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0", entries: GenerateChangeActions(3)),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+ var changes = item.changes.ToList();
+
+ Assert.AreEqual("Path0", changes[0].path);
+ Assert.AreEqual("Path1", changes[1].path);
+ Assert.AreEqual("Path2", changes[2].path);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__ChangesTotalIsCalculated()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0", entries: GenerateChangeActions(3)),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(3, item.changes.Count);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__ChangesTruncatedIsCalculated()
+ {
+ for (var i = 0; i < 20; i++)
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(revisionID: "0", entries: GenerateChangeActions(i)),
+ }
+ };
+
+ _presenter.OnUpdatePage(0);
+ var item = _window.items.First();
+
+ Assert.AreEqual(i > 10, item.changesTruncated);
+ }
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__OnlyKeeps10ChangeActions()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision(authorName: "Test", author: "test", entries: GenerateChangeActions(12)),
+ }
+ };
+
+ _presenter.OnUpdatePage(1);
+ var item = _window.items.First();
+
+ Assert.AreEqual(10, item.changes.Count);
+ Assert.AreEqual(12, item.changesTotal);
+ Assert.AreEqual(true, item.changesTruncated);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__DeduplicatesMetaFiles()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision
+ (
+ authorName: "Test",
+ author: "test",
+ revisionID: "",
+ entries: new ChangeAction[2]
+ {
+ new ChangeAction(path: "Path1", action: "Action1"),
+ new ChangeAction(path: "Path1.meta", action: "Action1"),
+ }
+ ),
+ }
+ };
+
+ _presenter.OnUpdatePage(1);
+ var item = _window.items.First();
+
+ Assert.AreEqual(1, item.changes.Count);
+ Assert.AreEqual(1, item.changesTotal);
+ Assert.AreEqual("Path1", item.changes.First().path);
+ }
+
+ [Test]
+ public void CollabHistoryPresenter_OnUpdatePage__FolderMetaFilesAreCounted()
+ {
+ _service.result = new RevisionsResult()
+ {
+ Revisions = new List()
+ {
+ new Revision
+ (
+ authorName: "Test",
+ author: "test",
+ entries: new ChangeAction[1]
+ {
+ new ChangeAction(path: "Folder1.meta", action: "Action1"),
+ }
+ ),
+ }
+ };
+
+ _presenter.OnUpdatePage(1);
+ var item = _window.items.First();
+
+ Assert.AreEqual(1, item.changes.Count);
+ Assert.AreEqual(1, item.changesTotal);
+ Assert.AreEqual("Folder1", item.changes.First().path);
+ }
+
+ private static ChangeAction[] GenerateChangeActions(int count)
+ {
+ var entries = new ChangeAction[count];
+ for (var i = 0; i < count; i++)
+ entries[i] = new ChangeAction(path: "Path" + i, action: "Action" + i);
+ return entries;
+ }
+ }
+
+ internal class TestRevisionsService : IRevisionsService
+ {
+ public RevisionsResult result;
+ public event RevisionsDelegate FetchRevisionsCallback;
+
+ public string tipRevision { get; set; }
+ public string currentUser { get; set; }
+
+ public void GetRevisions(int offset, int count)
+ {
+ if(FetchRevisionsCallback != null)
+ {
+ FetchRevisionsCallback(result);
+ }
+ }
+ }
+
+ internal class TestHistoryWindow : ICollabHistoryWindow
+ {
+ public IEnumerable items;
+
+ public bool revisionActionsEnabled { get; set; }
+ public int itemsPerPage { get; set; }
+ public string errMessage { get; set; }
+ public string inProgressRevision { get; set; }
+ public PageChangeAction OnPageChangeAction { get; set; }
+ public RevisionAction OnGoBackAction { get; set; }
+ public RevisionAction OnUpdateAction { get; set; }
+ public RevisionAction OnRestoreAction { get; set; }
+ public ShowBuildAction OnShowBuildAction { get; set; }
+ public Action OnShowServicesAction { get; set; }
+
+ public void UpdateState(HistoryState state, bool force)
+ {
+ }
+
+ public void UpdateRevisions(IEnumerable items, string tip, int totalRevisions, int currPage)
+ {
+ this.items = items;
+ }
+ }
+}
diff --git a/Tests/Editor/HistoryTests.cs.meta b/Tests/Editor/HistoryTests.cs.meta
new file mode 100644
index 0000000..d648a7f
--- /dev/null
+++ b/Tests/Editor/HistoryTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 23a56a19774ed42b6b65646af08a003c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Tests/Editor/Unity.CollabProxy.EditorTests.asmdef b/Tests/Editor/Unity.CollabProxy.EditorTests.asmdef
new file mode 100644
index 0000000..3467a9e
--- /dev/null
+++ b/Tests/Editor/Unity.CollabProxy.EditorTests.asmdef
@@ -0,0 +1,13 @@
+{
+ "name": "Unity.CollabProxy.EditorTests",
+ "references": [
+ "Unity.CollabProxy.Editor"
+ ],
+ "optionalUnityReferences": [
+ "TestAssemblies"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": []
+}
diff --git a/Tests/Editor/Unity.CollabProxy.EditorTests.asmdef.meta b/Tests/Editor/Unity.CollabProxy.EditorTests.asmdef.meta
new file mode 100644
index 0000000..57db5c7
--- /dev/null
+++ b/Tests/Editor/Unity.CollabProxy.EditorTests.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 782de34c17796430ba8d0ceddb60944e
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/package.json b/package.json
index 4fa9168..f0c7e8e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "com.unity.collab-proxy",
"displayName":"Unity Collaborate",
- "version": "1.2.6",
+ "version": "1.2.7",
"unity": "2018.3",
"description": "Collaborate is a simple way for teams to save, share, and sync their Unity project",
"keywords": ["collab", "collaborate", "teams", "team", "cloud", "backup"],