first commit
This commit is contained in:
279
Packages/com.unity.asset-store-tools/CHANGELOG.md
Normal file
279
Packages/com.unity.asset-store-tools/CHANGELOG.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# Changelog
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
## [11.4.3] - 2024-08-01
|
||||
|
||||
### Validator Changes
|
||||
- Hotfix: Remove non-ascii characters from the demo scene validation
|
||||
|
||||
## [11.4.2] - 2024-07-30
|
||||
|
||||
### Validator Changes
|
||||
- Check for nested .unitypackage files in the demo scene validation
|
||||
- Prevent normal map test from erroring when misc importer types are detected
|
||||
- Remove Templates category from the uncompressed images requirement list
|
||||
|
||||
## [11.4.1] - 2024-05-10
|
||||
|
||||
### Exporter Changes
|
||||
- Fixed an issue with bundled plugin folder contents not being exported
|
||||
|
||||
### Other
|
||||
- Miscellaneous internal changes
|
||||
|
||||
## [11.4.0] - 2024-01-23
|
||||
|
||||
### Uploader Changes
|
||||
- Added prevention of uploading packages larger than 6 GB
|
||||
- Added a prompt to allow automatically generating meta files within hidden folders
|
||||
- Fixed some obsolete API usage warnings in newer Unity versions
|
||||
|
||||
### Validator Changes
|
||||
- Added validation tests for:
|
||||
- Animation Clip take names
|
||||
- Model import logs
|
||||
- Uncompressed Package size
|
||||
- Updated the fail severity of Audio Clipping validation test
|
||||
- Updated the Demo Scene test to treat default scenes with custom skyboxes as valid demo scenes
|
||||
- Fixed some obsolete API usage warnings in newer Unity versions
|
||||
|
||||
### Other
|
||||
- Added an option to check for Asset Store Publishing Tools updates
|
||||
|
||||
## [11.3.1] - 2023-08-14
|
||||
|
||||
### Uploader Changes
|
||||
- Added the option to select indirect package dependencies from the project (e.g. Mathematics package installed by the Burst package)
|
||||
|
||||
### Validator Changes
|
||||
- Updated the Texture Dimensions test to ignore 'Sprite' and 'Editor GUI' texture types
|
||||
|
||||
### Exporter Changes
|
||||
- Updated exporter to ignore the 'ProjectSettings/ProjectVersion.txt' asset when exporting 'Complete Project' category packages
|
||||
|
||||
## [11.3.0] - 2023-07-04
|
||||
|
||||
### Uploader Changes
|
||||
|
||||
- Added the option to validate a pre-exported package
|
||||
- Added the option to export a .unitypackage file without uploading
|
||||
- Updated the dependency selection UI
|
||||
|
||||
### Validator Changes
|
||||
|
||||
- Added the option to validate several asset paths at once
|
||||
- Note: when validating package that is comprised of several folders (e.g. Assets/MyPackage +
|
||||
Assets/StreamingAssets + Assets/WebGLTemplates), please select all applicable paths that would be included in the package
|
||||
- Added several new validation tests for:
|
||||
- File Menu Names
|
||||
- Compressed files
|
||||
- Model Types
|
||||
- Texture Dimensions
|
||||
- Particle Systems
|
||||
- Normal Map Textures
|
||||
- Audio Clipping
|
||||
- Path Lengths
|
||||
- Script Compilation
|
||||
- Updated validation test severities based on package category
|
||||
- Updated validation tests to each have their own test logic class
|
||||
- Updated validation tests to be displayed in alphabetical order
|
||||
- Fixed several issues with the namespace check test
|
||||
- Fixed scenes in Samples~ folders not being taken into account for the sample scene check test
|
||||
- Other internal changes
|
||||
|
||||
### Exporter Changes
|
||||
|
||||
- Package exporter is now a separate module (similar to Uploader and Validator)
|
||||
- Fixed hidden folders being included when exporting package content
|
||||
- Note: this prevents an issue with the Unity Editor, where exported hidden folders would appear in the Project window
|
||||
as empty folders when imported, despite having content on disk. Content nested within hidden folders is still collected,
|
||||
provided it contains unique .meta files
|
||||
|
||||
## [11.2.2] - 2023-02-23
|
||||
|
||||
### Validator Changes
|
||||
|
||||
- Updated the 'LOD Setup' test to address some issues
|
||||
- Added additional checks for LOD renderers (inactive renderer check, LOD Group reference check, relative hierarchy position to LOD Group check)
|
||||
- LOD Group Component is no longer required to be on the root of the Prefab
|
||||
- Updated the test result message interface when invalid Prefabs are found
|
||||
|
||||
## [11.2.1] - 2023-01-17
|
||||
|
||||
### Uploader Changes
|
||||
|
||||
- Added a more informative error when exporting content with clashing guid meta files in hidden folders
|
||||
- Fixed a compilation issue for Unity 2020.1 and 2020.2
|
||||
- Fixed a rare error condition when queueing multiple package uploads in quick succession
|
||||
- Fixed Asset Store Uploader state not being properly reset if the uploading process fails
|
||||
|
||||
### Validator Changes
|
||||
|
||||
- Updated the Asset Store Validator description
|
||||
- Fixed a rare memory overflow issue when performing package validation
|
||||
|
||||
## [11.2.0] - 2022-11-03
|
||||
|
||||
### Uploader Changes
|
||||
|
||||
- Uploader will now use the custom package exporter by default
|
||||
- An option to use the legacy (native) exporter can be found in the Asset Store Publishing Tools' settings window
|
||||
- When exporting from the Assets folder, package dependencies can now be selected individually instead of being a choice between 'All' or 'None'
|
||||
- This option is only available with the custom exporter
|
||||
- Changed the way the Uploader reports completed uploading tasks
|
||||
- Modal pop-up has been replaced by a new UI view state
|
||||
- Added an option to the Asset Store Publishing Tools' Settings to display the pop-up after a completed upload
|
||||
- Changed exported .unitypackage files to have distinguishable file names
|
||||
- Fixed the Uploader window indefinitely stalling at 100% upload progress when a response from the Asset Store server is not received
|
||||
- Fixed native package exporter producing broken packages when the export path contained hidden folders
|
||||
- Fixed an issue with high CPU usage when uploading packages
|
||||
- Fixed Asset Store Publishing Tools' settings not being saved between Editor sessions on macOS
|
||||
- Other minor changes and tweaks
|
||||
|
||||
### Validator Changes
|
||||
|
||||
- Added two new tests:
|
||||
- 'Types have namespaces': checks whether scripts and native libraries under the validated path are nested under a namespace
|
||||
- 'Consistent line endings': checks whether scripts under the validated path have consistent line endings. This is similar to the warning from the Unity Editor compilation pipeline when a script contains both Windows and UNIX line endings.
|
||||
- Improved 'Reset Prefabs' test to display and be more informative about prefabs with unusually low transform values
|
||||
- Improved 'SpeedTree asset inclusion' test to search for '.st' files
|
||||
- Improved 'Documentation inclusion' test to treat '.md' files as valid documentation files
|
||||
- Improved 'Lossy audio file inclusion' test to treat '.aif' and '.aiff' files as valid non-lossy audio files
|
||||
- Improved 'Lossy audio file inclusion' test to search the project for non-lossy variants of existing lossy audio files
|
||||
- Removed 'Duplicate animation names' test
|
||||
- Tweaked validation severities for several tests
|
||||
- Other minor changes and tweaks
|
||||
|
||||
## [11.1.0] - 2022-09-14
|
||||
|
||||
### Uploader Changes
|
||||
|
||||
- Package Publisher Portal links can now be opened for all packages regardless of package status
|
||||
- External Dependency Manager can now be selected as a 'Special Folder' if found in the root Assets folder
|
||||
|
||||
### Validator Changes
|
||||
|
||||
- Added category selection for the Validator
|
||||
- Categories help determine the outcome of package validation more accurately. For example, documentation is not crucial for art packages, but is required for tooling packages.
|
||||
- Added a list of prefabs with missing mesh references to 'Meshes have Prefabs' test when the test fails
|
||||
- Corrected the message for a passing 'Shader compilation errors' test
|
||||
- Improved the floating point precision accuracy of 'Reset Prefabs' test
|
||||
- Fixed 'Missing Components in Assets' test checking all project folders instead of only the set path
|
||||
- Fixed 'Prefabs for meshes' test not checking meshes in certain paths
|
||||
- Fixed 'Reset Prefabs' test failing because of Prefabs with a Rect Transform Component
|
||||
- Fixed 'Reset Prefabs' test ignoring Transform rotation
|
||||
- Fixed test description text overlapping in some cases
|
||||
- Other minor changes and tweaks
|
||||
|
||||
## [11.0.2] - 2022-08-09
|
||||
|
||||
- Corrected some namespaces which were causing issues when deriving classes from Editor class
|
||||
|
||||
## [11.0.1] - 2022-08-05
|
||||
|
||||
### Uploader Changes
|
||||
|
||||
- Added Settings window (Asset Store Tools > Settings)
|
||||
- Added Soft/Junction Symlink support (enable through Settings)
|
||||
- Added workflow and path selection serialization (workflow saved locally, paths locally and online)
|
||||
- No more logs when using the `-nullable` compiler option (thanks @alfish)
|
||||
- Some API refactoring in preparation for CLI support
|
||||
- Other minor fixes/improvements
|
||||
|
||||
**Note:** when updating Asset Store Tools from the Package Manager, don't forget to remove the old version from the project (V11.0.0) before importing the new one (V11.0.1)
|
||||
|
||||
|
||||
## [11.0.0] - 2022-07-20
|
||||
|
||||
### Uploader changes
|
||||
|
||||
- UI has been reworked using UI Toolkit
|
||||
- New login window, allowing to login using Unity Cloud Services
|
||||
- Improved top bar, including search and sorting
|
||||
- Draft packages moved to the top
|
||||
- Added category, size, and last modified date next to the package
|
||||
- Added a link to the publishing portal next to the package
|
||||
- New uploading flow: “Pre-exported .unitypackage”
|
||||
- Previous uploading flow (folder selection) has been renamed to “From Assets Folder”
|
||||
- Dependencies check has been renamed to “Include Package Manifest” for clarity
|
||||
- Special Folders can now be selected and uploaded together with the package’s main folder (i.e. StreamingAssets, Plugins)
|
||||
- You can now upload to multiple packages at the same time without waiting for the first one to finish
|
||||
- Package can now be validated in the Uploading window by pressing the “Validate” button
|
||||
- Added refresh and logout buttons to the bottom toolbar for easier access
|
||||
- Packages caching - package information will no longer be redownloaded every time you open the Uploader window during the same Editor session
|
||||
- (Experimental) Custom exporter - will export your package ~2 times faster, but may miss some asset previews in the final product. To enable it - click three dots on the top left side of the window and enable “Use Custom Exporting”
|
||||
|
||||
|
||||
### Validator changes
|
||||
|
||||
- UI has been reworked using UI Toolkit
|
||||
- New tests based on the new guidelines
|
||||
- Updated tests’ titles, descriptions, and error reporting
|
||||
|
||||
## [5.0.5] - 2021-11-04
|
||||
|
||||
- Fixed namespace issues
|
||||
|
||||
## [5.0.4] - 2020-07-28
|
||||
|
||||
- Fixed issues with Unity 2020.1
|
||||
|
||||
## [5.0.3] - 2020-05-07
|
||||
|
||||
- Remove "Remove Standard Assets" check
|
||||
|
||||
## [5.0.2] - 2020-04-21
|
||||
|
||||
- Enable auto login with Unity account
|
||||
- Upload package with thread
|
||||
|
||||
## [5.0.1] - 2020-03-23
|
||||
|
||||
- Fix domain resolve issue
|
||||
|
||||
## [5.0.0] - 2019-10-09
|
||||
|
||||
- Added "Package Validator" tool
|
||||
- Added Help window
|
||||
- Added logout confirmation popup
|
||||
- Updated toolbar menu layout
|
||||
- Removed "Mass Labeler" tool
|
||||
- Updated layout of Login and Package Upload windows
|
||||
- Error messages are now more elaborate and user-friendly
|
||||
- Removed deprecated "Main Assets" step from the Package Upload window
|
||||
- Package Upload window now has a step for including package manager dependencies
|
||||
- Tooltips are now added to each upload process step
|
||||
|
||||
|
||||
## [4.1.0] - 2018-05-14
|
||||
|
||||
- Made Tool compatible with 2017.1
|
||||
|
||||
## [4.0.7] - 2017-07-10
|
||||
|
||||
- Tweaked menu items.
|
||||
|
||||
## [4.0.6] - 2016-07-15
|
||||
|
||||
- Improved error messages.
|
||||
|
||||
## [4.0.5] - 2016-03-17
|
||||
|
||||
- Enabling upload of fbm files.
|
||||
|
||||
## [4.0.4] - 2015-11-16
|
||||
|
||||
- Login improvements
|
||||
|
||||
## [4.0.3] - 2015-11-16
|
||||
|
||||
- Prepare the Tools for Unity 5.3
|
||||
|
||||
## [4.0.2] - 2015-10-23
|
||||
|
||||
- Fixed issue where Upload button would not work for some projects.
|
||||
- Fixed issues for publishers that only had one package.
|
||||
|
||||
## [4.0.0] - 2015-09-01
|
||||
|
||||
- Replaced Package Manager with Package Upload. Package management is now handled by Publisher Administration
|
||||
14
Packages/com.unity.asset-store-tools/CHANGELOG.md.meta
Normal file
14
Packages/com.unity.asset-store-tools/CHANGELOG.md.meta
Normal file
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06607220dbd46414e8f66bf9c5e3eb79
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/CHANGELOG.md
|
||||
uploadId: 681981
|
||||
8
Packages/com.unity.asset-store-tools/Editor.meta
Normal file
8
Packages/com.unity.asset-store-tools/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 166da5c6fc70e814a8262463903b2714
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.AssetStoreTools.Editor.Tests.asmdef")]
|
||||
[assembly: InternalsVisibleTo("ab-builder")]
|
||||
[assembly: InternalsVisibleTo("Inspector-Editor")]
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccfd7faf75ab3c74a98015e772288d86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/AssemblyInfo.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using AssetStoreTools.Uploader;
|
||||
using AssetStoreTools.Validator;
|
||||
using AssetStoreTools.Utility;
|
||||
|
||||
namespace AssetStoreTools
|
||||
{
|
||||
internal class AssetStoreTools : EditorWindow
|
||||
{
|
||||
[MenuItem("Asset Store Tools/Asset Store Uploader", false, 0)]
|
||||
public static void ShowAssetStoreToolsUploader()
|
||||
{
|
||||
Type inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll");
|
||||
var wnd = GetWindow<AssetStoreUploader>(inspectorType);
|
||||
wnd.Show();
|
||||
}
|
||||
|
||||
|
||||
[MenuItem("Asset Store Tools/Asset Store Validator", false, 1)]
|
||||
public static void ShowAssetStoreToolsValidator()
|
||||
{
|
||||
Type inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll");
|
||||
var wnd = GetWindow<AssetStoreValidator>(typeof(AssetStoreUploader), inspectorType);
|
||||
wnd.Show();
|
||||
}
|
||||
|
||||
[MenuItem("Asset Store Tools/Publisher Portal", false, 20)]
|
||||
public static void OpenPublisherPortal()
|
||||
{
|
||||
Application.OpenURL("https://publisher.unity.com/");
|
||||
}
|
||||
|
||||
[MenuItem("Asset Store Tools/Submission Guidelines", false, 21)]
|
||||
public static void OpenSubmissionGuidelines()
|
||||
{
|
||||
Application.OpenURL("https://assetstore.unity.com/publishing/submission-guidelines/");
|
||||
}
|
||||
|
||||
[MenuItem("Asset Store Tools/Provide Feedback", false, 22)]
|
||||
public static void OpenFeedback()
|
||||
{
|
||||
Application.OpenURL("https://forum.unity.com/threads/new-asset-store-tools-version-coming-july-20th-2022.1310939/");
|
||||
}
|
||||
|
||||
[MenuItem("Asset Store Tools/Check for Updates", false, 45)]
|
||||
public static void OpenUpdateChecker()
|
||||
{
|
||||
var wnd = GetWindowWithRect<ASToolsUpdater>(new Rect(Screen.width / 2, Screen.height / 2, 400, 150), true);
|
||||
wnd.Show();
|
||||
}
|
||||
|
||||
[MenuItem("Asset Store Tools/Settings", false, 50)]
|
||||
public static void OpenSettings()
|
||||
{
|
||||
ASToolsPreferencesProvider.OpenSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6060eef206afc844caaa1732538e8890
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/AssetStoreTools.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,21 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AssetStoreTools
|
||||
{
|
||||
internal abstract class AssetStoreToolsWindow : EditorWindow
|
||||
{
|
||||
protected abstract string WindowTitle { get; }
|
||||
|
||||
protected virtual void Init()
|
||||
{
|
||||
titleContent = new GUIContent(WindowTitle);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1057a05baaa45942808573065c02a03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/AssetStoreToolsWindow.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f5ca981958937a43997a9f365759edf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
using AssetStoreTools.Utility;
|
||||
|
||||
namespace AssetStoreTools.Exporter
|
||||
{
|
||||
internal class ExportResult
|
||||
{
|
||||
public bool Success;
|
||||
public string ExportedPath;
|
||||
public ASError Error;
|
||||
|
||||
public static implicit operator bool(ExportResult value)
|
||||
{
|
||||
return value != null && value.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce99a618d1e211444b53f18bb3444f75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/ExportResult.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace AssetStoreTools.Exporter
|
||||
{
|
||||
public abstract class ExporterSettings
|
||||
{
|
||||
public string[] ExportPaths;
|
||||
public string OutputFilename;
|
||||
}
|
||||
|
||||
public class DefaultExporterSettings : ExporterSettings
|
||||
{
|
||||
public string[] Dependencies;
|
||||
}
|
||||
|
||||
public class LegacyExporterSettings : ExporterSettings
|
||||
{
|
||||
public bool IncludeDependencies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 399b115514c617d47a00b8c0a5e430fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/ExporterSettings.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,137 @@
|
||||
using AssetStoreTools.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
|
||||
namespace AssetStoreTools.Exporter
|
||||
{
|
||||
internal abstract class PackageExporter
|
||||
{
|
||||
protected const string ProgressBarTitle = "Exporting Package";
|
||||
protected const string ProgressBarStepSavingAssets = "Saving Assets...";
|
||||
protected const string ProgressBarStepGatheringFiles = "Gathering files...";
|
||||
protected const string ProgressBarStepCompressingPackage = "Compressing package...";
|
||||
|
||||
private static readonly string[] PluginFolderExtensions = { "androidlib", "bundle", "plugin", "framework", "xcframework" };
|
||||
|
||||
public static async Task<ExportResult> ExportPackage(ExporterSettings exportSettings)
|
||||
{
|
||||
if (!IsSettingsValid(exportSettings, out Exception e))
|
||||
return new ExportResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
|
||||
switch (exportSettings)
|
||||
{
|
||||
case LegacyExporterSettings legacySettings:
|
||||
return await PackageExporterLegacy.ExportPackage(legacySettings);
|
||||
case DefaultExporterSettings defaultSettings:
|
||||
return PackageExporterDefault.ExportPackage(defaultSettings);
|
||||
default:
|
||||
return new ExportResult() { Success = false, Error = ASError.GetGenericError(new ArgumentException("Unrecognized ExportSettings type was provided")) };
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSettingsValid(ExporterSettings settings, out Exception e)
|
||||
{
|
||||
e = null;
|
||||
|
||||
if (settings == null)
|
||||
e = new ArgumentException("Package Exporting failed: ExporterSettings cannot be null");
|
||||
else if (settings.ExportPaths == null || settings.ExportPaths.Length == 0)
|
||||
e = new ArgumentException("Package Exporting failed: received an invalid export paths array");
|
||||
else if (string.IsNullOrEmpty(settings.OutputFilename))
|
||||
e = new ArgumentException("Package Exporting failed: received an invalid output path");
|
||||
else if (settings.OutputFilename.EndsWith("/") || settings.OutputFilename.EndsWith("\\"))
|
||||
e = new ArgumentException("Package Exporting failed: output path must be a valid filename and not end with a directory separator character");
|
||||
|
||||
return e == null;
|
||||
}
|
||||
|
||||
protected string[] GetAssetPaths(string rootPath)
|
||||
{
|
||||
// To-do: slight optimization is possible in the future by having a list of excluded folders/file extensions
|
||||
List<string> paths = new List<string>();
|
||||
|
||||
// Add files within given directory
|
||||
var filePaths = Directory.GetFiles(rootPath).Select(p => p.Replace('\\', '/')).ToArray();
|
||||
paths.AddRange(filePaths);
|
||||
|
||||
// Add directories within given directory
|
||||
var directoryPaths = Directory.GetDirectories(rootPath).Select(p => p.Replace('\\', '/')).ToArray();
|
||||
foreach (var nestedDirectory in directoryPaths)
|
||||
paths.AddRange(GetAssetPaths(nestedDirectory));
|
||||
|
||||
// Add the given directory itself if it is not empty
|
||||
if (filePaths.Length > 0 || directoryPaths.Length > 0)
|
||||
paths.Add(rootPath);
|
||||
|
||||
return paths.ToArray();
|
||||
}
|
||||
|
||||
protected string GetAssetGuid(string assetPath, bool generateForPlugin, bool hiddenSearch)
|
||||
{
|
||||
// Skip meta files as they do not have guids
|
||||
if (assetPath.EndsWith(".meta"))
|
||||
return string.Empty;
|
||||
|
||||
// Skip hidden assets. They normally do not have meta files, but
|
||||
// have been observed to retain them in the past due to a Unity bug
|
||||
if (assetPath.EndsWith("~"))
|
||||
return string.Empty;
|
||||
|
||||
// Skip ProjectVersion.txt file specifically as it may introduce
|
||||
// project compatibility issues when imported
|
||||
if (string.Compare(assetPath, "ProjectSettings/ProjectVersion.txt", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
return string.Empty;
|
||||
|
||||
// Attempt retrieving guid from the Asset Database first
|
||||
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
if (guid != string.Empty)
|
||||
return guid;
|
||||
|
||||
// Some special folders (e.g. SomeName.framework) do not have meta files inside them.
|
||||
// Their contents should be exported with any arbitrary GUID so that Unity Importer could pick them up
|
||||
if (generateForPlugin && PathBelongsToPlugin(assetPath))
|
||||
return GUID.Generate().ToString();
|
||||
|
||||
// Files in hidden folders (e.g. Samples~) are not part of the Asset Database,
|
||||
// therefore GUIDs need to be scraped from the .meta file.
|
||||
// Note: only do this for custom exporter since the native exporter
|
||||
// will not be able to retrieve the asset path from a hidden folder
|
||||
if (hiddenSearch)
|
||||
{
|
||||
// To-do: handle hidden folders without meta files
|
||||
var metaPath = $"{assetPath}.meta";
|
||||
|
||||
if (!File.Exists(metaPath))
|
||||
return string.Empty;
|
||||
|
||||
using (StreamReader reader = new StreamReader(metaPath))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != string.Empty)
|
||||
{
|
||||
if (!line.StartsWith("guid:"))
|
||||
continue;
|
||||
var metaGuid = line.Substring("guid:".Length).Trim();
|
||||
return metaGuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private bool PathBelongsToPlugin(string assetPath)
|
||||
{
|
||||
return PluginFolderExtensions.Any(extension => assetPath.ToLower().Contains($".{extension}/"));
|
||||
}
|
||||
|
||||
protected virtual void PostExportCleanup()
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52ef11a59e545544fafaa99a5fa6cce9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporter.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,321 @@
|
||||
using AssetStoreTools.Uploader.Utility;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AssetStoreTools.Exporter
|
||||
{
|
||||
internal class PackageExporterDefault : PackageExporter
|
||||
{
|
||||
private const string TemporaryExportPathName = "CustomExport";
|
||||
|
||||
private DefaultExporterSettings _exportSettings;
|
||||
|
||||
private PackageExporterDefault(DefaultExporterSettings exportSettings)
|
||||
{
|
||||
_exportSettings = exportSettings;
|
||||
}
|
||||
|
||||
public static ExportResult ExportPackage(DefaultExporterSettings exportSettings)
|
||||
{
|
||||
var exporter = new PackageExporterDefault(exportSettings);
|
||||
return exporter.ExportPackage();
|
||||
}
|
||||
|
||||
private ExportResult ExportPackage()
|
||||
{
|
||||
ASDebug.Log("Using custom package exporter");
|
||||
|
||||
// Save assets before exporting
|
||||
EditorUtility.DisplayProgressBar(ProgressBarTitle, ProgressBarStepSavingAssets, 0.1f);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
try
|
||||
{
|
||||
// Create a temporary export path
|
||||
var temporaryExportPath = GetTemporaryExportPath();
|
||||
if (!Directory.Exists(temporaryExportPath))
|
||||
Directory.CreateDirectory(temporaryExportPath);
|
||||
|
||||
// Construct an unzipped package structure
|
||||
CreateTempPackageStructure(temporaryExportPath);
|
||||
|
||||
// Build a .unitypackage file from the temporary folder
|
||||
CreateUnityPackage(temporaryExportPath, _exportSettings.OutputFilename);
|
||||
|
||||
EditorUtility.RevealInFinder(_exportSettings.OutputFilename);
|
||||
|
||||
ASDebug.Log($"Package file has been created at {_exportSettings.OutputFilename}");
|
||||
return new ExportResult() { Success = true, ExportedPath = _exportSettings.OutputFilename };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExportResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
finally
|
||||
{
|
||||
PostExportCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTemporaryExportPath()
|
||||
{
|
||||
return $"{AssetStoreCache.TempCachePath}/{TemporaryExportPathName}";
|
||||
}
|
||||
|
||||
private void CreateTempPackageStructure(string tempOutputPath)
|
||||
{
|
||||
EditorUtility.DisplayProgressBar(ProgressBarTitle, ProgressBarStepGatheringFiles, 0.4f);
|
||||
var pathGuidPairs = GetPathGuidPairs(_exportSettings.ExportPaths);
|
||||
|
||||
// Caching asset previews takes time, so we'll start doing it as we
|
||||
// iterate through assets and only retrieve them after generating the rest
|
||||
// of the package structure
|
||||
AssetPreview.SetPreviewTextureCacheSize(pathGuidPairs.Count + 100);
|
||||
var pathObjectPairs = new Dictionary<string, UnityEngine.Object>();
|
||||
|
||||
foreach (var pair in pathGuidPairs)
|
||||
{
|
||||
var originalAssetPath = pair.Key;
|
||||
var outputAssetPath = $"{tempOutputPath}/{pair.Value}";
|
||||
|
||||
if (Directory.Exists(outputAssetPath))
|
||||
{
|
||||
var path1 = File.ReadAllText($"{outputAssetPath}/pathname");
|
||||
var path2 = originalAssetPath;
|
||||
throw new InvalidOperationException($"Multiple assets with guid {pair.Value} have been detected " +
|
||||
$"when exporting the package. Please resolve the guid conflicts and try again:\n{path1}\n{path2}");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(outputAssetPath);
|
||||
|
||||
// Every exported asset has a pathname file
|
||||
using (StreamWriter writer = new StreamWriter($"{outputAssetPath}/pathname"))
|
||||
writer.Write(originalAssetPath);
|
||||
|
||||
// Only files (not folders) have an asset file
|
||||
if (File.Exists(originalAssetPath))
|
||||
File.Copy(originalAssetPath, $"{outputAssetPath}/asset");
|
||||
|
||||
// Most files and folders have an asset.meta file (but ProjectSettings folder assets do not)
|
||||
if (File.Exists($"{originalAssetPath}.meta"))
|
||||
File.Copy($"{originalAssetPath}.meta", $"{outputAssetPath}/asset.meta");
|
||||
|
||||
// To-do: handle previews in hidden folders as they are not part of the AssetDatabase
|
||||
var previewObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(originalAssetPath);
|
||||
if (previewObject == null)
|
||||
continue;
|
||||
// Start caching the asset preview
|
||||
AssetPreview.GetAssetPreview(previewObject);
|
||||
pathObjectPairs.Add(outputAssetPath, previewObject);
|
||||
}
|
||||
|
||||
WritePreviewTextures(pathObjectPairs);
|
||||
|
||||
if (_exportSettings.Dependencies == null || _exportSettings.Dependencies.Length == 0)
|
||||
return;
|
||||
|
||||
var exportDependenciesDict = JsonValue.NewDict();
|
||||
var allRegistryPackages = PackageUtility.GetAllRegistryPackages();
|
||||
|
||||
foreach(var exportDependency in _exportSettings.Dependencies)
|
||||
{
|
||||
var registryPackage = allRegistryPackages.FirstOrDefault(x => x.name == exportDependency);
|
||||
if (registryPackage == null)
|
||||
{
|
||||
// Package is either not from a registry source or does not exist in the project
|
||||
UnityEngine.Debug.LogWarning($"Found an unsupported Package Manager dependency: {exportDependency}.\n" +
|
||||
"This dependency is not supported in the project's manifest.json and will be skipped.");
|
||||
continue;
|
||||
}
|
||||
|
||||
exportDependenciesDict[registryPackage.name] = registryPackage.version;
|
||||
}
|
||||
|
||||
var exportManifestJson = JsonValue.NewDict();
|
||||
exportManifestJson["dependencies"] = exportDependenciesDict;
|
||||
|
||||
var tempManifestDirectoryPath = $"{tempOutputPath}/packagemanagermanifest";
|
||||
Directory.CreateDirectory(tempManifestDirectoryPath);
|
||||
var tempManifestFilePath = $"{tempManifestDirectoryPath}/asset";
|
||||
|
||||
File.WriteAllText(tempManifestFilePath, exportManifestJson.ToString());
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetPathGuidPairs(string[] exportPaths)
|
||||
{
|
||||
var pathGuidPairs = new Dictionary<string, string>();
|
||||
|
||||
foreach (var exportPath in exportPaths)
|
||||
{
|
||||
var assetPaths = GetAssetPaths(exportPath);
|
||||
|
||||
foreach (var assetPath in assetPaths)
|
||||
{
|
||||
var guid = GetAssetGuid(assetPath, true, true);
|
||||
if (string.IsNullOrEmpty(guid))
|
||||
continue;
|
||||
|
||||
pathGuidPairs.Add(assetPath, guid);
|
||||
}
|
||||
}
|
||||
|
||||
return pathGuidPairs;
|
||||
}
|
||||
|
||||
private void WritePreviewTextures(Dictionary<string, UnityEngine.Object> pathObjectPairs)
|
||||
{
|
||||
foreach (var kvp in pathObjectPairs)
|
||||
{
|
||||
var obj = kvp.Value;
|
||||
var queuePreview = false;
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case Material _:
|
||||
case TerrainLayer _:
|
||||
case AudioClip _:
|
||||
case Mesh _:
|
||||
case Texture _:
|
||||
case UnityEngine.Tilemaps.Tile _:
|
||||
case GameObject _:
|
||||
queuePreview = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!queuePreview)
|
||||
continue;
|
||||
|
||||
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _);
|
||||
var preview = GetAssetPreviewFromGuid(guid);
|
||||
|
||||
if (!preview)
|
||||
continue;
|
||||
|
||||
var thumbnailWidth = Mathf.Min(preview.width, 128);
|
||||
var thumbnailHeight = Mathf.Min(preview.height, 128);
|
||||
var rt = RenderTexture.GetTemporary(thumbnailWidth, thumbnailHeight, 0, RenderTextureFormat.Default, RenderTextureReadWrite.sRGB);
|
||||
|
||||
var copy = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);
|
||||
|
||||
RenderTexture.active = rt;
|
||||
GL.Clear(true, true, new Color(0, 0, 0, 0));
|
||||
Graphics.Blit(preview, rt);
|
||||
copy.ReadPixels(new Rect(0, 0, copy.width, copy.height), 0, 0, false);
|
||||
copy.Apply();
|
||||
RenderTexture.active = null;
|
||||
|
||||
var bytes = copy.EncodeToPNG();
|
||||
if (bytes != null && bytes.Length > 0)
|
||||
{
|
||||
File.WriteAllBytes(kvp.Key + "/preview.png", bytes);
|
||||
}
|
||||
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
}
|
||||
}
|
||||
|
||||
private Texture2D GetAssetPreviewFromGuid(string guid)
|
||||
{
|
||||
var method = typeof(AssetPreview).GetMethod("GetAssetPreviewFromGUID", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(string) }, null);
|
||||
var args = new object[] { guid };
|
||||
|
||||
return method?.Invoke(null, args) as Texture2D;
|
||||
}
|
||||
|
||||
private void CreateUnityPackage(string pathToArchive, string outputPath)
|
||||
{
|
||||
if (Directory.GetDirectories(pathToArchive).Length == 0)
|
||||
throw new InvalidOperationException("Unable to export package. The specified path is empty");
|
||||
|
||||
EditorUtility.DisplayProgressBar(ProgressBarTitle, ProgressBarStepCompressingPackage, 0.5f);
|
||||
|
||||
// Archiving process working path will be set to the
|
||||
// temporary package path so adjust the output path accordingly
|
||||
if (!Path.IsPathRooted(outputPath))
|
||||
outputPath = $"{Application.dataPath.Substring(0, Application.dataPath.Length - "/Assets".Length)}/{outputPath}";
|
||||
|
||||
#if UNITY_EDITOR_WIN
|
||||
CreateUnityPackageUniversal(pathToArchive, outputPath);
|
||||
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
CreateUnityPackageOsxLinux(pathToArchive, outputPath);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CreateUnityPackageUniversal(string pathToArchive, string outputPath)
|
||||
{
|
||||
var _7zPath = EditorApplication.applicationContentsPath;
|
||||
#if UNITY_EDITOR_WIN
|
||||
_7zPath = Path.Combine(_7zPath, "Tools", "7z.exe");
|
||||
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
_7zPath = Path.Combine(_7zPath, "Tools", "7za");
|
||||
#endif
|
||||
if (!File.Exists(_7zPath))
|
||||
throw new FileNotFoundException("Archiving utility was not found in your Unity installation directory");
|
||||
|
||||
var argumentsTar = $"a -r -ttar -y -bd archtemp.tar .";
|
||||
var result = StartProcess(_7zPath, argumentsTar, pathToArchive);
|
||||
if (result != 0)
|
||||
throw new Exception("Failed to compress the package");
|
||||
|
||||
// Create a GZIP archive
|
||||
var argumentsGzip = $"a -tgzip -bd -y \"{outputPath}\" archtemp.tar";
|
||||
result = StartProcess(_7zPath, argumentsGzip, pathToArchive);
|
||||
if (result != 0)
|
||||
throw new Exception("Failed to compress the package");
|
||||
}
|
||||
|
||||
private void CreateUnityPackageOsxLinux(string pathToArchive, string outputPath)
|
||||
{
|
||||
var tarPath = "/usr/bin/tar";
|
||||
|
||||
if (!File.Exists(tarPath))
|
||||
{
|
||||
// Fallback to the universal export method
|
||||
ASDebug.LogWarning("'/usr/bin/tar' executable not found. Falling back to 7za");
|
||||
CreateUnityPackageUniversal(pathToArchive, outputPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a TAR archive
|
||||
var arguments = $"-czpf \"{outputPath}\" .";
|
||||
var result = StartProcess(tarPath, arguments, pathToArchive);
|
||||
if (result != 0)
|
||||
throw new Exception("Failed to compress the package");
|
||||
}
|
||||
|
||||
private int StartProcess(string processPath, string arguments, string workingDirectory)
|
||||
{
|
||||
var info = new ProcessStartInfo()
|
||||
{
|
||||
FileName = processPath,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = workingDirectory,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
using (Process process = Process.Start(info))
|
||||
{
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PostExportCleanup()
|
||||
{
|
||||
base.PostExportCleanup();
|
||||
|
||||
var tempExportPath = GetTemporaryExportPath();
|
||||
if (Directory.Exists(tempExportPath))
|
||||
Directory.Delete(tempExportPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32f50122a1b2bc2428cf8fba321e2414
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporterDefault.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,102 @@
|
||||
using AssetStoreTools.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AssetStoreTools.Exporter
|
||||
{
|
||||
internal class PackageExporterLegacy : PackageExporter
|
||||
{
|
||||
private const string ExportMethodWithoutDependencies = "UnityEditor.PackageUtility.ExportPackage";
|
||||
private const string ExportMethodWithDependencies = "UnityEditor.PackageUtility.ExportPackageAndPackageManagerManifest";
|
||||
|
||||
private LegacyExporterSettings _exportSettings;
|
||||
|
||||
private PackageExporterLegacy(LegacyExporterSettings exportSettings)
|
||||
{
|
||||
_exportSettings = exportSettings;
|
||||
}
|
||||
|
||||
public static async Task<ExportResult> ExportPackage(LegacyExporterSettings exportSettings)
|
||||
{
|
||||
var exporter = new PackageExporterLegacy(exportSettings);
|
||||
return await exporter.ExportPackage();
|
||||
}
|
||||
|
||||
private async Task<ExportResult> ExportPackage()
|
||||
{
|
||||
ASDebug.Log("Using native package exporter");
|
||||
|
||||
try
|
||||
{
|
||||
var guids = GetGuids(_exportSettings.ExportPaths, out bool onlyFolders);
|
||||
|
||||
if (guids.Length == 0 || onlyFolders)
|
||||
throw new ArgumentException("Package Exporting failed: provided export paths are empty or only contain empty folders");
|
||||
|
||||
string exportMethod = ExportMethodWithoutDependencies;
|
||||
if (_exportSettings.IncludeDependencies)
|
||||
exportMethod = ExportMethodWithDependencies;
|
||||
|
||||
var split = exportMethod.Split('.');
|
||||
var assembly = Assembly.Load(split[0]); // UnityEditor
|
||||
var typeName = $"{split[0]}.{split[1]}"; // UnityEditor.PackageUtility
|
||||
var methodName = split[2]; // ExportPackage or ExportPackageAndPackageManagerManifest
|
||||
|
||||
var type = assembly.GetType(typeName);
|
||||
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
|
||||
null, new Type[] { typeof(string[]), typeof(string) }, null);
|
||||
|
||||
ASDebug.Log("Invoking native export method");
|
||||
|
||||
method?.Invoke(null, new object[] { guids, _exportSettings.OutputFilename });
|
||||
|
||||
// The internal exporter methods are asynchronous, therefore
|
||||
// we need to wait for exporting to finish before returning
|
||||
await Task.Run(() =>
|
||||
{
|
||||
while (!File.Exists(_exportSettings.OutputFilename))
|
||||
Thread.Sleep(100);
|
||||
});
|
||||
|
||||
ASDebug.Log($"Package file has been created at {_exportSettings.OutputFilename}");
|
||||
return new ExportResult() { Success = true, ExportedPath = _exportSettings.OutputFilename };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ExportResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
finally
|
||||
{
|
||||
PostExportCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetGuids(string[] exportPaths, out bool onlyFolders)
|
||||
{
|
||||
var guids = new List<string>();
|
||||
onlyFolders = true;
|
||||
|
||||
foreach (var exportPath in exportPaths)
|
||||
{
|
||||
var assetPaths = GetAssetPaths(exportPath);
|
||||
|
||||
foreach (var assetPath in assetPaths)
|
||||
{
|
||||
var guid = GetAssetGuid(assetPath, false, false);
|
||||
if (string.IsNullOrEmpty(guid))
|
||||
continue;
|
||||
|
||||
guids.Add(guid);
|
||||
if (onlyFolders == true && (File.Exists(assetPath)))
|
||||
onlyFolders = false;
|
||||
}
|
||||
}
|
||||
|
||||
return guids.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3200380dff2de104aa79620e4b41dc70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporterLegacy.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "asset-store-tools-editor",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c183be512f4485d40a3437fabd6c81cf
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Unity.AssetStoreTools.Editor.asmdef
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9722d52df16aab742b26fe301782c74c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,227 @@
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AssetStoreTools.Utility;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using AssetStoreTools.Uploader.Utility;
|
||||
using AssetStoreTools.Uploader.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader
|
||||
{
|
||||
internal class AssetStoreUploader : AssetStoreToolsWindow
|
||||
{
|
||||
public const string MinRequiredPackageVersion = "2021.3";
|
||||
public const long MaxPackageSizeBytes = 6442450944; // 6 GB
|
||||
|
||||
private const string MainWindowVisualTree = "Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Base/BaseWindow_Main";
|
||||
private const string DebugPhrase = "debug";
|
||||
|
||||
// UI Windows
|
||||
private LoginWindow _loginWindow;
|
||||
private UploadWindow _uploadWindow;
|
||||
|
||||
private readonly List<char> _debugBuffer = new List<char>();
|
||||
|
||||
public static bool ShowPackageVersionDialog
|
||||
{
|
||||
get => string.Compare(Application.unityVersion, MinRequiredPackageVersion, StringComparison.Ordinal) == -1 && ASToolsPreferences.Instance.UploadVersionCheck;
|
||||
set { ASToolsPreferences.Instance.UploadVersionCheck = value; ASToolsPreferences.Instance.Save(); }
|
||||
}
|
||||
|
||||
protected override string WindowTitle => "Asset Store Uploader";
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
if (_loginWindow != null && _uploadWindow != null)
|
||||
return;
|
||||
|
||||
minSize = new Vector2(400, 430);
|
||||
this.SetAntiAliasing(4);
|
||||
|
||||
base.Init();
|
||||
|
||||
VisualElement root = rootVisualElement;
|
||||
root.AddToClassList("root");
|
||||
|
||||
// Getting a reference to the UXML Document and adding to the root
|
||||
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{MainWindowVisualTree}.uxml");
|
||||
VisualElement uxmlRoot = visualTree.CloneTree();
|
||||
uxmlRoot.style.flexGrow = 1;
|
||||
root.Add(uxmlRoot);
|
||||
|
||||
root.styleSheets.Add(StyleSelector.UploaderWindow.BaseWindowStyle);
|
||||
root.styleSheets.Add(StyleSelector.UploaderWindow.BaseWindowTheme);
|
||||
|
||||
|
||||
// Find necessary windows / views and sets up appropriate functionality
|
||||
SetupCoreElements();
|
||||
|
||||
if (!AssetStoreAPI.IsUploading)
|
||||
{
|
||||
// Should only authenticate if the session is available. Other authentications are only available
|
||||
// in the login window. See "SetupLoginElements".
|
||||
HideElement(_uploadWindow);
|
||||
Authenticate();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowUploadWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
CheckForDebugMode();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (AssetStoreAPI.IsUploading)
|
||||
EditorUtility.DisplayDialog("Notice", "Assets are still being uploaded to the Asset Store. " +
|
||||
"If you wish to check on the progress, please re-open the Asset Store Uploader window", "OK");
|
||||
}
|
||||
|
||||
private void SetupCoreElements()
|
||||
{
|
||||
_loginWindow = rootVisualElement.Q<LoginWindow>("LoginWindow");
|
||||
_uploadWindow = rootVisualElement.Q<UploadWindow>("UploadWindow");
|
||||
|
||||
_loginWindow.SetupLoginElements(OnLoginSuccess, OnLoginFail);
|
||||
_uploadWindow.SetupWindows(OnLogout, OnPackageDownloadFail);
|
||||
}
|
||||
|
||||
#region Login Interface
|
||||
|
||||
private async void Authenticate()
|
||||
{
|
||||
ShowLoginWindow();
|
||||
|
||||
// 1 - Check if there's an active session
|
||||
// 2 - Check if there's a saved session
|
||||
// 3 - Attempt to login via Cloud session token
|
||||
// 4 - Prompt manual login
|
||||
EnableLoginWindow(false);
|
||||
var result = await AssetStoreAPI.LoginWithSessionAsync();
|
||||
if (result.Success)
|
||||
OnLoginSuccess(result.Response);
|
||||
else if (result.SilentFail)
|
||||
OnLoginFailSession();
|
||||
else
|
||||
OnLoginFail(result.Error);
|
||||
}
|
||||
|
||||
private void OnLoginFail(ASError error)
|
||||
{
|
||||
Debug.LogError(error.Message);
|
||||
|
||||
_loginWindow.EnableErrorBox(true, error.Message);
|
||||
EnableLoginWindow(true);
|
||||
}
|
||||
|
||||
private void OnLoginFailSession()
|
||||
{
|
||||
// All previous login methods are unavailable
|
||||
EnableLoginWindow(true);
|
||||
}
|
||||
|
||||
private void OnLoginSuccess(JsonValue json)
|
||||
{
|
||||
ASDebug.Log($"Login json\n{json}");
|
||||
|
||||
if (!AssetStoreAPI.IsPublisherValid(json, out var error))
|
||||
{
|
||||
EnableLoginWindow(true);
|
||||
_loginWindow.EnableErrorBox(true, error.Message);
|
||||
ASDebug.Log($"Publisher {json["name"]} is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
ASDebug.Log($"Publisher {json["name"]} is valid.");
|
||||
AssetStoreAPI.SavedSessionId = json["xunitysession"].AsString();
|
||||
AssetStoreAPI.LastLoggedInUser = json["username"].AsString();
|
||||
|
||||
ShowUploadWindow();
|
||||
}
|
||||
|
||||
private void OnPackageDownloadFail(ASError error)
|
||||
{
|
||||
_loginWindow.EnableErrorBox(true, error.Message);
|
||||
EnableLoginWindow(true);
|
||||
ShowLoginWindow();
|
||||
}
|
||||
|
||||
private void OnLogout()
|
||||
{
|
||||
AssetStoreAPI.SavedSessionId = String.Empty;
|
||||
AssetStoreCache.ClearTempCache();
|
||||
|
||||
_loginWindow.ClearLoginBoxes();
|
||||
ShowLoginWindow();
|
||||
EnableLoginWindow(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Window Utils
|
||||
private void ShowLoginWindow()
|
||||
{
|
||||
HideElement(_uploadWindow);
|
||||
ShowElement(_loginWindow);
|
||||
}
|
||||
|
||||
private void ShowUploadWindow()
|
||||
{
|
||||
HideElement(_loginWindow);
|
||||
ShowElement(_uploadWindow);
|
||||
|
||||
_uploadWindow.ShowAllPackagesView();
|
||||
_uploadWindow.ShowPublisherEmail(AssetStoreAPI.LastLoggedInUser);
|
||||
_uploadWindow.LoadPackages(true, OnPackageDownloadFail);
|
||||
}
|
||||
|
||||
private void ShowElement(params VisualElement[] elements)
|
||||
{
|
||||
foreach(var e in elements)
|
||||
e.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
private void HideElement(params VisualElement[] elements)
|
||||
{
|
||||
foreach(var e in elements)
|
||||
e.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void EnableLoginWindow(bool enable)
|
||||
{
|
||||
_loginWindow.SetEnabled(enable);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Utility
|
||||
|
||||
private void CheckForDebugMode()
|
||||
{
|
||||
Event e = Event.current;
|
||||
|
||||
if (e.type != EventType.KeyDown || e.keyCode == KeyCode.None)
|
||||
return;
|
||||
|
||||
_debugBuffer.Add(e.keyCode.ToString().ToLower()[0]);
|
||||
if (_debugBuffer.Count > DebugPhrase.Length)
|
||||
_debugBuffer.RemoveAt(0);
|
||||
|
||||
if (string.Join(string.Empty, _debugBuffer.ToArray()) != DebugPhrase)
|
||||
return;
|
||||
|
||||
ASDebug.DebugModeEnabled = !ASDebug.DebugModeEnabled;
|
||||
ASDebug.Log($"DEBUG MODE ENABLED: {ASDebug.DebugModeEnabled}");
|
||||
_debugBuffer.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b5319699cc84194a9a768ad33b86c21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/AssetStoreUploader.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab9d0e254817f4f4589a6a378d77babc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 878 B |
@@ -0,0 +1,154 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7df43612bbf44d4692de879c751902a
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
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
|
||||
ignoreMasterTextureLimit: 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: 2
|
||||
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
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 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
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 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
|
||||
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
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/open-in-browser.png
|
||||
uploadId: 681981
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,135 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e0749dce5b14cc46b73b0303375c162
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
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
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 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: 2
|
||||
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: 0
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/publisher_portal_black.png
|
||||
uploadId: 681981
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,135 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 003e2710f9b29d94c87632022a3c7c48
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
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
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 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: 18
|
||||
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
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/publisher_portal_white.png
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15b24ad8f9d236249910fb8eef1e30ea
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,796 @@
|
||||
using AssetStoreTools.Uploader.Data;
|
||||
using AssetStoreTools.Uploader.Utility;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AssetStoreTools.Uploader
|
||||
{
|
||||
/// <summary>
|
||||
/// A class for retrieving data from the Asset Store backend <para/>
|
||||
/// <b>Note:</b> most data retrieval methods require <see cref="SavedSessionId"/> to be set
|
||||
/// </summary>
|
||||
internal static class AssetStoreAPI
|
||||
{
|
||||
public const string ToolVersion = "V11.4.3";
|
||||
|
||||
private const string UnauthSessionId = "26c4202eb475d02864b40827dfff11a14657aa41";
|
||||
private const string KharmaSessionId = "kharma.sessionid";
|
||||
private const int UploadResponseTimeoutMs = 10000;
|
||||
|
||||
public static string AssetStoreProdUrl = "https://kharma.unity3d.com";
|
||||
private static string s_sessionId = EditorPrefs.GetString(KharmaSessionId);
|
||||
private static HttpClient httpClient = new HttpClient();
|
||||
private static CancellationTokenSource s_downloadCancellationSource;
|
||||
|
||||
public static string SavedSessionId
|
||||
{
|
||||
get => s_sessionId;
|
||||
set
|
||||
{
|
||||
s_sessionId = value;
|
||||
EditorPrefs.SetString(KharmaSessionId, value);
|
||||
httpClient.DefaultRequestHeaders.Clear();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
httpClient.DefaultRequestHeaders.Add("X-Unity-Session", SavedSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsCloudUserAvailable => CloudProjectSettings.userName != "anonymous";
|
||||
public static string LastLoggedInUser = "";
|
||||
public static ConcurrentDictionary<string, OngoingUpload> ActiveUploads = new ConcurrentDictionary<string, OngoingUpload>();
|
||||
public static bool IsUploading => (ActiveUploads.Count > 0);
|
||||
|
||||
static AssetStoreAPI()
|
||||
{
|
||||
ServicePointManager.DefaultConnectionLimit = 500;
|
||||
httpClient.DefaultRequestHeaders.ConnectionClose = false;
|
||||
httpClient.Timeout = TimeSpan.FromMinutes(1320);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A structure used to return the success outcome and the result of Asset Store API calls
|
||||
/// </summary>
|
||||
internal class APIResult
|
||||
{
|
||||
public JsonValue Response;
|
||||
public bool Success;
|
||||
public bool SilentFail;
|
||||
public ASError Error;
|
||||
|
||||
public static implicit operator bool(APIResult value)
|
||||
{
|
||||
return value != null && value.Success != false;
|
||||
}
|
||||
}
|
||||
|
||||
#region Login API
|
||||
|
||||
/// <summary>
|
||||
/// A login API call that uses the email and password credentials
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Note:</b> this method only returns a response from the server and does not set the <see cref="SavedSessionId"/> itself
|
||||
/// </remarks>
|
||||
public static async Task<APIResult> LoginWithCredentialsAsync(string email, string password)
|
||||
{
|
||||
FormUrlEncodedContent data = GetLoginContent(new Dictionary<string, string> { { "user", email }, { "pass", password } });
|
||||
return await LoginAsync(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A login API call that uses the <see cref="SavedSessionId"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Note:</b> this method only returns a response from the server and does not set the <see cref="SavedSessionId"/> itself
|
||||
/// </remarks>
|
||||
public static async Task<APIResult> LoginWithSessionAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SavedSessionId))
|
||||
return new APIResult() { Success = false, SilentFail = true, Error = ASError.GetGenericError(new Exception("No active session available")) };
|
||||
|
||||
FormUrlEncodedContent data = GetLoginContent(new Dictionary<string, string> { { "reuse_session", SavedSessionId }, { "xunitysession", UnauthSessionId } });
|
||||
return await LoginAsync(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A login API call that uses the <see cref="CloudProjectSettings.accessToken"/><para/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Note:</b> this method only returns a response from the server and does not set the <see cref="SavedSessionId"/> itself
|
||||
/// </remarks>
|
||||
/// <param name="token">Cloud access token. Can be retrieved by calling <see cref="CloudProjectSettings.accessToken"/></param>
|
||||
public static async Task<APIResult> LoginWithTokenAsync(string token)
|
||||
{
|
||||
FormUrlEncodedContent data = GetLoginContent(new Dictionary<string, string> { { "user_access_token", token } });
|
||||
return await LoginAsync(data);
|
||||
}
|
||||
|
||||
private static async Task<APIResult> LoginAsync(FormUrlEncodedContent data)
|
||||
{
|
||||
OverrideAssetStoreUrl();
|
||||
Uri uri = new Uri($"{AssetStoreProdUrl}/login");
|
||||
|
||||
httpClient.DefaultRequestHeaders.Clear();
|
||||
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.PostAsync(uri, data);
|
||||
return UploadValuesCompletedLogin(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new APIResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
private static APIResult UploadValuesCompletedLogin(HttpResponseMessage response)
|
||||
{
|
||||
ASDebug.Log($"Upload Values Complete {response.ReasonPhrase}");
|
||||
ASDebug.Log($"Login success? {response.IsSuccessStatusCode}");
|
||||
try
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseResult = response.Content.ReadAsStringAsync().Result;
|
||||
var success = JSONParser.AssetStoreResponseParse(responseResult, out ASError error, out JsonValue jsonResult);
|
||||
if (success)
|
||||
return new APIResult() { Success = true, Response = jsonResult };
|
||||
else
|
||||
return new APIResult() { Success = false, Error = error };
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return new APIResult() { Success = false, Error = ASError.GetLoginError(response, ex) };
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Metadata API
|
||||
|
||||
private static async Task<JsonValue> GetPackageDataMain()
|
||||
{
|
||||
return await GetAssetStoreData(APIUri("asset-store-tools", "metadata/0", SavedSessionId));
|
||||
}
|
||||
|
||||
private static async Task<JsonValue> GetPackageDataExtra()
|
||||
{
|
||||
return await GetAssetStoreData(APIUri("management", "packages", SavedSessionId));
|
||||
}
|
||||
|
||||
private static async Task<JsonValue> GetCategories(bool useCached)
|
||||
{
|
||||
if (useCached)
|
||||
{
|
||||
if (AssetStoreCache.GetCachedCategories(out JsonValue cachedCategoryJson))
|
||||
return cachedCategoryJson;
|
||||
|
||||
ASDebug.LogWarning("Failed to retrieve cached category data. Proceeding to download");
|
||||
}
|
||||
var categoryJson = await GetAssetStoreData(APIUri("management", "categories", SavedSessionId));
|
||||
AssetStoreCache.CacheCategories(categoryJson);
|
||||
|
||||
return categoryJson;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve data for all packages associated with the currently logged in account (identified by <see cref="SavedSessionId"/>)
|
||||
/// </summary>
|
||||
/// <param name="useCached"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<APIResult> GetFullPackageDataAsync(bool useCached)
|
||||
{
|
||||
if (useCached)
|
||||
{
|
||||
if (AssetStoreCache.GetCachedPackageMetadata(out JsonValue cachedData))
|
||||
return new APIResult() { Success = true, Response = cachedData };
|
||||
|
||||
ASDebug.LogWarning("Failed to retrieve cached package metadata. Proceeding to download");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jsonMainData = await GetPackageDataMain();
|
||||
var jsonExtraData = await GetPackageDataExtra();
|
||||
var jsonCategoryData = await GetCategories(useCached);
|
||||
|
||||
var joinedData = MergePackageData(jsonMainData, jsonExtraData, jsonCategoryData);
|
||||
AssetStoreCache.CachePackageMetadata(joinedData);
|
||||
|
||||
return new APIResult() { Success = true, Response = joinedData };
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
ASDebug.Log("Package metadata download operation cancelled");
|
||||
DisposeDownloadCancellation();
|
||||
return new APIResult() { Success = false, SilentFail = true, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new APIResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the thumbnail textures for all packages within the provided json structure and perform a given action after each retrieval
|
||||
/// </summary>
|
||||
/// <param name="packageJson">A json file retrieved from <see cref="GetFullPackageDataAsync(bool)"/></param>
|
||||
/// <param name="useCached">Return cached thumbnails if they are found</param>
|
||||
/// <param name="onSuccess">
|
||||
/// Action to perform upon a successful thumbnail retrieval <para/>
|
||||
/// <see cref="string"/> - Package Id <br/>
|
||||
/// <see cref="Texture2D"/> - Associated Thumbnail
|
||||
/// </param>
|
||||
/// <param name="onFail">
|
||||
/// Action to perform upon a failed thumbnail retrieval <para/>
|
||||
/// <see cref="string"/> - Package Id <br/>
|
||||
/// <see cref="ASError"/> - Associated error
|
||||
/// </param>
|
||||
public static async void GetPackageThumbnails(JsonValue packageJson, bool useCached, Action<string, Texture2D> onSuccess, Action<string, ASError> onFail)
|
||||
{
|
||||
SetupDownloadCancellation();
|
||||
var packageDict = packageJson["packages"].AsDict();
|
||||
var packageEnum = packageDict.GetEnumerator();
|
||||
|
||||
for (int i = 0; i < packageDict.Count; i++)
|
||||
{
|
||||
packageEnum.MoveNext();
|
||||
var package = packageEnum.Current;
|
||||
|
||||
try
|
||||
{
|
||||
s_downloadCancellationSource.Token.ThrowIfCancellationRequested();
|
||||
|
||||
if (package.Value["icon_url"]
|
||||
.IsNull()) // If no URL is found in the package metadata, use the default image
|
||||
{
|
||||
Texture2D fallbackTexture = null;
|
||||
ASDebug.Log($"Package {package.Key} has no thumbnail. Returning default image");
|
||||
onSuccess?.Invoke(package.Key, fallbackTexture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (useCached &&
|
||||
AssetStoreCache.GetCachedTexture(package.Key,
|
||||
out Texture2D texture)) // Try returning cached thumbnails first
|
||||
{
|
||||
ASDebug.Log($"Returning cached thumbnail for package {package.Key}");
|
||||
onSuccess?.Invoke(package.Key, texture);
|
||||
continue;
|
||||
}
|
||||
|
||||
var textureBytes =
|
||||
await DownloadPackageThumbnail(package.Value["icon_url"].AsString());
|
||||
Texture2D tex = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
tex.LoadImage(textureBytes);
|
||||
AssetStoreCache.CacheTexture(package.Key, tex);
|
||||
ASDebug.Log($"Returning downloaded thumbnail for package {package.Key}");
|
||||
onSuccess?.Invoke(package.Key, tex);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
DisposeDownloadCancellation();
|
||||
ASDebug.Log("Package thumbnail download operation cancelled");
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
onFail?.Invoke(package.Key, ASError.GetGenericError(e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
packageEnum.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> DownloadPackageThumbnail(string url)
|
||||
{
|
||||
// icon_url is presented without http/https
|
||||
Uri uri = new Uri($"https:{url}");
|
||||
|
||||
var textureBytes = await httpClient.GetAsync(uri, s_downloadCancellationSource.Token).
|
||||
ContinueWith((response) => response.Result.Content.ReadAsByteArrayAsync().Result, s_downloadCancellationSource.Token);
|
||||
s_downloadCancellationSource.Token.ThrowIfCancellationRequested();
|
||||
return textureBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve, update the cache and return the updated data for a previously cached package
|
||||
/// </summary>
|
||||
public static async Task<APIResult> GetRefreshedPackageData(string packageId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var refreshedDataJson = await GetPackageDataExtra();
|
||||
var refreshedPackage = default(JsonValue);
|
||||
|
||||
// Find the updated package data in the latest data json
|
||||
foreach (var p in refreshedDataJson["packages"].AsList())
|
||||
{
|
||||
if (p["id"] == packageId)
|
||||
{
|
||||
refreshedPackage = p["versions"].AsList()[p["versions"].AsList().Count - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshedPackage.Equals(default(JsonValue)))
|
||||
return new APIResult() { Success = false, Error = ASError.GetGenericError(new MissingMemberException($"Unable to find downloaded package data for package id {packageId}")) };
|
||||
|
||||
// Check if the supplied package id data has been cached and if it contains the corresponding package
|
||||
if (!AssetStoreCache.GetCachedPackageMetadata(out JsonValue cachedData) ||
|
||||
!cachedData["packages"].AsDict().ContainsKey(packageId))
|
||||
return new APIResult() { Success = false, Error = ASError.GetGenericError(new MissingMemberException($"Unable to find cached package id {packageId}")) };
|
||||
|
||||
var cachedPackage = cachedData["packages"].AsDict()[packageId];
|
||||
|
||||
// Retrieve the category map
|
||||
var categoryJson = await GetCategories(true);
|
||||
var categories = CreateCategoryDictionary(categoryJson);
|
||||
|
||||
// Update the package data
|
||||
cachedPackage["name"] = refreshedPackage["name"].AsString();
|
||||
cachedPackage["status"] = refreshedPackage["status"].AsString();
|
||||
cachedPackage["extra_info"].AsDict()["category_info"].AsDict()["id"] = refreshedPackage["category_id"].AsString();
|
||||
cachedPackage["extra_info"].AsDict()["category_info"].AsDict()["name"] =
|
||||
categories.ContainsKey(refreshedPackage["category_id"]) ? categories[refreshedPackage["category_id"].AsString()] : "Unknown";
|
||||
cachedPackage["extra_info"].AsDict()["modified"] = refreshedPackage["modified"].AsString();
|
||||
cachedPackage["extra_info"].AsDict()["size"] = refreshedPackage["size"].AsString();
|
||||
|
||||
AssetStoreCache.CachePackageMetadata(cachedData);
|
||||
return new APIResult() { Success = true, Response = cachedPackage };
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ASDebug.Log("Package metadata download operation cancelled");
|
||||
DisposeDownloadCancellation();
|
||||
return new APIResult() { Success = false, SilentFail = true };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new APIResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all Unity versions that the given package has already had uploaded content with
|
||||
/// </summary>
|
||||
/// <param name="packageId"></param>
|
||||
/// <param name="versionId"></param>
|
||||
/// <returns></returns>
|
||||
public static List<string> GetPackageUploadedVersions(string packageId, string versionId)
|
||||
{
|
||||
var versions = new List<string>();
|
||||
try
|
||||
{
|
||||
// Retrieve the data for already uploaded versions (should prevent interaction with Uploader)
|
||||
var versionsTask = Task.Run(() => GetAssetStoreData(APIUri("content", $"preview/{packageId}/{versionId}", SavedSessionId)));
|
||||
if (!versionsTask.Wait(5000))
|
||||
throw new TimeoutException("Could not retrieve uploaded versions within a reasonable time interval");
|
||||
|
||||
var versionsJson = versionsTask.Result;
|
||||
foreach (var version in versionsJson["content"].AsDict()["unity_versions"].AsList())
|
||||
versions.Add(version.AsString());
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ASDebug.Log("Package version download operation cancelled");
|
||||
DisposeDownloadCancellation();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ASDebug.LogError(e);
|
||||
}
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Upload API
|
||||
|
||||
/// <summary>
|
||||
/// Upload a content file (.unitypackage) to a provided package version
|
||||
/// </summary>
|
||||
/// <param name="versionId"></param>
|
||||
/// <param name="packageName">Name of the package. Only used for identifying the package in <see cref="OngoingUpload"/> class</param>
|
||||
/// <param name="filePath">Path to the .unitypackage file</param>
|
||||
/// <param name="localPackageGuid">The <see cref="AssetDatabase.AssetPathToGUID(string)"/> value of the main content folder for the provided package</param>
|
||||
/// <param name="localPackagePath">The local path (relative to the root project folder) of the main content folder for the provided package</param>
|
||||
/// <param name="localProjectPath">The path to the project that this package was built from</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<PackageUploadResult> UploadPackageAsync(string versionId, string packageName, string filePath,
|
||||
string localPackageGuid, string localPackagePath, string localProjectPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
ASDebug.Log("Upload task starting");
|
||||
EditorApplication.LockReloadAssemblies();
|
||||
|
||||
if (!IsUploading) // Only subscribe before the first upload
|
||||
EditorApplication.playModeStateChanged += EditorPlayModeStateChangeHandler;
|
||||
|
||||
var progressData = new OngoingUpload(versionId, packageName);
|
||||
ActiveUploads.TryAdd(versionId, progressData);
|
||||
|
||||
var result = await Task.Run(() => UploadPackageTask(progressData, filePath, localPackageGuid, localPackagePath, localProjectPath));
|
||||
|
||||
ActiveUploads.TryRemove(versionId, out OngoingUpload _);
|
||||
|
||||
ASDebug.Log("Upload task finished");
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ASDebug.LogError("Upload task failed with an exception: " + e);
|
||||
ActiveUploads.TryRemove(versionId, out OngoingUpload _);
|
||||
return PackageUploadResult.PackageUploadFail(ASError.GetGenericError(e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!IsUploading) // Only unsubscribe after the last upload
|
||||
EditorApplication.playModeStateChanged -= EditorPlayModeStateChangeHandler;
|
||||
|
||||
EditorApplication.UnlockReloadAssemblies();
|
||||
}
|
||||
}
|
||||
|
||||
private static PackageUploadResult UploadPackageTask(OngoingUpload currentUpload, string filePath,
|
||||
string localPackageGuid, string localPackagePath, string localProjectPath)
|
||||
{
|
||||
ASDebug.Log("Preparing to upload package within API");
|
||||
string api = "asset-store-tools";
|
||||
string uri = $"package/{currentUpload.VersionId}/unitypackage";
|
||||
|
||||
Dictionary<string, string> packageParams = new Dictionary<string, string>
|
||||
{
|
||||
// Note: project_path is currently used to store UI selections
|
||||
{"root_guid", localPackageGuid},
|
||||
{"root_path", localPackagePath},
|
||||
{"project_path", localProjectPath}
|
||||
};
|
||||
|
||||
ASDebug.Log($"Creating upload request for {currentUpload.VersionId} {currentUpload.PackageName}");
|
||||
|
||||
FileStream requestFileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
bool responseTimedOut = false;
|
||||
long chunkSize = 32768;
|
||||
try
|
||||
{
|
||||
ASDebug.Log("Starting upload process...");
|
||||
|
||||
var content = new StreamContent(requestFileStream, (int)chunkSize);
|
||||
var response = httpClient.PutAsync(APIUri(api, uri, SavedSessionId, packageParams), content, currentUpload.CancellationToken);
|
||||
|
||||
// Progress tracking
|
||||
int updateIntervalMs = 100;
|
||||
bool allBytesSent = false;
|
||||
DateTime timeOfCompletion = default(DateTime);
|
||||
|
||||
while (!response.IsCompleted)
|
||||
{
|
||||
float uploadProgress = (float)requestFileStream.Position / requestFileStream.Length * 100;
|
||||
currentUpload.UpdateProgress(uploadProgress);
|
||||
Thread.Sleep(updateIntervalMs);
|
||||
|
||||
// A timeout for rare cases, when package uploading reaches 100%, but PutAsync task IsComplete value remains 'False'
|
||||
if (requestFileStream.Position == requestFileStream.Length)
|
||||
{
|
||||
if (!allBytesSent)
|
||||
{
|
||||
allBytesSent = true;
|
||||
timeOfCompletion = DateTime.UtcNow;
|
||||
}
|
||||
else if (DateTime.UtcNow.Subtract(timeOfCompletion).TotalMilliseconds > UploadResponseTimeoutMs)
|
||||
{
|
||||
responseTimedOut = true;
|
||||
currentUpload.Cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2020.3 - although cancellation token shows a requested cancellation, the HttpClient
|
||||
// tends to return a false 'IsCanceled' value, thus yielding an exception when attempting to read the response.
|
||||
// For now we'll just check the token as well, but this needs to be investigated later on.
|
||||
if (response.IsCanceled || currentUpload.CancellationToken.IsCancellationRequested)
|
||||
currentUpload.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var responseString = response.Result.Content.ReadAsStringAsync().Result;
|
||||
|
||||
var success = JSONParser.AssetStoreResponseParse(responseString, out ASError error, out JsonValue json);
|
||||
ASDebug.Log("Upload response JSON: " + json.ToString());
|
||||
if (success)
|
||||
return PackageUploadResult.PackageUploadSuccess();
|
||||
else
|
||||
return PackageUploadResult.PackageUploadFail(error);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Uploading is canceled
|
||||
if (!responseTimedOut)
|
||||
{
|
||||
ASDebug.Log("Upload operation cancelled");
|
||||
return PackageUploadResult.PackageUploadCancelled();
|
||||
}
|
||||
else
|
||||
{
|
||||
ASDebug.LogWarning("All data has been uploaded, but waiting for the response timed out");
|
||||
return PackageUploadResult.PackageUploadResponseTimeout();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ASDebug.LogError("Upload operation encountered an undefined exception: " + e);
|
||||
var fullError = e.InnerException != null ? ASError.GetGenericError(e.InnerException) : ASError.GetGenericError(e);
|
||||
return PackageUploadResult.PackageUploadFail(fullError);
|
||||
}
|
||||
finally
|
||||
{
|
||||
requestFileStream.Dispose();
|
||||
currentUpload.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel the uploading task for a package with the provided package id
|
||||
/// </summary>
|
||||
public static void AbortPackageUpload(string packageId)
|
||||
{
|
||||
ActiveUploads[packageId]?.Cancel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
public static async Task<APIResult> GetLatestAssetStoreToolsVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = "https://api.assetstore.unity3d.com/package/latest-version/115";
|
||||
var result = await httpClient.GetAsync(url);
|
||||
|
||||
result.EnsureSuccessStatusCode();
|
||||
|
||||
var resultStr = await result.Content.ReadAsStringAsync();
|
||||
|
||||
var json = JSONParser.SimpleParse(resultStr);
|
||||
|
||||
return new APIResult() { Success = true, Response = json };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new APIResult() { Success = false, Error = ASError.GetGenericError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLicenseHash()
|
||||
{
|
||||
return UnityEditorInternal.InternalEditorUtility.GetAuthToken().Substring(0, 40);
|
||||
}
|
||||
|
||||
private static string GetHardwareHash()
|
||||
{
|
||||
return UnityEditorInternal.InternalEditorUtility.GetAuthToken().Substring(40, 40);
|
||||
}
|
||||
|
||||
private static FormUrlEncodedContent GetLoginContent(Dictionary<string, string> loginData)
|
||||
{
|
||||
loginData.Add("unityversion", Application.unityVersion);
|
||||
loginData.Add("toolversion", ToolVersion);
|
||||
loginData.Add("license_hash", GetLicenseHash());
|
||||
loginData.Add("hardware_hash", GetHardwareHash());
|
||||
|
||||
return new FormUrlEncodedContent(loginData);
|
||||
}
|
||||
|
||||
private static async Task<JsonValue> GetAssetStoreData(Uri uri)
|
||||
{
|
||||
SetupDownloadCancellation();
|
||||
|
||||
var response = await httpClient.GetAsync(uri, s_downloadCancellationSource.Token)
|
||||
.ContinueWith((x) => x.Result.Content.ReadAsStringAsync().Result, s_downloadCancellationSource.Token);
|
||||
s_downloadCancellationSource.Token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!JSONParser.AssetStoreResponseParse(response, out var error, out var jsonMainData))
|
||||
throw error.Exception;
|
||||
|
||||
return jsonMainData;
|
||||
}
|
||||
|
||||
private static Uri APIUri(string apiPath, string endPointPath, string sessionId)
|
||||
{
|
||||
return APIUri(apiPath, endPointPath, sessionId, null);
|
||||
}
|
||||
|
||||
// Method borrowed from A$ tools, could maybe be simplified to only retain what is necessary?
|
||||
private static Uri APIUri(string apiPath, string endPointPath, string sessionId, IDictionary<string, string> extraQuery)
|
||||
{
|
||||
Dictionary<string, string> extraQueryMerged;
|
||||
|
||||
if (extraQuery == null)
|
||||
extraQueryMerged = new Dictionary<string, string>();
|
||||
else
|
||||
extraQueryMerged = new Dictionary<string, string>(extraQuery);
|
||||
|
||||
extraQueryMerged.Add("unityversion", Application.unityVersion);
|
||||
extraQueryMerged.Add("toolversion", ToolVersion);
|
||||
extraQueryMerged.Add("xunitysession", sessionId);
|
||||
|
||||
string uriPath = $"{AssetStoreProdUrl}/api/{apiPath}/{endPointPath}.json";
|
||||
UriBuilder uriBuilder = new UriBuilder(uriPath);
|
||||
|
||||
StringBuilder queryToAppend = new StringBuilder();
|
||||
foreach (KeyValuePair<string, string> queryPair in extraQueryMerged)
|
||||
{
|
||||
string queryName = queryPair.Key;
|
||||
string queryValue = Uri.EscapeDataString(queryPair.Value);
|
||||
|
||||
queryToAppend.AppendFormat("&{0}={1}", queryName, queryValue);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(uriBuilder.Query))
|
||||
uriBuilder.Query = uriBuilder.Query.Substring(1) + queryToAppend;
|
||||
else
|
||||
uriBuilder.Query = queryToAppend.Remove(0, 1).ToString();
|
||||
|
||||
return uriBuilder.Uri;
|
||||
}
|
||||
|
||||
private static JsonValue MergePackageData(JsonValue mainPackageData, JsonValue extraPackageData, JsonValue categoryData)
|
||||
{
|
||||
ASDebug.Log($"Main package data\n{mainPackageData}");
|
||||
var mainDataDict = mainPackageData["packages"].AsDict();
|
||||
|
||||
// Most likely both of them will be true at the same time, but better to be safe
|
||||
if (mainDataDict.Count == 0 || !extraPackageData.ContainsKey("packages"))
|
||||
return new JsonValue();
|
||||
|
||||
ASDebug.Log($"Extra package data\n{extraPackageData}");
|
||||
var extraDataDict = extraPackageData["packages"].AsList();
|
||||
|
||||
var categories = CreateCategoryDictionary(categoryData);
|
||||
|
||||
foreach (var md in mainDataDict)
|
||||
{
|
||||
foreach (var ed in extraDataDict)
|
||||
{
|
||||
if (ed["id"].AsString() != md.Key)
|
||||
continue;
|
||||
|
||||
// Create a field for extra data
|
||||
var extraData = JsonValue.NewDict();
|
||||
|
||||
// Add category field
|
||||
var categoryEntry = JsonValue.NewDict();
|
||||
|
||||
var categoryId = ed["category_id"].AsString();
|
||||
var categoryName = categories.ContainsKey(categoryId) ? categories[categoryId] : "Unknown";
|
||||
|
||||
categoryEntry["id"] = categoryId;
|
||||
categoryEntry["name"] = categoryName;
|
||||
|
||||
extraData["category_info"] = categoryEntry;
|
||||
|
||||
// Add modified time and size
|
||||
var versions = ed["versions"].AsList();
|
||||
extraData["modified"] = versions[versions.Count - 1]["modified"];
|
||||
extraData["size"] = versions[versions.Count - 1]["size"];
|
||||
|
||||
md.Value.AsDict()["extra_info"] = extraData;
|
||||
}
|
||||
}
|
||||
|
||||
mainPackageData.AsDict()["packages"] = new JsonValue(mainDataDict);
|
||||
return mainPackageData;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> CreateCategoryDictionary(JsonValue json)
|
||||
{
|
||||
var categories = new Dictionary<string, string>();
|
||||
|
||||
var list = json.AsList();
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var category = list[i].AsDict();
|
||||
if (category["status"].AsString() == "deprecated")
|
||||
continue;
|
||||
categories.Add(category["id"].AsString(), category["assetstore_name"].AsString());
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the account data is for a valid publisher account
|
||||
/// </summary>
|
||||
/// <param name="json">Json structure retrieved from one of the API login methods</param>
|
||||
public static bool IsPublisherValid(JsonValue json, out ASError error)
|
||||
{
|
||||
error = ASError.GetPublisherNullError(json["name"]);
|
||||
|
||||
if (!json.ContainsKey("publisher"))
|
||||
return false;
|
||||
|
||||
// If publisher account is not created - let them know
|
||||
return !json["publisher"].IsNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel all data retrieval tasks
|
||||
/// </summary>
|
||||
public static void AbortDownloadTasks()
|
||||
{
|
||||
s_downloadCancellationSource?.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel all data uploading tasks
|
||||
/// </summary>
|
||||
public static void AbortUploadTasks()
|
||||
{
|
||||
foreach (var upload in ActiveUploads)
|
||||
{
|
||||
AbortPackageUpload(upload.Key);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupDownloadCancellation()
|
||||
{
|
||||
if (s_downloadCancellationSource != null && s_downloadCancellationSource.IsCancellationRequested)
|
||||
DisposeDownloadCancellation();
|
||||
|
||||
if (s_downloadCancellationSource == null)
|
||||
s_downloadCancellationSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
private static void DisposeDownloadCancellation()
|
||||
{
|
||||
s_downloadCancellationSource?.Dispose();
|
||||
s_downloadCancellationSource = null;
|
||||
}
|
||||
|
||||
private static void EditorPlayModeStateChangeHandler(PlayModeStateChange state)
|
||||
{
|
||||
if (state != PlayModeStateChange.ExitingEditMode)
|
||||
return;
|
||||
|
||||
EditorApplication.ExitPlaymode();
|
||||
EditorUtility.DisplayDialog("Notice", "Entering Play Mode is not allowed while there's a package upload in progress.\n\n" +
|
||||
"Please wait until the upload is finished or cancel the upload from the Asset Store Uploader window", "OK");
|
||||
}
|
||||
|
||||
private static void OverrideAssetStoreUrl()
|
||||
{
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (!args[i].Equals("-assetStoreUrl"))
|
||||
continue;
|
||||
|
||||
if (i + 1 >= args.Length)
|
||||
return;
|
||||
|
||||
ASDebug.Log($"Overriding A$ URL to: {args[i + 1]}");
|
||||
AssetStoreProdUrl = args[i + 1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 684fca3fffd79d944a32d9b3adbfc007
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/AssetStoreAPI.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e3cae7082463da41b807724242fd617
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Analytics;
|
||||
|
||||
namespace AssetStoreTools.Uploader.Data
|
||||
{
|
||||
internal static class ASAnalytics
|
||||
{
|
||||
private const int VersionId = 3;
|
||||
private const int MaxEventsPerHour = 20;
|
||||
private const int MaxNumberOfElements = 1000;
|
||||
|
||||
private const string VendorKey = "unity.assetStoreTools";
|
||||
private const string EventName = "assetStoreTools";
|
||||
|
||||
static bool EnableAnalytics()
|
||||
{
|
||||
#if UNITY_2023_2_OR_NEWER
|
||||
return true;
|
||||
#else
|
||||
var result = EditorAnalytics.RegisterEventWithLimit(EventName, MaxEventsPerHour, MaxNumberOfElements, VendorKey, VersionId);
|
||||
return result == AnalyticsResult.Ok;
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct AnalyticsData
|
||||
#if UNITY_2023_2_OR_NEWER
|
||||
: IAnalytic.IData
|
||||
#endif
|
||||
{
|
||||
public string ToolVersion;
|
||||
public string PackageId;
|
||||
public string Category;
|
||||
public bool UsedValidator;
|
||||
public string ValidatorResults;
|
||||
public string UploadFinishedReason;
|
||||
public double TimeTaken;
|
||||
public long PackageSize;
|
||||
public string Workflow;
|
||||
public string EndpointUrl;
|
||||
}
|
||||
|
||||
#if UNITY_2023_2_OR_NEWER
|
||||
[AnalyticInfo(eventName: EventName, vendorKey: VendorKey, version: VersionId, maxEventsPerHour: MaxEventsPerHour, maxNumberOfElements: MaxNumberOfElements)]
|
||||
private class AssetStoreToolsAnalytic : IAnalytic
|
||||
{
|
||||
private AnalyticsData _data;
|
||||
|
||||
public AssetStoreToolsAnalytic(AnalyticsData data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public bool TryGatherData(out IAnalytic.IData data, out Exception error)
|
||||
{
|
||||
error = null;
|
||||
data = _data;
|
||||
return data != null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void SendUploadingEvent(AnalyticsData data)
|
||||
{
|
||||
if (!EditorAnalytics.enabled)
|
||||
return;
|
||||
|
||||
if (!EnableAnalytics())
|
||||
return;
|
||||
|
||||
#if UNITY_2023_2_OR_NEWER
|
||||
var analytic = new AssetStoreToolsAnalytic(data);
|
||||
EditorAnalytics.SendAnalytic(analytic);
|
||||
#else
|
||||
EditorAnalytics.SendEventWithLimit(EventName, data, VersionId);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1095145789a64767a6add837eea19786
|
||||
timeCreated: 1658832954
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/ASAnalytics.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace AssetStoreTools.Uploader.Data
|
||||
{
|
||||
internal class OngoingUpload : IDisposable
|
||||
{
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public string VersionId { get; }
|
||||
public string PackageName { get; }
|
||||
public float Progress { get; private set; }
|
||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
|
||||
public OngoingUpload(string versionId, string packageName)
|
||||
{
|
||||
VersionId = versionId;
|
||||
PackageName = packageName;
|
||||
Progress = 0f;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
public void UpdateProgress(float newProgress)
|
||||
{
|
||||
Progress = newProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 601fdada4edc5b94eb83a21d1a01ed26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/OngoingUpload.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace AssetStoreTools.Uploader.Data
|
||||
{
|
||||
internal class PackageData
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
public string VersionId { get; }
|
||||
public string Status { get; }
|
||||
public string Category { get; }
|
||||
public bool IsCompleteProject { get; }
|
||||
public string LastUploadedPath { get; }
|
||||
public string LastUploadedGuid { get; }
|
||||
|
||||
public string LastDate { get; }
|
||||
public string LastSize { get; }
|
||||
|
||||
public PackageData(string id, string name, string versionId, string status, string category, bool isCompleteProject, string lastUploadedPath, string lastUploadedGuid, string lastDate, string lastSize)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
VersionId = versionId;
|
||||
Status = status;
|
||||
Category = category;
|
||||
IsCompleteProject = isCompleteProject;
|
||||
LastUploadedPath = lastUploadedPath;
|
||||
LastUploadedGuid = lastUploadedGuid;
|
||||
LastDate = lastDate;
|
||||
LastSize = lastSize;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Id} {Name} {VersionId} {Status} {Category} {LastUploadedPath} {LastUploadedGuid} {IsCompleteProject} {LastDate} {LastSize}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8157930875be4972a48c870a3d1e8ff1
|
||||
timeCreated: 1658919930
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageData.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,46 @@
|
||||
using AssetStoreTools.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AssetStoreTools.Uploader.Data
|
||||
{
|
||||
internal class PackageUploadResult
|
||||
{
|
||||
public enum UploadStatus
|
||||
{
|
||||
Default = 0,
|
||||
Success = 1,
|
||||
Fail = 2,
|
||||
Cancelled = 3,
|
||||
ResponseTimeout = 4
|
||||
}
|
||||
|
||||
public UploadStatus Status;
|
||||
public ASError Error;
|
||||
|
||||
private PackageUploadResult() { }
|
||||
|
||||
public static PackageUploadResult PackageUploadSuccess() => new PackageUploadResult() { Status = UploadStatus.Success };
|
||||
|
||||
public static PackageUploadResult PackageUploadFail(ASError e) => new PackageUploadResult() { Status = UploadStatus.Fail, Error = e };
|
||||
|
||||
public static PackageUploadResult PackageUploadCancelled() => new PackageUploadResult() { Status = UploadStatus.Cancelled };
|
||||
|
||||
public static PackageUploadResult PackageUploadResponseTimeout() => new PackageUploadResult() { Status = UploadStatus.ResponseTimeout };
|
||||
|
||||
public static Color GetColorByStatus(UploadStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
default:
|
||||
case UploadStatus.Default:
|
||||
return new Color(0.13f, 0.59f, 0.95f);
|
||||
case UploadStatus.Success:
|
||||
return new Color(0f, 0.50f, 0.14f);
|
||||
case UploadStatus.Cancelled:
|
||||
return new Color(0.78f, 0.59f, 0f);
|
||||
case UploadStatus.Fail:
|
||||
return new Color(0.69f, 0.04f, 0.04f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 188361b01a1450145a6fc2a7aa0a3a3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageUploadResult.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3eb6991a3db8cc34dad63504bc6c3c0e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6e2d6bcfe000764e9330d78017e32bc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,233 @@
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class LoginWindow : VisualElement
|
||||
{
|
||||
private readonly string REGISTER_URL = "https://publisher.unity.com/access";
|
||||
private readonly string FORGOT_PASSWORD_URL = "https://id.unity.com/password/new";
|
||||
|
||||
private Button _cloudLoginButton;
|
||||
private Button _credentialsLoginButton;
|
||||
|
||||
private Label _cloudLoginLabel;
|
||||
|
||||
private TextField _emailField;
|
||||
private TextField _passwordField;
|
||||
|
||||
private Box _errorBox;
|
||||
private Label _errorLabel;
|
||||
|
||||
private double _cloudLoginRefreshTime = 1d;
|
||||
private double _lastRefreshTime;
|
||||
|
||||
public new class UxmlFactory : UxmlFactory<LoginWindow> { }
|
||||
|
||||
public LoginWindow()
|
||||
{
|
||||
styleSheets.Add(StyleSelector.UploaderWindow.LoginWindowStyle);
|
||||
styleSheets.Add(StyleSelector.UploaderWindow.LoginWindowTheme);
|
||||
ConstructLoginWindow();
|
||||
EditorApplication.update += UpdateCloudLoginButton;
|
||||
}
|
||||
|
||||
public void SetupLoginElements(Action<JsonValue> onSuccess, Action<ASError> onFail)
|
||||
{
|
||||
this.SetEnabled(true);
|
||||
|
||||
_cloudLoginLabel = _cloudLoginButton.Q<Label>(className: "login-description");
|
||||
|
||||
_cloudLoginLabel.text = "Cloud login unavailable.";
|
||||
_cloudLoginButton.SetEnabled(false);
|
||||
|
||||
_cloudLoginButton.clicked += async () =>
|
||||
{
|
||||
EnableErrorBox(false);
|
||||
this.SetEnabled(false);
|
||||
var result = await AssetStoreAPI.LoginWithTokenAsync(CloudProjectSettings.accessToken);
|
||||
if (result.Success)
|
||||
onSuccess(result.Response);
|
||||
else
|
||||
onFail(result.Error);
|
||||
};
|
||||
|
||||
// Normal login
|
||||
_credentialsLoginButton.clicked += async () =>
|
||||
{
|
||||
EnableErrorBox(false);
|
||||
|
||||
var validatedFields = ValidateLoginFields(_emailField.text, _passwordField.value);
|
||||
this.SetEnabled(!validatedFields);
|
||||
|
||||
if (validatedFields)
|
||||
{
|
||||
var result = await AssetStoreAPI.LoginWithCredentialsAsync(_emailField.text, _passwordField.text);
|
||||
if (result.Success)
|
||||
onSuccess(result.Response);
|
||||
else
|
||||
onFail(result.Error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void EnableErrorBox(bool enable, string message=null)
|
||||
{
|
||||
var displayStyle = enable ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
_errorBox.style.display = displayStyle;
|
||||
|
||||
if (!String.IsNullOrEmpty(message))
|
||||
_errorLabel.text = message;
|
||||
}
|
||||
|
||||
public void ClearLoginBoxes()
|
||||
{
|
||||
_emailField.value = String.Empty;
|
||||
_passwordField.value = String.Empty;
|
||||
}
|
||||
|
||||
private bool ValidateLoginFields(string email, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email))
|
||||
{
|
||||
EnableErrorBox(true, "Email field cannot be empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
EnableErrorBox(true, "Password field cannot be empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ConstructLoginWindow()
|
||||
{
|
||||
// Asset Store logo
|
||||
Image assetStoreLogo = new Image {name = "AssetStoreLogo"};
|
||||
assetStoreLogo.AddToClassList("asset-store-logo");
|
||||
|
||||
Add(assetStoreLogo);
|
||||
|
||||
// Cloud login
|
||||
VisualElement cloudLogin = new VisualElement {name = "CloudLogin"};
|
||||
|
||||
_cloudLoginButton = new Button {name = "LoginButtonCloud"};
|
||||
_cloudLoginButton.AddToClassList("login-button-cloud");
|
||||
|
||||
Label loginDescription = new Label {text = "Cloud login unavailable"};
|
||||
loginDescription.AddToClassList("login-description");
|
||||
|
||||
Label orLabel = new Label {text = "or"};
|
||||
orLabel.AddToClassList("or-label");
|
||||
|
||||
_cloudLoginButton.Add(loginDescription);
|
||||
|
||||
cloudLogin.Add(_cloudLoginButton);
|
||||
cloudLogin.Add(orLabel);
|
||||
|
||||
Add(cloudLogin);
|
||||
|
||||
_errorBox = new Box() { name = "LoginErrorBox" };
|
||||
_errorBox.AddToClassList("login-error-box");
|
||||
|
||||
var errorImage = new Image();
|
||||
_errorBox.Add(errorImage);
|
||||
|
||||
_errorLabel = new Label();
|
||||
_errorBox.Add(_errorLabel);
|
||||
|
||||
Add(_errorBox);
|
||||
EnableErrorBox(false);
|
||||
|
||||
// Manual login
|
||||
VisualElement manualLoginBox = new VisualElement {name = "ManualLoginBox"};
|
||||
manualLoginBox.AddToClassList("manual-login-box");
|
||||
|
||||
// Email input box
|
||||
VisualElement inputBoxEmail = new VisualElement();
|
||||
inputBoxEmail.AddToClassList("input-box-login");
|
||||
|
||||
Label emailTitle = new Label {text = "Email"};
|
||||
_emailField = new TextField();
|
||||
|
||||
inputBoxEmail.Add(emailTitle);
|
||||
inputBoxEmail.Add(_emailField);
|
||||
|
||||
manualLoginBox.Add(inputBoxEmail);
|
||||
|
||||
// Password input box
|
||||
VisualElement inputBoxPassword = new VisualElement();
|
||||
inputBoxPassword.AddToClassList("input-box-login");
|
||||
|
||||
Label passwordTitle = new Label {text = "Password"};
|
||||
_passwordField = new TextField {isPasswordField = true};
|
||||
|
||||
inputBoxPassword.Add(passwordTitle);
|
||||
inputBoxPassword.Add(_passwordField);
|
||||
|
||||
manualLoginBox.Add(inputBoxPassword);
|
||||
|
||||
// Login button
|
||||
_credentialsLoginButton = new Button {name = "LoginButtonCredentials"};
|
||||
_credentialsLoginButton.AddToClassList("login-button-cred");
|
||||
|
||||
Label loginDescriptionCredentials = new Label {text = "Login"};
|
||||
loginDescriptionCredentials.AddToClassList("login-description");
|
||||
|
||||
_credentialsLoginButton.Add(loginDescriptionCredentials);
|
||||
|
||||
manualLoginBox.Add(_credentialsLoginButton);
|
||||
|
||||
Add(manualLoginBox);
|
||||
|
||||
// Helper buttons
|
||||
VisualElement helperBox = new VisualElement {name = "HelperBox"};
|
||||
helperBox.AddToClassList("helper-button-box");
|
||||
|
||||
Button createAccountButton = new Button {name = "CreateAccountButton", text = "Create Publisher ID"};
|
||||
Button forgotPasswordButton = new Button {name = "ForgotPasswordButton", text = "Reset Password"};
|
||||
|
||||
createAccountButton.AddToClassList("hyperlink-button");
|
||||
forgotPasswordButton.AddToClassList("hyperlink-button");
|
||||
|
||||
createAccountButton.clicked += () => Application.OpenURL(REGISTER_URL);
|
||||
forgotPasswordButton.clicked += () => Application.OpenURL(FORGOT_PASSWORD_URL);
|
||||
|
||||
helperBox.Add(createAccountButton);
|
||||
helperBox.Add(forgotPasswordButton);
|
||||
|
||||
Add(helperBox);
|
||||
}
|
||||
|
||||
private void UpdateCloudLoginButton()
|
||||
{
|
||||
if (_cloudLoginLabel == null)
|
||||
return;
|
||||
|
||||
if (_lastRefreshTime + _cloudLoginRefreshTime > EditorApplication.timeSinceStartup)
|
||||
return;
|
||||
|
||||
_lastRefreshTime = EditorApplication.timeSinceStartup;
|
||||
|
||||
// Cloud login
|
||||
if (AssetStoreAPI.IsCloudUserAvailable)
|
||||
{
|
||||
_cloudLoginLabel.text = $"Login with {CloudProjectSettings.userName}";
|
||||
_cloudLoginButton.SetEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cloudLoginLabel.text = "Cloud login unavailable";
|
||||
_cloudLoginButton.SetEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4a93170d5bda304895e5feaf6e34aa8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Login/LoginWindow.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 854f6f9e93b37204eb2e6042138643bc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AssetStoreTools.Uploader.Data;
|
||||
using AssetStoreTools.Uploader.Utility;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Validator.Data;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class AllPackageView : VisualElement
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<AllPackageView> { }
|
||||
|
||||
private enum PackageSorting
|
||||
{
|
||||
Name,
|
||||
Category,
|
||||
Date
|
||||
}
|
||||
|
||||
// Package Data
|
||||
private readonly string[] _priorityGroups = { "Draft", "Published" };
|
||||
private readonly List<PackageGroup> _packageGroups;
|
||||
private List<PackageView> _allPackages;
|
||||
|
||||
// Visual Elements
|
||||
private readonly ScrollView _packageScrollView;
|
||||
private Scroller _packageViewScroller;
|
||||
|
||||
// Sorting data
|
||||
private PackageSorting _activeSorting;
|
||||
|
||||
// Spinner
|
||||
private VisualElement _spinnerBox;
|
||||
private Image _loadingSpinner;
|
||||
private int _spinIndex;
|
||||
private double _spinTimer;
|
||||
private double _spinThreshold = 0.1;
|
||||
|
||||
public Action<bool> RefreshingPackages;
|
||||
|
||||
public AllPackageView()
|
||||
{
|
||||
_packageScrollView = new ScrollView();
|
||||
_allPackages = new List<PackageView>();
|
||||
_packageGroups = new List<PackageGroup>();
|
||||
|
||||
_activeSorting = PackageSorting.Name; // Default sorting type returned by the metadata JSON
|
||||
|
||||
styleSheets.Add(StyleSelector.UploaderWindow.AllPackagesStyle);
|
||||
styleSheets.Add(StyleSelector.UploaderWindow.AllPackagesTheme);
|
||||
ConstructAllPackageView();
|
||||
|
||||
EditorApplication.playModeStateChanged -= PlayModeStateChanged;
|
||||
EditorApplication.playModeStateChanged += PlayModeStateChanged;
|
||||
|
||||
ValidationState.Instance.OnJsonSave -= RepaintPackageIcons;
|
||||
ValidationState.Instance.OnJsonSave += RepaintPackageIcons;
|
||||
}
|
||||
|
||||
#region Element Setup
|
||||
|
||||
private void ConstructAllPackageView()
|
||||
{
|
||||
SetupFilteringTools();
|
||||
SetupSpinner();
|
||||
|
||||
Add(_packageScrollView);
|
||||
}
|
||||
|
||||
private void SetupFilteringTools()
|
||||
{
|
||||
// Top Toolbar
|
||||
var topToolsRow = new VisualElement { name = "TopToolsRow" };
|
||||
topToolsRow.AddToClassList("top-tools-row");
|
||||
|
||||
// Search
|
||||
var searchField = new ToolbarSearchField { name = "SearchField" };
|
||||
searchField.AddToClassList("package-search-field");
|
||||
|
||||
// Sorting menu button
|
||||
var sortMenu = new ToolbarMenu() { text = "Sort by name, A→Z" };
|
||||
sortMenu.menu.AppendAction("Sort by name, A→Z", (_) => { sortMenu.text = "Sort by name, A→Z"; Sort(PackageSorting.Name); });
|
||||
sortMenu.menu.AppendAction("Sort by last updated", (_) => { sortMenu.text = "Sort by last updated"; Sort(PackageSorting.Date); });
|
||||
sortMenu.menu.AppendAction("Sort by category, A→Z", (_) => { sortMenu.text = "Sort by category, A→Z"; Sort(PackageSorting.Category); });
|
||||
sortMenu.AddToClassList("sort-menu");
|
||||
|
||||
// Finalize the bar
|
||||
topToolsRow.Add(searchField);
|
||||
topToolsRow.Add(sortMenu);
|
||||
Add(topToolsRow);
|
||||
|
||||
// Add Callbacks and click events
|
||||
searchField.RegisterCallback<ChangeEvent<string>>(evt =>
|
||||
{
|
||||
var searchString = evt.newValue.ToLower();
|
||||
SearchFilter(searchString);
|
||||
});
|
||||
}
|
||||
|
||||
private void SearchFilter(string filter)
|
||||
{
|
||||
foreach (var g in _packageGroups)
|
||||
g.SearchFilter(filter);
|
||||
}
|
||||
|
||||
private void SetupReadOnlyInfoBox(string infoText)
|
||||
{
|
||||
var groupHeader = new Box { name = "GroupReadOnlyInfoBox" };
|
||||
groupHeader.AddToClassList("group-info-box");
|
||||
|
||||
var infoImage = new Image();
|
||||
groupHeader.Add(infoImage);
|
||||
|
||||
var infoLabel = new Label { text = infoText };
|
||||
groupHeader.Add(infoLabel);
|
||||
|
||||
_packageScrollView.Add(groupHeader);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Display
|
||||
|
||||
public async void ShowPackagesList(bool useCached, Action<ASError> onFail)
|
||||
{
|
||||
// Clear existing packages in the UI
|
||||
ClearPackages();
|
||||
|
||||
// Enable spinner and disable refreshing
|
||||
EnableSpinner();
|
||||
RefreshingPackages?.Invoke(true);
|
||||
|
||||
// Read package metadata from the Publisher portal
|
||||
PackageFetcher packageFetcher = new PackageFetcher();
|
||||
var result = await packageFetcher.Fetch(useCached);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
if (result.SilentFail)
|
||||
return;
|
||||
|
||||
ASDebug.LogError(result.Error.Message);
|
||||
onFail?.Invoke(result.Error);
|
||||
}
|
||||
|
||||
var packages = result.Packages;
|
||||
var json = result.Json;
|
||||
|
||||
// Clear before appending as well
|
||||
ClearPackages();
|
||||
|
||||
if (packages == null)
|
||||
{
|
||||
RefreshingPackages?.Invoke(false);
|
||||
DisplayNoPackages();
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayAllPackages(packages);
|
||||
|
||||
// Only performed after adding all packages to prevent slowdowns. Sorting also repaints the view
|
||||
Sort(_activeSorting);
|
||||
|
||||
RefreshingPackages?.Invoke(false);
|
||||
DisableSpinner();
|
||||
|
||||
AssetStoreAPI.GetPackageThumbnails(json, true, (id, texture) =>
|
||||
{
|
||||
var package = GetPackage(id);
|
||||
var packageImage = package.Q<Image>();
|
||||
packageImage.style.backgroundImage = texture;
|
||||
|
||||
if (texture == null)
|
||||
packageImage.AddToClassList("package-image-not-found");
|
||||
},
|
||||
(id, error) =>
|
||||
{
|
||||
ASDebug.LogWarning($"Package {id} could not download thumbnail successfully\n{error.Exception}");
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearPackages()
|
||||
{
|
||||
_allPackages.Clear();
|
||||
_packageScrollView.Clear();
|
||||
_packageGroups.Clear();
|
||||
}
|
||||
|
||||
private void DisplayNoPackages()
|
||||
{
|
||||
SetupReadOnlyInfoBox("You don't have packages yet. Please visit Publishing Portal if you " +
|
||||
"would like to create one.");
|
||||
DisableSpinner();
|
||||
}
|
||||
|
||||
private void DisplayAllPackages(ICollection<PackageData> packages)
|
||||
{
|
||||
// Each package has an identifier and a bunch of data (current version id, name, etc.)
|
||||
foreach (var package in packages)
|
||||
{
|
||||
AddPackage(package);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPackage(PackageData packageData)
|
||||
{
|
||||
var newEntry = PackageViewStorer.GetPackage(packageData);
|
||||
_allPackages.Add(newEntry);
|
||||
}
|
||||
|
||||
private VisualElement GetPackage(string id)
|
||||
{
|
||||
return _allPackages.FirstOrDefault(package => package.PackageId == id);
|
||||
}
|
||||
|
||||
private void Repaint()
|
||||
{
|
||||
_packageScrollView.Clear();
|
||||
_packageGroups.Clear();
|
||||
|
||||
var groupedDict = new SortedDictionary<string, List<PackageView>>();
|
||||
|
||||
// Group packages by status into a dictionary
|
||||
foreach (var p in _allPackages)
|
||||
{
|
||||
var status = char.ToUpper(p.Status.First()) + p.Status.Substring(1);
|
||||
|
||||
if (!groupedDict.ContainsKey(status))
|
||||
groupedDict.Add(status, new List<PackageView>());
|
||||
|
||||
groupedDict[status].Add(p);
|
||||
}
|
||||
|
||||
// Add prioritized status groups first
|
||||
foreach (var group in _priorityGroups)
|
||||
{
|
||||
if (!groupedDict.ContainsKey(group))
|
||||
continue;
|
||||
|
||||
AddGroup(group, groupedDict[group], true);
|
||||
groupedDict.Remove(group);
|
||||
|
||||
// After adding the 'Draft' group, an infobox indicating that other groups are non-interactable is added
|
||||
if (group == "Draft" && groupedDict.Count > 0)
|
||||
SetupReadOnlyInfoBox("Only packages with a 'Draft' status can be selected for uploading Assets");
|
||||
}
|
||||
|
||||
// Add any leftover status groups
|
||||
foreach (var c in groupedDict.Keys)
|
||||
{
|
||||
AddGroup(c, groupedDict[c], false);
|
||||
}
|
||||
|
||||
// Shared group adding method for priority and non-priority groups
|
||||
void AddGroup(string groupName, List<PackageView> packages, bool createExpanded)
|
||||
{
|
||||
var group = new PackageGroup(groupName, createExpanded)
|
||||
{
|
||||
OnSliderChange = AdjustVerticalSliderPosition
|
||||
};
|
||||
|
||||
foreach (var p in packages)
|
||||
group.AddPackage(p);
|
||||
|
||||
_packageGroups.Add(group);
|
||||
_packageScrollView.Add(group);
|
||||
}
|
||||
}
|
||||
|
||||
private void RepaintPackageIcons()
|
||||
{
|
||||
foreach (var package in _allPackages)
|
||||
{
|
||||
if (!AssetStoreCache.GetCachedTexture(package.PackageId, out Texture2D texture))
|
||||
continue;
|
||||
|
||||
var packageImage = package.Q<Image>();
|
||||
packageImage.style.backgroundImage = texture;
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustVerticalSliderPosition(float delta)
|
||||
{
|
||||
if (_packageViewScroller == null)
|
||||
_packageViewScroller = this.Q<Scroller>(className: "unity-scroll-view__vertical-scroller");
|
||||
|
||||
_packageViewScroller.value += delta;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package View Sorting
|
||||
|
||||
private void Sort(PackageSorting sortBy)
|
||||
{
|
||||
if (sortBy == _activeSorting && _packageScrollView.childCount > 0)
|
||||
return;
|
||||
|
||||
switch (sortBy)
|
||||
{
|
||||
case PackageSorting.Name:
|
||||
SortByName(false);
|
||||
break;
|
||||
case PackageSorting.Date:
|
||||
SortByDate(true);
|
||||
break;
|
||||
case PackageSorting.Category:
|
||||
SortByCategory(false);
|
||||
break;
|
||||
}
|
||||
|
||||
_activeSorting = sortBy;
|
||||
}
|
||||
|
||||
private void SortByName(bool descending)
|
||||
{
|
||||
if (!descending)
|
||||
_allPackages = _allPackages.OrderBy(p => p.PackageName).ToList();
|
||||
else
|
||||
_allPackages = _allPackages.OrderByDescending(p => p.PackageName).ToList();
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void SortByCategory(bool descending)
|
||||
{
|
||||
if (!descending)
|
||||
_allPackages = _allPackages.OrderBy(p => p.Category).ThenBy(p => p.PackageName).ToList();
|
||||
else
|
||||
_allPackages = _allPackages.OrderByDescending(p => p.Category).ThenBy(p => p.PackageName).ToList();
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void SortByDate(bool descending)
|
||||
{
|
||||
if (!descending)
|
||||
_allPackages = _allPackages.OrderBy(p => p.LastUpdatedDate).ThenBy(p => p.PackageName).ToList();
|
||||
else
|
||||
_allPackages = _allPackages.OrderByDescending(p => p.LastUpdatedDate).ThenBy(p => p.PackageName).ToList();
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void PlayModeStateChanged(PlayModeStateChange playModeState)
|
||||
{
|
||||
if (playModeState == PlayModeStateChange.EnteredEditMode)
|
||||
{
|
||||
RepaintPackageIcons();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Spinner
|
||||
|
||||
private void SetupSpinner()
|
||||
{
|
||||
_spinnerBox = new VisualElement {name = "SpinnerBox"};
|
||||
_spinnerBox.AddToClassList("spinner-box");
|
||||
|
||||
_loadingSpinner = new Image {name = "SpinnerImage"};
|
||||
_loadingSpinner.AddToClassList("spinner-image");
|
||||
|
||||
_spinnerBox.Add(_loadingSpinner);
|
||||
Add(_spinnerBox);
|
||||
}
|
||||
|
||||
private void UpdateSpinner()
|
||||
{
|
||||
if (_loadingSpinner == null)
|
||||
return;
|
||||
|
||||
if (_spinTimer + _spinThreshold > EditorApplication.timeSinceStartup)
|
||||
return;
|
||||
|
||||
_spinTimer = EditorApplication.timeSinceStartup;
|
||||
_loadingSpinner.image = EditorGUIUtility.IconContent($"WaitSpin{_spinIndex:00}").image;
|
||||
|
||||
_spinIndex += 1;
|
||||
|
||||
if (_spinIndex > 11)
|
||||
_spinIndex = 0;
|
||||
}
|
||||
|
||||
private void EnableSpinner()
|
||||
{
|
||||
EditorApplication.update += UpdateSpinner;
|
||||
_packageScrollView.style.display = DisplayStyle.None;
|
||||
_spinnerBox.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
private void DisableSpinner()
|
||||
{
|
||||
EditorApplication.update -= UpdateSpinner;
|
||||
_packageScrollView.style.display = DisplayStyle.Flex;
|
||||
_spinnerBox.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 607cf9e3fb4a49839f2e6a82e0d8d535
|
||||
timeCreated: 1651220955
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/AllPackageView.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class PackageGroup : VisualElement
|
||||
{
|
||||
// Category Data
|
||||
private string GroupName { get; }
|
||||
private readonly List<PackageView> _packages;
|
||||
|
||||
// Visual Elements
|
||||
private Button _groupExpanderBox;
|
||||
private VisualElement _groupContent;
|
||||
|
||||
private Label _expanderLabel;
|
||||
private Label _groupLabel;
|
||||
|
||||
// Other
|
||||
private PackageView _expandedPackageView;
|
||||
|
||||
private bool _expanded;
|
||||
private bool? _expandingOverriden;
|
||||
|
||||
// Actions
|
||||
public Action<float> OnSliderChange;
|
||||
|
||||
public PackageGroup(string groupName, bool createExpanded)
|
||||
{
|
||||
GroupName = groupName;
|
||||
AddToClassList("package-group");
|
||||
|
||||
_packages = new List<PackageView>();
|
||||
_expanded = createExpanded;
|
||||
|
||||
SetupSingleGroupElement();
|
||||
HandleExpanding();
|
||||
}
|
||||
|
||||
public void AddPackage(PackageView packageView)
|
||||
{
|
||||
_packages.Add(packageView);
|
||||
_groupContent.Add(packageView);
|
||||
|
||||
UpdateGroupLabel();
|
||||
packageView.OnPackageSelection = HandlePackageSelection;
|
||||
packageView.ShowFunctions(false);
|
||||
}
|
||||
|
||||
public void SearchFilter(string filter)
|
||||
{
|
||||
var foundPackageCount = 0;
|
||||
foreach(var p in _packages)
|
||||
{
|
||||
if (p.SearchableText.Contains(filter))
|
||||
{
|
||||
foundPackageCount++;
|
||||
p.style.display = DisplayStyle.Flex;
|
||||
_groupContent.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
else
|
||||
p.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
_expandingOverriden = null;
|
||||
|
||||
UpdateGroupLabel();
|
||||
SetEnabled(true);
|
||||
HandleExpanding();
|
||||
}
|
||||
else
|
||||
{
|
||||
OverwriteGroupLabel($"{GroupName} ({foundPackageCount} found)");
|
||||
SetEnabled(foundPackageCount > 0);
|
||||
HandleExpanding(foundPackageCount > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupSingleGroupElement()
|
||||
{
|
||||
_groupExpanderBox = new Button();
|
||||
_groupExpanderBox.AddToClassList("group-expander-box");
|
||||
|
||||
_expanderLabel = new Label { name = "ExpanderLabel", text = "►" };
|
||||
_expanderLabel.AddToClassList("expander");
|
||||
|
||||
_groupLabel = new Label {text = $"{GroupName} ({_packages.Count})"};
|
||||
_groupLabel.AddToClassList("group-label");
|
||||
|
||||
_groupExpanderBox.Add(_expanderLabel);
|
||||
_groupExpanderBox.Add(_groupLabel);
|
||||
|
||||
_groupContent = new VisualElement {name = "GroupContentBox"};
|
||||
_groupContent.AddToClassList("group-content-box");
|
||||
|
||||
_groupExpanderBox.clicked += () =>
|
||||
{
|
||||
if (_expandingOverriden == null)
|
||||
_expanded = !_expanded;
|
||||
else
|
||||
_expandingOverriden = !_expandingOverriden;
|
||||
|
||||
HandleExpanding();
|
||||
};
|
||||
|
||||
var groupSeparator = new VisualElement {name = "GroupSeparator"};
|
||||
groupSeparator.AddToClassList("group-separator");
|
||||
|
||||
if (GroupName.ToLower() != "draft")
|
||||
{
|
||||
_groupLabel.SetEnabled(false);
|
||||
_groupContent.AddToClassList("unity-disabled");
|
||||
groupSeparator.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
Add(_groupExpanderBox);
|
||||
Add(_groupContent);
|
||||
Add(groupSeparator);
|
||||
}
|
||||
|
||||
private void HandleExpanding(bool? overrideExpanding=null)
|
||||
{
|
||||
var expanded = _expanded;
|
||||
|
||||
if (overrideExpanding != null)
|
||||
{
|
||||
expanded = (bool) overrideExpanding;
|
||||
_expandingOverriden = expanded;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_expandingOverriden != null)
|
||||
expanded = (bool) _expandingOverriden;
|
||||
}
|
||||
|
||||
_expanderLabel.text = !expanded ? "►" : "▼";
|
||||
var displayStyle = expanded ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
_groupContent.style.display = displayStyle;
|
||||
}
|
||||
|
||||
private void HandlePackageSelection(PackageView packageView)
|
||||
{
|
||||
if (_expandedPackageView == packageView)
|
||||
{
|
||||
_expandedPackageView = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_expandedPackageView == null)
|
||||
{
|
||||
_expandedPackageView = packageView;
|
||||
return;
|
||||
}
|
||||
|
||||
// Always where it was
|
||||
if (packageView.worldBound.y > _expandedPackageView.worldBound.y)
|
||||
{
|
||||
var sliderChangeDelta = -(_expandedPackageView.worldBound.height - packageView.worldBound.height);
|
||||
OnSliderChange?.Invoke(sliderChangeDelta);
|
||||
}
|
||||
|
||||
_expandedPackageView?.ShowFunctions(false);
|
||||
_expandedPackageView = packageView;
|
||||
|
||||
}
|
||||
|
||||
private void UpdateGroupLabel()
|
||||
{
|
||||
_groupLabel.text = $"{GroupName} ({_packages.Count})";
|
||||
}
|
||||
|
||||
private void OverwriteGroupLabel(string text)
|
||||
{
|
||||
_groupLabel.text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd683831688cd414f8cc9cd352689b4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/PackageGroup.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,720 @@
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using AssetStoreTools.Utility;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using AssetStoreTools.Exporter;
|
||||
using AssetStoreTools.Uploader.Data;
|
||||
using AssetStoreTools.Uploader.Utility;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class PackageView : VisualElement
|
||||
{
|
||||
public string PackageId => _packageData.Id;
|
||||
public string VersionId => _packageData.VersionId;
|
||||
public string PackageName => _packageData.Name;
|
||||
public string Status => _packageData.Status;
|
||||
public string Category => _packageData.Category;
|
||||
public string LastUpdatedDate => FormatDate(_packageData.LastDate);
|
||||
public string LastUpdatedSize => FormatSize(_packageData.LastSize);
|
||||
public bool IsCompleteProject => _packageData.IsCompleteProject;
|
||||
public string LastUploadedPath => _packageData.LastUploadedPath;
|
||||
public string LastUploadedGuid => _packageData.LastUploadedGuid;
|
||||
public string SearchableText { get; private set; }
|
||||
|
||||
private PackageData _packageData;
|
||||
|
||||
// Unexpanded state dynamic elements
|
||||
private Button _foldoutBox;
|
||||
private Label _expanderLabel;
|
||||
private Label _assetLabel;
|
||||
private Label _lastDateSizeLabel;
|
||||
private Button _openInBrowserButton;
|
||||
|
||||
// Expanded state dynamic elements
|
||||
private VisualElement _functionsBox;
|
||||
|
||||
private VisualElement _exportAndUploadContainer;
|
||||
private VisualElement _uploadProgressContainer;
|
||||
private Button _exportButton;
|
||||
private Button _uploadButton;
|
||||
private Button _cancelUploadButton;
|
||||
private ProgressBar _uploadProgressBarFlow;
|
||||
private ProgressBar _uploadProgressBarHeader;
|
||||
|
||||
private VisualElement _uploadProgressFlowBg;
|
||||
private VisualElement _uploadProgressHeaderBg;
|
||||
|
||||
private bool _expanded;
|
||||
public Action<PackageView> OnPackageSelection;
|
||||
|
||||
private VisualElement _workflowSelectionBox;
|
||||
private UploadWorkflowView _activeWorkflowElement;
|
||||
private Dictionary<string, UploadWorkflowView> _uploadWorkflows;
|
||||
|
||||
public PackageView(PackageData packageData)
|
||||
{
|
||||
UpdateDataValues(packageData);
|
||||
SetupPackageElement();
|
||||
}
|
||||
|
||||
public void UpdateDataValues(PackageData packageData)
|
||||
{
|
||||
_packageData = packageData;
|
||||
|
||||
SearchableText = $"{PackageName} {Category}".ToLower();
|
||||
|
||||
if (_foldoutBox == null)
|
||||
return;
|
||||
|
||||
_assetLabel.text = PackageName;
|
||||
_lastDateSizeLabel.text = $"{Category} | {LastUpdatedSize} | {LastUpdatedDate}";
|
||||
|
||||
if (_uploadWorkflows != null && _uploadWorkflows.ContainsKey(FolderUploadWorkflowView.WorkflowName))
|
||||
((FolderUploadWorkflowView) _uploadWorkflows[FolderUploadWorkflowView.WorkflowName]).SetCompleteProject(packageData.IsCompleteProject);
|
||||
|
||||
if (Status == "draft")
|
||||
return;
|
||||
|
||||
ResetPostUpload();
|
||||
SetupExpander();
|
||||
}
|
||||
|
||||
public void ShowFunctions(bool show)
|
||||
{
|
||||
if (_functionsBox == null)
|
||||
{
|
||||
if (show)
|
||||
SetupFunctionsElement();
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
if (show == _expanded)
|
||||
return;
|
||||
|
||||
_expanded = show;
|
||||
_expanderLabel.text = !_expanded ? "►" : "▼";
|
||||
|
||||
if (_expanded)
|
||||
_foldoutBox.AddToClassList("foldout-box-expanded");
|
||||
else
|
||||
_foldoutBox.RemoveFromClassList("foldout-box-expanded");
|
||||
|
||||
if (_functionsBox != null)
|
||||
_functionsBox.style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void SetupPackageElement()
|
||||
{
|
||||
AddToClassList("full-package-box");
|
||||
|
||||
_foldoutBox = new Button {name = "Package"};
|
||||
_foldoutBox.AddToClassList("foldout-box");
|
||||
|
||||
// Expander, Icon and Asset Label
|
||||
VisualElement foldoutBoxInfo = new VisualElement { name = "foldoutBoxInfo" };
|
||||
foldoutBoxInfo.AddToClassList("foldout-box-info");
|
||||
|
||||
VisualElement labelExpanderRow = new VisualElement { name = "labelExpanderRow" };
|
||||
labelExpanderRow.AddToClassList("expander-label-row");
|
||||
|
||||
_expanderLabel = new Label { name = "ExpanderLabel", text = "►" };
|
||||
_expanderLabel.AddToClassList("expander");
|
||||
|
||||
Image assetImage = new Image { name = "AssetImage" };
|
||||
assetImage.AddToClassList("package-image");
|
||||
|
||||
VisualElement assetLabelInfoBox = new VisualElement { name = "assetLabelInfoBox" };
|
||||
assetLabelInfoBox.AddToClassList("asset-label-info-box");
|
||||
|
||||
_assetLabel = new Label { name = "AssetLabel", text = PackageName };
|
||||
_assetLabel.AddToClassList("asset-label");
|
||||
|
||||
_lastDateSizeLabel = new Label {name = "AssetInfoLabel", text = $"{Category} | {LastUpdatedSize} | {LastUpdatedDate}"};
|
||||
_lastDateSizeLabel.AddToClassList("asset-info");
|
||||
|
||||
assetLabelInfoBox.Add(_assetLabel);
|
||||
assetLabelInfoBox.Add(_lastDateSizeLabel);
|
||||
|
||||
labelExpanderRow.Add(_expanderLabel);
|
||||
labelExpanderRow.Add(assetImage);
|
||||
labelExpanderRow.Add(assetLabelInfoBox);
|
||||
|
||||
_openInBrowserButton = new Button
|
||||
{
|
||||
name = "OpenInBrowserButton",
|
||||
tooltip = "View your package in the Publishing Portal."
|
||||
};
|
||||
_openInBrowserButton.AddToClassList("open-in-browser-button");
|
||||
|
||||
// Header Progress bar
|
||||
_uploadProgressBarHeader = new ProgressBar { name = "HeaderProgressBar" };
|
||||
_uploadProgressBarHeader.AddToClassList("header-progress-bar");
|
||||
_uploadProgressBarHeader.style.display = DisplayStyle.None;
|
||||
_uploadProgressHeaderBg = _uploadProgressBarHeader.Q<VisualElement>(className:"unity-progress-bar__progress");
|
||||
|
||||
// Connect it all
|
||||
foldoutBoxInfo.Add(labelExpanderRow);
|
||||
foldoutBoxInfo.Add(_openInBrowserButton);
|
||||
|
||||
_foldoutBox.Add(foldoutBoxInfo);
|
||||
_foldoutBox.Add(_uploadProgressBarHeader);
|
||||
|
||||
Add(_foldoutBox);
|
||||
SetupExpander();
|
||||
}
|
||||
|
||||
private void SetupExpander()
|
||||
{
|
||||
if (_foldoutBox != null)
|
||||
{
|
||||
_foldoutBox.clickable = null;
|
||||
|
||||
// If not draft - hide expander, open a listing page on click
|
||||
if (Status != "draft")
|
||||
{
|
||||
_expanderLabel.style.display = DisplayStyle.None;
|
||||
_foldoutBox.clicked += () =>
|
||||
{
|
||||
Application.OpenURL($"https://publisher.unity.com/packages/{VersionId}/edit/upload");
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else open functions box
|
||||
_foldoutBox.clicked += () =>
|
||||
{
|
||||
OnPackageSelection?.Invoke(this);
|
||||
ShowFunctions(!_expanded);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (_openInBrowserButton != null)
|
||||
{
|
||||
_openInBrowserButton.clickable = null;
|
||||
|
||||
_openInBrowserButton.clicked += () =>
|
||||
{
|
||||
Application.OpenURL($"https://publisher.unity.com/packages/{VersionId}/edit/upload");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupFunctionsElement()
|
||||
{
|
||||
_functionsBox = new VisualElement { name = "FunctionalityBox" };
|
||||
_functionsBox.AddToClassList("functionality-box");
|
||||
|
||||
_functionsBox.style.display = DisplayStyle.None;
|
||||
|
||||
// Validation and uploading boxes
|
||||
var uploadingWorkflow = ConstructUploadingWorkflow();
|
||||
_functionsBox.Add(uploadingWorkflow);
|
||||
|
||||
Add(_functionsBox);
|
||||
}
|
||||
|
||||
private VisualElement ConstructUploadingWorkflow()
|
||||
{
|
||||
// Upload Box
|
||||
VisualElement uploadBox = new VisualElement { name = "UploadBox" };
|
||||
uploadBox.AddToClassList("upload-box");
|
||||
|
||||
var folderUploadWorkflow = FolderUploadWorkflowView.Create(Category, IsCompleteProject, SerializeWorkflowSelections);
|
||||
var unitypackageUploadWorkflow = UnityPackageUploadWorkflowView.Create(Category, SerializeWorkflowSelections);
|
||||
var hybridPackageUploadWorkflow = HybridPackageUploadWorkflowView.Create(Category, SerializeWorkflowSelections);
|
||||
|
||||
// Workflow selection
|
||||
_workflowSelectionBox = new VisualElement();
|
||||
_workflowSelectionBox.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement labelHelpRow = new VisualElement();
|
||||
labelHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label workflowLabel = new Label { text = "Upload type" };
|
||||
Image workflowLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "Select what content you are uploading to the Asset Store"
|
||||
+ "\n\n• From Assets Folder - content located within the project's 'Assets' folder or one of its subfolders"
|
||||
+ "\n\n• Pre-exported .unitypackage - content that has already been compressed into a .unitypackage file"
|
||||
#if UNITY_ASTOOLS_EXPERIMENTAL
|
||||
+ "\n\n• Local UPM Package - content that is located within the project's 'Packages' folder. Only embedded and local packages are supported"
|
||||
#endif
|
||||
};
|
||||
|
||||
labelHelpRow.Add(workflowLabel);
|
||||
labelHelpRow.Add(workflowLabelTooltip);
|
||||
|
||||
var flowDrop = new ToolbarMenu();
|
||||
flowDrop.menu.AppendAction(FolderUploadWorkflowView.WorkflowDisplayName, _ => { SetActiveWorkflowElement(folderUploadWorkflow, flowDrop); });
|
||||
flowDrop.menu.AppendAction(UnityPackageUploadWorkflowView.WorkflowDisplayName, _ => { SetActiveWorkflowElement(unitypackageUploadWorkflow, flowDrop); });
|
||||
#if UNITY_ASTOOLS_EXPERIMENTAL
|
||||
flowDrop.menu.AppendAction(HybridPackageUploadWorkflowView.WorkflowDisplayName, _ => { SetActiveWorkflowElement(hybridPackageUploadWorkflow, flowDrop); });
|
||||
#endif // UNITY_ASTOOLS_EXPERIMENTAL
|
||||
flowDrop.AddToClassList("workflow-dropdown");
|
||||
|
||||
_workflowSelectionBox.Add(labelHelpRow);
|
||||
_workflowSelectionBox.Add(flowDrop);
|
||||
|
||||
uploadBox.Add(_workflowSelectionBox);
|
||||
|
||||
_uploadWorkflows = new Dictionary<string, UploadWorkflowView>
|
||||
{
|
||||
{FolderUploadWorkflowView.WorkflowName, folderUploadWorkflow},
|
||||
{UnityPackageUploadWorkflowView.WorkflowName, unitypackageUploadWorkflow},
|
||||
{HybridPackageUploadWorkflowView.WorkflowName, hybridPackageUploadWorkflow}
|
||||
};
|
||||
|
||||
foreach (var kvp in _uploadWorkflows)
|
||||
uploadBox.Add(kvp.Value);
|
||||
|
||||
var progressUploadBox = SetupProgressUploadBox();
|
||||
uploadBox.Add(progressUploadBox);
|
||||
|
||||
DeserializeWorkflowSelections(flowDrop);
|
||||
|
||||
return uploadBox;
|
||||
}
|
||||
|
||||
private void SerializeWorkflowSelections()
|
||||
{
|
||||
ASDebug.Log("Serializing workflow selections");
|
||||
var json = JsonValue.NewDict();
|
||||
|
||||
// Active workflow
|
||||
var activeWorkflow = JsonValue.NewString(_activeWorkflowElement.Name);
|
||||
json["ActiveWorkflow"] = activeWorkflow;
|
||||
|
||||
// Workflow Selections
|
||||
foreach(var kvp in _uploadWorkflows)
|
||||
json[kvp.Key] = kvp.Value.SerializeWorkflow();
|
||||
|
||||
AssetStoreCache.CacheUploadSelections(PackageId, json);
|
||||
}
|
||||
|
||||
private void DeserializeWorkflowSelections(ToolbarMenu activeFlowMenu)
|
||||
{
|
||||
AssetStoreCache.GetCachedUploadSelections(PackageId, out JsonValue cachedSelections);
|
||||
|
||||
// Individual workflow selections
|
||||
foreach (var kvp in _uploadWorkflows)
|
||||
{
|
||||
if (cachedSelections.ContainsKey(kvp.Key))
|
||||
kvp.Value.LoadSerializedWorkflow(cachedSelections[kvp.Key], LastUploadedPath, LastUploadedGuid);
|
||||
else
|
||||
kvp.Value.LoadSerializedWorkflowFallback(LastUploadedPath, LastUploadedGuid);
|
||||
}
|
||||
|
||||
// Active workflow selection
|
||||
if (!cachedSelections.ContainsKey("ActiveWorkflow"))
|
||||
{
|
||||
// Set default to folder workflow
|
||||
SetActiveWorkflowElement(_uploadWorkflows[FolderUploadWorkflowView.WorkflowName], activeFlowMenu);
|
||||
return;
|
||||
}
|
||||
|
||||
var serializedWorkflow = cachedSelections["ActiveWorkflow"].AsString();
|
||||
SetActiveWorkflowElement(_uploadWorkflows[serializedWorkflow], activeFlowMenu);
|
||||
}
|
||||
|
||||
private void SetActiveWorkflowElement(UploadWorkflowView newActiveWorkflowElement, ToolbarMenu activeFlowMenu)
|
||||
{
|
||||
if (_activeWorkflowElement != null)
|
||||
_activeWorkflowElement.style.display = DisplayStyle.None;
|
||||
|
||||
_activeWorkflowElement = newActiveWorkflowElement;
|
||||
_activeWorkflowElement.style.display = DisplayStyle.Flex;
|
||||
activeFlowMenu.text = newActiveWorkflowElement.DisplayName;
|
||||
|
||||
UpdateActionButtons();
|
||||
SerializeWorkflowSelections();
|
||||
}
|
||||
|
||||
private void UpdateActionButtons()
|
||||
{
|
||||
switch (_activeWorkflowElement)
|
||||
{
|
||||
case UnityPackageUploadWorkflowView _:
|
||||
_exportButton.style.display = DisplayStyle.None;
|
||||
_uploadButton.style.marginLeft = 0f;
|
||||
_uploadButton.text = "Upload";
|
||||
break;
|
||||
default:
|
||||
_exportButton.style.display = DisplayStyle.Flex;
|
||||
_uploadButton.style.marginLeft = 5f;
|
||||
_uploadButton.text = "Export and Upload";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private VisualElement SetupProgressUploadBox()
|
||||
{
|
||||
var progressUploadBox = new VisualElement();
|
||||
progressUploadBox.AddToClassList("progress-upload-box");
|
||||
|
||||
_exportAndUploadContainer = new VisualElement();
|
||||
_exportAndUploadContainer.AddToClassList("export-and-upload-container");
|
||||
|
||||
_exportButton = new Button(ExportWithoutUploading) { name = "ExportButton", text = "Export" };
|
||||
_exportButton.AddToClassList("export-button");
|
||||
|
||||
_uploadButton = new Button(PreparePackageUpload) { name = "UploadButton", text = "Export and Upload" };
|
||||
_uploadButton.AddToClassList("upload-button");
|
||||
|
||||
_exportAndUploadContainer.Add(_exportButton);
|
||||
_exportAndUploadContainer.Add(_uploadButton);
|
||||
|
||||
_uploadProgressContainer = new VisualElement();
|
||||
_uploadProgressContainer.AddToClassList("upload-progress-container");
|
||||
_uploadProgressContainer.style.display = DisplayStyle.None;
|
||||
|
||||
_uploadProgressBarFlow = new ProgressBar { name = "UploadProgressBar" };
|
||||
_uploadProgressBarFlow.AddToClassList("upload-progress-bar");
|
||||
_uploadProgressFlowBg = _uploadProgressBarFlow.Q<VisualElement>(className: "unity-progress-bar__progress");
|
||||
|
||||
_cancelUploadButton = new Button() { name = "CancelButton", text = "Cancel" };
|
||||
_cancelUploadButton.AddToClassList("cancel-button");
|
||||
|
||||
_uploadProgressContainer.Add(_uploadProgressBarFlow);
|
||||
_uploadProgressContainer.Add(_cancelUploadButton);
|
||||
|
||||
progressUploadBox.Add(_exportAndUploadContainer);
|
||||
progressUploadBox.Add(_uploadProgressContainer);
|
||||
|
||||
return progressUploadBox;
|
||||
}
|
||||
|
||||
private string FormatSize(string size)
|
||||
{
|
||||
if (string.IsNullOrEmpty(size))
|
||||
return "0.00 MB";
|
||||
|
||||
float.TryParse(size, out var sizeBytes);
|
||||
return $"{sizeBytes / (1024f * 1024f):0.00} MB";
|
||||
}
|
||||
|
||||
private string FormatDate(string date)
|
||||
{
|
||||
DateTime dt = DateTime.Parse(date);
|
||||
return dt.Date.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
#region Package Uploading
|
||||
|
||||
private async void ExportWithoutUploading()
|
||||
{
|
||||
var paths = _activeWorkflowElement.GetAllExportPaths();
|
||||
if(paths.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Exporting failed", "No path was selected. Please " +
|
||||
"select a path and try again.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
|
||||
var packageNameStripped = Regex.Replace(PackageName, "[^a-zA-Z0-9]", "");
|
||||
var outputPath = EditorUtility.SaveFilePanel("Export Package", rootProjectPath,
|
||||
$"{packageNameStripped}-{DateTime.Now:yyyy-dd-M--HH-mm-ss}", "unitypackage");
|
||||
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
return;
|
||||
|
||||
var exportResult = await ExportPackage(outputPath);
|
||||
if (!exportResult.Success)
|
||||
Debug.LogError($"Package exporting failed: {exportResult.Error}");
|
||||
else
|
||||
Debug.Log($"Package exported to '{Path.GetFullPath(exportResult.ExportedPath).Replace("\\", "/")}'");
|
||||
}
|
||||
|
||||
private async Task<ExportResult> ExportPackage(string outputPath)
|
||||
{
|
||||
var exportResult = await _activeWorkflowElement.ExportPackage(outputPath, IsCompleteProject);
|
||||
return exportResult;
|
||||
}
|
||||
|
||||
private bool ValidatePackageSize(string packagePath)
|
||||
{
|
||||
long packageSize = new FileInfo(packagePath).Length;
|
||||
long packageSizeLimit = AssetStoreUploader.MaxPackageSizeBytes;
|
||||
float packageSizeInGB = packageSize / (1024f * 1024f * 1024f);
|
||||
float maxPackageSizeInGB = packageSizeLimit / (1024f * 1024f * 1024f);
|
||||
|
||||
if (packageSizeInGB - maxPackageSizeInGB < 0.1f)
|
||||
return true;
|
||||
|
||||
var message = $"The size of your package ({packageSizeInGB:0.#} GB) exceeds the maximum allowed package size of {maxPackageSizeInGB:0.#} GB.\n\n" +
|
||||
$"Please reduce the size of your package.";
|
||||
|
||||
EditorUtility.DisplayDialog("Asset Store Tools", message, "OK");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ValidateUnityVersionsForUpload()
|
||||
{
|
||||
if (!AssetStoreUploader.ShowPackageVersionDialog)
|
||||
return true;
|
||||
|
||||
EditorUtility.DisplayProgressBar("Preparing...", "Checking version compatibility", 0.4f);
|
||||
var versions = AssetStoreAPI.GetPackageUploadedVersions(PackageId, VersionId);
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
if (versions.Any(x => string.Compare(x, AssetStoreUploader.MinRequiredPackageVersion, StringComparison.Ordinal) >= 0))
|
||||
return true;
|
||||
|
||||
var result = EditorUtility.DisplayDialogComplex("Asset Store Tools", $"You may upload this package, but you will need to add a package using Unity version {AssetStoreUploader.MinRequiredPackageVersion} " +
|
||||
"or higher to be able to submit a new asset", "Upload", "Cancel", "Upload and do not display this again");
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case 1:
|
||||
return false;
|
||||
case 2:
|
||||
AssetStoreUploader.ShowPackageVersionDialog = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void PreparePackageUpload()
|
||||
{
|
||||
var paths = _activeWorkflowElement.GetAllExportPaths();
|
||||
if (paths.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Uploading failed", "No path was selected. Please " +
|
||||
"select a path and try again.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
var packageNameStripped = Regex.Replace(PackageName, "[^a-zA-Z0-9]", "");
|
||||
var outputPath = $"Temp/{packageNameStripped}-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.unitypackage";
|
||||
var exportResult = await ExportPackage(outputPath);
|
||||
if (!exportResult.Success)
|
||||
{
|
||||
Debug.LogError($"Package exporting failed: {exportResult.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidatePackageSize(exportResult.ExportedPath))
|
||||
return;
|
||||
|
||||
if (!ValidateUnityVersionsForUpload())
|
||||
return;
|
||||
|
||||
var localPackageGuid = _activeWorkflowElement.GetLocalPackageGuid();
|
||||
var localPackagePath = _activeWorkflowElement.GetLocalPackagePath();
|
||||
var localProjectPath = _activeWorkflowElement.GetLocalProjectPath();
|
||||
BeginPackageUpload(exportResult.ExportedPath, localPackageGuid, localPackagePath, localProjectPath);
|
||||
}
|
||||
|
||||
private async void BeginPackageUpload(string exportedPackagePath, string packageGuid, string packagePath, string projectPath)
|
||||
{
|
||||
// Configure the UI
|
||||
// Disable Active Workflow
|
||||
EnableWorkflowElements(false);
|
||||
|
||||
// Progress bar
|
||||
_exportAndUploadContainer.style.display = DisplayStyle.None;
|
||||
_uploadProgressContainer.style.display = DisplayStyle.Flex;
|
||||
|
||||
// Configure the upload cancel button
|
||||
_cancelUploadButton.clickable = null;
|
||||
_cancelUploadButton.clicked += () => AssetStoreAPI.AbortPackageUpload(VersionId);
|
||||
_cancelUploadButton.text = "Cancel";
|
||||
|
||||
// Set up upload progress tracking for the unexpanded package progress bar
|
||||
EditorApplication.update += OnPackageUploadProgressHeader;
|
||||
|
||||
// Set up upload progress tracking for the expanded package progress bar
|
||||
EditorApplication.update += OnPackageUploadProgressContent;
|
||||
|
||||
// Set up base analytics data
|
||||
var analyticsData = ConstructAnalyticsData(exportedPackagePath);
|
||||
|
||||
// Start tracking uploading time
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew(); // Debugging
|
||||
|
||||
// Start uploading the package
|
||||
var result = await AssetStoreAPI.UploadPackageAsync(VersionId, PackageName, exportedPackagePath, packageGuid, packagePath, projectPath);
|
||||
|
||||
watch.Stop();
|
||||
analyticsData.TimeTaken = watch.Elapsed.TotalSeconds;
|
||||
|
||||
switch (result.Status)
|
||||
{
|
||||
case PackageUploadResult.UploadStatus.Success:
|
||||
analyticsData.UploadFinishedReason = "Success";
|
||||
ASDebug.Log($"Finished uploading, time taken: {watch.Elapsed.TotalSeconds} seconds");
|
||||
await OnPackageUploadSuccess();
|
||||
break;
|
||||
case PackageUploadResult.UploadStatus.Cancelled:
|
||||
analyticsData.UploadFinishedReason = "Cancelled";
|
||||
ASDebug.Log($"Uploading cancelled, time taken: {watch.Elapsed.TotalSeconds} seconds");
|
||||
break;
|
||||
case PackageUploadResult.UploadStatus.Fail:
|
||||
analyticsData.UploadFinishedReason = result.Error.Exception.ToString();
|
||||
OnPackageUploadFail(result.Error);
|
||||
break;
|
||||
case PackageUploadResult.UploadStatus.ResponseTimeout:
|
||||
analyticsData.UploadFinishedReason = "ResponseTimeout";
|
||||
Debug.LogWarning($"All bytes for the package '{PackageName}' have been uploaded, but a response " +
|
||||
$"from the server was not received. This can happen because of Firewall restrictions. " +
|
||||
$"Please make sure that a new version of your package has reached the Publishing Portal.");
|
||||
await OnPackageUploadSuccess();
|
||||
break;
|
||||
}
|
||||
|
||||
ASAnalytics.SendUploadingEvent(analyticsData);
|
||||
PostUploadCleanup(result.Status);
|
||||
}
|
||||
|
||||
private ASAnalytics.AnalyticsData ConstructAnalyticsData(string exportedPackagePath)
|
||||
{
|
||||
bool validated;
|
||||
string validationResults;
|
||||
|
||||
validated = _activeWorkflowElement.GetValidationSummary(out validationResults);
|
||||
|
||||
FileInfo packageFileInfo = new FileInfo(exportedPackagePath);
|
||||
string workflow = _activeWorkflowElement.Name;
|
||||
|
||||
ASAnalytics.AnalyticsData data = new ASAnalytics.AnalyticsData
|
||||
{
|
||||
ToolVersion = AssetStoreAPI.ToolVersion,
|
||||
EndpointUrl = AssetStoreAPI.AssetStoreProdUrl,
|
||||
PackageId = PackageId,
|
||||
Category = Category,
|
||||
UsedValidator = validated,
|
||||
ValidatorResults = validationResults,
|
||||
PackageSize = packageFileInfo.Length,
|
||||
Workflow = workflow
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void OnPackageUploadProgressHeader()
|
||||
{
|
||||
// Header progress bar is only shown when the package is not expanded and has progress
|
||||
if (_uploadProgressBarHeader.value > 0.0f)
|
||||
_uploadProgressBarHeader.style.display = !_expanded ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
|
||||
if (!AssetStoreAPI.ActiveUploads.ContainsKey(VersionId))
|
||||
return;
|
||||
|
||||
_uploadProgressBarHeader.value = AssetStoreAPI.ActiveUploads[VersionId].Progress;
|
||||
}
|
||||
|
||||
private void OnPackageUploadProgressContent()
|
||||
{
|
||||
if (!AssetStoreAPI.ActiveUploads.ContainsKey(VersionId))
|
||||
return;
|
||||
|
||||
var progressValue = AssetStoreAPI.ActiveUploads[VersionId].Progress;
|
||||
_uploadProgressBarFlow.value = progressValue;
|
||||
_uploadProgressBarFlow.title = $"{progressValue:0.#}%";
|
||||
|
||||
if(progressValue == 100f && _cancelUploadButton.enabledInHierarchy)
|
||||
_cancelUploadButton.SetEnabled(false);
|
||||
}
|
||||
|
||||
private async Task OnPackageUploadSuccess()
|
||||
{
|
||||
if (ASToolsPreferences.Instance.DisplayUploadDialog)
|
||||
EditorUtility.DisplayDialog("Success!", $"Package for '{PackageName}' has been uploaded successfully!", "OK");
|
||||
|
||||
SetEnabled(false);
|
||||
PackageFetcher fetcher = new PackageFetcher();
|
||||
var result = await fetcher.FetchRefreshedPackage(PackageId);
|
||||
if(!result.Success)
|
||||
{
|
||||
ASDebug.LogError(result.Error);
|
||||
SetEnabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateDataValues(result.Package);
|
||||
ASDebug.Log($"Updated name, status, date and size values for package version id {VersionId}");
|
||||
SetEnabled(true);
|
||||
}
|
||||
|
||||
private void OnPackageUploadFail(ASError error)
|
||||
{
|
||||
if (ASToolsPreferences.Instance.DisplayUploadDialog)
|
||||
EditorUtility.DisplayDialog("Upload failed", "Package uploading failed. See Console for details", "OK");
|
||||
|
||||
Debug.LogError(error);
|
||||
}
|
||||
|
||||
private void PostUploadCleanup(PackageUploadResult.UploadStatus uploadStatus)
|
||||
{
|
||||
if (_activeWorkflowElement == null)
|
||||
return;
|
||||
|
||||
SetProgressBarColorByStatus(uploadStatus);
|
||||
|
||||
_uploadProgressBarFlow.title = $"Upload: {uploadStatus.ToString()}";
|
||||
|
||||
_cancelUploadButton.clickable = null;
|
||||
_cancelUploadButton.clicked += ResetPostUpload;
|
||||
|
||||
_cancelUploadButton.text = "Done";
|
||||
|
||||
// Re-enable the Cancel/Done button since it gets disabled at 100% progress
|
||||
_cancelUploadButton.SetEnabled(true);
|
||||
}
|
||||
|
||||
private void ResetPostUpload()
|
||||
{
|
||||
if (_activeWorkflowElement == null)
|
||||
return;
|
||||
|
||||
// Cleanup the progress bars
|
||||
EditorApplication.update -= OnPackageUploadProgressContent;
|
||||
EditorApplication.update -= OnPackageUploadProgressHeader;
|
||||
|
||||
EnableWorkflowElements(true);
|
||||
ResetProgressBar();
|
||||
_exportAndUploadContainer.style.display = DisplayStyle.Flex;
|
||||
_uploadProgressContainer.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void ResetProgressBar()
|
||||
{
|
||||
SetProgressBarColorByStatus(PackageUploadResult.UploadStatus.Default);
|
||||
|
||||
_uploadProgressBarHeader.style.display = DisplayStyle.None;
|
||||
_uploadProgressBarHeader.value = 0f;
|
||||
|
||||
_uploadProgressBarFlow.value = 0f;
|
||||
_uploadProgressBarFlow.title = string.Empty;
|
||||
}
|
||||
|
||||
private void EnableWorkflowElements(bool enable)
|
||||
{
|
||||
_workflowSelectionBox?.SetEnabled(enable);
|
||||
_activeWorkflowElement?.SetEnabled(enable);
|
||||
}
|
||||
|
||||
private void SetProgressBarColorByStatus(PackageUploadResult.UploadStatus status)
|
||||
{
|
||||
var color = PackageUploadResult.GetColorByStatus(status);
|
||||
|
||||
_uploadProgressFlowBg.style.backgroundColor = color;
|
||||
_uploadProgressHeaderBg.style.backgroundColor = color;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c2205d924ccc4a458abc3d370143740
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/PackageView.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using AssetStoreTools.Uploader.Utility;
|
||||
using AssetStoreTools.Utility;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class UploadWindow : VisualElement
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<UploadWindow> { }
|
||||
|
||||
// Views
|
||||
private AllPackageView _allPackageView;
|
||||
|
||||
// Toolbar elements
|
||||
private Label _accountEmailLabel;
|
||||
private Button _refreshButton;
|
||||
private Button _logoutButton;
|
||||
|
||||
public UploadWindow()
|
||||
{
|
||||
styleSheets.Add(StyleSelector.UploaderWindow.UploadWindowStyle);
|
||||
styleSheets.Add(StyleSelector.UploaderWindow.UploadWindowTheme);
|
||||
}
|
||||
|
||||
public void SetupWindows(Action onLogout, Action<ASError> onPackageDownloadFail)
|
||||
{
|
||||
_allPackageView = this.Q<AllPackageView>("AllPackageView");
|
||||
SetupBottomToolbar(onLogout, onPackageDownloadFail);
|
||||
}
|
||||
|
||||
private void SetupBottomToolbar(Action onLogout, Action<ASError> onPackageDownloadFail)
|
||||
{
|
||||
// Bottom Tools Row
|
||||
VisualElement bottomToolsRow = new VisualElement { name = "BottomToolsRow" };
|
||||
bottomToolsRow.AddToClassList("bottom-tools-row");
|
||||
|
||||
// Left side of the toolbar
|
||||
VisualElement leftSideContainer = new VisualElement { name = "LeftSideContainer" };
|
||||
leftSideContainer.AddToClassList("toolbar-left-side-container");
|
||||
|
||||
_accountEmailLabel = new Label { name = "AccountEmail" };
|
||||
_accountEmailLabel.AddToClassList("account-name");
|
||||
|
||||
leftSideContainer.Add(_accountEmailLabel);
|
||||
|
||||
// Right side of the toolbar
|
||||
VisualElement rightSideContainer = new VisualElement { name = "RightSideContainer" };
|
||||
rightSideContainer.AddToClassList("toolbar-right-side-container");
|
||||
|
||||
// Refresh button
|
||||
_refreshButton = new Button { name = "RefreshButton", text = "Refresh" };
|
||||
_refreshButton.AddToClassList("refresh-button");
|
||||
_refreshButton.clicked += () => _allPackageView.ShowPackagesList(false, onPackageDownloadFail);
|
||||
_allPackageView.RefreshingPackages += (isRefreshing) => _refreshButton.SetEnabled(!isRefreshing);
|
||||
|
||||
// Logout button
|
||||
_logoutButton = new Button { name = "LogoutButton", text = "Logout" };
|
||||
_logoutButton.AddToClassList("logout-button");
|
||||
_logoutButton.clicked += () => Logout(onLogout);
|
||||
|
||||
rightSideContainer.Add(_refreshButton);
|
||||
rightSideContainer.Add(_logoutButton);
|
||||
|
||||
// Constructing the final toolbar
|
||||
bottomToolsRow.Add(leftSideContainer);
|
||||
bottomToolsRow.Add(rightSideContainer);
|
||||
|
||||
Add(bottomToolsRow);
|
||||
}
|
||||
|
||||
public void LoadPackages(bool useCached, Action<ASError> onPackageDownloadFail)
|
||||
{
|
||||
_allPackageView.ShowPackagesList(useCached, onPackageDownloadFail);
|
||||
}
|
||||
|
||||
public void ShowAllPackagesView()
|
||||
{
|
||||
_logoutButton.style.display = DisplayStyle.Flex;
|
||||
_allPackageView.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
public void ShowPublisherEmail(string publisherEmail)
|
||||
{
|
||||
_accountEmailLabel.text = publisherEmail;
|
||||
}
|
||||
|
||||
private void Logout(Action onLogout)
|
||||
{
|
||||
if (AssetStoreAPI.IsUploading && !EditorUtility.DisplayDialog("Notice",
|
||||
"Assets are still being uploaded to the Asset Store. Logging out will cancel all uploads in progress.\n\n" +
|
||||
"Would you still like to log out?", "Yes", "No"))
|
||||
return;
|
||||
|
||||
AssetStoreAPI.AbortDownloadTasks();
|
||||
AssetStoreAPI.AbortUploadTasks();
|
||||
PackageViewStorer.Reset();
|
||||
|
||||
_allPackageView.ClearPackages();
|
||||
onLogout?.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52c6e85acbb00794686387cd876ffc81
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/UploadWindow.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7562bae25c6218744a023670e3a2f06f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,116 @@
|
||||
using AssetStoreTools.Validator;
|
||||
using AssetStoreTools.Validator.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class AssetValidationElement : ValidationElement
|
||||
{
|
||||
private Button _viewReportButton;
|
||||
|
||||
private string[] _validationPaths;
|
||||
|
||||
protected override void SetupInfoBox(string infoText)
|
||||
{
|
||||
InfoBox = new Box { name = "InfoBox" };
|
||||
InfoBox.style.display = DisplayStyle.None;
|
||||
InfoBox.AddToClassList("info-box");
|
||||
|
||||
InfoBoxImage = new Image();
|
||||
InfoBoxLabel = new Label { name = "ValidationLabel", text = infoText };
|
||||
_viewReportButton = new Button(ViewReport) { text = "View report" };
|
||||
_viewReportButton.AddToClassList("hyperlink-button");
|
||||
|
||||
InfoBox.Add(InfoBoxImage);
|
||||
InfoBox.Add(InfoBoxLabel);
|
||||
InfoBox.Add(_viewReportButton);
|
||||
|
||||
Add(InfoBox);
|
||||
}
|
||||
|
||||
public override void SetValidationPaths(params string[] paths)
|
||||
{
|
||||
_validationPaths = paths;
|
||||
EnableValidation(true);
|
||||
}
|
||||
|
||||
protected override async void RunValidation()
|
||||
{
|
||||
ValidateButton.SetEnabled(false);
|
||||
|
||||
// Make sure everything is collected and validation button is disabled
|
||||
await Task.Delay(100);
|
||||
|
||||
var outcomeList = new List<TestResult>();
|
||||
|
||||
var validationSettings = new ValidationSettings()
|
||||
{
|
||||
ValidationPaths = _validationPaths.ToList(),
|
||||
Category = Category
|
||||
};
|
||||
|
||||
var validationResult = PackageValidator.ValidatePackage(validationSettings);
|
||||
|
||||
if(validationResult.Status != ValidationStatus.RanToCompletion)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Validation failed", $"Package validation failed: {validationResult.Error}", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var test in validationResult.AutomatedTests)
|
||||
outcomeList.Add(test.Result);
|
||||
|
||||
EnableInfoBox(true, validationResult.HadCompilationErrors, outcomeList);
|
||||
ValidateButton.SetEnabled(true);
|
||||
}
|
||||
|
||||
private void ViewReport()
|
||||
{
|
||||
var validationStateData = ValidationState.Instance.ValidationStateData;
|
||||
|
||||
// Re-run validation if paths are out of sync
|
||||
if (validationStateData.SerializedValidationPaths.Count != _validationPaths.Length ||
|
||||
!validationStateData.SerializedValidationPaths.All(_validationPaths.Contains))
|
||||
RunValidation();
|
||||
|
||||
// Re-run validation if category is out of sync
|
||||
if (validationStateData.SerializedCategory != Category)
|
||||
RunValidation();
|
||||
|
||||
// Show the Validator
|
||||
AssetStoreTools.ShowAssetStoreToolsValidator();
|
||||
}
|
||||
|
||||
private void EnableInfoBox(bool enable, bool hasCompilationErrors, List<TestResult> outcomeList)
|
||||
{
|
||||
if (!enable)
|
||||
{
|
||||
InfoBox.style.display = DisplayStyle.None;
|
||||
return;
|
||||
}
|
||||
|
||||
var errorCount = outcomeList.Count(x => x.Result == TestResult.ResultStatus.Fail);
|
||||
var warningCount = outcomeList.Count(x => x.Result == TestResult.ResultStatus.Warning);
|
||||
|
||||
PopulateInfoBox(hasCompilationErrors, errorCount, warningCount);
|
||||
|
||||
ValidateButton.text = "Re-validate";
|
||||
InfoBox.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
public override bool GetValidationSummary(out string validationSummary)
|
||||
{
|
||||
validationSummary = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(InfoBoxLabel.text))
|
||||
return false;
|
||||
|
||||
var data = ValidationState.Instance.ValidationStateData;
|
||||
return ValidationState.GetValidationSummaryJson(data, out validationSummary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 118376dcb318c4341b1df6773e3d8d4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Validation/AssetValidationElement.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,190 @@
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Validator;
|
||||
using AssetStoreTools.Validator.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class PackageValidationElement : ValidationElement
|
||||
{
|
||||
private string _packagePath;
|
||||
private ValidationStateData _packageValidationStateData;
|
||||
|
||||
private VisualElement _projectButtonContainer;
|
||||
|
||||
protected override void SetupInfoBox(string infoText)
|
||||
{
|
||||
InfoBox = new Box { name = "InfoBox" };
|
||||
InfoBox.style.display = DisplayStyle.None;
|
||||
InfoBox.AddToClassList("info-box");
|
||||
|
||||
InfoBoxImage = new Image();
|
||||
InfoBoxLabel = new Label { name = "ValidationLabel", text = infoText };
|
||||
|
||||
_projectButtonContainer = new VisualElement() { name = "Button Container" };
|
||||
_projectButtonContainer.AddToClassList("hyperlink-button-container");
|
||||
|
||||
InfoBox.Add(InfoBoxImage);
|
||||
InfoBox.Add(InfoBoxLabel);
|
||||
InfoBox.Add(_projectButtonContainer);
|
||||
|
||||
Add(InfoBox);
|
||||
}
|
||||
|
||||
public override void SetValidationPaths(params string[] paths)
|
||||
{
|
||||
if (paths == null || paths.Length != 1)
|
||||
throw new ArgumentException("Package Validation only accepts a single path argument");
|
||||
|
||||
_packagePath = paths[0];
|
||||
EnableValidation(true);
|
||||
}
|
||||
|
||||
protected override void RunValidation()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("Notice", "Pre-exported package validation is performed in a separate temporary project. " +
|
||||
"It may take some time for the temporary project to be created, which will halt any actions in the current project. " +
|
||||
"The current project will resume work after the temporary project is exited.\n\nDo you wish to proceed?", "Yes", "No"))
|
||||
return;
|
||||
|
||||
var validationSettings = new ValidationSettings()
|
||||
{
|
||||
ValidationPaths = new List<string> { _packagePath },
|
||||
Category = Category
|
||||
};
|
||||
var validationResult = PackageValidator.ValidatePreExportedPackage(validationSettings, false);
|
||||
|
||||
switch (validationResult.Status)
|
||||
{
|
||||
case ValidationStatus.RanToCompletion:
|
||||
DisplayValidationResult(validationResult.ProjectPath);
|
||||
break;
|
||||
case ValidationStatus.Failed:
|
||||
EditorUtility.DisplayDialog("Error", validationResult.Error, "OK");
|
||||
break;
|
||||
case ValidationStatus.Cancelled:
|
||||
ASDebug.Log("Validation cancelled");
|
||||
break;
|
||||
case ValidationStatus.NotRun:
|
||||
throw new InvalidOperationException("Validation was not run. Please report this issue");
|
||||
default:
|
||||
throw new ArgumentException("Received an invalid validation status");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool GetValidationSummary(out string validationSummary)
|
||||
{
|
||||
validationSummary = string.Empty;
|
||||
|
||||
if (_packageValidationStateData == null)
|
||||
return false;
|
||||
|
||||
return ValidationState.GetValidationSummaryJson(_packageValidationStateData, out validationSummary);
|
||||
}
|
||||
|
||||
private void DisplayValidationResult(string projectPath)
|
||||
{
|
||||
// Retrieve the validation state data from the validation project
|
||||
var validationStateDataPath = $"{projectPath}/{ValidationState.PersistentDataLocation}/{ValidationState.ValidationDataFilename}";
|
||||
if (File.Exists(validationStateDataPath))
|
||||
_packageValidationStateData = JsonUtility.FromJson<ValidationStateData>(File.ReadAllText(validationStateDataPath));
|
||||
|
||||
var validationComplete = _packageValidationStateData != null;
|
||||
EnableInfoBox(true, validationComplete, projectPath);
|
||||
}
|
||||
|
||||
private void EnableInfoBox(bool enable, bool validationComplete, string projectPath)
|
||||
{
|
||||
if (!enable)
|
||||
{
|
||||
InfoBox.style.display = DisplayStyle.None;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validationComplete)
|
||||
{
|
||||
InfoBoxImage.image = EditorGUIUtility.IconContent("console.erroricon@2x").image;
|
||||
InfoBoxLabel.text = "Validation status unknown. Please report this issue";
|
||||
return;
|
||||
}
|
||||
|
||||
InfoBox.style.display = DisplayStyle.Flex;
|
||||
|
||||
var compilationFailed = _packageValidationStateData.HasCompilationErrors;
|
||||
var failCount = _packageValidationStateData.SerializedValues.Count(x => x.Result.Result == TestResult.ResultStatus.Fail);
|
||||
var warningCount = _packageValidationStateData.SerializedValues.Count(x => x.Result.Result == TestResult.ResultStatus.Warning);
|
||||
|
||||
PopulateInfoBox(compilationFailed, failCount, warningCount);
|
||||
|
||||
_projectButtonContainer.Clear();
|
||||
var openButton = new Button(() => OpenTemporaryProject(projectPath)) { text = "Open Project" };
|
||||
openButton.AddToClassList("hyperlink-button");
|
||||
var saveButton = new Button(() => SaveTemporaryProject(projectPath)) { text = "Save Project" };
|
||||
saveButton.AddToClassList("hyperlink-button");
|
||||
|
||||
_projectButtonContainer.Add(openButton);
|
||||
_projectButtonContainer.Add(saveButton);
|
||||
}
|
||||
|
||||
private void OpenTemporaryProject(string projectPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
EditorUtility.DisplayProgressBar("Waiting...", "Validation project is open. Waiting for it to exit...", 0.4f);
|
||||
|
||||
#if UNITY_EDITOR_OSX
|
||||
var unityPath = Path.Combine(EditorApplication.applicationPath, "Contents", "MacOS", "Unity");
|
||||
#else
|
||||
var unityPath = EditorApplication.applicationPath;
|
||||
#endif
|
||||
|
||||
var logFilePath = $"{projectPath}/editor.log";
|
||||
|
||||
var processInfo = new System.Diagnostics.ProcessStartInfo()
|
||||
{
|
||||
FileName = unityPath,
|
||||
Arguments = $"-projectPath \"{projectPath}\" -logFile \"{logFilePath}\" -executeMethod AssetStoreTools.AssetStoreTools.ShowAssetStoreToolsValidator"
|
||||
};
|
||||
|
||||
using (var process = System.Diagnostics.Process.Start(processInfo))
|
||||
{
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveTemporaryProject(string projectPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var savePath = EditorUtility.SaveFolderPanel("Select a folder", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), string.Empty);
|
||||
if (string.IsNullOrEmpty(savePath))
|
||||
return;
|
||||
|
||||
var saveDir = new DirectoryInfo(savePath);
|
||||
if(!saveDir.Exists || saveDir.GetFileSystemInfos().Length != 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Saving project failed", "Selected directory must be an empty folder", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUtility.DisplayProgressBar("Saving...", "Saving project...", 0.4f);
|
||||
FileUtility.CopyDirectory(projectPath, savePath, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33a72e7596565c749a495b4213579a67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Validation/PackageValidationElement.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,99 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal abstract class ValidationElement : VisualElement
|
||||
{
|
||||
protected Button ValidateButton;
|
||||
|
||||
protected VisualElement InfoBox;
|
||||
protected Label InfoBoxLabel;
|
||||
protected Image InfoBoxImage;
|
||||
|
||||
protected string Category;
|
||||
|
||||
public ValidationElement()
|
||||
{
|
||||
ConstructValidationElement();
|
||||
SetupInfoBox(string.Empty);
|
||||
EnableValidation(false);
|
||||
}
|
||||
|
||||
public void SetCategory(string category)
|
||||
{
|
||||
Category = category;
|
||||
}
|
||||
|
||||
private void ConstructValidationElement()
|
||||
{
|
||||
VisualElement validatorButtonRow = new VisualElement();
|
||||
validatorButtonRow.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement validatorLabelHelpRow = new VisualElement();
|
||||
validatorLabelHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label validatorLabel = new Label { text = "Validation" };
|
||||
Image validatorLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "You can use the Asset Store Validator to check your package for common publishing issues"
|
||||
};
|
||||
|
||||
ValidateButton = new Button(RunValidation) { name = "ValidateButton", text = "Validate" };
|
||||
ValidateButton.AddToClassList("validation-button");
|
||||
|
||||
validatorLabelHelpRow.Add(validatorLabel);
|
||||
validatorLabelHelpRow.Add(validatorLabelTooltip);
|
||||
|
||||
validatorButtonRow.Add(validatorLabelHelpRow);
|
||||
validatorButtonRow.Add(ValidateButton);
|
||||
|
||||
Add(validatorButtonRow);
|
||||
}
|
||||
|
||||
protected void EnableValidation(bool enable)
|
||||
{
|
||||
style.display = enable ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
protected void PopulateInfoBox(bool hasCompilationErrors, int errorCount, int warningCount)
|
||||
{
|
||||
Texture infoImage = null;
|
||||
var infoText = string.Empty;
|
||||
|
||||
if (hasCompilationErrors)
|
||||
{
|
||||
infoImage = EditorGUIUtility.IconContent("console.erroricon@2x").image;
|
||||
infoText += "• Package caused compilation errors\n";
|
||||
}
|
||||
if (errorCount > 0)
|
||||
{
|
||||
infoImage = EditorGUIUtility.IconContent("console.erroricon@2x").image;
|
||||
infoText += $"• Validation reported {errorCount} error(s)\n";
|
||||
}
|
||||
if (warningCount > 0)
|
||||
{
|
||||
if (infoImage == null)
|
||||
infoImage = EditorGUIUtility.IconContent("console.warnicon@2x").image;
|
||||
infoText += $"• Validation reported {warningCount} warning(s)\n";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(infoText))
|
||||
{
|
||||
infoText = "No issues were found!";
|
||||
infoImage = InfoBoxImage.image = EditorGUIUtility.IconContent("console.infoicon@2x").image;
|
||||
}
|
||||
else
|
||||
infoText = infoText.Substring(0, infoText.Length - "\n".Length);
|
||||
|
||||
InfoBoxImage.image = infoImage;
|
||||
InfoBoxLabel.text = infoText;
|
||||
}
|
||||
|
||||
protected abstract void SetupInfoBox(string infoText);
|
||||
public abstract void SetValidationPaths(params string[] paths);
|
||||
protected abstract void RunValidation();
|
||||
public abstract bool GetValidationSummary(out string validationSummary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cee024b6c5a4407780a9f8677f7a6e97
|
||||
timeCreated: 1654674025
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Validation/ValidationElement.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c85e58f7d4786a40a140c67b0d124a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,628 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AssetStoreTools.Exporter;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class FolderUploadWorkflowView : UploadWorkflowView
|
||||
{
|
||||
public const string WorkflowName = "FolderWorkflow";
|
||||
public const string WorkflowDisplayName = "From Assets Folder";
|
||||
|
||||
public override string Name => WorkflowName;
|
||||
public override string DisplayName => WorkflowDisplayName;
|
||||
|
||||
private Toggle _dependenciesToggle;
|
||||
private List<string> _includedDependencies = new List<string>();
|
||||
|
||||
private bool _isCompleteProject;
|
||||
|
||||
private VisualElement _specialFolderTogglesBox;
|
||||
private VisualElement _specialFoldersElement;
|
||||
|
||||
private VisualElement _packageDependencyBox;
|
||||
private ScrollView _packagesTogglesBox;
|
||||
|
||||
private ToolbarMenu _filteringDropdown;
|
||||
private Label _noPackagesLabel;
|
||||
private string _packagesFilter;
|
||||
|
||||
// Special folders that would not work if not placed directly in the 'Assets' folder
|
||||
private readonly string[] _extraAssetFolderNames =
|
||||
{
|
||||
"Editor Default Resources", "Gizmos", "Plugins",
|
||||
"StreamingAssets", "Standard Assets", "WebGLTemplates",
|
||||
"ExternalDependencyManager", "XR"
|
||||
};
|
||||
|
||||
private readonly List<string> _packageSelectionFilters = new List<string> { "All", "Selected", "Not Selected" };
|
||||
|
||||
private FolderUploadWorkflowView(string category, bool isCompleteProject, Action serializeSelection) : base(serializeSelection)
|
||||
{
|
||||
_isCompleteProject = isCompleteProject;
|
||||
Category = category;
|
||||
|
||||
SetupWorkflow();
|
||||
}
|
||||
|
||||
public static FolderUploadWorkflowView Create(string category, bool isCompleteProject, Action serializeAction)
|
||||
{
|
||||
return new FolderUploadWorkflowView(category, isCompleteProject, serializeAction);
|
||||
}
|
||||
|
||||
public void SetCompleteProject(bool isCompleteProject)
|
||||
{
|
||||
_isCompleteProject = isCompleteProject;
|
||||
}
|
||||
|
||||
private bool GetIncludeDependenciesToggle()
|
||||
{
|
||||
return _dependenciesToggle.value;
|
||||
}
|
||||
|
||||
private List<string> GetIncludedDependencies()
|
||||
{
|
||||
return _includedDependencies;
|
||||
}
|
||||
|
||||
protected sealed override void SetupWorkflow()
|
||||
{
|
||||
// Path selection
|
||||
VisualElement folderPathSelectionRow = new VisualElement();
|
||||
folderPathSelectionRow.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement labelHelpRow = new VisualElement();
|
||||
labelHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label folderPathLabel = new Label { text = "Folder path" };
|
||||
Image folderPathLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "Select the main folder of your package" +
|
||||
"\n\nAll files and folders of your package should preferably be contained within a single root folder that is named after your package" +
|
||||
"\n\nExample: 'Assets/[MyPackageName]'" +
|
||||
"\n\nNote: If your content makes use of special folders that are required to be placed in the root Assets folder (e.g. 'StreamingAssets')," +
|
||||
" you will be able to include them after selecting the main folder"
|
||||
};
|
||||
|
||||
labelHelpRow.Add(folderPathLabel);
|
||||
labelHelpRow.Add(folderPathLabelTooltip);
|
||||
|
||||
PathSelectionField = new TextField();
|
||||
PathSelectionField.AddToClassList("path-selection-field");
|
||||
PathSelectionField.isReadOnly = true;
|
||||
|
||||
Button browsePathButton = new Button(BrowsePath) { name = "BrowsePathButton", text = "Browse" };
|
||||
browsePathButton.AddToClassList("browse-button");
|
||||
|
||||
folderPathSelectionRow.Add(labelHelpRow);
|
||||
folderPathSelectionRow.Add(PathSelectionField);
|
||||
folderPathSelectionRow.Add(browsePathButton);
|
||||
|
||||
Add(folderPathSelectionRow);
|
||||
|
||||
// Dependencies selection
|
||||
VisualElement dependenciesSelectionRow = new VisualElement();
|
||||
dependenciesSelectionRow.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement dependenciesLabelHelpRow = new VisualElement();
|
||||
dependenciesLabelHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label dependenciesLabel = new Label { text = "Dependencies" };
|
||||
Image dependenciesLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "Tick this checkbox if your package content has dependencies on Unity packages from the Package Manager"
|
||||
};
|
||||
|
||||
_dependenciesToggle = new Toggle { name = "DependenciesToggle", text = "Include Package Manifest" };
|
||||
_dependenciesToggle.AddToClassList("dependencies-toggle");
|
||||
|
||||
_dependenciesToggle.RegisterValueChangedCallback((_) => SerializeSelection?.Invoke());
|
||||
_dependenciesToggle.RegisterValueChangedCallback(OnDependencyToggleValueChange);
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>((_) => {ASToolsPreferences.OnSettingsChange += OnASTSettingsChange;});
|
||||
RegisterCallback<DetachFromPanelEvent>((_) => {ASToolsPreferences.OnSettingsChange -= OnASTSettingsChange;});
|
||||
|
||||
// Dependencies selection
|
||||
_packageDependencyBox = new VisualElement();
|
||||
_packageDependencyBox.AddToClassList("selection-box-row");
|
||||
_packageDependencyBox.style.display = DisplayStyle.None;
|
||||
|
||||
dependenciesLabelHelpRow.Add(dependenciesLabel);
|
||||
dependenciesLabelHelpRow.Add(dependenciesLabelTooltip);
|
||||
|
||||
dependenciesSelectionRow.Add(dependenciesLabelHelpRow);
|
||||
dependenciesSelectionRow.Add(_dependenciesToggle);
|
||||
|
||||
Add(dependenciesSelectionRow);
|
||||
Add(_packageDependencyBox);
|
||||
|
||||
ValidationElement = new AssetValidationElement();
|
||||
Add(ValidationElement);
|
||||
|
||||
ValidationElement.SetCategory(Category);
|
||||
}
|
||||
|
||||
public override JsonValue SerializeWorkflow()
|
||||
{
|
||||
var workflowDict = base.SerializeWorkflow();
|
||||
workflowDict["dependencies"] = GetIncludeDependenciesToggle();
|
||||
workflowDict["dependenciesNames"] = GetIncludedDependencies().Select(JsonValue.NewString).ToList();
|
||||
|
||||
return workflowDict;
|
||||
}
|
||||
|
||||
public override void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid)
|
||||
{
|
||||
if (!DeserializeMainExportPath(json, out string mainExportPath) || (!Directory.Exists(mainExportPath) && mainExportPath != String.Empty))
|
||||
{
|
||||
ASDebug.Log("Unable to restore Folder upload workflow paths from the local cache");
|
||||
LoadSerializedWorkflowFallback(lastUploadedPath, lastUploadedGuid);
|
||||
return;
|
||||
}
|
||||
|
||||
DeserializeExtraExportPaths(json, out List<string> extraExportPaths);
|
||||
DeserializeDependencies(json, out List<string> dependencies);
|
||||
DeserializeDependenciesToggle(json, out var dependenciesToggle);
|
||||
|
||||
ASDebug.Log($"Restoring serialized Folder workflow values from local cache");
|
||||
HandleFolderUploadPathSelection(mainExportPath, extraExportPaths, dependencies, false);
|
||||
|
||||
if (dependenciesToggle)
|
||||
{
|
||||
_dependenciesToggle.SetValueWithoutNotify(true);
|
||||
FindAndPopulateDependencies(_includedDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid)
|
||||
{
|
||||
var mainExportPath = AssetDatabase.GUIDToAssetPath(lastUploadedGuid);
|
||||
if (string.IsNullOrEmpty(mainExportPath))
|
||||
mainExportPath = lastUploadedPath;
|
||||
|
||||
if ((!mainExportPath.StartsWith("Assets/") && mainExportPath != "Assets") || !Directory.Exists(mainExportPath))
|
||||
{
|
||||
ASDebug.Log("Unable to restore Folder workflow paths from previous upload values");
|
||||
return;
|
||||
}
|
||||
|
||||
ASDebug.Log($"Restoring serialized Folder workflow values from previous upload values");
|
||||
HandleFolderUploadPathSelection(mainExportPath, null, null, false);
|
||||
}
|
||||
|
||||
#region Folder Upload
|
||||
|
||||
protected override void BrowsePath()
|
||||
{
|
||||
// Path retrieval
|
||||
var absoluteExportPath = string.Empty;
|
||||
var relativeExportPath = string.Empty;
|
||||
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
|
||||
|
||||
bool includeAllAssets = false;
|
||||
|
||||
if (_isCompleteProject)
|
||||
{
|
||||
includeAllAssets = EditorUtility.DisplayDialog("Notice",
|
||||
"Your package draft is set to a category that is treated" +
|
||||
" as a complete project. Project settings will be included automatically. Would you like everything in the " +
|
||||
"'Assets' folder to be included?\n\nYou will still be able to change the selected assets before uploading",
|
||||
"Yes, include all folders and assets",
|
||||
"No, I'll select what to include manually");
|
||||
if (includeAllAssets)
|
||||
absoluteExportPath = Application.dataPath;
|
||||
}
|
||||
|
||||
if (!includeAllAssets)
|
||||
{
|
||||
absoluteExportPath =
|
||||
EditorUtility.OpenFolderPanel("Select folder to compress into a package", "Assets/", "");
|
||||
if (string.IsNullOrEmpty(absoluteExportPath))
|
||||
return;
|
||||
}
|
||||
|
||||
if (absoluteExportPath.StartsWith(rootProjectPath))
|
||||
{
|
||||
relativeExportPath = absoluteExportPath.Substring(rootProjectPath.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ASToolsPreferences.Instance.EnableSymlinkSupport)
|
||||
SymlinkUtil.FindSymlinkFolderRelative(absoluteExportPath, out relativeExportPath);
|
||||
}
|
||||
|
||||
if (!relativeExportPath.StartsWith("Assets/") && !(relativeExportPath == "Assets" && _isCompleteProject))
|
||||
{
|
||||
if (relativeExportPath.StartsWith("Assets") && !_isCompleteProject)
|
||||
EditorUtility.DisplayDialog("Invalid selection",
|
||||
"'Assets' folder is only available for packages tagged as a 'Complete Project'.", "OK");
|
||||
else
|
||||
EditorUtility.DisplayDialog("Invalid selection", "Selected folder path must be within the project.",
|
||||
"OK");
|
||||
return;
|
||||
}
|
||||
|
||||
HandleFolderUploadPathSelection(relativeExportPath, null, _includedDependencies, true);
|
||||
}
|
||||
|
||||
private void HandleFolderUploadPathSelection(string relativeExportPath, List<string> serializedToggles, List<string> dependencies, bool serializeValues)
|
||||
{
|
||||
if (relativeExportPath != String.Empty)
|
||||
PathSelectionField.value = relativeExportPath + "/";
|
||||
|
||||
MainExportPath = relativeExportPath;
|
||||
ExtraExportPaths = new List<string>();
|
||||
_includedDependencies = new List<string>();
|
||||
|
||||
LocalPackageGuid = AssetDatabase.AssetPathToGUID(MainExportPath);
|
||||
LocalPackagePath = MainExportPath;
|
||||
LocalProjectPath = MainExportPath;
|
||||
|
||||
if (_specialFoldersElement != null)
|
||||
{
|
||||
_specialFoldersElement.Clear();
|
||||
Remove(_specialFoldersElement);
|
||||
|
||||
_specialFoldersElement = null;
|
||||
}
|
||||
|
||||
// Prompt additional path selection (e.g. StreamingAssets, WebGLTemplates, etc.)
|
||||
List<string> specialFoldersFound = new List<string>();
|
||||
|
||||
foreach (var extraAssetFolderName in _extraAssetFolderNames)
|
||||
{
|
||||
var fullExtraPath = "Assets/" + extraAssetFolderName;
|
||||
|
||||
if (!Directory.Exists(fullExtraPath))
|
||||
continue;
|
||||
|
||||
if (MainExportPath.ToLower().StartsWith(fullExtraPath.ToLower()))
|
||||
continue;
|
||||
|
||||
// Don't include nested paths
|
||||
if (!fullExtraPath.ToLower().StartsWith(MainExportPath.ToLower()))
|
||||
specialFoldersFound.Add(fullExtraPath);
|
||||
}
|
||||
|
||||
if (specialFoldersFound.Count != 0)
|
||||
PopulateSpecialFoldersBox(specialFoldersFound, serializedToggles);
|
||||
|
||||
if (dependencies != null && dependencies.Count != 0)
|
||||
FindAndPopulateDependencies(dependencies);
|
||||
|
||||
// After setting up the main and extra paths, check for missing metas and update validation paths
|
||||
CheckForMissingMetas();
|
||||
UpdateValidationPaths();
|
||||
|
||||
// Only serialize current selection when no serialized toggles were passed
|
||||
if (serializeValues)
|
||||
SerializeSelection?.Invoke();
|
||||
}
|
||||
|
||||
private void InitializeSpecialFoldersSelection()
|
||||
{
|
||||
// Dependencies selection
|
||||
_specialFoldersElement = new VisualElement();
|
||||
_specialFoldersElement.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement specialFoldersHelpRow = new VisualElement();
|
||||
specialFoldersHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label specialFoldersLabel = new Label { text = "Special folders" };
|
||||
Image specialFoldersLabelTooltip = new Image
|
||||
{
|
||||
tooltip =
|
||||
"If your package content relies on Special Folders (e.g. StreamingAssets), please select which of these folders should be included in the package"
|
||||
};
|
||||
|
||||
_specialFolderTogglesBox = new VisualElement { name = "SpecialFolderToggles" };
|
||||
_specialFolderTogglesBox.AddToClassList("special-folders-toggles-box");
|
||||
|
||||
specialFoldersHelpRow.Add(specialFoldersLabel);
|
||||
specialFoldersHelpRow.Add(specialFoldersLabelTooltip);
|
||||
|
||||
_specialFoldersElement.Add(specialFoldersHelpRow);
|
||||
_specialFoldersElement.Add(_specialFolderTogglesBox);
|
||||
|
||||
|
||||
Add(_specialFoldersElement);
|
||||
}
|
||||
|
||||
private void PopulateSpecialFoldersBox(List<string> specialFoldersFound, List<string> checkedToggles)
|
||||
{
|
||||
InitializeSpecialFoldersSelection();
|
||||
|
||||
EventCallback<ChangeEvent<bool>, string> toggleChangeCallback = OnSpecialFolderPathToggledAsset;
|
||||
|
||||
foreach (var path in specialFoldersFound)
|
||||
{
|
||||
var toggle = new Toggle { value = false, text = path };
|
||||
toggle.AddToClassList("special-folder-toggle");
|
||||
if (checkedToggles != null && checkedToggles.Contains(toggle.text))
|
||||
{
|
||||
toggle.SetValueWithoutNotify(true);
|
||||
ExtraExportPaths.Add(toggle.text);
|
||||
}
|
||||
|
||||
toggle.RegisterCallback(toggleChangeCallback, toggle.text);
|
||||
_specialFolderTogglesBox.Add(toggle);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSpecialFolderPathToggledAsset(ChangeEvent<bool> evt, string folderPath)
|
||||
{
|
||||
switch (evt.newValue)
|
||||
{
|
||||
case true when !ExtraExportPaths.Contains(folderPath):
|
||||
ExtraExportPaths.Add(folderPath);
|
||||
break;
|
||||
case false when ExtraExportPaths.Contains(folderPath):
|
||||
ExtraExportPaths.Remove(folderPath);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateValidationPaths();
|
||||
SerializeSelection?.Invoke();
|
||||
}
|
||||
|
||||
private void UpdateValidationPaths()
|
||||
{
|
||||
var validationPaths = new List<string>() { MainExportPath };
|
||||
validationPaths.AddRange(ExtraExportPaths);
|
||||
ValidationElement.SetValidationPaths(validationPaths.ToArray());
|
||||
}
|
||||
|
||||
private void OnToggleDependency(ChangeEvent<bool> evt, string dependency)
|
||||
{
|
||||
switch (evt.newValue)
|
||||
{
|
||||
case true when !_includedDependencies.Contains(dependency):
|
||||
_includedDependencies.Add(dependency);
|
||||
break;
|
||||
case false when _includedDependencies.Contains(dependency):
|
||||
_includedDependencies.Remove(dependency);
|
||||
break;
|
||||
}
|
||||
|
||||
FilterPackageSelection(_packagesFilter);
|
||||
SerializeSelection?.Invoke();
|
||||
}
|
||||
|
||||
private void OnDependencyToggleValueChange(ChangeEvent<bool> evt)
|
||||
{
|
||||
CheckDependencyBoxState();
|
||||
}
|
||||
|
||||
private void OnASTSettingsChange()
|
||||
{
|
||||
CheckDependencyBoxState();
|
||||
}
|
||||
|
||||
private void CheckDependencyBoxState()
|
||||
{
|
||||
if (_dependenciesToggle.value && !ASToolsPreferences.Instance.UseLegacyExporting)
|
||||
{
|
||||
FindAndPopulateDependencies(_includedDependencies);
|
||||
}
|
||||
else
|
||||
{
|
||||
_packageDependencyBox.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void FindAndPopulateDependencies(List<string> checkedToggles)
|
||||
{
|
||||
_packageDependencyBox?.Clear();
|
||||
var registryPackages = PackageUtility.GetAllRegistryPackages();
|
||||
|
||||
if (registryPackages == null)
|
||||
{
|
||||
ASDebug.LogWarning("Package Manifest was not found or could not be parsed.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> packagesFound = new List<string>(registryPackages.Select(x => x.name));
|
||||
PopulatePackagesSelectionBox(packagesFound, checkedToggles);
|
||||
}
|
||||
|
||||
private void PopulatePackagesSelectionBox(List<string> packagesFound, List<string> checkedToggles)
|
||||
{
|
||||
InitializePackageSelection();
|
||||
|
||||
EventCallback<ChangeEvent<bool>, string> toggleChangeCallback = OnToggleDependency;
|
||||
|
||||
if (packagesFound.Count == 0 || ASToolsPreferences.Instance.UseLegacyExporting)
|
||||
{
|
||||
_packageDependencyBox.style.display = DisplayStyle.None;
|
||||
return;
|
||||
}
|
||||
|
||||
_packageDependencyBox.style.display = DisplayStyle.Flex;
|
||||
|
||||
foreach (var path in packagesFound)
|
||||
{
|
||||
var toggle = new Toggle { value = false, text = path };
|
||||
toggle.AddToClassList("extra-packages-toggle");
|
||||
if (checkedToggles != null && checkedToggles.Contains(toggle.text))
|
||||
{
|
||||
toggle.SetValueWithoutNotify(true);
|
||||
|
||||
if (!_includedDependencies.Contains(toggle.text))
|
||||
_includedDependencies.Add(toggle.text);
|
||||
}
|
||||
|
||||
toggle.RegisterCallback(toggleChangeCallback, toggle.text);
|
||||
_packagesTogglesBox.Add(toggle);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializePackageSelection()
|
||||
{
|
||||
VisualElement dependenciesHelpRow = new VisualElement();
|
||||
dependenciesHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label allPackagesLabel = new Label { text = "All Packages" };
|
||||
Image allPackagesLabelTooltip = new Image
|
||||
{
|
||||
tooltip =
|
||||
"Select UPM dependencies you would like to include with your package."
|
||||
};
|
||||
|
||||
VisualElement fullPackageSelectionBox = new VisualElement();
|
||||
fullPackageSelectionBox.AddToClassList("extra-packages-box");
|
||||
|
||||
_packagesTogglesBox = new ScrollView { name = "DependencyToggles" };
|
||||
_packagesTogglesBox.AddToClassList("extra-packages-scroll-view");
|
||||
|
||||
_noPackagesLabel = new Label("No packages were found that match this criteria.");
|
||||
_noPackagesLabel.AddToClassList("no-packages-label");
|
||||
|
||||
var scrollContainer = _packagesTogglesBox.Q<VisualElement>("unity-content-viewport");
|
||||
scrollContainer.Add(_noPackagesLabel);
|
||||
|
||||
VisualElement packagesFilteringBox = new VisualElement();
|
||||
packagesFilteringBox.AddToClassList("packages-filtering-box");
|
||||
|
||||
// Select - deselect buttons
|
||||
VisualElement selectingPackagesBox = new VisualElement();
|
||||
selectingPackagesBox.AddToClassList("filtering-packages-buttons-box");
|
||||
|
||||
Button selectAllButton = new Button(() => SelectAllPackages(true))
|
||||
{
|
||||
text = "Select All"
|
||||
};
|
||||
|
||||
Button deSelectAllButton = new Button(() => SelectAllPackages(false))
|
||||
{
|
||||
text = "Deselect All"
|
||||
};
|
||||
|
||||
selectAllButton.AddToClassList("filter-packages-button");
|
||||
deSelectAllButton.AddToClassList("filter-packages-button");
|
||||
|
||||
selectingPackagesBox.Add(selectAllButton);
|
||||
selectingPackagesBox.Add(deSelectAllButton);
|
||||
|
||||
// Filtering dropdown
|
||||
VisualElement filteringDropdownBox = new VisualElement();
|
||||
filteringDropdownBox.AddToClassList("filtering-packages-dropdown-box");
|
||||
|
||||
_filteringDropdown = new ToolbarMenu {text = _packagesFilter = _packageSelectionFilters[0]};
|
||||
_filteringDropdown.AddToClassList("filter-packages-dropdown");
|
||||
|
||||
foreach (var filter in _packageSelectionFilters)
|
||||
_filteringDropdown.menu.AppendAction(filter, delegate { FilterPackageSelection(filter);});
|
||||
|
||||
filteringDropdownBox.Add(_filteringDropdown);
|
||||
|
||||
VisualElement packageSelectionButtonsBox = new VisualElement();
|
||||
packageSelectionButtonsBox.AddToClassList("extra-packages-buttons-box");
|
||||
|
||||
// Final adding
|
||||
packagesFilteringBox.Add(filteringDropdownBox);
|
||||
packagesFilteringBox.Add(selectingPackagesBox);
|
||||
|
||||
fullPackageSelectionBox.Add(_packagesTogglesBox);
|
||||
fullPackageSelectionBox.Add(packagesFilteringBox);
|
||||
|
||||
dependenciesHelpRow.Add(allPackagesLabel);
|
||||
dependenciesHelpRow.Add(allPackagesLabelTooltip);
|
||||
|
||||
_packageDependencyBox.Add(dependenciesHelpRow);
|
||||
_packageDependencyBox.Add(fullPackageSelectionBox);
|
||||
}
|
||||
|
||||
private void FilterPackageSelection(string filter)
|
||||
{
|
||||
var allToggles = _packagesTogglesBox.Children().Cast<Toggle>().ToArray();
|
||||
var selectedIndex = _packageSelectionFilters.FindIndex(x => x == filter);
|
||||
|
||||
switch (selectedIndex)
|
||||
{
|
||||
case 0:
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.style.display = DisplayStyle.Flex;
|
||||
break;
|
||||
case 1:
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.style.display = toggle.value ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
break;
|
||||
case 2:
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.style.display = toggle.value ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if any toggles are displayed
|
||||
var count = allToggles.Count(toggle => toggle.style.display == DisplayStyle.Flex);
|
||||
_noPackagesLabel.style.display = count > 0 ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
|
||||
_packagesFilter = filter;
|
||||
_filteringDropdown.text = filter;
|
||||
}
|
||||
|
||||
private void SelectAllPackages(bool shouldSelect)
|
||||
{
|
||||
var allToggles = _packagesTogglesBox.Children().Cast<Toggle>();
|
||||
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.value = shouldSelect;
|
||||
}
|
||||
|
||||
public override async Task<ExportResult> ExportPackage(string outputPath, bool isCompleteProject)
|
||||
{
|
||||
var paths = GetAllExportPaths();
|
||||
if (isCompleteProject)
|
||||
paths = IncludeProjectSettings(paths);
|
||||
|
||||
var includeDependencies = GetIncludeDependenciesToggle();
|
||||
|
||||
var dependenciesToInclude = Array.Empty<string>();
|
||||
|
||||
if (includeDependencies)
|
||||
dependenciesToInclude = GetIncludedDependencies().ToArray();
|
||||
|
||||
ExporterSettings exportSettings;
|
||||
|
||||
if (ASToolsPreferences.Instance.UseLegacyExporting)
|
||||
exportSettings = new LegacyExporterSettings()
|
||||
{
|
||||
ExportPaths = paths,
|
||||
OutputFilename = outputPath,
|
||||
IncludeDependencies = includeDependencies,
|
||||
};
|
||||
else
|
||||
exportSettings = new DefaultExporterSettings()
|
||||
{
|
||||
ExportPaths = paths,
|
||||
OutputFilename = outputPath,
|
||||
Dependencies = dependenciesToInclude,
|
||||
};
|
||||
|
||||
return await PackageExporter.ExportPackage(exportSettings);
|
||||
}
|
||||
|
||||
private string[] IncludeProjectSettings(string[] exportPaths)
|
||||
{
|
||||
if (exportPaths.Contains("ProjectSettings"))
|
||||
return exportPaths;
|
||||
|
||||
var updatedExportPaths = new string[exportPaths.Length + 1];
|
||||
exportPaths.CopyTo(updatedExportPaths, 0);
|
||||
updatedExportPaths[updatedExportPaths.Length - 1] = "ProjectSettings";
|
||||
|
||||
return updatedExportPaths;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8bafd0b9c5b47bc985d17a18fc07978
|
||||
timeCreated: 1654089523
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/FolderUploadWorkflowView.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,415 @@
|
||||
using AssetStoreTools.Exporter;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class HybridPackageUploadWorkflowView : UploadWorkflowView
|
||||
{
|
||||
public const string WorkflowName = "HybridPackageWorkflow";
|
||||
public const string WorkflowDisplayName = "Local UPM Package";
|
||||
|
||||
public override string Name => WorkflowName;
|
||||
public override string DisplayName => WorkflowDisplayName;
|
||||
|
||||
private VisualElement _extraPackagesBox;
|
||||
private Label _noExtraPackagesLabel;
|
||||
private ScrollView _extraPackagesTogglesBox;
|
||||
|
||||
private ToolbarMenu _filteringDropdown;
|
||||
private string _extraPackagesFilter;
|
||||
private readonly List<string> _extraPackageSelectionFilters = new List<string> { "All", "Selected", "Not Selected" };
|
||||
|
||||
private HybridPackageUploadWorkflowView(string category, Action serializeSelection) : base(serializeSelection)
|
||||
{
|
||||
Category = category;
|
||||
|
||||
SetupWorkflow();
|
||||
}
|
||||
|
||||
public static HybridPackageUploadWorkflowView Create(string category, Action serializeAction)
|
||||
{
|
||||
return new HybridPackageUploadWorkflowView(category, serializeAction);
|
||||
}
|
||||
|
||||
protected sealed override void SetupWorkflow()
|
||||
{
|
||||
// Path selection
|
||||
VisualElement folderPathSelectionRow = new VisualElement();
|
||||
folderPathSelectionRow.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement labelHelpRow = new VisualElement();
|
||||
labelHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label folderPathLabel = new Label { text = "Package path" };
|
||||
Image folderPathLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "Select a local Package you would like to export and upload to the Store."
|
||||
};
|
||||
|
||||
labelHelpRow.Add(folderPathLabel);
|
||||
labelHelpRow.Add(folderPathLabelTooltip);
|
||||
|
||||
PathSelectionField = new TextField();
|
||||
PathSelectionField.AddToClassList("path-selection-field");
|
||||
PathSelectionField.isReadOnly = true;
|
||||
|
||||
Button browsePathButton = new Button(BrowsePath) { name = "BrowsePathButton", text = "Browse" };
|
||||
browsePathButton.AddToClassList("browse-button");
|
||||
|
||||
folderPathSelectionRow.Add(labelHelpRow);
|
||||
folderPathSelectionRow.Add(PathSelectionField);
|
||||
folderPathSelectionRow.Add(browsePathButton);
|
||||
|
||||
Add(folderPathSelectionRow);
|
||||
|
||||
ValidationElement = new AssetValidationElement();
|
||||
Add(ValidationElement);
|
||||
|
||||
ValidationElement.SetCategory(Category);
|
||||
}
|
||||
|
||||
public override void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid)
|
||||
{
|
||||
if(!DeserializeMainExportPath(json, out string mainExportPath) || !Directory.Exists(mainExportPath))
|
||||
{
|
||||
ASDebug.Log("Unable to restore Hybrid Package workflow paths from local cache");
|
||||
LoadSerializedWorkflowFallback(lastUploadedGuid, lastUploadedGuid);
|
||||
return;
|
||||
}
|
||||
|
||||
DeserializeExtraExportPaths(json, out List<string> extraExportPaths);
|
||||
|
||||
ASDebug.Log($"Restoring serialized Hybrid Package workflow values from local cache");
|
||||
LoadSerializedWorkflow(mainExportPath, extraExportPaths);
|
||||
}
|
||||
|
||||
public override void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid)
|
||||
{
|
||||
var mainExportPath = AssetDatabase.GUIDToAssetPath(lastUploadedGuid);
|
||||
if (string.IsNullOrEmpty(mainExportPath))
|
||||
mainExportPath = lastUploadedPath;
|
||||
|
||||
if (!mainExportPath.StartsWith("Packages/") || !Directory.Exists(mainExportPath))
|
||||
{
|
||||
ASDebug.Log("Unable to restore Hybrid Package workflow paths from previous upload values");
|
||||
return;
|
||||
}
|
||||
|
||||
ASDebug.Log($"Restoring serialized Hybrid Package workflow values from previous upload values");
|
||||
LoadSerializedWorkflow(mainExportPath, null);
|
||||
}
|
||||
|
||||
private void LoadSerializedWorkflow(string relativeAssetDatabasePath, List<string> extraExportPaths)
|
||||
{
|
||||
// Expected path is in ADB form, so we need to reconstruct it first
|
||||
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
|
||||
var realPath = Path.GetFullPath(relativeAssetDatabasePath).Replace('\\', '/');
|
||||
if (realPath.StartsWith(rootProjectPath))
|
||||
realPath = realPath.Substring(rootProjectPath.Length);
|
||||
|
||||
if (!IsValidLocalPackage(realPath, out relativeAssetDatabasePath))
|
||||
{
|
||||
ASDebug.Log("Unable to restore Hybrid Package workflow path - package is not a valid UPM package");
|
||||
return;
|
||||
}
|
||||
|
||||
// Treat this as a manual selection
|
||||
HandleHybridUploadPathSelection(realPath, relativeAssetDatabasePath, extraExportPaths, false);
|
||||
}
|
||||
|
||||
protected override void BrowsePath()
|
||||
{
|
||||
// Path retrieval
|
||||
string relativeExportPath = string.Empty;
|
||||
string rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
|
||||
|
||||
var absoluteExportPath = EditorUtility.OpenFolderPanel("Select the Package", "Packages/", "");
|
||||
|
||||
if (string.IsNullOrEmpty(absoluteExportPath))
|
||||
return;
|
||||
|
||||
if (absoluteExportPath.StartsWith(rootProjectPath))
|
||||
relativeExportPath = absoluteExportPath.Substring(rootProjectPath.Length);
|
||||
|
||||
var workingPath = !string.IsNullOrEmpty(relativeExportPath) ? relativeExportPath : absoluteExportPath;
|
||||
if (!IsValidLocalPackage(workingPath, out string relativeAssetDatabasePath))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Invalid selection", "Selected export path must be a valid local package", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
HandleHybridUploadPathSelection(workingPath, relativeAssetDatabasePath, null, true);
|
||||
}
|
||||
|
||||
private void HandleHybridUploadPathSelection(string relativeExportPath, string relativeAssetDatabasePath, List<string> serializedToggles, bool serializeValues)
|
||||
{
|
||||
PathSelectionField.value = relativeExportPath + "/";
|
||||
|
||||
// Reset and reinitialize the selected export path(s) array
|
||||
MainExportPath = relativeAssetDatabasePath;
|
||||
ExtraExportPaths = new List<string>();
|
||||
|
||||
// Set additional upload data for the Publisher Portal backend (GUID and Package Path).
|
||||
// The backend workflow currently accepts only 1 package guid and path, so we'll use the main folder data
|
||||
LocalPackageGuid = AssetDatabase.AssetPathToGUID(relativeAssetDatabasePath);
|
||||
LocalPackagePath = relativeAssetDatabasePath;
|
||||
LocalProjectPath = relativeAssetDatabasePath;
|
||||
|
||||
if (_extraPackagesBox != null)
|
||||
{
|
||||
_extraPackagesBox.Clear();
|
||||
Remove(_extraPackagesBox);
|
||||
|
||||
_extraPackagesBox = null;
|
||||
}
|
||||
|
||||
List<string> pathsToAdd = new List<string>();
|
||||
foreach (var package in PackageUtility.GetAllLocalPackages())
|
||||
{
|
||||
// Exclude the Asset Store Tools themselves
|
||||
if (package.name == "com.unity.asset-store-tools")
|
||||
continue;
|
||||
|
||||
var localPackagePath = package.GetConvenientPath();
|
||||
|
||||
if (localPackagePath == relativeExportPath)
|
||||
continue;
|
||||
|
||||
pathsToAdd.Add(package.assetPath);
|
||||
}
|
||||
|
||||
pathsToAdd.Sort();
|
||||
|
||||
if (pathsToAdd.Count != 0)
|
||||
PopulateExtraPackagesBox(pathsToAdd, serializedToggles);
|
||||
|
||||
// After setting up the main and extra paths, check for missing metas and update validation paths
|
||||
CheckForMissingMetas();
|
||||
UpdateValidationPaths();
|
||||
|
||||
if (serializeValues)
|
||||
SerializeSelection?.Invoke();
|
||||
}
|
||||
|
||||
private void PopulateExtraPackagesBox(List<string> otherPackagesFound, List<string> checkedToggles)
|
||||
{
|
||||
// Dependencies selection
|
||||
_extraPackagesBox = new VisualElement();
|
||||
_extraPackagesBox.AddToClassList("selection-box-row");
|
||||
|
||||
InitializeExtraPackageSelection();
|
||||
|
||||
EventCallback<ChangeEvent<bool>, string> toggleChangeCallback = OnToggledPackage;
|
||||
|
||||
foreach (var path in otherPackagesFound)
|
||||
{
|
||||
var toggle = new Toggle { value = false, text = path };
|
||||
toggle.AddToClassList("extra-packages-toggle");
|
||||
toggle.tooltip = path;
|
||||
if (checkedToggles != null && checkedToggles.Contains(toggle.text))
|
||||
{
|
||||
toggle.SetValueWithoutNotify(true);
|
||||
ExtraExportPaths.Add(toggle.text);
|
||||
}
|
||||
|
||||
toggle.RegisterCallback(toggleChangeCallback, toggle.text);
|
||||
_extraPackagesTogglesBox.Add(toggle);
|
||||
}
|
||||
|
||||
Add(_extraPackagesBox);
|
||||
}
|
||||
|
||||
private void InitializeExtraPackageSelection()
|
||||
{
|
||||
VisualElement extraPackagesHelpRow = new VisualElement();
|
||||
extraPackagesHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label extraPackagesLabel = new Label { text = "Extra Packages" };
|
||||
Image extraPackagesLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "If your package has dependencies on other local packages, please select which of these packages should also be included in the resulting package"
|
||||
};
|
||||
|
||||
var fullPackageSelectionBox = new VisualElement();
|
||||
fullPackageSelectionBox.AddToClassList("extra-packages-box");
|
||||
|
||||
_extraPackagesTogglesBox = new ScrollView { name = "ExtraPackageToggles" };
|
||||
_extraPackagesTogglesBox.AddToClassList("extra-packages-scroll-view");
|
||||
|
||||
_noExtraPackagesLabel = new Label("No packages were found that match this criteria.");
|
||||
_noExtraPackagesLabel.AddToClassList("no-packages-label");
|
||||
|
||||
var scrollContainer = _extraPackagesTogglesBox.Q<VisualElement>("unity-content-viewport");
|
||||
scrollContainer.Add(_noExtraPackagesLabel);
|
||||
|
||||
VisualElement extraPackagesFilteringBox = new VisualElement();
|
||||
extraPackagesFilteringBox.AddToClassList("packages-filtering-box");
|
||||
|
||||
// Select - deselect buttons
|
||||
VisualElement selectingPackagesBox = new VisualElement();
|
||||
selectingPackagesBox.AddToClassList("filtering-packages-buttons-box");
|
||||
|
||||
Button selectAllButton = new Button(() => SelectAllPackages(true))
|
||||
{
|
||||
text = "Select All"
|
||||
};
|
||||
|
||||
Button deSelectAllButton = new Button(() => SelectAllPackages(false))
|
||||
{
|
||||
text = "Deselect All"
|
||||
};
|
||||
|
||||
selectAllButton.AddToClassList("filter-packages-button");
|
||||
deSelectAllButton.AddToClassList("filter-packages-button");
|
||||
|
||||
selectingPackagesBox.Add(selectAllButton);
|
||||
selectingPackagesBox.Add(deSelectAllButton);
|
||||
|
||||
// Filtering dropdown
|
||||
VisualElement filteringDropdownBox = new VisualElement();
|
||||
filteringDropdownBox.AddToClassList("filtering-packages-dropdown-box");
|
||||
|
||||
_filteringDropdown = new ToolbarMenu { text = _extraPackagesFilter = _extraPackageSelectionFilters[0] };
|
||||
_filteringDropdown.AddToClassList("filter-packages-dropdown");
|
||||
|
||||
foreach (var filter in _extraPackageSelectionFilters)
|
||||
_filteringDropdown.menu.AppendAction(filter, delegate { FilterPackageSelection(filter); });
|
||||
|
||||
filteringDropdownBox.Add(_filteringDropdown);
|
||||
|
||||
VisualElement packageSelectionButtonsBox = new VisualElement();
|
||||
packageSelectionButtonsBox.AddToClassList("extra-packages-buttons-box");
|
||||
|
||||
// Final adding
|
||||
extraPackagesFilteringBox.Add(filteringDropdownBox);
|
||||
extraPackagesFilteringBox.Add(selectingPackagesBox);
|
||||
|
||||
fullPackageSelectionBox.Add(_extraPackagesTogglesBox);
|
||||
fullPackageSelectionBox.Add(extraPackagesFilteringBox);
|
||||
|
||||
extraPackagesHelpRow.Add(extraPackagesLabel);
|
||||
extraPackagesHelpRow.Add(extraPackagesLabelTooltip);
|
||||
|
||||
_extraPackagesBox.Add(extraPackagesHelpRow);
|
||||
_extraPackagesBox.Add(fullPackageSelectionBox);
|
||||
}
|
||||
|
||||
private void SelectAllPackages(bool shouldSelect)
|
||||
{
|
||||
var allToggles = _extraPackagesTogglesBox.Children().Cast<Toggle>();
|
||||
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.value = shouldSelect;
|
||||
}
|
||||
|
||||
private void FilterPackageSelection(string filter)
|
||||
{
|
||||
var allToggles = _extraPackagesTogglesBox.Children().Cast<Toggle>().ToArray();
|
||||
var selectedIndex = _extraPackageSelectionFilters.FindIndex(x => x == filter);
|
||||
|
||||
switch (selectedIndex)
|
||||
{
|
||||
case 0:
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.style.display = DisplayStyle.Flex;
|
||||
break;
|
||||
case 1:
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.style.display = toggle.value ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
break;
|
||||
case 2:
|
||||
foreach (var toggle in allToggles)
|
||||
toggle.style.display = toggle.value ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if any toggles are displayed
|
||||
var count = allToggles.Count(toggle => toggle.style.display == DisplayStyle.Flex);
|
||||
_noExtraPackagesLabel.style.display = count > 0 ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
|
||||
_extraPackagesFilter = filter;
|
||||
_filteringDropdown.text = filter;
|
||||
}
|
||||
|
||||
private void OnToggledPackage(ChangeEvent<bool> evt, string folderPath)
|
||||
{
|
||||
switch (evt.newValue)
|
||||
{
|
||||
case true when !ExtraExportPaths.Contains(folderPath):
|
||||
ExtraExportPaths.Add(folderPath);
|
||||
break;
|
||||
case false when ExtraExportPaths.Contains(folderPath):
|
||||
ExtraExportPaths.Remove(folderPath);
|
||||
break;
|
||||
}
|
||||
|
||||
FilterPackageSelection(_extraPackagesFilter);
|
||||
UpdateValidationPaths();
|
||||
SerializeSelection?.Invoke();
|
||||
}
|
||||
|
||||
private void UpdateValidationPaths()
|
||||
{
|
||||
var validationPaths = new List<string>() { MainExportPath };
|
||||
validationPaths.AddRange(ExtraExportPaths);
|
||||
ValidationElement.SetValidationPaths(validationPaths.ToArray());
|
||||
}
|
||||
|
||||
private bool IsValidLocalPackage(string packageFolderPath, out string assetDatabasePackagePath)
|
||||
{
|
||||
assetDatabasePackagePath = string.Empty;
|
||||
|
||||
string packageManifestPath = $"{packageFolderPath}/package.json";
|
||||
|
||||
if (!File.Exists(packageManifestPath))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
var localPackages = PackageUtility.GetAllLocalPackages();
|
||||
|
||||
if (localPackages == null || localPackages.Length == 0)
|
||||
return false;
|
||||
|
||||
foreach (var package in localPackages)
|
||||
{
|
||||
var localPackagePath = package.GetConvenientPath();
|
||||
|
||||
if (localPackagePath != packageFolderPath)
|
||||
continue;
|
||||
|
||||
assetDatabasePackagePath = package.assetPath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task<ExportResult> ExportPackage(string outputPath, bool _)
|
||||
{
|
||||
var paths = GetAllExportPaths();
|
||||
|
||||
var exportSettings = new DefaultExporterSettings()
|
||||
{
|
||||
ExportPaths = paths,
|
||||
OutputFilename = outputPath
|
||||
};
|
||||
|
||||
return await PackageExporter.ExportPackage(exportSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5472a1f8e8745779ee82d18b63ec19c
|
||||
timeCreated: 1654112492
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/HybridPackageUploadWorkflowView.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,140 @@
|
||||
using AssetStoreTools.Exporter;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal class UnityPackageUploadWorkflowView : UploadWorkflowView
|
||||
{
|
||||
public const string WorkflowName = "UnitypackageWorkflow";
|
||||
public const string WorkflowDisplayName = "Pre-exported .unitypackage";
|
||||
|
||||
public override string Name => WorkflowName;
|
||||
public override string DisplayName => WorkflowDisplayName;
|
||||
|
||||
private UnityPackageUploadWorkflowView(string category, Action serializeSelection) : base(serializeSelection)
|
||||
{
|
||||
Category = category;
|
||||
SetupWorkflow();
|
||||
}
|
||||
|
||||
public static UnityPackageUploadWorkflowView Create(string category, Action serializeAction)
|
||||
{
|
||||
return new UnityPackageUploadWorkflowView(category, serializeAction);
|
||||
}
|
||||
|
||||
protected sealed override void SetupWorkflow()
|
||||
{
|
||||
// Path selection
|
||||
VisualElement folderPathSelectionRow = new VisualElement();
|
||||
folderPathSelectionRow.AddToClassList("selection-box-row");
|
||||
|
||||
VisualElement labelHelpRow = new VisualElement();
|
||||
labelHelpRow.AddToClassList("label-help-row");
|
||||
|
||||
Label folderPathLabel = new Label { text = "Package path" };
|
||||
Image folderPathLabelTooltip = new Image
|
||||
{
|
||||
tooltip = "Select the .unitypackage file you would like to upload."
|
||||
};
|
||||
|
||||
labelHelpRow.Add(folderPathLabel);
|
||||
labelHelpRow.Add(folderPathLabelTooltip);
|
||||
|
||||
PathSelectionField = new TextField();
|
||||
PathSelectionField.AddToClassList("path-selection-field");
|
||||
PathSelectionField.isReadOnly = true;
|
||||
|
||||
Button browsePathButton = new Button(BrowsePath) { name = "BrowsePathButton", text = "Browse" };
|
||||
browsePathButton.AddToClassList("browse-button");
|
||||
|
||||
folderPathSelectionRow.Add(labelHelpRow);
|
||||
folderPathSelectionRow.Add(PathSelectionField);
|
||||
folderPathSelectionRow.Add(browsePathButton);
|
||||
|
||||
Add(folderPathSelectionRow);
|
||||
|
||||
ValidationElement = new PackageValidationElement();
|
||||
Add(ValidationElement);
|
||||
|
||||
ValidationElement.SetCategory(Category);
|
||||
}
|
||||
|
||||
public override void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid)
|
||||
{
|
||||
if (!DeserializeMainExportPath(json, out string mainPackagePath) || !File.Exists(mainPackagePath))
|
||||
{
|
||||
ASDebug.Log("Unable to restore .unitypackage upload workflow path from the local cache");
|
||||
LoadSerializedWorkflowFallback(lastUploadedPath, lastUploadedGuid);
|
||||
return;
|
||||
}
|
||||
|
||||
ASDebug.Log($"Restoring serialized .unitypackage workflow values from local cache");
|
||||
HandleUnityPackageUploadPathSelection(mainPackagePath, false);
|
||||
}
|
||||
|
||||
public override void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid)
|
||||
{
|
||||
var packagePath = AssetDatabase.GUIDToAssetPath(lastUploadedGuid);
|
||||
if (string.IsNullOrEmpty(packagePath))
|
||||
packagePath = lastUploadedPath;
|
||||
|
||||
if (!packagePath.EndsWith(".unitypackage") || !File.Exists(packagePath))
|
||||
{
|
||||
ASDebug.Log("Unable to restore .unitypackage workflow path from previous upload values");
|
||||
return;
|
||||
}
|
||||
|
||||
ASDebug.Log($"Restoring serialized .unitypackage workflow values from previous upload values");
|
||||
HandleUnityPackageUploadPathSelection(packagePath, false);
|
||||
}
|
||||
|
||||
protected override void BrowsePath()
|
||||
{
|
||||
// Path retrieval
|
||||
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
|
||||
var absolutePackagePath = EditorUtility.OpenFilePanel("Select a .unitypackage file", rootProjectPath, "unitypackage");
|
||||
|
||||
if (string.IsNullOrEmpty(absolutePackagePath))
|
||||
return;
|
||||
|
||||
var relativeExportPath = string.Empty;
|
||||
if (absolutePackagePath.StartsWith(rootProjectPath))
|
||||
relativeExportPath = absolutePackagePath.Substring(rootProjectPath.Length);
|
||||
var selectedPackagePath = !string.IsNullOrEmpty(relativeExportPath) ? relativeExportPath : absolutePackagePath;
|
||||
|
||||
HandleUnityPackageUploadPathSelection(selectedPackagePath, true);
|
||||
}
|
||||
|
||||
private void HandleUnityPackageUploadPathSelection(string selectedPackagePath, bool serializeValues)
|
||||
{
|
||||
// Main upload path
|
||||
PathSelectionField.value = selectedPackagePath;
|
||||
|
||||
// Export data
|
||||
MainExportPath = selectedPackagePath;
|
||||
|
||||
LocalPackageGuid = string.Empty;
|
||||
LocalPackagePath = string.Empty;
|
||||
LocalProjectPath = selectedPackagePath;
|
||||
|
||||
if (serializeValues)
|
||||
SerializeSelection?.Invoke();
|
||||
|
||||
ValidationElement.SetValidationPaths(MainExportPath);
|
||||
}
|
||||
|
||||
public override Task<ExportResult> ExportPackage(string __, bool _)
|
||||
{
|
||||
if (string.IsNullOrEmpty(MainExportPath))
|
||||
return Task.FromResult(new ExportResult() { Success = false, Error = ASError.GetGenericError(new ArgumentException("Package export path is empty or null")) });
|
||||
return Task.FromResult(new ExportResult() { Success = true, ExportedPath = MainExportPath });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9e30749a5784d18a1a2644cc44dda29
|
||||
timeCreated: 1654112475
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/UnityPackageUploadWorkflowView.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,210 @@
|
||||
using AssetStoreTools.Exporter;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace AssetStoreTools.Uploader.UIElements
|
||||
{
|
||||
internal abstract class UploadWorkflowView : VisualElement
|
||||
{
|
||||
protected TextField PathSelectionField;
|
||||
|
||||
// Upload data
|
||||
protected List<string> ExtraExportPaths = new List<string>();
|
||||
|
||||
protected string MainExportPath = String.Empty;
|
||||
protected string LocalPackageGuid;
|
||||
protected string LocalPackagePath;
|
||||
protected string LocalProjectPath;
|
||||
|
||||
protected string Category;
|
||||
protected ValidationElement ValidationElement;
|
||||
|
||||
protected readonly Action SerializeSelection;
|
||||
|
||||
public abstract string Name { get; }
|
||||
public abstract string DisplayName { get; }
|
||||
|
||||
protected UploadWorkflowView(Action serializeSelection)
|
||||
{
|
||||
SerializeSelection = serializeSelection;
|
||||
style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
public string[] GetAllExportPaths()
|
||||
{
|
||||
var allPaths = new List<string>(ExtraExportPaths);
|
||||
if (!string.IsNullOrEmpty(MainExportPath))
|
||||
allPaths.Insert(0, MainExportPath);
|
||||
return allPaths.ToArray();
|
||||
}
|
||||
|
||||
public string GetLocalPackageGuid()
|
||||
{
|
||||
return LocalPackageGuid;
|
||||
}
|
||||
|
||||
public string GetLocalPackagePath()
|
||||
{
|
||||
return LocalPackagePath;
|
||||
}
|
||||
|
||||
public string GetLocalProjectPath()
|
||||
{
|
||||
return LocalProjectPath;
|
||||
}
|
||||
|
||||
protected abstract void SetupWorkflow();
|
||||
|
||||
public virtual JsonValue SerializeWorkflow()
|
||||
{
|
||||
var workflowDict = JsonValue.NewDict();
|
||||
|
||||
var mainExportPathDict = JsonValue.NewDict();
|
||||
mainExportPathDict["path"] = MainExportPath;
|
||||
|
||||
if (MainExportPath != null && !MainExportPath.StartsWith("Assets/") && !MainExportPath.StartsWith("Packages/"))
|
||||
mainExportPathDict["guid"] = new JsonValue("");
|
||||
else
|
||||
mainExportPathDict["guid"] = AssetDatabase.AssetPathToGUID(MainExportPath);
|
||||
|
||||
workflowDict["mainPath"] = mainExportPathDict;
|
||||
|
||||
var extraExportPathsList = new List<JsonValue>();
|
||||
foreach (var path in ExtraExportPaths)
|
||||
{
|
||||
var pathDict = JsonValue.NewDict();
|
||||
pathDict["path"] = path;
|
||||
pathDict["guid"] = AssetDatabase.AssetPathToGUID(path);
|
||||
extraExportPathsList.Add(pathDict);
|
||||
}
|
||||
workflowDict["extraPaths"] = extraExportPathsList;
|
||||
|
||||
return workflowDict;
|
||||
}
|
||||
|
||||
protected bool DeserializeMainExportPath(JsonValue json, out string mainExportPath)
|
||||
{
|
||||
mainExportPath = string.Empty;
|
||||
try
|
||||
{
|
||||
var mainPathDict = json["mainPath"];
|
||||
|
||||
if (!mainPathDict.ContainsKey("path") || !mainPathDict["path"].IsString())
|
||||
return false;
|
||||
|
||||
mainExportPath = DeserializePath(mainPathDict);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void DeserializeExtraExportPaths(JsonValue json, out List<string> extraExportPaths)
|
||||
{
|
||||
extraExportPaths = new List<string>();
|
||||
try
|
||||
{
|
||||
var extraPathsList = json["extraPaths"].AsList();
|
||||
extraExportPaths.AddRange(extraPathsList.Select(DeserializePath));
|
||||
}
|
||||
catch
|
||||
{
|
||||
ASDebug.LogWarning($"Deserializing extra export paths for {Name} failed");
|
||||
extraExportPaths.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void DeserializeDependencies(JsonValue json, out List<string> dependencies)
|
||||
{
|
||||
dependencies = new List<string>();
|
||||
try
|
||||
{
|
||||
var packageJsonList = json["dependenciesNames"].AsList();
|
||||
dependencies.AddRange(packageJsonList.Select(package => package.AsString()));
|
||||
}
|
||||
catch
|
||||
{
|
||||
ASDebug.LogWarning($"Deserializing dependencies for {Name} failed");
|
||||
dependencies.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void DeserializeDependenciesToggle(JsonValue json, out bool dependenciesBool)
|
||||
{
|
||||
bool includeDependencies;
|
||||
try
|
||||
{
|
||||
includeDependencies = json["dependencies"].AsBool();
|
||||
}
|
||||
catch
|
||||
{
|
||||
ASDebug.LogWarning($"Deserializing dependencies toggle for {Name} failed");
|
||||
includeDependencies = false;
|
||||
}
|
||||
|
||||
dependenciesBool = includeDependencies;
|
||||
}
|
||||
|
||||
|
||||
private string DeserializePath(JsonValue pathDict)
|
||||
{
|
||||
// First pass - retrieve from GUID
|
||||
var exportPath = AssetDatabase.GUIDToAssetPath(pathDict["guid"].AsString());
|
||||
// Second pass - retrieve directly
|
||||
if (string.IsNullOrEmpty(exportPath))
|
||||
exportPath = pathDict["path"].AsString();
|
||||
return exportPath;
|
||||
}
|
||||
|
||||
protected void CheckForMissingMetas()
|
||||
{
|
||||
if (ASToolsPreferences.Instance.DisplayHiddenMetaDialog && FileUtility.IsMissingMetaFiles(GetAllExportPaths()))
|
||||
{
|
||||
var selectedOption = EditorUtility.DisplayDialogComplex(
|
||||
"Notice",
|
||||
"Your package includes hidden folders which do not contain meta files. " +
|
||||
"Hidden folders will not be exported unless they contain meta files.\n\nWould you like meta files to be generated?",
|
||||
"Yes", "No", "No and do not display this again");
|
||||
|
||||
switch (selectedOption)
|
||||
{
|
||||
case 0:
|
||||
FileUtility.GenerateMetaFiles(GetAllExportPaths());
|
||||
EditorUtility.DisplayDialog(
|
||||
"Success",
|
||||
"Meta files have been generated. Please note that further manual tweaking may be required to set up correct references",
|
||||
"OK");
|
||||
break;
|
||||
case 1:
|
||||
// Do nothing
|
||||
return;
|
||||
case 2:
|
||||
ASToolsPreferences.Instance.DisplayHiddenMetaDialog = false;
|
||||
ASToolsPreferences.Instance.Save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetValidationSummary(out string validationSummary)
|
||||
{
|
||||
return ValidationElement.GetValidationSummary(out validationSummary);
|
||||
}
|
||||
|
||||
public abstract void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid);
|
||||
|
||||
public abstract void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid);
|
||||
|
||||
protected abstract void BrowsePath();
|
||||
|
||||
public abstract Task<ExportResult> ExportPackage(string outputPath, bool isCompleteProject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f84b47b78aca74c4db1e9b753d41422f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/UploadWorkflowView.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 342ae2f53a9d6714ca92eacbecb27801
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
using AssetStoreTools.Utility.Json;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AssetStoreTools.Uploader.Utility
|
||||
{
|
||||
internal static class AssetStoreCache
|
||||
{
|
||||
public const string TempCachePath = "Temp/AssetStoreToolsCache";
|
||||
public const string PersistentCachePath = "Library/AssetStoreToolsCache";
|
||||
|
||||
private const string PackageDataFile = "PackageMetadata.json";
|
||||
private const string CategoryDataFile = "Categories.json";
|
||||
|
||||
private static void CreateFileInTempCache(string fileName, object content, bool overwrite)
|
||||
{
|
||||
CreateCacheFile(TempCachePath, fileName, content, overwrite);
|
||||
}
|
||||
|
||||
private static void CreateFileInPersistentCache(string fileName, object content, bool overwrite)
|
||||
{
|
||||
CreateCacheFile(PersistentCachePath, fileName, content, overwrite);
|
||||
}
|
||||
|
||||
private static void CreateCacheFile(string rootPath, string fileName, object content, bool overwrite)
|
||||
{
|
||||
if (!Directory.Exists(rootPath))
|
||||
Directory.CreateDirectory(rootPath);
|
||||
|
||||
var fullPath = Path.Combine(rootPath, fileName);
|
||||
|
||||
if(File.Exists(fullPath))
|
||||
{
|
||||
if (overwrite)
|
||||
File.Delete(fullPath);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
switch (content)
|
||||
{
|
||||
case byte[] bytes:
|
||||
File.WriteAllBytes(fullPath, bytes);
|
||||
break;
|
||||
default:
|
||||
File.WriteAllText(fullPath, content.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearTempCache()
|
||||
{
|
||||
if (!File.Exists(Path.Combine(TempCachePath, PackageDataFile)))
|
||||
return;
|
||||
|
||||
// Cache consists of package data and package texture thumbnails. We don't clear
|
||||
// texture thumbnails here since they are less likely to change. They are still
|
||||
// deleted and redownloaded every project restart (because of being stored in the 'Temp' folder)
|
||||
File.Delete(Path.Combine(TempCachePath, PackageDataFile));
|
||||
}
|
||||
|
||||
public static void CacheCategories(JsonValue data)
|
||||
{
|
||||
CreateFileInTempCache(CategoryDataFile, data, true);
|
||||
}
|
||||
|
||||
public static bool GetCachedCategories(out JsonValue data)
|
||||
{
|
||||
data = new JsonValue();
|
||||
var path = Path.Combine(TempCachePath, CategoryDataFile);
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
|
||||
data = JSONParser.SimpleParse(File.ReadAllText(path, Encoding.UTF8));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CachePackageMetadata(JsonValue data)
|
||||
{
|
||||
CreateFileInTempCache(PackageDataFile, data.ToString(), true);
|
||||
}
|
||||
|
||||
public static bool GetCachedPackageMetadata(out JsonValue data)
|
||||
{
|
||||
data = new JsonValue();
|
||||
var path = Path.Combine(TempCachePath, PackageDataFile);
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
|
||||
data = JSONParser.SimpleParse(File.ReadAllText(path, Encoding.UTF8));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CacheTexture(string packageId, Texture2D texture)
|
||||
{
|
||||
CreateFileInTempCache($"{packageId}.png", texture.EncodeToPNG(), true);
|
||||
}
|
||||
|
||||
public static bool GetCachedTexture(string packageId, out Texture2D texture)
|
||||
{
|
||||
texture = new Texture2D(1, 1);
|
||||
var path = Path.Combine(TempCachePath, $"{packageId}.png");
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CacheUploadSelections(string packageId, JsonValue json)
|
||||
{
|
||||
var fileName = $"{packageId}-uploadselection.asset";
|
||||
CreateFileInPersistentCache(fileName, json.ToString(), true);
|
||||
}
|
||||
|
||||
public static bool GetCachedUploadSelections(string packageId, out JsonValue json)
|
||||
{
|
||||
json = new JsonValue();
|
||||
var path = Path.Combine(PersistentCachePath, $"{packageId}-uploadselection.asset");
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
|
||||
json = JSONParser.SimpleParse(File.ReadAllText(path, Encoding.UTF8));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e5fee0cad7655f458d9b600f4ae6d02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Utility/AssetStoreCache.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AssetStoreTools.Uploader.Data;
|
||||
using AssetStoreTools.Utility;
|
||||
using AssetStoreTools.Utility.Json;
|
||||
|
||||
namespace AssetStoreTools.Uploader.Utility
|
||||
{
|
||||
internal class PackageFetcher
|
||||
{
|
||||
public abstract class PackageFetcherResult
|
||||
{
|
||||
public bool Success;
|
||||
public bool SilentFail;
|
||||
public ASError Error;
|
||||
public JsonValue Json;
|
||||
}
|
||||
|
||||
public class PackageFetcherResultSingle : PackageFetcherResult
|
||||
{
|
||||
public PackageData Package;
|
||||
}
|
||||
|
||||
public class PackageFetcherResultCollection : PackageFetcherResult
|
||||
{
|
||||
public ICollection<PackageData> Packages;
|
||||
}
|
||||
|
||||
public async Task<PackageFetcherResultCollection> Fetch(bool useCached)
|
||||
{
|
||||
var result = await AssetStoreAPI.GetFullPackageDataAsync(useCached);
|
||||
if (!result.Success)
|
||||
return new PackageFetcherResultCollection() { Success = false, Error = result.Error, SilentFail = result.SilentFail };
|
||||
|
||||
if (result.Response.Equals(default(JsonValue)))
|
||||
{
|
||||
ASDebug.Log("No packages fetched");
|
||||
return new PackageFetcherResultCollection() { Success = true, Packages = null, Json = result.Response };
|
||||
}
|
||||
|
||||
var packages = ParsePackages(result.Response);
|
||||
return new PackageFetcherResultCollection() { Success = true, Packages = packages, Json = result.Response };
|
||||
}
|
||||
|
||||
public async Task<PackageFetcherResultSingle> FetchRefreshedPackage(string packageId)
|
||||
{
|
||||
var result = await AssetStoreAPI.GetRefreshedPackageData(packageId);
|
||||
if (!result.Success)
|
||||
{
|
||||
ASDebug.LogError(result.Error.Message);
|
||||
return new PackageFetcherResultSingle() { Success = false, Error = result.Error, SilentFail = result.SilentFail };
|
||||
}
|
||||
|
||||
var package = ParseRefreshedPackage(packageId, result.Response);
|
||||
return new PackageFetcherResultSingle() { Success = true, Package = package };
|
||||
}
|
||||
|
||||
private ICollection<PackageData> ParsePackages(JsonValue json)
|
||||
{
|
||||
List<PackageData> packageList = new List<PackageData>();
|
||||
|
||||
var packageDict = json["packages"].AsDict(true);
|
||||
ASDebug.Log($"All packages\n{json["packages"]}");
|
||||
// Each package has an identifier and a bunch of data (current version id, name, etc.)
|
||||
foreach (var p in packageDict)
|
||||
{
|
||||
var packageId = p.Key;
|
||||
var package = ParseRefreshedPackage(packageId, p.Value);
|
||||
packageList.Add(package);
|
||||
}
|
||||
|
||||
return packageList;
|
||||
}
|
||||
|
||||
private PackageData ParseRefreshedPackage(string packageId, JsonValue json)
|
||||
{
|
||||
var packageName = json["name"].AsString(true);
|
||||
var versionId = json["id"].AsString(true);
|
||||
var statusName = json["status"].AsString(true);
|
||||
var isCompleteProject = json["is_complete_project"].AsBool(true);
|
||||
var categoryName = json["extra_info"].Get("category_info").Get("name").AsString(true);
|
||||
var lastUploadedPath = json["project_path"].IsString() ? json["project_path"].AsString() : string.Empty;
|
||||
var lastUploadedGuid = json["root_guid"].IsString() ? json["root_guid"].AsString() : string.Empty;
|
||||
|
||||
var lastDate = json["extra_info"].Get("modified").AsString(true);
|
||||
var lastSize = json["extra_info"].Get("size").AsString(true);
|
||||
|
||||
var package = new PackageData(packageId, packageName, versionId, statusName, categoryName, isCompleteProject, lastUploadedPath, lastUploadedGuid, lastDate, lastSize);
|
||||
ASDebug.Log(package);
|
||||
return package;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24e1d75365cc42a09e5c5daec071813e
|
||||
timeCreated: 1658918833
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Utility/PackageFetcher.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,31 @@
|
||||
using AssetStoreTools.Uploader.Data;
|
||||
using AssetStoreTools.Uploader.UIElements;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AssetStoreTools.Uploader.Utility
|
||||
{
|
||||
internal static class PackageViewStorer
|
||||
{
|
||||
private static readonly Dictionary<string, PackageView> SavedPackages = new Dictionary<string, PackageView>();
|
||||
|
||||
public static PackageView GetPackage(PackageData packageData)
|
||||
{
|
||||
string versionId = packageData.VersionId;
|
||||
if (SavedPackages.ContainsKey(versionId))
|
||||
{
|
||||
var savedPackage = SavedPackages[versionId];
|
||||
savedPackage.UpdateDataValues(packageData);
|
||||
return savedPackage;
|
||||
}
|
||||
|
||||
var package = new PackageView(packageData);
|
||||
SavedPackages.Add(package.VersionId, package);
|
||||
return package;
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
SavedPackages.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 582639e8ed53f37499d12efcb4cde2c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Utility/PackageViewStorer.cs
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9398c14296d30f479b9de5f3ec3b827
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2d1d4985f5314246a7cb4ef749974af
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
.primary-colors
|
||||
{
|
||||
/* Light - lighter */
|
||||
background-color: rgb(220, 220, 220);
|
||||
/* Light - middle */
|
||||
background-color: rgb(200, 200, 200);
|
||||
/* Light - darker */
|
||||
background-color: rgb(180, 180, 180);
|
||||
|
||||
/* Dark - lighter */
|
||||
background-color: rgb(78, 78, 78);
|
||||
/* Dark - middle */
|
||||
background-color: rgb(68, 68, 68);
|
||||
/* Dark - darker */
|
||||
background-color: rgb(58, 58, 58);
|
||||
|
||||
/* Border color - light */
|
||||
border-color: rgb(200, 200, 200);
|
||||
/* Border color - dark */
|
||||
border-color: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
.accent-color
|
||||
{
|
||||
border-color: rgb(58, 121, 187);
|
||||
}
|
||||
|
||||
.empty-color
|
||||
{
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.root
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d00412342a1b6c943b91cc06edad1202
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Base/BaseWindow_Dark.uss
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,37 @@
|
||||
.primary-colors
|
||||
{
|
||||
/* Light - lighter */
|
||||
background-color: rgb(220, 220, 220);
|
||||
/* Light - middle */
|
||||
background-color: rgb(200, 200, 200);
|
||||
/* Light - darker */
|
||||
background-color: rgb(180, 180, 180);
|
||||
|
||||
/* Dark - lighter */
|
||||
background-color: rgb(50, 50, 50);
|
||||
/* Dark - middle */
|
||||
background-color: rgb(28, 28, 28);
|
||||
/* Dark - darker */
|
||||
background-color: rgb(0, 0, 0);
|
||||
|
||||
/* Border color - light */
|
||||
border-color: rgb(200, 200, 200);
|
||||
/* Border color - dark */
|
||||
border-color: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
.accent-color
|
||||
{
|
||||
border-color: rgb(58, 121, 187);
|
||||
}
|
||||
|
||||
.empty-color
|
||||
{
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.root
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9e3b7e7e1851a140b1a5c23270ded34
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Base/BaseWindow_Light.uss
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,15 @@
|
||||
#MainScrollView > * > .unity-scroll-view__content-container
|
||||
{
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#MainScrollView > * > .unity-scroll-view__content-viewport
|
||||
{
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
#MainScrollView > * > * > .unity-scroll-view__content-container
|
||||
{
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bebc765ff411f3428c6b3b23d5eb8e7
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Base/BaseWindow_Main.uss
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<engine:UXML
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:engine="UnityEngine.UIElements"
|
||||
xmlns:editor="UnityEditor.UIElements"
|
||||
xmlns:uploader="AssetStoreTools.Uploader.UIElements"
|
||||
>
|
||||
<engine:ScrollView name="MainScrollView" style="flex-grow:1;">
|
||||
<uploader:LoginWindow name="LoginWindow"/>
|
||||
<uploader:UploadWindow name="UploadWindow" style="flex-grow:1;">
|
||||
<uploader:AllPackageView name="AllPackageView" style="flex-grow:1;"/>
|
||||
</uploader:UploadWindow>
|
||||
</engine:ScrollView>
|
||||
</engine:UXML>
|
||||
@@ -0,0 +1,17 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc5a13143b609774da7101bb8313dd7a
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Base/BaseWindow_Main.uxml
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0a004d57c1a4db4a83df517f42de8a1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
.asset-store-logo
|
||||
{
|
||||
background-image: url("../../Icons/publisher_portal_white.png");
|
||||
}
|
||||
|
||||
.or-label
|
||||
{
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.login-error-box
|
||||
{
|
||||
background-color: rgb(63, 63, 63);
|
||||
}
|
||||
|
||||
.manual-login-box
|
||||
{
|
||||
background-color: rgb(63, 63, 63);
|
||||
}
|
||||
|
||||
.hyperlink-button
|
||||
{
|
||||
color: rgb(68, 113, 229);
|
||||
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.hyperlink-button:hover
|
||||
{
|
||||
color: rgb(68, 133, 229);
|
||||
}
|
||||
|
||||
.hyperlink-button:active
|
||||
{
|
||||
color: rgb(68, 93, 229);
|
||||
}
|
||||
|
||||
.login-error-box > Image
|
||||
{
|
||||
--unity-image: resource("console.erroricon@2x");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13a732708670b6040ac77e49fceae9c5
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Login/Login_Dark.uss
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,43 @@
|
||||
.asset-store-logo
|
||||
{
|
||||
background-image: url("../../Icons/publisher_portal_black.png");
|
||||
}
|
||||
|
||||
.or-label
|
||||
{
|
||||
color: rgb(28, 28, 28);
|
||||
}
|
||||
|
||||
.login-error-box
|
||||
{
|
||||
background-color: rgb(212, 212, 212);
|
||||
}
|
||||
|
||||
.manual-login-box
|
||||
{
|
||||
background-color: rgb(212, 212, 212);
|
||||
}
|
||||
|
||||
.hyperlink-button
|
||||
{
|
||||
color: rgb(68, 113, 229);
|
||||
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.hyperlink-button:hover
|
||||
{
|
||||
color: rgb(68, 133, 229);
|
||||
}
|
||||
|
||||
.hyperlink-button:active
|
||||
{
|
||||
color: rgb(68, 93, 229);
|
||||
}
|
||||
|
||||
.login-error-box > Image
|
||||
{
|
||||
--unity-image: resource("console.erroricon@2x");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 526a426fa6626ce498c3bd03c67737e4
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Login/Login_Light.uss
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,135 @@
|
||||
.popup-window-test
|
||||
{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.asset-store-logo
|
||||
{
|
||||
flex-shrink: 0;
|
||||
flex-grow: 1;
|
||||
|
||||
align-self: center;
|
||||
|
||||
height: 32px;
|
||||
width: 250px;
|
||||
|
||||
margin: 40px 0;
|
||||
|
||||
-unity-background-scale-mode: scale-to-fit;
|
||||
}
|
||||
|
||||
.login-button-cloud
|
||||
{
|
||||
align-self: center;
|
||||
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.login-description
|
||||
{
|
||||
-unity-text-align: middle-center;
|
||||
white-space: normal;
|
||||
|
||||
min-height: 24px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.or-label
|
||||
{
|
||||
align-self: center;
|
||||
|
||||
-unity-text-align: middle-center;
|
||||
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.manual-login-box
|
||||
{
|
||||
align-self: center;
|
||||
|
||||
width: 300px;
|
||||
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.input-box-login
|
||||
{
|
||||
align-self: center;
|
||||
|
||||
width: 250px;
|
||||
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.input-box-login > Label
|
||||
{
|
||||
-unity-text-align: upper-left;
|
||||
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.input-box-login > TextField
|
||||
{
|
||||
height: 20px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.login-button-cred
|
||||
{
|
||||
align-self: center;
|
||||
|
||||
width: 250px;
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
|
||||
.login-error-box
|
||||
{
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
|
||||
align-self: center;
|
||||
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
|
||||
margin: 5px 0 5px 1px;
|
||||
}
|
||||
|
||||
.login-error-box > Image
|
||||
{
|
||||
flex-direction: row;
|
||||
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
margin: 5px 10px;
|
||||
}
|
||||
|
||||
.login-error-box > Label
|
||||
{
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
-unity-text-align: middle-left;
|
||||
white-space: normal;
|
||||
|
||||
margin-right: 5px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.helper-button-box
|
||||
{
|
||||
flex-direction: row;
|
||||
|
||||
align-self: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 300px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hyperlink-button
|
||||
{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c6827adc58f1b146bde5b52679588d7
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 115
|
||||
packageName: Asset Store Publishing Tools
|
||||
packageVersion: 11.4.3
|
||||
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Login/Login_Main.uss
|
||||
uploadId: 681981
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fcc7023ae026264a9f05c37d0064aea
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be1208fb3d0e8174a8075d0078069e72
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,322 @@
|
||||
/* Uploading Window */
|
||||
|
||||
.assets-scroll-view
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.unity-scroller
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
.assets-scroll-view > .unity-scroll-view__content-viewport
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
|
||||
background-color: rgb(68, 68, 68);
|
||||
}
|
||||
|
||||
.assets-scroll-view > * > .unity-scroll-view__vertical-scroller
|
||||
{
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.spinner-image
|
||||
{
|
||||
--unity-image: resource("WaitSpin00");
|
||||
}
|
||||
|
||||
.top-tools-row
|
||||
{
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
|
||||
background-color: rgb(68, 68, 68);
|
||||
}
|
||||
|
||||
.sort-menu
|
||||
{
|
||||
color: rgb(238, 238, 238);
|
||||
|
||||
border-width: 1px;
|
||||
border-radius: 3px;
|
||||
border-color: rgb(36, 36, 36);
|
||||
|
||||
background-color: rgb(88, 88, 88);
|
||||
}
|
||||
|
||||
.sort-menu:hover
|
||||
{
|
||||
background-color: rgb(103, 103, 103);
|
||||
}
|
||||
|
||||
.sort-menu:active
|
||||
{
|
||||
background-color: rgb(73, 73, 73);
|
||||
}
|
||||
|
||||
.sorting-header
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
.sort-button
|
||||
{
|
||||
color: rgb(200, 200, 200);
|
||||
|
||||
border-width: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.sort-button:hover
|
||||
{
|
||||
color: rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
.sort-button:active
|
||||
{
|
||||
color: rgb(180, 180, 180);
|
||||
}
|
||||
|
||||
.sort-separator
|
||||
{
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.group-expander-box
|
||||
{
|
||||
border-width: 0;
|
||||
border-color: rgba(33, 33, 33, 0);
|
||||
|
||||
background-color: rgba(68, 68, 68, 0);
|
||||
}
|
||||
|
||||
.group-label
|
||||
{
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.info-box, .group-info-box
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
.info-box > Image, .group-info-box > Image
|
||||
{
|
||||
--unity-image: resource("console.infoicon@2x");
|
||||
}
|
||||
|
||||
.hyperlink-button
|
||||
{
|
||||
color: rgb(68, 113, 229);
|
||||
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.hyperlink-button:hover
|
||||
{
|
||||
color: rgb(68, 133, 229);
|
||||
}
|
||||
|
||||
.hyperlink-button:active
|
||||
{
|
||||
color: rgb(68, 93, 229);
|
||||
}
|
||||
|
||||
.group-content-box
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.foldout-box
|
||||
{
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
background-color: rgb(56, 56, 56);
|
||||
}
|
||||
|
||||
.foldout-box:hover
|
||||
{
|
||||
background-color: rgb(68, 68, 68);
|
||||
}
|
||||
|
||||
.foldout-box:active
|
||||
{
|
||||
background-color: rgb(48, 48, 48);
|
||||
}
|
||||
|
||||
.foldout-box-expanded
|
||||
{
|
||||
background-color: rgb(68, 68, 68);
|
||||
}
|
||||
|
||||
.open-in-browser-button
|
||||
{
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
background-image: url("../../../Icons/open-in-browser.png");
|
||||
|
||||
-unity-background-image-tint-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.open-in-browser-button:hover
|
||||
{
|
||||
-unity-background-image-tint-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.open-in-browser-button:active
|
||||
{
|
||||
-unity-background-image-tint-color: rgb(155, 155, 155);
|
||||
}
|
||||
|
||||
.expander
|
||||
{
|
||||
color: rgb(104, 104, 104);
|
||||
}
|
||||
|
||||
.package-image
|
||||
{
|
||||
border-radius: 5px;
|
||||
background-image: resource("Sprite Icon");
|
||||
}
|
||||
|
||||
.package-image-not-found
|
||||
{
|
||||
background-color: rgb(88, 88, 88);
|
||||
}
|
||||
|
||||
.asset-label
|
||||
{
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.asset-info
|
||||
{
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.category-label
|
||||
{
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.header-progress-bar > .unity-progress-bar__container > .unity-progress-bar__background
|
||||
{
|
||||
border-width: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.header-progress-bar > .unity-progress-bar__container > .unity-progress-bar__background > .unity-progress-bar__progress
|
||||
{
|
||||
background-image: none;
|
||||
background-color: rgb(33, 150, 243);
|
||||
}
|
||||
|
||||
.group-separator
|
||||
{
|
||||
background-color: rgb(48, 48, 48);
|
||||
}
|
||||
|
||||
/* Uploading functionality Window */
|
||||
|
||||
.functionality-box
|
||||
{
|
||||
background-color: rgb(68, 68, 68);
|
||||
}
|
||||
|
||||
.workflow-dropdown
|
||||
{
|
||||
color: rgb(238, 238, 238);
|
||||
|
||||
border-width: 1px;
|
||||
border-radius: 3px;
|
||||
border-color: rgb(36, 36, 36);
|
||||
|
||||
background-color: rgb(88, 88, 88);
|
||||
}
|
||||
|
||||
.workflow-dropdown:hover
|
||||
{
|
||||
background-color: rgb(103, 103, 103);
|
||||
}
|
||||
|
||||
.extra-packages-scroll-view
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
|
||||
background-color: rgb(58, 58, 58);
|
||||
}
|
||||
|
||||
.extra-packages-scroll-view > * > .unity-scroll-view__vertical-scroller
|
||||
{
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.filter-packages-dropdown
|
||||
{
|
||||
color: rgb(188, 188, 188);
|
||||
|
||||
border-width: 1px;
|
||||
border-radius: 3px;
|
||||
border-color: rgb(36, 36, 36);
|
||||
|
||||
background-color: rgb(88, 88, 88);
|
||||
}
|
||||
|
||||
.filter-packages-dropdown:hover
|
||||
{
|
||||
background-color: rgb(103, 103, 103);
|
||||
}
|
||||
|
||||
.filter-packages-dropdown:active
|
||||
{
|
||||
background-color: rgb(73, 73, 73);
|
||||
}
|
||||
|
||||
.no-packages-label
|
||||
{
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.packages-filtering-box
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
|
||||
background-color: rgb(58, 58, 58);
|
||||
}
|
||||
|
||||
.label-help-row > Image
|
||||
{
|
||||
--unity-image: resource("d__Help@2x");
|
||||
}
|
||||
|
||||
.upload-progress-bar > .unity-progress-bar__container > .unity-progress-bar__background
|
||||
{
|
||||
border-width: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.upload-progress-bar > .unity-progress-bar__container > .unity-progress-bar__background > .unity-progress-bar__progress
|
||||
{
|
||||
background-image: none;
|
||||
background-color: rgb(33, 150, 243);
|
||||
}
|
||||
|
||||
.export-button, .upload-button, .cancel-button
|
||||
{
|
||||
color: rgb(220, 220, 220);
|
||||
|
||||
border-width: 1px;
|
||||
border-color: rgb(33, 33, 33);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user