mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-17 18:40:33 +00:00
=> USE MRG RELEASE https://github.com/MrGadget1024/Mirror/releases/tag/ <=
Asset Store Checklist: - PRODUCTION GAME TEST (!) - remove Tests - remove StreamingAssets - Edgegap asset store version - try Unity 2020 - Add Preprocessordefines - Update Version.txt - Include Dependencies: newtonsoft.json
This commit is contained in:
parent
c1f71dffe9
commit
370ed8c049
16
Assets/Mirror/Hosting/Edgegap/Dockerfile
Normal file
16
Assets/Mirror/Hosting/Edgegap/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM ubuntu:bionic
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY Builds/EdgegapServer /root/build/
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
RUN chmod +x /root/build/ServerBuild
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates && \
|
||||
apt-get clean && \
|
||||
update-ca-certificates
|
||||
|
||||
ENTRYPOINT [ "/root/build/ServerBuild", "-batchmode", "-nographics"]
|
7
Assets/Mirror/Hosting/Edgegap/Dockerfile.meta
Normal file
7
Assets/Mirror/Hosting/Edgegap/Dockerfile.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78b80371aabba1d48aac39ec7ccfe7c5
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,9 +1,8 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG SERVER_BUILD_PATH=Builds/EdgegapServer
|
||||
|
||||
COPY ${SERVER_BUILD_PATH} /root/build/
|
||||
COPY Builds/EdgegapServer /root/build/
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
@ -14,4 +13,4 @@ RUN apt-get update && \
|
||||
apt-get clean && \
|
||||
update-ca-certificates
|
||||
|
||||
CMD [ "/root/build/ServerBuild", "-batchmode", "-nographics", "$UNITY_COMMANDLINE_ARGS"]
|
||||
ENTRYPOINT [ "/root/build/ServerBuild", "-batchmode", "-nographics"]
|
||||
|
@ -63,7 +63,7 @@ public enum LogLevel
|
||||
public const string EDGEGAP_GET_A_TOKEN_URL = "https://app.edgegap.com/?oneClick=true";
|
||||
public const string EDGEGAP_ADD_MORE_GAME_SERVERS_URL = "https://edgegap.com/resources/contact";
|
||||
public const string EDGEGAP_DOC_BTN_HOW_TO_LOGIN_VIA_CLI_URL = "https://docs.edgegap.com/docs/container/edgegap-container-registry/#getting-your-credentials";
|
||||
private const string DEFAULT_UTM_SOURCE_TAG = "partner_mirror_source_unity";
|
||||
private const string DEFAULT_UTM_SOURCE_TAG = "partner_mirror_assetstore_unity";
|
||||
private const string DEFAULT_UTM_MEDIUM_TAG = "servers_quickstart_plugin";
|
||||
private const string DEFAULT_UTM_CONTENT_TAG = "plugin_button";
|
||||
public const string DEFAULT_UTM_TAGS = "utm_source=" + DEFAULT_UTM_SOURCE_TAG +
|
||||
|
@ -112,8 +112,7 @@ public class EdgegapWindowV2 : EditorWindow
|
||||
Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this)));
|
||||
// END MIRROR CHANGE
|
||||
internal string ProjectRootPath => Directory.GetCurrentDirectory();
|
||||
internal string ThisScriptPath => Directory.GetFiles(ProjectRootPath, GetType().Name + ".cs", SearchOption.AllDirectories)[0];
|
||||
internal string DockerFilePath => $"{Directory.GetParent(ThisScriptPath).FullName}{Path.DirectorySeparatorChar}Dockerfile";
|
||||
internal string DockerFilePath => $"{Directory.GetParent(Directory.GetFiles(ProjectRootPath, GetType().Name + ".cs", SearchOption.AllDirectories)[0]).FullName}{Path.DirectorySeparatorChar}Dockerfile";
|
||||
|
||||
[MenuItem("Tools/Edgegap Hosting")] // MIRROR CHANGE: more obvious title
|
||||
public static void ShowEdgegapToolWindow()
|
||||
@ -546,11 +545,7 @@ private void debugEnableAllGroups()
|
||||
_containerCustomRegistryWrapper.SetEnabled(true);
|
||||
}
|
||||
|
||||
private void onApiTokenVerifyBtnClick()
|
||||
{
|
||||
_ = verifyApiTokenGetRegistryCredsAsync();
|
||||
_ = checkForUpdates();
|
||||
}
|
||||
private void onApiTokenVerifyBtnClick() => _ = verifyApiTokenGetRegistryCredsAsync();
|
||||
private void onApiTokenGetBtnClick() => openGetApiTokenWebsite();
|
||||
|
||||
/// <summary>Process UI + validation before/after API logic</summary>
|
||||
@ -920,24 +915,6 @@ private string getBase64StrFromSprite(Sprite sprite, int maxKbSize = 200)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch latest github release and compare with local package.json version
|
||||
/// </summary>
|
||||
private async Task checkForUpdates()
|
||||
{
|
||||
// get local package.json version
|
||||
DirectoryInfo thisScriptDir = new DirectoryInfo(ThisScriptPath);
|
||||
PackageJSON local = PackageJSON.PackageJSONFromJSON($"{thisScriptDir.Parent.Parent.ToString()}{Path.DirectorySeparatorChar}package.json");
|
||||
|
||||
// get latest release from github repository
|
||||
string releaseJSON = await GithubRelease.GithubReleaseFromAPI();
|
||||
GithubRelease latest = GithubRelease.GithubReleaseFromJSON(releaseJSON);
|
||||
|
||||
if (local.version != latest.name) {
|
||||
Debug.LogWarning($"Please update your Edgegap Quickstart plugin - local version `{local.version}` < latest version `{latest.name}`. See https://github.com/edgegap/edgegap-unity-plugin.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies token => apps/container groups -> gets registry creds (if any).
|
||||
/// TODO: UX - Show loading spinner.
|
||||
|
@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Edgegap.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public class GithubRelease
|
||||
{
|
||||
public string name;
|
||||
|
||||
public static async Task<string> GithubReleaseFromAPI()
|
||||
{
|
||||
HttpClient http = new HttpClient();
|
||||
http.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json");
|
||||
http.DefaultRequestHeaders.Add("X-GitHub-Api-Version", "2022-11-28");
|
||||
http.DefaultRequestHeaders.Add("User-Agent", "Unity");
|
||||
http.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
HttpResponseMessage response = await http.GetAsync("https://api.github.com/repos/edgegap/edgegap-unity-plugin/releases/latest").ConfigureAwait(false);
|
||||
|
||||
return response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : "{}";
|
||||
}
|
||||
|
||||
public static GithubRelease GithubReleaseFromJSON(string json)
|
||||
{
|
||||
return JsonUtility.FromJson<GithubRelease>(json);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Edgegap.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public class PackageJSON
|
||||
{
|
||||
[Serializable]
|
||||
public struct Author
|
||||
{
|
||||
public string name;
|
||||
public string email;
|
||||
public string url;
|
||||
}
|
||||
|
||||
public string name;
|
||||
public string version;
|
||||
public string displayName;
|
||||
public string description;
|
||||
public string unity;
|
||||
public string unityRelease;
|
||||
public string documentationUrl;
|
||||
public string changelogUrl;
|
||||
public string licensesUrl;
|
||||
|
||||
public Author author;
|
||||
|
||||
// dependencies omitted since JsonUtility doesn't support dictionaries
|
||||
|
||||
public static PackageJSON PackageJSONFromJSON(string path)
|
||||
{
|
||||
return JsonUtility.FromJson<PackageJSON>(File.ReadAllText(path));
|
||||
}
|
||||
}
|
||||
}
|
@ -42,25 +42,15 @@ This plugin does not need to be included in your builds, as it's only a developm
|
||||
## Other sources
|
||||
|
||||
The only other official distribution channels for this plugin are:
|
||||
- [Unity Asset Store package](https://assetstore.unity.com/packages/tools/network/edgegap-game-server-hosting-212563)
|
||||
- [Mirror Networking source](https://github.com/MirrorNetworking/Mirror)
|
||||
- [Mirror Networking free package](https://assetstore.unity.com/packages/tools/network/mirror-129321)
|
||||
- [Mirror Networking LTS package](https://assetstore.unity.com/packages/tools/network/mirror-lts-102631)
|
||||
- [Fish Networking source](https://github.com/FirstGearGames/FishNet)
|
||||
- [Fish Networking free package](https://assetstore.unity.com/packages/tools/network/fishnet-networking-evolved-207815)
|
||||
- [Fish Networking Pro package](https://assetstore.unity.com/packages/tools/network/fishnet-pro-networking-evolved-287711)
|
||||
- [Mirror Networking samples](https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide)
|
||||
- [Fish Networking samples](https://fish-networking.gitbook.io/docs/manual/server-hosting/edgegap-official-partner)
|
||||
|
||||
**WARNING!** The [Edgegap plugin published on Unity Asset Store](https://assetstore.unity.com/packages/tools/network/edgegap-game-server-hosting-212563) is outdated and not supported anymore. If you've previously installed our plugin by another method than described above, please remove any Edgegap files or dependencies related before updating your plugin using the git URL.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once you have it, check for **Tools** -> **Edgegap Hosting** in Unity's top menu.
|
||||
|
||||
### Usage requirements
|
||||
|
||||
To take full advantage of our Unity plugin's build features, you will need to:
|
||||
- [Create an Edgegap Free Tier account](https://app.edgegap.com/auth/register),
|
||||
- [Install Docker Desktop](https://www.docker.com/products/docker-desktop/) (or Docker CLI),
|
||||
- Install Unity Linux Build Support modules for Unity.
|
||||
|
||||
From here, we recommend following our [Unity Plugin Guide](https://docs.edgegap.com/docs/tools-and-integrations/unity-plugin-guide) to get your first dedicated server deployed.
|
||||
|
||||
### Update the Plugin in Unity
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.edgegap.unity-servers-plugin",
|
||||
"version": "2.3.1",
|
||||
"version": "1.0.8",
|
||||
"displayName": "Edgegap Servers Quickstart",
|
||||
"description": "Get started quickly with Edgegap Dedicated Server hosting.",
|
||||
"unity": "2021.3",
|
||||
|
123
Assets/Mirror/Notice.txt
Normal file
123
Assets/Mirror/Notice.txt
Normal file
@ -0,0 +1,123 @@
|
||||
This asset is governed by the Asset Store EULA; however, the following
|
||||
components are governed by the licenses indicated below:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Mirror
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015, Unity Technologies
|
||||
Copyright (c) 2019, vis2k, Paul and Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
kcp2k
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 limpo1989
|
||||
Copyright (c) 2020 Paul Pacheco
|
||||
Copyright (c) 2020 Lymdun
|
||||
Copyright (c) 2020 vis2k
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Mono.Cecil
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
Copyright (c) 2008 - 2011 Novell, Inc.
|
||||
|
||||
https://github.com/jbevain/cecil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Telepathy
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018, vis2k
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
7
Assets/Mirror/Notice.txt.meta
Normal file
7
Assets/Mirror/Notice.txt.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a7b49ad188074707b004e7bb8824857
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1
Assets/Mirror/Version.txt
Normal file
1
Assets/Mirror/Version.txt
Normal file
@ -0,0 +1 @@
|
||||
93.0.0
|
7
Assets/Mirror/Version.txt.meta
Normal file
7
Assets/Mirror/Version.txt.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6b1f72568a9340178b4c34608fbdbc3
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
229
Packages/com.unity.asset-store-tools/CHANGELOG.md
Normal file
229
Packages/com.unity.asset-store-tools/CHANGELOG.md
Normal file
@ -0,0 +1,229 @@
|
||||
# Changelog
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
## [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
|
7
Packages/com.unity.asset-store-tools/CHANGELOG.md.meta
Normal file
7
Packages/com.unity.asset-store-tools/CHANGELOG.md.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06607220dbd46414e8f66bf9c5e3eb79
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
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,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.AssetStoreTools.Editor.Tests.asmdef")]
|
||||
[assembly: InternalsVisibleTo("ab-builder")]
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ef365e2acd5a5044a4a14fa83c7d9b9
|
||||
guid: ccfd7faf75ab3c74a98015e772288d86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -0,0 +1,53 @@
|
||||
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/Settings", false, 50)]
|
||||
public static void OpenSettings()
|
||||
{
|
||||
ASToolsPreferencesProvider.OpenSettings();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8abfa078881a944a81ac3bcc8e5e0f4
|
||||
guid: 6060eef206afc844caaa1732538e8890
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1057a05baaa45942808573065c02a03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce99a618d1e211444b53f18bb3444f75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 399b115514c617d47a00b8c0a5e430fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,120 @@
|
||||
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...";
|
||||
|
||||
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 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;
|
||||
|
||||
// Attempt retrieving guid from the Asset Database first
|
||||
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
if (guid != string.Empty)
|
||||
return guid;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
protected virtual void PostExportCleanup()
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52ef11a59e545544fafaa99a5fa6cce9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,336 @@
|
||||
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 const string ManifestJsonPath = "Packages/manifest.json";
|
||||
|
||||
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 manifestJson = GetPackageManifestJson();
|
||||
var allDependenciesDict = manifestJson["dependencies"].AsDict();
|
||||
|
||||
var allLocalPackages = PackageUtility.GetAllLocalPackages();
|
||||
List<string> allPackagesList = new List<string>(allDependenciesDict.Keys);
|
||||
|
||||
foreach (var package in allPackagesList)
|
||||
{
|
||||
if (!_exportSettings.Dependencies.Any(x => x == package))
|
||||
{
|
||||
allDependenciesDict.Remove(package);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!allLocalPackages.Select(x => x.name).Contains(package))
|
||||
continue;
|
||||
|
||||
allDependenciesDict.Remove(package);
|
||||
UnityEngine.Debug.LogWarning($"Found an unsupported Package Manager dependency: {package}.\n" +
|
||||
"This dependency is not supported in the project's manifest.json and will be skipped.");
|
||||
}
|
||||
|
||||
if (allDependenciesDict.Count == 0)
|
||||
return;
|
||||
|
||||
var tempManifestDirectoryPath = $"{tempOutputPath}/packagemanagermanifest";
|
||||
Directory.CreateDirectory(tempManifestDirectoryPath);
|
||||
var tempManifestFilePath = $"{tempManifestDirectoryPath}/asset";
|
||||
|
||||
File.WriteAllText(tempManifestFilePath, manifestJson.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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonValue GetPackageManifestJson()
|
||||
{
|
||||
string manifestJsonString = File.ReadAllText(ManifestJsonPath);
|
||||
JSONParser parser = new JSONParser(manifestJsonString);
|
||||
var manifestJson = parser.Parse();
|
||||
|
||||
return manifestJson;
|
||||
}
|
||||
|
||||
protected override void PostExportCleanup()
|
||||
{
|
||||
base.PostExportCleanup();
|
||||
|
||||
var tempExportPath = GetTemporaryExportPath();
|
||||
if (Directory.Exists(tempExportPath))
|
||||
Directory.Delete(tempExportPath, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32f50122a1b2bc2428cf8fba321e2414
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
if (string.IsNullOrEmpty(guid))
|
||||
continue;
|
||||
|
||||
guids.Add(guid);
|
||||
if (onlyFolders == true && (File.Exists(assetPath)))
|
||||
onlyFolders = false;
|
||||
}
|
||||
}
|
||||
|
||||
return guids.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3200380dff2de104aa79620e4b41dc70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c183be512f4485d40a3437fabd6c81cf
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9722d52df16aab742b26fe301782c74c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,226 @@
|
||||
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";
|
||||
|
||||
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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b5319699cc84194a9a768ad33b86c21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,147 @@
|
||||
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:
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,128 @@
|
||||
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:
|
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
@ -0,0 +1,128 @@
|
||||
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:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15b24ad8f9d236249910fb8eef1e30ea
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,774 @@
|
||||
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 = "V6.3.0";
|
||||
|
||||
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
|
||||
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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 684fca3fffd79d944a32d9b3adbfc007
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e3cae7082463da41b807724242fd617
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,46 @@
|
||||
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()
|
||||
{
|
||||
var result = EditorAnalytics.RegisterEventWithLimit(EventName, MaxEventsPerHour, MaxNumberOfElements, VendorKey, VersionId);
|
||||
return result == AnalyticsResult.Ok;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct AnalyticsData
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static void SendUploadingEvent(AnalyticsData data)
|
||||
{
|
||||
if (!EditorAnalytics.enabled)
|
||||
return;
|
||||
|
||||
if (!EnableAnalytics())
|
||||
return;
|
||||
EditorAnalytics.SendEventWithLimit(EventName, data, VersionId);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1095145789a64767a6add837eea19786
|
||||
timeCreated: 1658832954
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 601fdada4edc5b94eb83a21d1a01ed26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8157930875be4972a48c870a3d1e8ff1
|
||||
timeCreated: 1658919930
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 188361b01a1450145a6fc2a7aa0a3a3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4a93170d5bda304895e5feaf6e34aa8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 607cf9e3fb4a49839f2e6a82e0d8d535
|
||||
timeCreated: 1651220955
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd683831688cd414f8cc9cd352689b4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,699 @@
|
||||
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 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 (!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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c2205d924ccc4a458abc3d370143740
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52c6e85acbb00794686387cd876ffc81
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 118376dcb318c4341b1df6773e3d8d4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33a72e7596565c749a495b4213579a67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cee024b6c5a4407780a9f8677f7a6e97
|
||||
timeCreated: 1654674025
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c85e58f7d4786a40a140c67b0d124a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,627 @@
|
||||
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 update validation paths
|
||||
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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8bafd0b9c5b47bc985d17a18fc07978
|
||||
timeCreated: 1654089523
|
@ -0,0 +1,414 @@
|
||||
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 update validation paths
|
||||
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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5472a1f8e8745779ee82d18b63ec19c
|
||||
timeCreated: 1654112492
|
@ -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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9e30749a5784d18a1a2644cc44dda29
|
||||
timeCreated: 1654112475
|
@ -0,0 +1,180 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f84b47b78aca74c4db1e9b753d41422f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e5fee0cad7655f458d9b600f4ae6d02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24e1d75365cc42a09e5c5daec071813e
|
||||
timeCreated: 1658918833
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 582639e8ed53f37499d12efcb4cde2c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d00412342a1b6c943b91cc06edad1202
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9e3b7e7e1851a140b1a5c23270ded34
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
@ -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;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user