Added Unity project files
This commit is contained in:
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a3b976fcbb135046afbf6894a86382a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d8ee2f423f373f489148ca12b9a44ef
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 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:
|
||||
@ -0,0 +1,17 @@
|
||||
namespace VRC.ExampleCentral.Types.Algolia
|
||||
{
|
||||
public class UnityPackage
|
||||
{
|
||||
public string AssetId { get; set; }
|
||||
public string AssetRevision { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string UnityPackageFile { get; set; }
|
||||
public string ThumbnailImage { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Description { get; set; }
|
||||
[Newtonsoft.Json.JsonProperty("_tags")]
|
||||
public string[] Tags { get; set; }
|
||||
public string DocsLink { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe2092d56e9eb434285274ef1583ecaa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73a1f0f944d9faf40905cd89382d8617
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,104 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace VRC.ExampleCentral.Window
|
||||
{
|
||||
public static class ExampleCentralSettings
|
||||
{
|
||||
public static readonly string customSettingsPath = "VRChat/Example Central";
|
||||
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateExampleCentralSettingsProvider()
|
||||
{
|
||||
var provider = new SettingsProvider(customSettingsPath, SettingsScope.Project)
|
||||
{
|
||||
label = "Example Central",
|
||||
|
||||
// This method will draw the UI for the settings
|
||||
activateHandler = (searchContext, rootElement) =>
|
||||
{
|
||||
var settings = Data.Instance;
|
||||
|
||||
var styleSheet = Resources.Load<StyleSheet>("ExampleCentralSettingsStyle");
|
||||
rootElement.styleSheets.Add(styleSheet);
|
||||
|
||||
var container = new VisualElement()
|
||||
{
|
||||
name = "container",
|
||||
};
|
||||
rootElement.Add(container);
|
||||
|
||||
var titleLabel = new Label("Example Central Settings")
|
||||
{
|
||||
name = "title-label"
|
||||
};
|
||||
|
||||
container.Add(titleLabel);
|
||||
|
||||
var showCEField = new Toggle("Show Creator Economy Examples")
|
||||
{
|
||||
value = settings.ShowEconomyPackages,
|
||||
name = "show-economy-packages-field"
|
||||
};
|
||||
showCEField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
settings.ShowEconomyPackages = evt.newValue;
|
||||
settings.Save();
|
||||
|
||||
var existingExampleCentralWindows = Resources.FindObjectsOfTypeAll<ExampleDownloaderPanel>();
|
||||
if (existingExampleCentralWindows.Length > 0)
|
||||
{
|
||||
var target = existingExampleCentralWindows[0];
|
||||
target.Refresh();
|
||||
}
|
||||
});
|
||||
container.Add(showCEField);
|
||||
},
|
||||
|
||||
// Populate the search keywords to enable smart search filtering and label highlighting
|
||||
keywords = new[] { "example", "central", "vrchat", "show creator economy examples" }
|
||||
};
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static void OpenSettings()
|
||||
{
|
||||
// Open the Project Settings window with these settings selected
|
||||
SettingsService.OpenProjectSettings(customSettingsPath);
|
||||
}
|
||||
|
||||
// Uses simple embedded Data class which saves and loads from EditorPrefs
|
||||
public class Data
|
||||
{
|
||||
public bool ShowEconomyPackages;
|
||||
|
||||
private static Data _data;
|
||||
|
||||
public static Data Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
// Create if needed
|
||||
if(_data == null)
|
||||
{
|
||||
_data = new Data();
|
||||
|
||||
// Set properties from EditorPrefs
|
||||
_data.ShowEconomyPackages = EditorPrefs.GetBool(nameof(ShowEconomyPackages));
|
||||
}
|
||||
return _data;
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
EditorPrefs.SetBool(nameof(ShowEconomyPackages), ShowEconomyPackages);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76b1e1dae078236489b5d0c483ea0d69
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Algolia.Search.Clients;
|
||||
using Algolia.Search.Models.Search;
|
||||
using UnityEditor.UIElements;
|
||||
using VRC.Core;
|
||||
using AlgoliaPackage = VRC.ExampleCentral.Types.Algolia.UnityPackage;
|
||||
using Button = UnityEngine.UIElements.Button;
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
namespace VRC.ExampleCentral.Window
|
||||
{
|
||||
|
||||
public class ExampleDownloaderPanel : EditorWindow
|
||||
{
|
||||
|
||||
[SerializeField] private VisualTreeAsset VisualTree;
|
||||
|
||||
// Visual Elements, cached after creation
|
||||
private VisualElement goToLoginView;
|
||||
private VisualElement mainView;
|
||||
private VisualElement previewThumbnail;
|
||||
private Label previewTitle;
|
||||
private Label previewTags;
|
||||
private Label previewVersion;
|
||||
private Label previewDescription;
|
||||
private Button previewDownloadButton;
|
||||
private Button previewDocsButtons;
|
||||
private ToolbarSearchField searchField;
|
||||
private VisualElement examplesScrollviewContainer;
|
||||
private Button settingsButton;
|
||||
|
||||
// Searching
|
||||
private CancellationTokenSource typingCancellation;
|
||||
private int searchDelayMs = 500;
|
||||
|
||||
private AlgoliaPackage selectedPackage;
|
||||
private PackageButton selectedButton;
|
||||
|
||||
// Algolia Info
|
||||
public const string AlgoliaSearchKey = "60b185254097bf630e913ceaf103822a";
|
||||
public const string AlgoliaAppKey = "P787M8AJR8";
|
||||
public const string AlgoliaIndexName = "unity-packages";
|
||||
|
||||
// Analytics rate-limiting
|
||||
private const int EVENT_LIMIT_EXAMPLE_PREVIEWED = 10;
|
||||
private const int TIME_LIMIT_EXAMPLE_PREVIEWED = 60;
|
||||
private int eventCounterExamplePreviewed;
|
||||
private DateTime lastResetTimeExamplePreviewed = DateTime.Now;
|
||||
|
||||
#region Setup and Basics
|
||||
|
||||
|
||||
[MenuItem("VRChat SDK/🏠 Example Central", false, 980)]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
ExampleDownloaderPanel window = GetWindow<ExampleDownloaderPanel>();
|
||||
window.titleContent = new GUIContent("VRChat Example Central");
|
||||
window.Show();
|
||||
|
||||
AnalyticsSDK.ExampleCentralOpened();
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
// Set up base layout
|
||||
VisualTree.CloneTree(rootVisualElement);
|
||||
|
||||
// Get top-level UI containers
|
||||
goToLoginView = rootVisualElement.Query<VisualElement>("go-to-login");
|
||||
mainView = rootVisualElement.Query<VisualElement>("split-main");
|
||||
|
||||
// Get Auth View elements
|
||||
Button goToLoginButton = goToLoginView.Query<Button>("go-to-login-btn");
|
||||
|
||||
// If the user is already logged in, enable main view
|
||||
if (APIUser.IsLoggedIn)
|
||||
OnUserLoggedIn(null, null);
|
||||
|
||||
// Get Listing elements
|
||||
searchField = rootVisualElement.Query<ToolbarSearchField>("examples-search-field");
|
||||
searchField.RegisterValueChangedCallback(CheckSearch);
|
||||
|
||||
// Get Example elements
|
||||
examplesScrollviewContainer = rootVisualElement.Query<VisualElement>("examples-scrollview-container");
|
||||
examplesScrollviewContainer.Clear();
|
||||
|
||||
// Get Preview elements
|
||||
previewThumbnail = rootVisualElement.Query<VisualElement>("preview-thumbnail");
|
||||
previewTitle = rootVisualElement.Query<Label>("preview-title");
|
||||
previewTags = rootVisualElement.Query<Label>("preview-tags");
|
||||
previewVersion = rootVisualElement.Query<Label>("preview-version");
|
||||
previewDescription = rootVisualElement.Query<Label>("preview-description");
|
||||
previewDownloadButton = rootVisualElement.Query<Button>("preview-download");
|
||||
previewDocsButtons = rootVisualElement.Query<Button>("preview-documentation");
|
||||
|
||||
previewDownloadButton.clicked += DownloadSelectedPackage;
|
||||
previewDocsButtons.clicked += OpenSelectedPackageDocs;
|
||||
goToLoginButton.clicked += OpenControlPanel;
|
||||
|
||||
VRCSdkControlPanel.OnPanelLoggedIn += OnUserLoggedIn;
|
||||
VRCSdkControlPanel.OnPanelLoggedOut += OnUserLoggedOut;
|
||||
|
||||
settingsButton = rootVisualElement.Query<Button>("settings-button");
|
||||
var icon = Resources.Load<Texture2D>("gear");
|
||||
settingsButton.style.backgroundImage = new StyleBackground(icon);
|
||||
settingsButton.clicked += ExampleCentralSettings.OpenSettings;
|
||||
|
||||
// Set the default preview.
|
||||
DemoPreview();
|
||||
|
||||
// Fetch and display UnityPackages
|
||||
UpdatePackagesAsync();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
previewDownloadButton.clicked -= DownloadSelectedPackage;
|
||||
previewDocsButtons.clicked -= OpenSelectedPackageDocs;
|
||||
|
||||
VRCSdkControlPanel.OnPanelLoggedIn -= OnUserLoggedIn;
|
||||
VRCSdkControlPanel.OnPanelLoggedOut -= OnUserLoggedOut;
|
||||
}
|
||||
|
||||
private static void OpenControlPanel() => VRCSdkControlPanel.ShowControlPanel();
|
||||
private void OnUserLoggedIn(object _, APIUser __) => ToggleView(true);
|
||||
private void OnUserLoggedOut(object _, EventArgs __) => ToggleView(false);
|
||||
private void ToggleView(bool isUserLoggedIn)
|
||||
{
|
||||
goToLoginView.style.display = isUserLoggedIn ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
mainView.style.display = isUserLoggedIn ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void OpenSelectedPackageDocs()
|
||||
{
|
||||
if (selectedPackage == null) return;
|
||||
Application.OpenURL($"{selectedPackage.DocsLink}");
|
||||
}
|
||||
|
||||
private void DemoPreview()
|
||||
{
|
||||
previewTitle.text = "Select a package on the left to view and import.";
|
||||
previewVersion.text = "";
|
||||
previewTags.text = "";
|
||||
previewDescription.text = "";
|
||||
previewThumbnail.style.backgroundImage = new StyleBackground();
|
||||
}
|
||||
|
||||
private void AddPackageToList(AlgoliaPackage unityPackage)
|
||||
{
|
||||
PackageButton packageButton = new PackageButton();
|
||||
packageButton.name = unityPackage.Title;
|
||||
packageButton.PackageLabel.text = unityPackage.Title;
|
||||
packageButton.clicked += () =>
|
||||
{
|
||||
if (selectedButton != null) selectedButton.Select(false);
|
||||
selectedButton = packageButton;
|
||||
selectedButton.Select(true);
|
||||
|
||||
SelectExample(unityPackage);
|
||||
};
|
||||
|
||||
examplesScrollviewContainer.Add(packageButton);
|
||||
}
|
||||
|
||||
private void SelectExample(AlgoliaPackage unityPackage)
|
||||
{
|
||||
if (selectedPackage == unityPackage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedPackage = unityPackage;
|
||||
|
||||
previewTitle.text = unityPackage.Title;
|
||||
previewVersion.text = $"Version: {unityPackage.Version}";
|
||||
previewTags.text = $"Tags: {string.Join(',', unityPackage.Tags)}";
|
||||
previewDescription.text = unityPackage.Description;
|
||||
|
||||
string thumbnailPath = DownloadThumbnail(unityPackage.ThumbnailImage);
|
||||
previewThumbnail.style.backgroundImage = new StyleBackground(LoadTextureFromDisk(thumbnailPath));
|
||||
|
||||
// rate-limit event tracking for previewing examples to at most 10 events per minute
|
||||
DateTime currentTime = DateTime.Now;
|
||||
TimeSpan elapsedTime = currentTime - lastResetTimeExamplePreviewed;
|
||||
if (elapsedTime.TotalSeconds > TIME_LIMIT_EXAMPLE_PREVIEWED)
|
||||
{
|
||||
eventCounterExamplePreviewed = 0;
|
||||
lastResetTimeExamplePreviewed = currentTime;
|
||||
}
|
||||
|
||||
if (eventCounterExamplePreviewed < EVENT_LIMIT_EXAMPLE_PREVIEWED)
|
||||
{
|
||||
eventCounterExamplePreviewed++;
|
||||
AnalyticsSDK.ExamplePreviewed(selectedPackage.Title, selectedPackage.Version);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search the Index for Packages
|
||||
|
||||
private void UpdatePackagesAsync(string query = "")
|
||||
{
|
||||
ShowPackagesForQuery(query);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
ShowPackagesForQuery(searchField.value);
|
||||
}
|
||||
|
||||
private void ToggleSearchedButtons(string searchKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchKey) || string.IsNullOrWhiteSpace(searchKey))
|
||||
{
|
||||
foreach (VisualElement child in examplesScrollviewContainer.Children())
|
||||
{
|
||||
child.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (VisualElement child in examplesScrollviewContainer.Children())
|
||||
{
|
||||
bool shown = child.name.Contains(searchKey, StringComparison.InvariantCultureIgnoreCase);
|
||||
child.style.display = shown ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSearch(ChangeEvent<string> evt)
|
||||
{
|
||||
ToggleSearchedButtons(evt.newValue);
|
||||
|
||||
if (typingCancellation != null)
|
||||
{
|
||||
typingCancellation.Cancel();
|
||||
typingCancellation.Dispose();
|
||||
}
|
||||
|
||||
typingCancellation = new CancellationTokenSource();
|
||||
DebounceSearch(typingCancellation.Token, evt.newValue);
|
||||
}
|
||||
|
||||
private async Task DebounceSearch(CancellationToken token, string finalText)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(searchDelayMs, cancellationToken: token);
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
// User has finished typing
|
||||
UpdatePackagesAsync(finalText);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// This block is executed if the delay is cancelled
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<AlgoliaPackage>> FetchPackagesAlgolia(string query = "")
|
||||
{
|
||||
SearchClient algolia = new SearchClient(AlgoliaAppKey, AlgoliaSearchKey);
|
||||
SearchIndex index = algolia.InitIndex(AlgoliaIndexName);
|
||||
|
||||
// Construct the embedded list format needed for tag filters
|
||||
var tagFilters = new List<List<string>>();
|
||||
|
||||
if (!ExampleCentralSettings.Data.Instance.ShowEconomyPackages)
|
||||
{
|
||||
tagFilters.Add(new List<string>(){"-ce"});
|
||||
}
|
||||
|
||||
// Conduct the search, including tag filters from settings
|
||||
List<AlgoliaPackage> hits = (await index.SearchAsync<ExampleCentral.Types.Algolia.UnityPackage>(
|
||||
new Query(query)
|
||||
{
|
||||
TagFilters = tagFilters
|
||||
}
|
||||
)).Hits;
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
private async Task ShowPackagesForQuery(string query = "")
|
||||
{
|
||||
List<AlgoliaPackage> packages = await FetchPackagesAlgolia(query);
|
||||
// Clear the existing list
|
||||
examplesScrollviewContainer.Clear();
|
||||
|
||||
foreach (AlgoliaPackage unityPackage in packages)
|
||||
{
|
||||
// Add each package to the list
|
||||
AddPackageToList(unityPackage);
|
||||
}
|
||||
|
||||
ToggleSearchedButtons(query);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Downloads
|
||||
|
||||
private void DownloadSelectedPackage()
|
||||
{
|
||||
if (selectedPackage == null) return;
|
||||
AnalyticsSDK.ExampleDownloaded(selectedPackage.Title, selectedPackage.Version);
|
||||
DownloadUnityPackage(selectedPackage);
|
||||
}
|
||||
|
||||
private string DownloadThumbnail(string url)
|
||||
{
|
||||
// get or create path to store thumbnails, use Local App Data
|
||||
string thumbnailDir =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "VRChat",
|
||||
"Example Central", "Thumbnails");
|
||||
Directory.CreateDirectory(thumbnailDir);
|
||||
string thumbnailPath = Path.Combine(thumbnailDir, Path.GetFileName(url));
|
||||
// download thumbnail if it doesn't exist
|
||||
if (!File.Exists(thumbnailPath))
|
||||
{
|
||||
WebClient client = new System.Net.WebClient();
|
||||
client.DownloadFile(url, thumbnailPath);
|
||||
return thumbnailPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return thumbnailPath;
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadUnityPackage(AlgoliaPackage unityPackage)
|
||||
{
|
||||
string packageDownloadDir =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "VRChat",
|
||||
"Example Central", "Packages");
|
||||
Directory.CreateDirectory(packageDownloadDir);
|
||||
string packageDownloadPath = Path.Combine(packageDownloadDir,
|
||||
$"{unityPackage.Title}-{unityPackage.Version}.unitypackage");
|
||||
// download file if it doesn't exist
|
||||
if (!File.Exists(packageDownloadPath))
|
||||
{
|
||||
WebClient client = new System.Net.WebClient();
|
||||
client.DownloadFile(unityPackage.UnityPackageFile, packageDownloadPath);
|
||||
}
|
||||
|
||||
// import the package
|
||||
AssetDatabase.ImportPackage(packageDownloadPath, true);
|
||||
}
|
||||
|
||||
private Texture2D LoadTextureFromDisk(string path)
|
||||
{
|
||||
Texture2D texture = new Texture2D(2, 2);
|
||||
byte[] fileData = File.ReadAllBytes(path);
|
||||
texture.LoadImage(fileData);
|
||||
return texture;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2dd19633aa70e484a94e884e201460a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_ViewDataDictionary: {instanceID: 0}
|
||||
- VisualTree: {fileID: 9197481963319205126, guid: 850bbb1837d721e48a16d8567e3ecf7b,
|
||||
type: 3}
|
||||
- PackageTabButton: {fileID: 9197481963319205126, guid: f4f9892bd4d997b42923c93f4fb8519c,
|
||||
type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b148a8f35d62fa647b4ba3e93b5a241c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,14 @@
|
||||
#container {
|
||||
margin: 2px 10px;
|
||||
}
|
||||
|
||||
#title-label {
|
||||
-unity-font-style: bold;
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#settings-button {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4433d9fd51eebc348aafa2130d7d546b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@ -0,0 +1,40 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
|
||||
<Style src="project://database/Packages/com.vrchat.base/Integrations/VRChat%20Example%20Central/Editor/Example%20Downloader%20Panel/Resources/ExampleDownloaderPanelStyle.uss?fileID=7433441132597879392&guid=2bbc5f61b79c52c4ebcb9fcf6a8a4487&type=3#ExampleDownloaderPanelStyle" />
|
||||
<ui:VisualElement name="container">
|
||||
<ui:VisualElement name="go-to-login">
|
||||
<ui:Button text="Log in to use Example Central" parse-escape-sequences="true" display-tooltip-when-elided="true" name="go-to-login-btn" tooltip="Opens the VRC Control Panel in order to log in and enable Example Central." />
|
||||
</ui:VisualElement>
|
||||
<VRC.ExampleCentral.Window.SplitView name="split-main" fixed-pane-index="1" fixed-pane-initial-dimension="500" view-data-key="ExamplePanel-MainView">
|
||||
<ui:VisualElement name="examples-container">
|
||||
<ui:VisualElement name="header-examples">
|
||||
<ui:Label tabindex="-1" text="Search" parse-escape-sequences="true" display-tooltip-when-elided="true" name="examples-label" />
|
||||
<uie:ToolbarSearchField focusable="true" name="examples-search-field" />
|
||||
<ui:Button parse-escape-sequences="true" display-tooltip-when-elided="true" name="settings-button" />
|
||||
</ui:VisualElement>
|
||||
<ui:ScrollView name="examples-scrollview" horizontal-scroller-visibility="Hidden" vertical-scroller-visibility="Auto">
|
||||
<ui:VisualElement name="examples-scrollview-container">
|
||||
<VRC.ExampleCentral.Window.PackageButton style="color: rgb(255, 255, 255);" />
|
||||
<VRC.ExampleCentral.Window.PackageButton style="color: rgb(255, 255, 255);" />
|
||||
<VRC.ExampleCentral.Window.PackageButton style="color: rgb(255, 255, 255);" />
|
||||
<VRC.ExampleCentral.Window.PackageButton style="color: rgb(255, 255, 255);" />
|
||||
</ui:VisualElement>
|
||||
</ui:ScrollView>
|
||||
</ui:VisualElement>
|
||||
<VRC.ExampleCentral.Window.SplitView name="split-details" orientation="Vertical" fixed-pane-index="0" view-data-key="ExamplePanel-DetailsView" fixed-pane-initial-dimension="200">
|
||||
<ui:VisualElement name="preview-thumbnail" />
|
||||
<ui:VisualElement name="preview-settings">
|
||||
<ui:Label tabindex="-1" text="Examples Thing" parse-escape-sequences="true" display-tooltip-when-elided="true" name="preview-title" />
|
||||
<ui:ScrollView name="preview-settings-scrollview" horizontal-scroller-visibility="Hidden">
|
||||
<ui:Label tabindex="-1" text="Version: " parse-escape-sequences="true" display-tooltip-when-elided="true" name="preview-version" />
|
||||
<ui:Label tabindex="-1" text="Tags: Used, Unused" parse-escape-sequences="true" display-tooltip-when-elided="true" name="preview-tags" />
|
||||
<ui:Label tabindex="-1" text="Description" parse-escape-sequences="true" display-tooltip-when-elided="true" name="preview-description" style="-unity-font-style: normal; margin-left: 0; margin-right: 0; margin-top: 4px; margin-bottom: 4px; overflow: visible; flex-wrap: nowrap; white-space: normal; text-overflow: ellipsis; color: rgb(255, 255, 255);" />
|
||||
</ui:ScrollView>
|
||||
<ui:VisualElement name="preview-options">
|
||||
<ui:Button text="Import" parse-escape-sequences="true" display-tooltip-when-elided="true" name="preview-download" />
|
||||
<ui:Button text="Docs" parse-escape-sequences="true" display-tooltip-when-elided="true" name="preview-documentation" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</VRC.ExampleCentral.Window.SplitView>
|
||||
</VRC.ExampleCentral.Window.SplitView>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 850bbb1837d721e48a16d8567e3ecf7b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@ -0,0 +1,212 @@
|
||||
#container {
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
background-color: rgb(31, 31, 31);
|
||||
}
|
||||
|
||||
#go-to-login {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#split-main {
|
||||
display: none;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#split-details {
|
||||
background-color: rgb(20, 20, 20);
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
margin-left: 16px;
|
||||
min-width: 155px;
|
||||
}
|
||||
|
||||
#examples-container {
|
||||
flex-grow: 0;
|
||||
min-width: 126px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
#header-examples {
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#examples-label {
|
||||
-unity-font-style: bold;
|
||||
font-size: 20px;
|
||||
margin-right: 16px;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
#examples-search-field {
|
||||
flex-grow: 1;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
width: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#packages-header {
|
||||
background-color: rgb(41, 41, 41);
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
border-left-color: rgb(82, 82, 82);
|
||||
border-right-color: rgb(82, 82, 82);
|
||||
border-top-color: rgb(82, 82, 82);
|
||||
border-bottom-color: rgb(82, 82, 82);
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
#packages-label {
|
||||
-unity-font-style: bold;
|
||||
padding-left: 16px;
|
||||
padding-right: 4px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
flex-grow: 1;
|
||||
-unity-text-align: middle-left;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
#examples-scrollview {
|
||||
background-color: rgb(41, 41, 41);
|
||||
}
|
||||
|
||||
#examples-scrollview-container {
|
||||
background-color: rgb(82, 82, 82);
|
||||
}
|
||||
|
||||
#preview-thumbnail {
|
||||
height: 144px;
|
||||
background-color: rgba(89, 89, 89, 0);
|
||||
flex-shrink: 0;
|
||||
background-image: none;
|
||||
-unity-background-scale-mode: scale-to-fit;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
#preview-settings {
|
||||
flex-grow: 1;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
min-height: 164px;
|
||||
}
|
||||
|
||||
#preview-title {
|
||||
-unity-font-style: bold;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 20px;
|
||||
white-space: normal;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
#preview-settings-scrollview {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#preview-version {
|
||||
-unity-font-style: normal;
|
||||
flex-grow: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 4px;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 10px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#preview-tags {
|
||||
-unity-font-style: normal;
|
||||
flex-grow: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 4px;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 10px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#preview-options {
|
||||
flex-direction: row;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
min-height: 32px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#preview-download {
|
||||
flex-grow: 1;
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
width: auto;
|
||||
background-color: rgb(15, 145, 169);
|
||||
-unity-font-style: bold;
|
||||
font-size: 14px;
|
||||
border-left-color: rgb(48, 48, 48);
|
||||
border-right-color: rgb(48, 48, 48);
|
||||
border-top-color: rgb(48, 48, 48);
|
||||
border-bottom-color: rgb(36, 36, 36);
|
||||
color: rgb(238, 238, 238);
|
||||
}
|
||||
|
||||
#preview-documentation {
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
margin-left: 6px;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
flex-grow: 0;
|
||||
background-color: rgb(20, 20, 20);
|
||||
-unity-font-style: bold;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
font-size: 14px;
|
||||
color: rgb(255, 255, 255);
|
||||
border-left-color: rgb(48, 48, 48);
|
||||
border-right-color: rgb(48, 48, 48);
|
||||
border-top-color: rgb(48, 48, 48);
|
||||
border-bottom-color: rgb(36, 36, 36);
|
||||
}
|
||||
|
||||
#settings-button {
|
||||
background-image: none;
|
||||
flex-basis: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
-unity-background-scale-mode: scale-to-fit;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bbc5f61b79c52c4ebcb9fcf6a8a4487
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@ -0,0 +1,7 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
|
||||
<Style src="project://database/Packages/com.vrchat.base/Integrations/VRChat%20Sample%20Central/Editor/Sample%20Downloader%20Panel/Resources/PackageTabButtonStyle.uss?fileID=7433441132597879392&guid=7d25b279c57e51341bb553313ea74fc0&type=3#PackageTabButtonStyle" />
|
||||
<ui:VisualElement name="container">
|
||||
<ui:VisualElement name="selection-element" />
|
||||
<ui:Label tabindex="-1" text="Label" parse-escape-sequences="true" display-tooltip-when-elided="true" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4f9892bd4d997b42923c93f4fb8519c
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@ -0,0 +1,28 @@
|
||||
|
||||
#container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
height: 40px;
|
||||
background-color: rgb(41, 41, 41);
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
#selection-element {
|
||||
flex-grow: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgb(56, 56, 56);
|
||||
display: none;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#container Label {
|
||||
flex-grow: 1;
|
||||
-unity-text-align: middle-left;
|
||||
margin-left: 16px;
|
||||
font-size: 14px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d25b279c57e51341bb553313ea74fc0
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 387 B |
@ -0,0 +1,140 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d947179418dad34d9759416f16501d0
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace VRC.ExampleCentral.Window
|
||||
{
|
||||
|
||||
public class SplitView : TwoPaneSplitView
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<SplitView, UxmlTraits> { }
|
||||
}
|
||||
|
||||
public class PackageButton : VisualElement
|
||||
{
|
||||
|
||||
public new class UxmlFactory : UxmlFactory<PackageButton, UxmlTraits> { }
|
||||
public Label PackageLabel;
|
||||
|
||||
private VisualElement selectionElement;
|
||||
private Clickable clickable;
|
||||
|
||||
/// <summary>
|
||||
/// Called when Package Button is clicked.
|
||||
/// </summary>
|
||||
public event Action clicked
|
||||
{
|
||||
add { if (clickable != null) clickable.clicked += value; }
|
||||
remove { if (clickable != null) clickable.clicked -= value; }
|
||||
}
|
||||
|
||||
public PackageButton()
|
||||
{
|
||||
VisualTreeAsset uxml = Resources.Load<VisualTreeAsset>("PackageTabButton");
|
||||
uxml.CloneTree(this);
|
||||
|
||||
PackageLabel = this.Query<Label>();
|
||||
selectionElement = this.Query<VisualElement>("selection-element");
|
||||
|
||||
clickable = new Clickable(OnClicked);
|
||||
this.AddManipulator(clickable);
|
||||
}
|
||||
|
||||
private void OnClicked(EventBase obj)
|
||||
{
|
||||
Select(true);
|
||||
}
|
||||
|
||||
public void Select(bool value)
|
||||
{
|
||||
selectionElement.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67c2bdfc395e77b4abdc8b6e5c51653d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a183835343d1e7448143d7227e65054
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.ExampleCentral.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the GUID and Hash for each Asset in an Example
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AssetInfo
|
||||
{
|
||||
public string GUID { get; private set; }
|
||||
public string Hash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetInfo"/> class with the specified GUID and hash.
|
||||
/// Used for deserialization, after the hash is computed.
|
||||
/// </summary>
|
||||
/// <param name="guid">The GUID of the asset.</param>
|
||||
/// <param name="hash">The hash of the asset.</param>
|
||||
public AssetInfo(string guid, string hash)
|
||||
{
|
||||
GUID = guid;
|
||||
Hash = hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetInfo"/> class with the specified GUID.
|
||||
/// The hash is computed based on the asset's content.
|
||||
/// </summary>
|
||||
/// <param name="guid">The GUID of the asset.</param>
|
||||
public AssetInfo(string guid)
|
||||
{
|
||||
GUID = guid;
|
||||
Hash = ComputeAssetHash(guid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the hash of the asset based on its content.
|
||||
/// </summary>
|
||||
/// <param name="guid">The GUID of the asset.</param>
|
||||
/// <returns>The computed hash of the asset.</returns>
|
||||
private string ComputeAssetHash(string guid)
|
||||
{
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
string fullPath = Path.Combine(Application.dataPath, assetPath.Substring("Assets/".Length));
|
||||
if (!File.Exists(fullPath))
|
||||
return "File Not Found";
|
||||
|
||||
try
|
||||
{
|
||||
using FileStream stream = File.OpenRead(fullPath);
|
||||
using SHA256 sha = SHA256.Create();
|
||||
byte[] hashBytes = sha.ComputeHash(stream);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in hashBytes)
|
||||
sb.Append(b.ToString("X2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Error computing hash for {assetPath}: {ex.Message}");
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the asset to a string, using a pipe character as a separator.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the asset's guid and hash.</returns>
|
||||
public string Serialize()
|
||||
{
|
||||
return $"{GUID}|{Hash}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the asset information from a string.
|
||||
/// </summary>
|
||||
/// <param name="serializedData">The serialized data string.</param>
|
||||
/// <returns>An instance of <see cref="AssetInfo"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the serialized data format is invalid.</exception>
|
||||
public static AssetInfo Deserialize(string serializedData)
|
||||
{
|
||||
var parts = serializedData.Split('|');
|
||||
if (parts.Length != 2)
|
||||
throw new ArgumentException("Invalid serialized data format");
|
||||
|
||||
return new AssetInfo(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d95aa121fc5f5848b1a4a6b1eae7299
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace VRC.ExampleCentral.Editor
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Stores a description of changes made to an Example
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class Changes
|
||||
{
|
||||
/// <summary>
|
||||
/// SemVer-compatible version number (SemVer not currently enforced anywhere)
|
||||
/// </summary>
|
||||
public string Version;
|
||||
/// <summary>
|
||||
/// Plain text description of changes
|
||||
/// </summary>
|
||||
public string Value;
|
||||
|
||||
public Changes(string version, string value)
|
||||
{
|
||||
Version = version;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 305cd65774c510c409011962a5752a51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.ExampleCentral.Editor
|
||||
{
|
||||
public enum ExampleStatus
|
||||
{
|
||||
Local,
|
||||
Draft,
|
||||
Published
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ExampleData : ScriptableObject, ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField]
|
||||
public string author;
|
||||
|
||||
[SerializeField]
|
||||
public string title;
|
||||
|
||||
[SerializeField]
|
||||
public Texture2D thumbnail;
|
||||
|
||||
[SerializeField]
|
||||
public string description;
|
||||
|
||||
[SerializeField]
|
||||
public string version;
|
||||
|
||||
[SerializeField]
|
||||
public string documentationLink;
|
||||
|
||||
[SerializeField]
|
||||
public string vrcWorldId;
|
||||
|
||||
[SerializeField]
|
||||
public List<string> tags = new();
|
||||
|
||||
[SerializeField]
|
||||
public SceneAsset exampleScene;
|
||||
|
||||
[SerializeField]
|
||||
private List<string> serializedAssets;
|
||||
public List<AssetInfo> assets = new();
|
||||
|
||||
[SerializeField]
|
||||
public string sanityId = $"drafts.{Guid.NewGuid().ToString()}";
|
||||
|
||||
[SerializeField]
|
||||
public ExampleStatus ExampleStatus = ExampleStatus.Local;
|
||||
|
||||
[SerializeField]
|
||||
public List<Changes> changes = new();
|
||||
|
||||
// Serialize the AssetInfo list to a list of strings
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
serializedAssets = assets.ConvertAll(asset => asset.Serialize());
|
||||
}
|
||||
|
||||
// Deserialize the List<string> back to the AssetInfo list
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
assets = serializedAssets.ConvertAll(AssetInfo.Deserialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02be9066d74f7884ab56988f40f56297
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "VRC.ExampleCentral.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"UniTask",
|
||||
"VRC.SDKBase.Editor"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ff9a3db60cc184381fafd0c1e6b375
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user