Initial commit

This commit is contained in:
AbstractConcept 2022-09-13 00:36:34 -05:00
commit 3c7cc0c973
8391 changed files with 704313 additions and 0 deletions

View file

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
using UnityEngine;
[assembly: InternalsVisibleTo("Unity.CollabProxy.EditorTests")]

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d4ef26aa386b44923b61c9c4b505a67c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c18cb9388313e4287ad5895ee735c47d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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
});
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8aa8171e088f94069bbd1978a053f7dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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";
};
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f944311c8fff2479fa3ba741f6039fc8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,330 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEditor.Collaboration;
#if UNITY_2019_1_OR_NEWER
using UnityEditor.UIElements;
using UnityEngine.UIElements;
#else
using UnityEditor.Experimental.UIElements;
using UnityEngine.Experimental.UIElements;
using UnityEngine.Experimental.UIElements.StyleEnums;
#endif
using UnityEngine;
using UnityEditor.Connect;
namespace UnityEditor
{
internal class CollabHistoryWindow : EditorWindow, ICollabHistoryWindow
{
#if UNITY_2019_1_OR_NEWER
private const string ResourcesPath = "Packages/com.unity.collab-proxy/Editor/Resources/Styles/";
#else
private const string ResourcesPath = "StyleSheets/";
#endif
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<CollabHistoryWindow>(kWindowTitle);
}
[MenuItem("Window/Asset Management/Collab History", true)]
public static bool ValidateShowHistoryWindow()
{
return Collab.instance.IsCollabEnabledForCurrentProject();
}
CollabHistoryPresenter m_Presenter;
Dictionary<HistoryState, VisualElement> m_Views;
List<CollabHistoryItem> m_HistoryItems = new List<CollabHistoryItem>();
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;
}
}
}
private void AddStyleSheetPath(VisualElement root, string path)
{
#if UNITY_2019_1_OR_NEWER
root.styleSheets.Add(EditorGUIUtility.Load(path) as StyleSheet);
#else
root.AddStyleSheetPath(path);
#endif
}
public void SetupGUI()
{
#if UNITY_2019_1_OR_NEWER
var root = this.rootVisualElement;
#else
var root = this.GetRootVisualContainer();
#endif
AddStyleSheetPath(root, ResourcesPath + "CollabHistoryCommon.uss");
if (EditorGUIUtility.isProSkin)
{
AddStyleSheetPath(root, ResourcesPath + "CollabHistoryDark.uss");
}
else
{
AddStyleSheetPath(root, ResourcesPath + "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, VisualElement>()
{
{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<RevisionData> datas, string tip, int totalRevisions, int currentPage)
{
var elements = new List<VisualElement>();
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 VisualElement();
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();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fed9dda667cab45d398d06402bba03f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Linq;
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
}
private class CollabToolbarContent
{
readonly string m_iconName;
readonly string m_toolTip;
readonly CollabToolbarState m_state;
static Dictionary<CollabToolbarContent, GUIContent> m_CollabIcons;
public CollabToolbarState RegisteredForState
{
get { return m_state; }
}
public GUIContent GuiContent
{
get
{
if (m_CollabIcons == null)
{
m_CollabIcons = new Dictionary<CollabToolbarContent, GUIContent>();
}
if (!m_CollabIcons.ContainsKey(this))
{
m_CollabIcons.Add(this, EditorGUIUtility.TrTextContentWithIcon("Collab", m_toolTip, m_iconName));
}
return m_CollabIcons[this];
}
}
public CollabToolbarContent(CollabToolbarState state, string iconName, string toolTip)
{
m_state = state;
m_iconName = iconName;
m_toolTip = toolTip;
}
}
CollabToolbarContent[] m_toolbarContents;
CollabToolbarState m_CollabToolbarState = CollabToolbarState.UpToDate;
const float kCollabButtonWidth = 78.0f;
ButtonWithAnimatedIconRotation m_CollabButton;
string m_DynamicTooltip;
static bool m_ShowCollabTooltip = false;
private GUIContent currentCollabContent
{
get
{
CollabToolbarContent toolbarContent =
m_toolbarContents.FirstOrDefault(c => c.RegisteredForState.Equals(m_CollabToolbarState));
GUIContent content = new GUIContent(toolbarContent == null? m_toolbarContents.First().GuiContent : toolbarContent.GuiContent);
if (!m_ShowCollabTooltip)
{
content.tooltip = null;
}
else if (m_DynamicTooltip != "")
{
content.tooltip = m_DynamicTooltip;
}
if (Collab.instance.AreTestsRunning())
{
content.text = "CTF";
}
return content;
}
}
public CollabToolbarButton()
{
m_toolbarContents = new[]
{
new CollabToolbarContent(CollabToolbarState.NeedToEnableCollab, "CollabNew", " You need to enable collab."),
new CollabToolbarContent(CollabToolbarState.UpToDate, "Collab", " You are up to date."),
new CollabToolbarContent(CollabToolbarState.Conflict, "CollabConflict", " Please fix your conflicts prior to publishing."),
new CollabToolbarContent(CollabToolbarState.OperationError, "CollabError", " Last operation failed. Please retry later."),
new CollabToolbarContent(CollabToolbarState.ServerHasChanges, "CollabPull", " Please update, there are server changes."),
new CollabToolbarContent(CollabToolbarState.FilesToPush, "CollabPush", " You have files to publish."),
new CollabToolbarContent(CollabToolbarState.InProgress, "CollabProgress", " Operation in progress."),
new CollabToolbarContent(CollabToolbarState.Disabled, "CollabNew", " Collab is disabled."),
new CollabToolbarContent(CollabToolbarState.Offline, "CollabNew", " Please check your network connection.")
};
Collab.instance.StateChanged += OnCollabStateChanged;
UnityConnect.instance.StateChanged += OnUnityConnectStateChanged;
UnityConnect.instance.UserStateChanged += OnUnityConnectUserStateChanged;
}
void OnUnityConnectUserStateChanged(UserInfo state)
{
UpdateCollabToolbarState();
}
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 (GetCollabButton().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;
}
ButtonWithAnimatedIconRotation GetCollabButton()
{
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);
}
return m_CollabButton;
}
public void Dispose()
{
Collab.instance.StateChanged -= OnCollabStateChanged;
UnityConnect.instance.StateChanged -= OnUnityConnectStateChanged;
UnityConnect.instance.UserStateChanged -= OnUnityConnectUserStateChanged;
if (m_CollabButton != null)
m_CollabButton.Clear();
}
}
} // namespace

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 882f1a4147a284f028899b9c018e63eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,137 @@
using UnityEngine;
using UnityEditor.Collaboration;
using UnityEditor.Web;
using UnityEditor.Connect;
namespace UnityEditor
{
[InitializeOnLoad]
internal class WebViewStatic : ScriptableSingleton<WebViewStatic>
{
[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<CollabToolbarWindow>())
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<CollabToolbarWindow>(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<CollabToolbarWindow>() 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();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f516f1ec21a54a59a92bf99db2d9535
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d437fe60bb34f45728664a5d930c1635
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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<RevisionData> 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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7c91a123806d41a0873fcdcb629b1c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fd0a39b4d296d4d509b4f1dbd08d0630
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using System;
using UnityEditor;
using UnityEditor.Collaboration;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0217a80286f79419daa202f69409f19b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,78 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEditor.Connect;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
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<ChangeData> 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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a483595b0257945278dc75c5ff7d82ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using System;
using System.IO;
using System.Linq;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d912d4873af534bd4a9d44bf1b52f14e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,229 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using UnityEditor.Connect;
using UnityEditor.Web;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
using UnityEngine.Experimental.UIElements.StyleEnums;
#endif
namespace UnityEditor.Collaboration
{
internal class CollabHistoryItem : VisualElement
{
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 VisualElement 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 VisualElement {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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4c1445ee948a4124bfa9fb818a17e36
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Collaboration;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
using UnityEngine.Experimental.UIElements.StyleEnums;
#endif
namespace UnityEditor.Collaboration
{
internal class CollabHistoryItemFactory : ICollabHistoryItemFactory
{
const int k_MaxChangesPerRevision = 10;
public IEnumerable<RevisionData> GenerateElements(IEnumerable<Revision> 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<string, ChangeData>();
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fc46f91ea1e8e4ca2ab693fef9156dbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,94 @@
using System;
using UnityEditor;
using UnityEditor.Collaboration;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c737f7a9d78541d1ab25f28f045dd32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,69 @@
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
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();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf6aca931950a4a6a886e214e9e649c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using UnityEditor.Collaboration;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
namespace UnityEditor.Collaboration
{
internal interface ICollabHistoryItemFactory
{
IEnumerable<RevisionData> GenerateElements(IEnumerable<Revision> revsRevisions, int mTotalRevisions, int startIndex, string tipRev, string inProgressRevision, bool revisionActionsEnabled, bool buildServiceEnabled, string currentUser);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 821f5482c5a3f4389885f4432433f56f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
using UnityEngine.Experimental.UIElements.StyleEnums;
#endif
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<VisualElement> m_Items;
int m_TotalItems;
int m_CurPage;
public int pageSize
{
set { m_PageSize = value; }
}
public IEnumerable<VisualElement> 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<VisualElement>();
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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50de529b6a28f4a7093045e08810a5df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,88 @@
using System;
using UnityEditor;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
using UnityEngine.Experimental.UIElements.StyleEnums;
#endif
namespace UnityEditor.Collaboration
{
internal class StatusView : VisualElement
{
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 = 0f }};
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();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08e9894bdf0834710b22d3c0aa245ac0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a6ab6fd2b91214e8a9c8ec2224a528de
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b1ae1e78552c459d9ce27048ff51c7f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,259 @@
.unity-button {
min-height:0;
-unity-text-align:middle-center;
margin-left:4px;
margin-top:3px;
margin-right:4px;
margin-bottom:3px;
border-left-width:6px;
border-top-width:4px;
border-right-width:6px;
border-bottom-width:4px;
padding-left:6px;
padding-top:2px;
padding-right:6px;
padding-bottom:3px;
}
.unity-label {
overflow: hidden;
margin-left:4px;
margin-top:2px;
margin-right:4px;
margin-bottom:2px;
padding-left:2px;
padding-top:1px;
min-height: 0;
}
#HistoryContainer {
flex: 1 0 0;
}
#HistoryItem {
flex: 1 0 0;
flex-direction: row;
}
#HistoryDetail {
margin-top: 10px;
margin-left: 10px;
margin-bottom: 10px;
margin-right: 10px;
padding-top: 4px;
flex: 1 0 0;
}
#Author {
-unity-font-style: bold;
font-size: 12px;
}
#HistoryDetail > Button {
align-self: flex-end;
}
CollabHistoryRevisionLine {
width: 40px;
}
#RevisionLine {
flex: 1 0 0;
margin-left: 35px;
width: 1.5px;
}
#RevisionLine.topLine {
height: 20px;
flex: 0 0 auto;
}
#RevisionLine.absentDateLine {
background-color: #797676;
}
.absentRevision #RevisionLine {
background-color: #797676;
}
.currentRevision #RevisionLine.topLine {
background-color: #797676;
}
#RevisionIndex {
position: absolute;
min-width: 23px;
-unity-text-align: middle-right;
top: 15.8px;
font-size: 9px;
}
#RevisionIndicator {
position: absolute;
background-color: #000;
border-radius: 3px;
width: 8px;
height: 8px;
border-bottom-width: 2px;
border-left-width: 2px;
border-right-width: 2px;
border-top-width: 2px;
top: 20px;
left: 32px;
}
.revisionLineHeader {
width: 200px;
height: 20px;
}
.revisionLineHeader > .unity-label {
position: absolute;
margin-left: 47px;
margin-top: 3px;
}
#PagerItems {
flex-direction: column;
}
PagerElement > .unity-label {
margin-top: 8px;
}
.absentRevision #RevisionIndicator {
border-color: #797676;
}
.absentRevision #RevisionIndex {
color: #797676;
}
.currentRevision #HistoryDetail {
border-top-width: 2px;
}
#HistoryItem #RevisionDescription {
white-space: normal;
}
#HistoryItem #ToggleDescription {
align-self: flex-start;
padding-top: 0;
padding-left: 0;
padding-right: 0;
padding-bottom: 2px;
}
#HistoryItem #ActionButton {
position: absolute;
right: 0;
}
#HistoryItem #BuildIcon {
width: 16px;
height: 13px;
}
#HistoryItemActionsTray {
flex: 1 0 0;
flex-direction: row;
align-items: center;
height: 38px;
margin-left: 10px;
margin-right: 10px;
}
CollabHistoryDropDown {
border-top-width: 1px;
}
CollabHistoryDropDown > .unity-label {
padding-top: 10px;
padding-bottom: 10px;
}
CollabHistoryDropDownItem {
flex-direction: row;
border-top-width: 1px;
overflow: hidden;
}
#FileIcon {
align-self: center;
width: 26px;
height: 26px;
}
#FileName {
-unity-font-style: bold;
padding-bottom: 0;
margin-bottom: 0;
}
#FileIcon {
padding-top: 0;
margin-top: 0;
}
#ErrorBar {
height: 24px;
background-color: #ff0000;
color: #000;
font-size: 12px;
}
#ErrorBar > #CloseButton {
position: absolute;
right: 0;
top: 0;
width: 24px;
height: 24px;
color: #000;
font-size: 18px;
-unity-font-style: bold;
}
#StatusView {
flex-direction: column;
justify-content: center;
align-self: center;
align-items: center;
flex: 1 0 0;
}
#StatusView > #StatusIcon {
width: 115px;
height: 150px;
}
#StatusView > #StatusMessage {
font-size: 22px;
width: 230px;
white-space: normal;
-unity-text-align: middle-center;
}
#StatusView > #StatusButton {
font-size: 12px;
margin-top: 20px;
background-image: none;
width: 108px;
height: 29px;
}
BuildStatusButton.unity-button {
flex-direction: row;
align-self: flex-end;
align-items: center;
margin-right: 10px;
padding-left:0;
padding-top:0;
padding-right:0;
padding-bottom:0;
}
BuildStatusButton.unity-button .unity-label {
padding-left: 2px;
}

View file

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3a2d94c8977984b67984caeff9fa666e
ScriptedImporter:
fileIDToRecycleName:
11400000: stylesheet
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}

View file

@ -0,0 +1,86 @@
#HistoryContainer {
background-color: #292929;
}
.obtainedRevision #HistoryDetail {
background-color: #333;
}
.absentRevision #HistoryDetail {
background-color: #595959;
}
#StatusView {
background-color: #292929;
}
#StatusView > #StatusMessage {
color: #959995;
}
BuildStatusButton.unity-button {
color: #B4B4B4;
background-image: resource("Builtin Skins/DarkSkin/Images/btn.png");
}
BuildStatusButton.unity-button:hover {
color: #FFF;
}
BuildStatusButton.unity-button:hover:active {
background-image: resource("Builtin Skins/DarkSkin/Images/btn act.png");
}
BuildStatusButton.unity-button:checked {
color: #F0F0F0;
background-image: resource("Builtin Skins/DarkSkin/Images/btn on.png");
}
BuildStatusButton.unity-button:hover:checked {
color: #FFF;
}
BuildStatusButton.unity-button:hover:active:checked {
background-image: resource("Builtin Skins/DarkSkin/Images/btn onact.png");
}
BuildStatusButton.unity-button:focus:checked {
background-image: resource("Builtin Skins/DarkSkin/Images/btn on focus.png");
}
CollabHistoryDropDown {
border-color: #292929;
}
CollabHistoryDropDownItem {
border-color: #292929;
}
#RevisionLine.obtainedDateLine {
background-color: #0cb4cc;
}
.obtainedRevision #RevisionLine {
background-color: #0cb4cc;
}
#RevisionIndex {
color: #0cb4cc;
}
#RevisionIndicator {
border-color: #0cb4cc;
}
.currentRevision #RevisionIndicator {
background-color: #0cb4cc;
}
.currentRevision #HistoryDetail {
border-color: #0cb4cc;
}
#StatusView > #StatusButton {
background-color: #0cb4cc;
border-color: #0cb4cc;
}

View file

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 70d4d75a2877243758b0750cbc75b6eb
ScriptedImporter:
fileIDToRecycleName:
11400000: stylesheet
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}

View file

@ -0,0 +1,86 @@
#HistoryContainer {
background-color: #a2a2a2;
}
.obtainedRevision #HistoryDetail {
background-color: #c2c2c2;
}
.absentRevision #HistoryDetail {
background-color: #dedede;
}
#StatusView {
background-color: #a2a2a3;
}
#StatusView > #StatusMessage {
color: #000;
}
BuildStatusButton.unity-button {
color: #111;
background-image: resource("Builtin Skins/LightSkin/Images/btn.png");
}
BuildStatusButton.unity-button:hover {
color: #000;
}
BuildStatusButton.unity-button:hover:active {
background-image: resource("Builtin Skins/LightSkin/Images/btn act.png");
}
BuildStatusButton.unity-button:checked {
color: #F0F0F0;
background-image: resource("Builtin Skins/LightSkin/Images/btn on.png");
}
BuildStatusButton.unity-button:hover:checked {
color: #000;
}
BuildStatusButton.unity-button:hover:active:checked {
background-image: resource("Builtin Skins/LightSkin/Images/btn onact.png");
}
BuildStatusButton.unity-button:focus:checked {
background-image: resource("Builtin Skins/LightSkin/Images/btn on focus.png");
}
CollabHistoryDropDown {
border-color: #a2a2a2;
}
CollabHistoryDropDownItem {
border-color: #a2a2a2;
}
#RevisionLine.obtainedDateLine {
background-color: #018d98;
}
.obtainedRevision #RevisionLine {
background-color: #018d98;
}
#RevisionIndex {
color: #018d98;
}
#RevisionIndicator {
border-color: #018d98;
}
.currentRevision #RevisionIndicator {
background-color: #018d98;
}
.currentRevision #HistoryDetail {
border-color: #018d98;
}
#StatusView > #StatusButton {
background-color: #018d98;
border-color: #018d98;
}

View file

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b52bde26a83564960bcb90217f72b910
ScriptedImporter:
fileIDToRecycleName:
11400000: stylesheet
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}

View file

@ -0,0 +1,7 @@
{
"name": "Unity.CollabProxy.Editor",
"includePlatforms": [
"Editor"
],
"excludePlatforms": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 645165c8169474bfbbeb8fb0bcfd26f5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: