using System.Collections; using System.Collections.Generic; using System.Linq; using System.IO; using UnityEngine; using UnityEditor; using UnityEngine.Networking; using VRC.Core; public partial class VRCSdkControlPanel : EditorWindow { const int PageLimit = 20; static List uploadedAvatars = null; static List uploadedWorlds = null; static List testAvatars = null; public static Dictionary ImageCache = new Dictionary(); static List justDeletedContents; static List justUpdatedAvatars; static EditorCoroutine fetchingAvatars = null, fetchingWorlds = null; private static string searchString = ""; private static bool WorldsToggle = true; private static bool AvatarsToggle = true; private static bool TestAvatarsToggle = true; const int SCROLLBAR_RESERVED_REGION_WIDTH = 50; const int WORLD_DESCRIPTION_FIELD_WIDTH = 140; const int WORLD_IMAGE_BUTTON_WIDTH = 100; const int WORLD_IMAGE_BUTTON_HEIGHT = 100; const int WORLD_RELEASE_STATUS_FIELD_WIDTH = 150; const int COPY_WORLD_ID_BUTTON_WIDTH = 75; const int DELETE_WORLD_BUTTON_WIDTH = 75; const int WORLD_ALL_INFORMATION_MAX_WIDTH = WORLD_DESCRIPTION_FIELD_WIDTH + WORLD_IMAGE_BUTTON_WIDTH + WORLD_RELEASE_STATUS_FIELD_WIDTH + COPY_WORLD_ID_BUTTON_WIDTH + DELETE_WORLD_BUTTON_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; const int WORLD_REDUCED_INFORMATION_MAX_WIDTH = WORLD_DESCRIPTION_FIELD_WIDTH + WORLD_IMAGE_BUTTON_WIDTH + WORLD_RELEASE_STATUS_FIELD_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; const int AVATAR_DESCRIPTION_FIELD_WIDTH = 140; const int AVATAR_IMAGE_BUTTON_WIDTH = WORLD_IMAGE_BUTTON_WIDTH; const int AVATAR_IMAGE_BUTTON_HEIGHT = WORLD_IMAGE_BUTTON_HEIGHT; const int AVATAR_RELEASE_STATUS_FIELD_WIDTH = 150; const int SET_AVATAR_STATUS_BUTTON_WIDTH = 100; const int COPY_AVATAR_ID_BUTTON_WIDTH = COPY_WORLD_ID_BUTTON_WIDTH; const int DELETE_AVATAR_BUTTON_WIDTH = DELETE_WORLD_BUTTON_WIDTH; const int AVATAR_ALL_INFORMATION_MAX_WIDTH = AVATAR_DESCRIPTION_FIELD_WIDTH + AVATAR_IMAGE_BUTTON_WIDTH + AVATAR_RELEASE_STATUS_FIELD_WIDTH + SET_AVATAR_STATUS_BUTTON_WIDTH + COPY_AVATAR_ID_BUTTON_WIDTH + DELETE_AVATAR_BUTTON_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; const int AVATAR_REDUCED_INFORMATION_MAX_WIDTH = AVATAR_DESCRIPTION_FIELD_WIDTH + AVATAR_IMAGE_BUTTON_WIDTH + AVATAR_RELEASE_STATUS_FIELD_WIDTH + SCROLLBAR_RESERVED_REGION_WIDTH; const int MAX_ALL_INFORMATION_WIDTH = WORLD_ALL_INFORMATION_MAX_WIDTH > AVATAR_ALL_INFORMATION_MAX_WIDTH ? WORLD_ALL_INFORMATION_MAX_WIDTH : AVATAR_ALL_INFORMATION_MAX_WIDTH; const int MAX_REDUCED_INFORMATION_WIDTH = WORLD_REDUCED_INFORMATION_MAX_WIDTH > AVATAR_REDUCED_INFORMATION_MAX_WIDTH ? WORLD_REDUCED_INFORMATION_MAX_WIDTH : AVATAR_REDUCED_INFORMATION_MAX_WIDTH; public static void ClearContent() { if (uploadedWorlds != null) uploadedWorlds = null; if (uploadedAvatars != null) uploadedAvatars = null; if (testAvatars != null) testAvatars = null; ImageCache.Clear(); } IEnumerator FetchUploadedData() { if (!ConfigManager.RemoteConfig.IsInitialized()) ConfigManager.RemoteConfig.Init(); if (!APIUser.IsLoggedIn) yield break; ApiCache.ClearResponseCache(); VRCCachedWebRequest.ClearOld(); if (fetchingAvatars == null) fetchingAvatars = EditorCoroutine.Start(() => FetchAvatars()); if (fetchingWorlds == null) fetchingWorlds = EditorCoroutine.Start(() => FetchWorlds()); FetchTestAvatars(); } private static void FetchAvatars(int offset = 0) { ApiAvatar.FetchList( delegate (IEnumerable obj) { if (obj.FirstOrDefault() != null) fetchingAvatars = EditorCoroutine.Start(() => { var l = obj.ToList(); int count = l.Count; SetupAvatarData(l); FetchAvatars(offset + count); }); else { fetchingAvatars = null; foreach (ApiAvatar a in uploadedAvatars) DownloadImage(a.id, a.thumbnailImageUrl); } }, delegate (string obj) { Debug.LogError("Error fetching your uploaded avatars:\n" + obj); fetchingAvatars = null; }, ApiAvatar.Owner.Mine, ApiAvatar.ReleaseStatus.All, null, PageLimit, offset, ApiAvatar.SortHeading.None, ApiAvatar.SortOrder.Descending, null, null, true, false, null, false ); } private static void FetchTestAvatars() { #if VRC_SDK_VRCSDK3 string sdkAvatarFolder = VRC.SDKBase.Editor.VRC_SdkBuilder.GetKnownFolderPath(VRC.SDKBase.Editor.VRC_SdkBuilder.LocalLowGUID) + "/VRChat/vrchat/Avatars/"; string[] sdkavatars = Directory.GetFiles(sdkAvatarFolder); string filename = ""; List avatars = new List(); foreach(string sdkap in sdkavatars) { if(Path.GetExtension(sdkap) != ".vrca") continue; filename = Path.GetFileNameWithoutExtension(sdkap); ApiAvatar sdka = API.FromCacheOrNew("local:sdk_" + filename); sdka.assetUrl = sdkap; sdka.name = filename; sdka.releaseStatus = "public"; ApiAvatar.AddLocal(sdka); avatars.Add(sdka); } testAvatars = avatars; #else testAvatars = new List(); #endif } private static void FetchWorlds(int offset = 0) { ApiWorld.FetchList( delegate (IEnumerable obj) { if (obj.FirstOrDefault() != null) fetchingWorlds = EditorCoroutine.Start(() => { var l = obj.ToList(); int count = l.Count; SetupWorldData(l); FetchWorlds(offset + count); }); else { fetchingWorlds = null; foreach (ApiWorld w in uploadedWorlds) DownloadImage(w.id, w.thumbnailImageUrl); } }, delegate (string obj) { Debug.LogError("Error fetching your uploaded worlds:\n" + obj); fetchingWorlds = null; }, ApiWorld.SortHeading.Updated, ApiWorld.SortOwnership.Mine, ApiWorld.SortOrder.Descending, offset, PageLimit, "", null, null, null, null, "", ApiWorld.ReleaseStatus.All, null, null, true, false); } static void SetupWorldData(List worlds) { if (worlds == null || uploadedWorlds == null) return; worlds.RemoveAll(w => w == null || w.name == null || uploadedWorlds.Any(w2 => w2.id == w.id)); if (worlds.Count > 0) { uploadedWorlds.AddRange(worlds); uploadedWorlds.Sort((w1, w2) => w1.name.CompareTo(w2.name)); } } static void SetupAvatarData(List avatars) { if (avatars == null || uploadedAvatars == null ) return; avatars.RemoveAll(a => a == null || uploadedAvatars.Any(a2 => a2.id == a.id)); foreach(var avatar in avatars) { if (string.IsNullOrEmpty(avatar.name)) avatar.name = "(unnamed)"; } if (avatars.Count > 0) { uploadedAvatars.AddRange(avatars); uploadedAvatars.Sort((w1, w2) => w1.name.CompareTo(w2.name)); } } private static void DownloadImage(string id, string url) { if (string.IsNullOrEmpty(url)) { return; } if (ImageCache.ContainsKey(id) && ImageCache[id] != null) { return; } EditorCoroutine.Start(VRCCachedWebRequest.Get(url, OnDone)); void OnDone(Texture2D texture) { if (texture != null) { ImageCache[id] = texture; } else if (ImageCache.ContainsKey(id)) { ImageCache.Remove(id); } } } Vector2 contentScrollPos; bool OnGUIUserInfo() { bool updatedContent = false; if (!ConfigManager.RemoteConfig.IsInitialized()) ConfigManager.RemoteConfig.Init(); if (APIUser.IsLoggedIn && uploadedWorlds != null && uploadedAvatars != null && testAvatars != null) { bool expandedLayout = false; // (position.width > MAX_ALL_INFORMATION_WIDTH); // uncomment for future wide layouts if (!expandedLayout) { EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); } GUILayout.BeginHorizontal(); GUILayout.BeginVertical(searchBarStyle); EditorGUILayout.BeginHorizontal(); float searchFieldShrinkOffset = 30f; GUILayoutOption layoutOption = (expandedLayout ? GUILayout.Width(position.width - searchFieldShrinkOffset) : GUILayout.Width(SdkWindowWidth - searchFieldShrinkOffset)); searchString = EditorGUILayout.TextField(searchString, GUI.skin.FindStyle("SearchTextField"), layoutOption); GUIStyle searchButtonStyle = searchString == string.Empty ? GUI.skin.FindStyle("SearchCancelButtonEmpty") : GUI.skin.FindStyle("SearchCancelButton"); if (GUILayout.Button(string.Empty, searchButtonStyle)) { searchString = string.Empty; GUI.FocusControl(null); } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); if (!expandedLayout) { GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); } layoutOption = (expandedLayout ? GUILayout.Width(position.width) : GUILayout.Width(SdkWindowWidth)); contentScrollPos = EditorGUILayout.BeginScrollView(contentScrollPos, layoutOption); GUIStyle descriptionStyle = new GUIStyle(EditorStyles.wordWrappedLabel); descriptionStyle.wordWrap = true; if (uploadedWorlds.Count > 0) { EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("WORLDS", EditorStyles.boldLabel, GUILayout.ExpandWidth(false), GUILayout.Width(58)); WorldsToggle = EditorGUILayout.Foldout(WorldsToggle, new GUIContent("")); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); if (WorldsToggle) { List tmpWorlds = new List(); if (uploadedWorlds.Count > 0) tmpWorlds = new List(uploadedWorlds); foreach (ApiWorld w in tmpWorlds) { if (justDeletedContents != null && justDeletedContents.Contains(w.id)) { uploadedWorlds.Remove(w); continue; } if (!w.name.ToLowerInvariant().Contains(searchString.ToLowerInvariant())) { continue; } EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(GUILayout.Width(WORLD_IMAGE_BUTTON_WIDTH)); if (ImageCache.ContainsKey(w.id)) { if (GUILayout.Button(ImageCache[w.id], GUILayout.Height(WORLD_IMAGE_BUTTON_HEIGHT), GUILayout.Width(WORLD_IMAGE_BUTTON_WIDTH))) { Application.OpenURL(w.imageUrl); } } else { if (GUILayout.Button("", GUILayout.Height(WORLD_IMAGE_BUTTON_HEIGHT), GUILayout.Width(WORLD_IMAGE_BUTTON_WIDTH))) { Application.OpenURL(w.imageUrl); } } if (expandedLayout) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(w.name, descriptionStyle, GUILayout.Width(position.width - MAX_ALL_INFORMATION_WIDTH + WORLD_DESCRIPTION_FIELD_WIDTH)); } else { EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(w.name, descriptionStyle); } EditorGUILayout.LabelField("Release Status: " + w.releaseStatus, GUILayout.Width(WORLD_RELEASE_STATUS_FIELD_WIDTH)); if (GUILayout.Button("Copy ID", GUILayout.Width(COPY_WORLD_ID_BUTTON_WIDTH))) { TextEditor te = new TextEditor(); te.text = w.id; te.SelectAll(); te.Copy(); } if (GUILayout.Button("Delete", GUILayout.Width(DELETE_WORLD_BUTTON_WIDTH))) { if (EditorUtility.DisplayDialog("Delete " + w.name + "?", "Are you sure you want to delete " + w.name + "? This cannot be undone.", "Delete", "Cancel")) { foreach (VRC.Core.PipelineManager pm in FindObjectsOfType() .Where(pm => pm.blueprintId == w.id)) { pm.blueprintId = ""; pm.completedSDKPipeline = false; UnityEditor.EditorUtility.SetDirty(pm); UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(pm.gameObject.scene); UnityEditor.SceneManagement.EditorSceneManager.SaveScene(pm.gameObject.scene); } API.Delete(w.id); uploadedWorlds.RemoveAll(world => world.id == w.id); if (ImageCache.ContainsKey(w.id)) ImageCache.Remove(w.id); if (justDeletedContents == null) justDeletedContents = new List(); justDeletedContents.Add(w.id); updatedContent = true; } } if (expandedLayout) EditorGUILayout.EndHorizontal(); else EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); } } } if (uploadedAvatars.Count > 0) { EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("AVATARS", EditorStyles.boldLabel, GUILayout.ExpandWidth(false), GUILayout.Width(65)); AvatarsToggle = EditorGUILayout.Foldout(AvatarsToggle, new GUIContent("")); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); if (AvatarsToggle) { List tmpAvatars = new List(); if (uploadedAvatars.Count > 0) tmpAvatars = new List(uploadedAvatars); if (justUpdatedAvatars != null) { foreach (ApiAvatar a in justUpdatedAvatars) { int index = tmpAvatars.FindIndex((av) => av.id == a.id); if (index != -1) tmpAvatars[index] = a; } } foreach (ApiAvatar a in tmpAvatars) { if (justDeletedContents != null && justDeletedContents.Contains(a.id)) { uploadedAvatars.Remove(a); continue; } if (!a.name.ToLowerInvariant().Contains(searchString.ToLowerInvariant())) { continue; } EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(GUILayout.Width(AVATAR_DESCRIPTION_FIELD_WIDTH)); if (ImageCache.ContainsKey(a.id)) { if (GUILayout.Button(ImageCache[a.id], GUILayout.Height(AVATAR_IMAGE_BUTTON_HEIGHT), GUILayout.Width(AVATAR_IMAGE_BUTTON_WIDTH))) { Application.OpenURL(a.imageUrl); } } else { if (GUILayout.Button("", GUILayout.Height(AVATAR_IMAGE_BUTTON_HEIGHT), GUILayout.Width(AVATAR_IMAGE_BUTTON_WIDTH))) { Application.OpenURL(a.imageUrl); } } if (expandedLayout) EditorGUILayout.BeginHorizontal(); else EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(a.name, descriptionStyle, GUILayout.Width(expandedLayout ? position.width - MAX_ALL_INFORMATION_WIDTH + AVATAR_DESCRIPTION_FIELD_WIDTH : AVATAR_DESCRIPTION_FIELD_WIDTH)); EditorGUILayout.LabelField("Release Status: " + a.releaseStatus, GUILayout.Width(AVATAR_RELEASE_STATUS_FIELD_WIDTH)); string oppositeReleaseStatus = a.releaseStatus == "public" ? "private" : "public"; if (GUILayout.Button("Make " + oppositeReleaseStatus, GUILayout.Width(SET_AVATAR_STATUS_BUTTON_WIDTH))) { a.releaseStatus = oppositeReleaseStatus; a.SaveReleaseStatus((c) => { ApiAvatar savedBP = (ApiAvatar) c.Model; if (justUpdatedAvatars == null) justUpdatedAvatars = new List(); justUpdatedAvatars.Add(savedBP); }, (c) => { Debug.LogError(c.Error); EditorUtility.DisplayDialog("Avatar Updated", "Failed to change avatar release status", "OK"); }); } if (GUILayout.Button("Copy ID", GUILayout.Width(COPY_AVATAR_ID_BUTTON_WIDTH))) { TextEditor te = new TextEditor(); te.text = a.id; te.SelectAll(); te.Copy(); } if (GUILayout.Button("Delete", GUILayout.Width(DELETE_AVATAR_BUTTON_WIDTH))) { if (EditorUtility.DisplayDialog("Delete " + a.name + "?", "Are you sure you want to delete " + a.name + "? This cannot be undone.", "Delete", "Cancel")) { foreach (VRC.Core.PipelineManager pm in FindObjectsOfType() .Where(pm => pm.blueprintId == a.id)) { pm.blueprintId = ""; pm.completedSDKPipeline = false; UnityEditor.EditorUtility.SetDirty(pm); UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(pm.gameObject.scene); UnityEditor.SceneManagement.EditorSceneManager.SaveScene(pm.gameObject.scene); } API.Delete(a.id); uploadedAvatars.RemoveAll(avatar => avatar.id == a.id); if (ImageCache.ContainsKey(a.id)) ImageCache.Remove(a.id); if (justDeletedContents == null) justDeletedContents = new List(); justDeletedContents.Add(a.id); updatedContent = true; } } if (expandedLayout) EditorGUILayout.EndHorizontal(); else EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); } } } if (testAvatars.Count > 0) { EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Test Avatars", EditorStyles.boldLabel, GUILayout.ExpandWidth(false), GUILayout.Width(100)); TestAvatarsToggle = EditorGUILayout.Foldout(TestAvatarsToggle, new GUIContent("")); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); if (TestAvatarsToggle) { List tmpAvatars = new List(); if (testAvatars.Count > 0) tmpAvatars = new List(testAvatars); foreach (ApiAvatar a in tmpAvatars) { if (!a.name.ToLowerInvariant().Contains(searchString.ToLowerInvariant())) { continue; } EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); if (expandedLayout) EditorGUILayout.BeginHorizontal(); else EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(a.name, descriptionStyle, GUILayout.Width(expandedLayout ? position.width - MAX_ALL_INFORMATION_WIDTH + AVATAR_DESCRIPTION_FIELD_WIDTH : AVATAR_DESCRIPTION_FIELD_WIDTH)); if (GUILayout.Button("Delete", GUILayout.Width(DELETE_AVATAR_BUTTON_WIDTH))) { if (EditorUtility.DisplayDialog("Delete " + a.name + "?", "Are you sure you want to delete " + a.name + "? This cannot be undone.", "Delete", "Cancel")) { API.Delete(a.id); testAvatars.RemoveAll(avatar => avatar.id == a.id); File.Delete(a.assetUrl); updatedContent = true; } } if (expandedLayout) EditorGUILayout.EndHorizontal(); else EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); } } } EditorGUILayout.EndScrollView(); if (!expandedLayout) { GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } if ((updatedContent) && (null != window)) window.Reset(); return true; } else { return false; } } void ShowContent() { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical(); if (uploadedWorlds == null || uploadedAvatars == null || testAvatars == null) { if (uploadedWorlds == null) uploadedWorlds = new List(); if (uploadedAvatars == null) uploadedAvatars = new List(); if (testAvatars == null) testAvatars = new List(); EditorCoroutine.Start(FetchUploadedData()); } if( fetchingWorlds != null || fetchingAvatars != null ) { GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth)); EditorGUILayout.Space(); EditorGUILayout.LabelField("Fetching Records", titleGuiStyle); EditorGUILayout.Space(); GUILayout.EndVertical(); } else { GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth)); EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); GUILayout.Label("Fetch updated records from the VRChat server"); if( GUILayout.Button("Fetch") ) { ClearContent(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); GUILayout.EndVertical(); } GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); OnGUIUserInfo(); } }