first commit

This commit is contained in:
Kirill Chikalin
2024-11-16 13:20:07 +03:00
commit a3072a3693
538 changed files with 108153 additions and 0 deletions

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d4a93170d5bda304895e5feaf6e34aa8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Login/LoginWindow.cs
uploadId: 681981

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 607cf9e3fb4a49839f2e6a82e0d8d535
timeCreated: 1651220955
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/AllPackageView.cs
uploadId: 681981

View File

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

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: dd683831688cd414f8cc9cd352689b4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/PackageGroup.cs
uploadId: 681981

View File

@@ -0,0 +1,720 @@
using AssetStoreTools.Utility.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System.Text.RegularExpressions;
using AssetStoreTools.Utility;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using AssetStoreTools.Exporter;
using AssetStoreTools.Uploader.Data;
using AssetStoreTools.Uploader.Utility;
namespace AssetStoreTools.Uploader.UIElements
{
internal class PackageView : VisualElement
{
public string PackageId => _packageData.Id;
public string VersionId => _packageData.VersionId;
public string PackageName => _packageData.Name;
public string Status => _packageData.Status;
public string Category => _packageData.Category;
public string LastUpdatedDate => FormatDate(_packageData.LastDate);
public string LastUpdatedSize => FormatSize(_packageData.LastSize);
public bool IsCompleteProject => _packageData.IsCompleteProject;
public string LastUploadedPath => _packageData.LastUploadedPath;
public string LastUploadedGuid => _packageData.LastUploadedGuid;
public string SearchableText { get; private set; }
private PackageData _packageData;
// Unexpanded state dynamic elements
private Button _foldoutBox;
private Label _expanderLabel;
private Label _assetLabel;
private Label _lastDateSizeLabel;
private Button _openInBrowserButton;
// Expanded state dynamic elements
private VisualElement _functionsBox;
private VisualElement _exportAndUploadContainer;
private VisualElement _uploadProgressContainer;
private Button _exportButton;
private Button _uploadButton;
private Button _cancelUploadButton;
private ProgressBar _uploadProgressBarFlow;
private ProgressBar _uploadProgressBarHeader;
private VisualElement _uploadProgressFlowBg;
private VisualElement _uploadProgressHeaderBg;
private bool _expanded;
public Action<PackageView> OnPackageSelection;
private VisualElement _workflowSelectionBox;
private UploadWorkflowView _activeWorkflowElement;
private Dictionary<string, UploadWorkflowView> _uploadWorkflows;
public PackageView(PackageData packageData)
{
UpdateDataValues(packageData);
SetupPackageElement();
}
public void UpdateDataValues(PackageData packageData)
{
_packageData = packageData;
SearchableText = $"{PackageName} {Category}".ToLower();
if (_foldoutBox == null)
return;
_assetLabel.text = PackageName;
_lastDateSizeLabel.text = $"{Category} | {LastUpdatedSize} | {LastUpdatedDate}";
if (_uploadWorkflows != null && _uploadWorkflows.ContainsKey(FolderUploadWorkflowView.WorkflowName))
((FolderUploadWorkflowView) _uploadWorkflows[FolderUploadWorkflowView.WorkflowName]).SetCompleteProject(packageData.IsCompleteProject);
if (Status == "draft")
return;
ResetPostUpload();
SetupExpander();
}
public void ShowFunctions(bool show)
{
if (_functionsBox == null)
{
if (show)
SetupFunctionsElement();
else
return;
}
if (show == _expanded)
return;
_expanded = show;
_expanderLabel.text = !_expanded ? "►" : "▼";
if (_expanded)
_foldoutBox.AddToClassList("foldout-box-expanded");
else
_foldoutBox.RemoveFromClassList("foldout-box-expanded");
if (_functionsBox != null)
_functionsBox.style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
}
private void SetupPackageElement()
{
AddToClassList("full-package-box");
_foldoutBox = new Button {name = "Package"};
_foldoutBox.AddToClassList("foldout-box");
// Expander, Icon and Asset Label
VisualElement foldoutBoxInfo = new VisualElement { name = "foldoutBoxInfo" };
foldoutBoxInfo.AddToClassList("foldout-box-info");
VisualElement labelExpanderRow = new VisualElement { name = "labelExpanderRow" };
labelExpanderRow.AddToClassList("expander-label-row");
_expanderLabel = new Label { name = "ExpanderLabel", text = "►" };
_expanderLabel.AddToClassList("expander");
Image assetImage = new Image { name = "AssetImage" };
assetImage.AddToClassList("package-image");
VisualElement assetLabelInfoBox = new VisualElement { name = "assetLabelInfoBox" };
assetLabelInfoBox.AddToClassList("asset-label-info-box");
_assetLabel = new Label { name = "AssetLabel", text = PackageName };
_assetLabel.AddToClassList("asset-label");
_lastDateSizeLabel = new Label {name = "AssetInfoLabel", text = $"{Category} | {LastUpdatedSize} | {LastUpdatedDate}"};
_lastDateSizeLabel.AddToClassList("asset-info");
assetLabelInfoBox.Add(_assetLabel);
assetLabelInfoBox.Add(_lastDateSizeLabel);
labelExpanderRow.Add(_expanderLabel);
labelExpanderRow.Add(assetImage);
labelExpanderRow.Add(assetLabelInfoBox);
_openInBrowserButton = new Button
{
name = "OpenInBrowserButton",
tooltip = "View your package in the Publishing Portal."
};
_openInBrowserButton.AddToClassList("open-in-browser-button");
// Header Progress bar
_uploadProgressBarHeader = new ProgressBar { name = "HeaderProgressBar" };
_uploadProgressBarHeader.AddToClassList("header-progress-bar");
_uploadProgressBarHeader.style.display = DisplayStyle.None;
_uploadProgressHeaderBg = _uploadProgressBarHeader.Q<VisualElement>(className:"unity-progress-bar__progress");
// Connect it all
foldoutBoxInfo.Add(labelExpanderRow);
foldoutBoxInfo.Add(_openInBrowserButton);
_foldoutBox.Add(foldoutBoxInfo);
_foldoutBox.Add(_uploadProgressBarHeader);
Add(_foldoutBox);
SetupExpander();
}
private void SetupExpander()
{
if (_foldoutBox != null)
{
_foldoutBox.clickable = null;
// If not draft - hide expander, open a listing page on click
if (Status != "draft")
{
_expanderLabel.style.display = DisplayStyle.None;
_foldoutBox.clicked += () =>
{
Application.OpenURL($"https://publisher.unity.com/packages/{VersionId}/edit/upload");
};
}
else
{
// Else open functions box
_foldoutBox.clicked += () =>
{
OnPackageSelection?.Invoke(this);
ShowFunctions(!_expanded);
};
}
}
if (_openInBrowserButton != null)
{
_openInBrowserButton.clickable = null;
_openInBrowserButton.clicked += () =>
{
Application.OpenURL($"https://publisher.unity.com/packages/{VersionId}/edit/upload");
};
}
}
private void SetupFunctionsElement()
{
_functionsBox = new VisualElement { name = "FunctionalityBox" };
_functionsBox.AddToClassList("functionality-box");
_functionsBox.style.display = DisplayStyle.None;
// Validation and uploading boxes
var uploadingWorkflow = ConstructUploadingWorkflow();
_functionsBox.Add(uploadingWorkflow);
Add(_functionsBox);
}
private VisualElement ConstructUploadingWorkflow()
{
// Upload Box
VisualElement uploadBox = new VisualElement { name = "UploadBox" };
uploadBox.AddToClassList("upload-box");
var folderUploadWorkflow = FolderUploadWorkflowView.Create(Category, IsCompleteProject, SerializeWorkflowSelections);
var unitypackageUploadWorkflow = UnityPackageUploadWorkflowView.Create(Category, SerializeWorkflowSelections);
var hybridPackageUploadWorkflow = HybridPackageUploadWorkflowView.Create(Category, SerializeWorkflowSelections);
// Workflow selection
_workflowSelectionBox = new VisualElement();
_workflowSelectionBox.AddToClassList("selection-box-row");
VisualElement labelHelpRow = new VisualElement();
labelHelpRow.AddToClassList("label-help-row");
Label workflowLabel = new Label { text = "Upload type" };
Image workflowLabelTooltip = new Image
{
tooltip = "Select what content you are uploading to the Asset Store"
+ "\n\n• From Assets Folder - content located within the project's 'Assets' folder or one of its subfolders"
+ "\n\n• Pre-exported .unitypackage - content that has already been compressed into a .unitypackage file"
#if UNITY_ASTOOLS_EXPERIMENTAL
+ "\n\n• Local UPM Package - content that is located within the project's 'Packages' folder. Only embedded and local packages are supported"
#endif
};
labelHelpRow.Add(workflowLabel);
labelHelpRow.Add(workflowLabelTooltip);
var flowDrop = new ToolbarMenu();
flowDrop.menu.AppendAction(FolderUploadWorkflowView.WorkflowDisplayName, _ => { SetActiveWorkflowElement(folderUploadWorkflow, flowDrop); });
flowDrop.menu.AppendAction(UnityPackageUploadWorkflowView.WorkflowDisplayName, _ => { SetActiveWorkflowElement(unitypackageUploadWorkflow, flowDrop); });
#if UNITY_ASTOOLS_EXPERIMENTAL
flowDrop.menu.AppendAction(HybridPackageUploadWorkflowView.WorkflowDisplayName, _ => { SetActiveWorkflowElement(hybridPackageUploadWorkflow, flowDrop); });
#endif // UNITY_ASTOOLS_EXPERIMENTAL
flowDrop.AddToClassList("workflow-dropdown");
_workflowSelectionBox.Add(labelHelpRow);
_workflowSelectionBox.Add(flowDrop);
uploadBox.Add(_workflowSelectionBox);
_uploadWorkflows = new Dictionary<string, UploadWorkflowView>
{
{FolderUploadWorkflowView.WorkflowName, folderUploadWorkflow},
{UnityPackageUploadWorkflowView.WorkflowName, unitypackageUploadWorkflow},
{HybridPackageUploadWorkflowView.WorkflowName, hybridPackageUploadWorkflow}
};
foreach (var kvp in _uploadWorkflows)
uploadBox.Add(kvp.Value);
var progressUploadBox = SetupProgressUploadBox();
uploadBox.Add(progressUploadBox);
DeserializeWorkflowSelections(flowDrop);
return uploadBox;
}
private void SerializeWorkflowSelections()
{
ASDebug.Log("Serializing workflow selections");
var json = JsonValue.NewDict();
// Active workflow
var activeWorkflow = JsonValue.NewString(_activeWorkflowElement.Name);
json["ActiveWorkflow"] = activeWorkflow;
// Workflow Selections
foreach(var kvp in _uploadWorkflows)
json[kvp.Key] = kvp.Value.SerializeWorkflow();
AssetStoreCache.CacheUploadSelections(PackageId, json);
}
private void DeserializeWorkflowSelections(ToolbarMenu activeFlowMenu)
{
AssetStoreCache.GetCachedUploadSelections(PackageId, out JsonValue cachedSelections);
// Individual workflow selections
foreach (var kvp in _uploadWorkflows)
{
if (cachedSelections.ContainsKey(kvp.Key))
kvp.Value.LoadSerializedWorkflow(cachedSelections[kvp.Key], LastUploadedPath, LastUploadedGuid);
else
kvp.Value.LoadSerializedWorkflowFallback(LastUploadedPath, LastUploadedGuid);
}
// Active workflow selection
if (!cachedSelections.ContainsKey("ActiveWorkflow"))
{
// Set default to folder workflow
SetActiveWorkflowElement(_uploadWorkflows[FolderUploadWorkflowView.WorkflowName], activeFlowMenu);
return;
}
var serializedWorkflow = cachedSelections["ActiveWorkflow"].AsString();
SetActiveWorkflowElement(_uploadWorkflows[serializedWorkflow], activeFlowMenu);
}
private void SetActiveWorkflowElement(UploadWorkflowView newActiveWorkflowElement, ToolbarMenu activeFlowMenu)
{
if (_activeWorkflowElement != null)
_activeWorkflowElement.style.display = DisplayStyle.None;
_activeWorkflowElement = newActiveWorkflowElement;
_activeWorkflowElement.style.display = DisplayStyle.Flex;
activeFlowMenu.text = newActiveWorkflowElement.DisplayName;
UpdateActionButtons();
SerializeWorkflowSelections();
}
private void UpdateActionButtons()
{
switch (_activeWorkflowElement)
{
case UnityPackageUploadWorkflowView _:
_exportButton.style.display = DisplayStyle.None;
_uploadButton.style.marginLeft = 0f;
_uploadButton.text = "Upload";
break;
default:
_exportButton.style.display = DisplayStyle.Flex;
_uploadButton.style.marginLeft = 5f;
_uploadButton.text = "Export and Upload";
break;
}
}
private VisualElement SetupProgressUploadBox()
{
var progressUploadBox = new VisualElement();
progressUploadBox.AddToClassList("progress-upload-box");
_exportAndUploadContainer = new VisualElement();
_exportAndUploadContainer.AddToClassList("export-and-upload-container");
_exportButton = new Button(ExportWithoutUploading) { name = "ExportButton", text = "Export" };
_exportButton.AddToClassList("export-button");
_uploadButton = new Button(PreparePackageUpload) { name = "UploadButton", text = "Export and Upload" };
_uploadButton.AddToClassList("upload-button");
_exportAndUploadContainer.Add(_exportButton);
_exportAndUploadContainer.Add(_uploadButton);
_uploadProgressContainer = new VisualElement();
_uploadProgressContainer.AddToClassList("upload-progress-container");
_uploadProgressContainer.style.display = DisplayStyle.None;
_uploadProgressBarFlow = new ProgressBar { name = "UploadProgressBar" };
_uploadProgressBarFlow.AddToClassList("upload-progress-bar");
_uploadProgressFlowBg = _uploadProgressBarFlow.Q<VisualElement>(className: "unity-progress-bar__progress");
_cancelUploadButton = new Button() { name = "CancelButton", text = "Cancel" };
_cancelUploadButton.AddToClassList("cancel-button");
_uploadProgressContainer.Add(_uploadProgressBarFlow);
_uploadProgressContainer.Add(_cancelUploadButton);
progressUploadBox.Add(_exportAndUploadContainer);
progressUploadBox.Add(_uploadProgressContainer);
return progressUploadBox;
}
private string FormatSize(string size)
{
if (string.IsNullOrEmpty(size))
return "0.00 MB";
float.TryParse(size, out var sizeBytes);
return $"{sizeBytes / (1024f * 1024f):0.00} MB";
}
private string FormatDate(string date)
{
DateTime dt = DateTime.Parse(date);
return dt.Date.ToString("yyyy-MM-dd");
}
#region Package Uploading
private async void ExportWithoutUploading()
{
var paths = _activeWorkflowElement.GetAllExportPaths();
if(paths.Length == 0)
{
EditorUtility.DisplayDialog("Exporting failed", "No path was selected. Please " +
"select a path and try again.", "OK");
return;
}
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
var packageNameStripped = Regex.Replace(PackageName, "[^a-zA-Z0-9]", "");
var outputPath = EditorUtility.SaveFilePanel("Export Package", rootProjectPath,
$"{packageNameStripped}-{DateTime.Now:yyyy-dd-M--HH-mm-ss}", "unitypackage");
if (string.IsNullOrEmpty(outputPath))
return;
var exportResult = await ExportPackage(outputPath);
if (!exportResult.Success)
Debug.LogError($"Package exporting failed: {exportResult.Error}");
else
Debug.Log($"Package exported to '{Path.GetFullPath(exportResult.ExportedPath).Replace("\\", "/")}'");
}
private async Task<ExportResult> ExportPackage(string outputPath)
{
var exportResult = await _activeWorkflowElement.ExportPackage(outputPath, IsCompleteProject);
return exportResult;
}
private bool ValidatePackageSize(string packagePath)
{
long packageSize = new FileInfo(packagePath).Length;
long packageSizeLimit = AssetStoreUploader.MaxPackageSizeBytes;
float packageSizeInGB = packageSize / (1024f * 1024f * 1024f);
float maxPackageSizeInGB = packageSizeLimit / (1024f * 1024f * 1024f);
if (packageSizeInGB - maxPackageSizeInGB < 0.1f)
return true;
var message = $"The size of your package ({packageSizeInGB:0.#} GB) exceeds the maximum allowed package size of {maxPackageSizeInGB:0.#} GB.\n\n" +
$"Please reduce the size of your package.";
EditorUtility.DisplayDialog("Asset Store Tools", message, "OK");
return false;
}
private bool ValidateUnityVersionsForUpload()
{
if (!AssetStoreUploader.ShowPackageVersionDialog)
return true;
EditorUtility.DisplayProgressBar("Preparing...", "Checking version compatibility", 0.4f);
var versions = AssetStoreAPI.GetPackageUploadedVersions(PackageId, VersionId);
EditorUtility.ClearProgressBar();
if (versions.Any(x => string.Compare(x, AssetStoreUploader.MinRequiredPackageVersion, StringComparison.Ordinal) >= 0))
return true;
var result = EditorUtility.DisplayDialogComplex("Asset Store Tools", $"You may upload this package, but you will need to add a package using Unity version {AssetStoreUploader.MinRequiredPackageVersion} " +
"or higher to be able to submit a new asset", "Upload", "Cancel", "Upload and do not display this again");
switch (result)
{
case 1:
return false;
case 2:
AssetStoreUploader.ShowPackageVersionDialog = false;
break;
}
return true;
}
private async void PreparePackageUpload()
{
var paths = _activeWorkflowElement.GetAllExportPaths();
if (paths.Length == 0)
{
EditorUtility.DisplayDialog("Uploading failed", "No path was selected. Please " +
"select a path and try again.", "OK");
return;
}
var packageNameStripped = Regex.Replace(PackageName, "[^a-zA-Z0-9]", "");
var outputPath = $"Temp/{packageNameStripped}-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.unitypackage";
var exportResult = await ExportPackage(outputPath);
if (!exportResult.Success)
{
Debug.LogError($"Package exporting failed: {exportResult.Error}");
return;
}
if (!ValidatePackageSize(exportResult.ExportedPath))
return;
if (!ValidateUnityVersionsForUpload())
return;
var localPackageGuid = _activeWorkflowElement.GetLocalPackageGuid();
var localPackagePath = _activeWorkflowElement.GetLocalPackagePath();
var localProjectPath = _activeWorkflowElement.GetLocalProjectPath();
BeginPackageUpload(exportResult.ExportedPath, localPackageGuid, localPackagePath, localProjectPath);
}
private async void BeginPackageUpload(string exportedPackagePath, string packageGuid, string packagePath, string projectPath)
{
// Configure the UI
// Disable Active Workflow
EnableWorkflowElements(false);
// Progress bar
_exportAndUploadContainer.style.display = DisplayStyle.None;
_uploadProgressContainer.style.display = DisplayStyle.Flex;
// Configure the upload cancel button
_cancelUploadButton.clickable = null;
_cancelUploadButton.clicked += () => AssetStoreAPI.AbortPackageUpload(VersionId);
_cancelUploadButton.text = "Cancel";
// Set up upload progress tracking for the unexpanded package progress bar
EditorApplication.update += OnPackageUploadProgressHeader;
// Set up upload progress tracking for the expanded package progress bar
EditorApplication.update += OnPackageUploadProgressContent;
// Set up base analytics data
var analyticsData = ConstructAnalyticsData(exportedPackagePath);
// Start tracking uploading time
var watch = System.Diagnostics.Stopwatch.StartNew(); // Debugging
// Start uploading the package
var result = await AssetStoreAPI.UploadPackageAsync(VersionId, PackageName, exportedPackagePath, packageGuid, packagePath, projectPath);
watch.Stop();
analyticsData.TimeTaken = watch.Elapsed.TotalSeconds;
switch (result.Status)
{
case PackageUploadResult.UploadStatus.Success:
analyticsData.UploadFinishedReason = "Success";
ASDebug.Log($"Finished uploading, time taken: {watch.Elapsed.TotalSeconds} seconds");
await OnPackageUploadSuccess();
break;
case PackageUploadResult.UploadStatus.Cancelled:
analyticsData.UploadFinishedReason = "Cancelled";
ASDebug.Log($"Uploading cancelled, time taken: {watch.Elapsed.TotalSeconds} seconds");
break;
case PackageUploadResult.UploadStatus.Fail:
analyticsData.UploadFinishedReason = result.Error.Exception.ToString();
OnPackageUploadFail(result.Error);
break;
case PackageUploadResult.UploadStatus.ResponseTimeout:
analyticsData.UploadFinishedReason = "ResponseTimeout";
Debug.LogWarning($"All bytes for the package '{PackageName}' have been uploaded, but a response " +
$"from the server was not received. This can happen because of Firewall restrictions. " +
$"Please make sure that a new version of your package has reached the Publishing Portal.");
await OnPackageUploadSuccess();
break;
}
ASAnalytics.SendUploadingEvent(analyticsData);
PostUploadCleanup(result.Status);
}
private ASAnalytics.AnalyticsData ConstructAnalyticsData(string exportedPackagePath)
{
bool validated;
string validationResults;
validated = _activeWorkflowElement.GetValidationSummary(out validationResults);
FileInfo packageFileInfo = new FileInfo(exportedPackagePath);
string workflow = _activeWorkflowElement.Name;
ASAnalytics.AnalyticsData data = new ASAnalytics.AnalyticsData
{
ToolVersion = AssetStoreAPI.ToolVersion,
EndpointUrl = AssetStoreAPI.AssetStoreProdUrl,
PackageId = PackageId,
Category = Category,
UsedValidator = validated,
ValidatorResults = validationResults,
PackageSize = packageFileInfo.Length,
Workflow = workflow
};
return data;
}
private void OnPackageUploadProgressHeader()
{
// Header progress bar is only shown when the package is not expanded and has progress
if (_uploadProgressBarHeader.value > 0.0f)
_uploadProgressBarHeader.style.display = !_expanded ? DisplayStyle.Flex : DisplayStyle.None;
if (!AssetStoreAPI.ActiveUploads.ContainsKey(VersionId))
return;
_uploadProgressBarHeader.value = AssetStoreAPI.ActiveUploads[VersionId].Progress;
}
private void OnPackageUploadProgressContent()
{
if (!AssetStoreAPI.ActiveUploads.ContainsKey(VersionId))
return;
var progressValue = AssetStoreAPI.ActiveUploads[VersionId].Progress;
_uploadProgressBarFlow.value = progressValue;
_uploadProgressBarFlow.title = $"{progressValue:0.#}%";
if(progressValue == 100f && _cancelUploadButton.enabledInHierarchy)
_cancelUploadButton.SetEnabled(false);
}
private async Task OnPackageUploadSuccess()
{
if (ASToolsPreferences.Instance.DisplayUploadDialog)
EditorUtility.DisplayDialog("Success!", $"Package for '{PackageName}' has been uploaded successfully!", "OK");
SetEnabled(false);
PackageFetcher fetcher = new PackageFetcher();
var result = await fetcher.FetchRefreshedPackage(PackageId);
if(!result.Success)
{
ASDebug.LogError(result.Error);
SetEnabled(true);
return;
}
UpdateDataValues(result.Package);
ASDebug.Log($"Updated name, status, date and size values for package version id {VersionId}");
SetEnabled(true);
}
private void OnPackageUploadFail(ASError error)
{
if (ASToolsPreferences.Instance.DisplayUploadDialog)
EditorUtility.DisplayDialog("Upload failed", "Package uploading failed. See Console for details", "OK");
Debug.LogError(error);
}
private void PostUploadCleanup(PackageUploadResult.UploadStatus uploadStatus)
{
if (_activeWorkflowElement == null)
return;
SetProgressBarColorByStatus(uploadStatus);
_uploadProgressBarFlow.title = $"Upload: {uploadStatus.ToString()}";
_cancelUploadButton.clickable = null;
_cancelUploadButton.clicked += ResetPostUpload;
_cancelUploadButton.text = "Done";
// Re-enable the Cancel/Done button since it gets disabled at 100% progress
_cancelUploadButton.SetEnabled(true);
}
private void ResetPostUpload()
{
if (_activeWorkflowElement == null)
return;
// Cleanup the progress bars
EditorApplication.update -= OnPackageUploadProgressContent;
EditorApplication.update -= OnPackageUploadProgressHeader;
EnableWorkflowElements(true);
ResetProgressBar();
_exportAndUploadContainer.style.display = DisplayStyle.Flex;
_uploadProgressContainer.style.display = DisplayStyle.None;
}
private void ResetProgressBar()
{
SetProgressBarColorByStatus(PackageUploadResult.UploadStatus.Default);
_uploadProgressBarHeader.style.display = DisplayStyle.None;
_uploadProgressBarHeader.value = 0f;
_uploadProgressBarFlow.value = 0f;
_uploadProgressBarFlow.title = string.Empty;
}
private void EnableWorkflowElements(bool enable)
{
_workflowSelectionBox?.SetEnabled(enable);
_activeWorkflowElement?.SetEnabled(enable);
}
private void SetProgressBarColorByStatus(PackageUploadResult.UploadStatus status)
{
var color = PackageUploadResult.GetColorByStatus(status);
_uploadProgressFlowBg.style.backgroundColor = color;
_uploadProgressHeaderBg.style.backgroundColor = color;
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7c2205d924ccc4a458abc3d370143740
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/PackageView.cs
uploadId: 681981

View File

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

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 52c6e85acbb00794686387cd876ffc81
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/UploadWindow.cs
uploadId: 681981

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 118376dcb318c4341b1df6773e3d8d4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Validation/AssetValidationElement.cs
uploadId: 681981

View File

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

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 33a72e7596565c749a495b4213579a67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Validation/PackageValidationElement.cs
uploadId: 681981

View File

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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: cee024b6c5a4407780a9f8677f7a6e97
timeCreated: 1654674025
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Validation/ValidationElement.cs
uploadId: 681981

View File

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

View File

@@ -0,0 +1,628 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AssetStoreTools.Exporter;
using AssetStoreTools.Utility;
using AssetStoreTools.Utility.Json;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace AssetStoreTools.Uploader.UIElements
{
internal class FolderUploadWorkflowView : UploadWorkflowView
{
public const string WorkflowName = "FolderWorkflow";
public const string WorkflowDisplayName = "From Assets Folder";
public override string Name => WorkflowName;
public override string DisplayName => WorkflowDisplayName;
private Toggle _dependenciesToggle;
private List<string> _includedDependencies = new List<string>();
private bool _isCompleteProject;
private VisualElement _specialFolderTogglesBox;
private VisualElement _specialFoldersElement;
private VisualElement _packageDependencyBox;
private ScrollView _packagesTogglesBox;
private ToolbarMenu _filteringDropdown;
private Label _noPackagesLabel;
private string _packagesFilter;
// Special folders that would not work if not placed directly in the 'Assets' folder
private readonly string[] _extraAssetFolderNames =
{
"Editor Default Resources", "Gizmos", "Plugins",
"StreamingAssets", "Standard Assets", "WebGLTemplates",
"ExternalDependencyManager", "XR"
};
private readonly List<string> _packageSelectionFilters = new List<string> { "All", "Selected", "Not Selected" };
private FolderUploadWorkflowView(string category, bool isCompleteProject, Action serializeSelection) : base(serializeSelection)
{
_isCompleteProject = isCompleteProject;
Category = category;
SetupWorkflow();
}
public static FolderUploadWorkflowView Create(string category, bool isCompleteProject, Action serializeAction)
{
return new FolderUploadWorkflowView(category, isCompleteProject, serializeAction);
}
public void SetCompleteProject(bool isCompleteProject)
{
_isCompleteProject = isCompleteProject;
}
private bool GetIncludeDependenciesToggle()
{
return _dependenciesToggle.value;
}
private List<string> GetIncludedDependencies()
{
return _includedDependencies;
}
protected sealed override void SetupWorkflow()
{
// Path selection
VisualElement folderPathSelectionRow = new VisualElement();
folderPathSelectionRow.AddToClassList("selection-box-row");
VisualElement labelHelpRow = new VisualElement();
labelHelpRow.AddToClassList("label-help-row");
Label folderPathLabel = new Label { text = "Folder path" };
Image folderPathLabelTooltip = new Image
{
tooltip = "Select the main folder of your package" +
"\n\nAll files and folders of your package should preferably be contained within a single root folder that is named after your package" +
"\n\nExample: 'Assets/[MyPackageName]'" +
"\n\nNote: If your content makes use of special folders that are required to be placed in the root Assets folder (e.g. 'StreamingAssets')," +
" you will be able to include them after selecting the main folder"
};
labelHelpRow.Add(folderPathLabel);
labelHelpRow.Add(folderPathLabelTooltip);
PathSelectionField = new TextField();
PathSelectionField.AddToClassList("path-selection-field");
PathSelectionField.isReadOnly = true;
Button browsePathButton = new Button(BrowsePath) { name = "BrowsePathButton", text = "Browse" };
browsePathButton.AddToClassList("browse-button");
folderPathSelectionRow.Add(labelHelpRow);
folderPathSelectionRow.Add(PathSelectionField);
folderPathSelectionRow.Add(browsePathButton);
Add(folderPathSelectionRow);
// Dependencies selection
VisualElement dependenciesSelectionRow = new VisualElement();
dependenciesSelectionRow.AddToClassList("selection-box-row");
VisualElement dependenciesLabelHelpRow = new VisualElement();
dependenciesLabelHelpRow.AddToClassList("label-help-row");
Label dependenciesLabel = new Label { text = "Dependencies" };
Image dependenciesLabelTooltip = new Image
{
tooltip = "Tick this checkbox if your package content has dependencies on Unity packages from the Package Manager"
};
_dependenciesToggle = new Toggle { name = "DependenciesToggle", text = "Include Package Manifest" };
_dependenciesToggle.AddToClassList("dependencies-toggle");
_dependenciesToggle.RegisterValueChangedCallback((_) => SerializeSelection?.Invoke());
_dependenciesToggle.RegisterValueChangedCallback(OnDependencyToggleValueChange);
RegisterCallback<AttachToPanelEvent>((_) => {ASToolsPreferences.OnSettingsChange += OnASTSettingsChange;});
RegisterCallback<DetachFromPanelEvent>((_) => {ASToolsPreferences.OnSettingsChange -= OnASTSettingsChange;});
// Dependencies selection
_packageDependencyBox = new VisualElement();
_packageDependencyBox.AddToClassList("selection-box-row");
_packageDependencyBox.style.display = DisplayStyle.None;
dependenciesLabelHelpRow.Add(dependenciesLabel);
dependenciesLabelHelpRow.Add(dependenciesLabelTooltip);
dependenciesSelectionRow.Add(dependenciesLabelHelpRow);
dependenciesSelectionRow.Add(_dependenciesToggle);
Add(dependenciesSelectionRow);
Add(_packageDependencyBox);
ValidationElement = new AssetValidationElement();
Add(ValidationElement);
ValidationElement.SetCategory(Category);
}
public override JsonValue SerializeWorkflow()
{
var workflowDict = base.SerializeWorkflow();
workflowDict["dependencies"] = GetIncludeDependenciesToggle();
workflowDict["dependenciesNames"] = GetIncludedDependencies().Select(JsonValue.NewString).ToList();
return workflowDict;
}
public override void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid)
{
if (!DeserializeMainExportPath(json, out string mainExportPath) || (!Directory.Exists(mainExportPath) && mainExportPath != String.Empty))
{
ASDebug.Log("Unable to restore Folder upload workflow paths from the local cache");
LoadSerializedWorkflowFallback(lastUploadedPath, lastUploadedGuid);
return;
}
DeserializeExtraExportPaths(json, out List<string> extraExportPaths);
DeserializeDependencies(json, out List<string> dependencies);
DeserializeDependenciesToggle(json, out var dependenciesToggle);
ASDebug.Log($"Restoring serialized Folder workflow values from local cache");
HandleFolderUploadPathSelection(mainExportPath, extraExportPaths, dependencies, false);
if (dependenciesToggle)
{
_dependenciesToggle.SetValueWithoutNotify(true);
FindAndPopulateDependencies(_includedDependencies);
}
}
public override void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid)
{
var mainExportPath = AssetDatabase.GUIDToAssetPath(lastUploadedGuid);
if (string.IsNullOrEmpty(mainExportPath))
mainExportPath = lastUploadedPath;
if ((!mainExportPath.StartsWith("Assets/") && mainExportPath != "Assets") || !Directory.Exists(mainExportPath))
{
ASDebug.Log("Unable to restore Folder workflow paths from previous upload values");
return;
}
ASDebug.Log($"Restoring serialized Folder workflow values from previous upload values");
HandleFolderUploadPathSelection(mainExportPath, null, null, false);
}
#region Folder Upload
protected override void BrowsePath()
{
// Path retrieval
var absoluteExportPath = string.Empty;
var relativeExportPath = string.Empty;
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
bool includeAllAssets = false;
if (_isCompleteProject)
{
includeAllAssets = EditorUtility.DisplayDialog("Notice",
"Your package draft is set to a category that is treated" +
" as a complete project. Project settings will be included automatically. Would you like everything in the " +
"'Assets' folder to be included?\n\nYou will still be able to change the selected assets before uploading",
"Yes, include all folders and assets",
"No, I'll select what to include manually");
if (includeAllAssets)
absoluteExportPath = Application.dataPath;
}
if (!includeAllAssets)
{
absoluteExportPath =
EditorUtility.OpenFolderPanel("Select folder to compress into a package", "Assets/", "");
if (string.IsNullOrEmpty(absoluteExportPath))
return;
}
if (absoluteExportPath.StartsWith(rootProjectPath))
{
relativeExportPath = absoluteExportPath.Substring(rootProjectPath.Length);
}
else
{
if (ASToolsPreferences.Instance.EnableSymlinkSupport)
SymlinkUtil.FindSymlinkFolderRelative(absoluteExportPath, out relativeExportPath);
}
if (!relativeExportPath.StartsWith("Assets/") && !(relativeExportPath == "Assets" && _isCompleteProject))
{
if (relativeExportPath.StartsWith("Assets") && !_isCompleteProject)
EditorUtility.DisplayDialog("Invalid selection",
"'Assets' folder is only available for packages tagged as a 'Complete Project'.", "OK");
else
EditorUtility.DisplayDialog("Invalid selection", "Selected folder path must be within the project.",
"OK");
return;
}
HandleFolderUploadPathSelection(relativeExportPath, null, _includedDependencies, true);
}
private void HandleFolderUploadPathSelection(string relativeExportPath, List<string> serializedToggles, List<string> dependencies, bool serializeValues)
{
if (relativeExportPath != String.Empty)
PathSelectionField.value = relativeExportPath + "/";
MainExportPath = relativeExportPath;
ExtraExportPaths = new List<string>();
_includedDependencies = new List<string>();
LocalPackageGuid = AssetDatabase.AssetPathToGUID(MainExportPath);
LocalPackagePath = MainExportPath;
LocalProjectPath = MainExportPath;
if (_specialFoldersElement != null)
{
_specialFoldersElement.Clear();
Remove(_specialFoldersElement);
_specialFoldersElement = null;
}
// Prompt additional path selection (e.g. StreamingAssets, WebGLTemplates, etc.)
List<string> specialFoldersFound = new List<string>();
foreach (var extraAssetFolderName in _extraAssetFolderNames)
{
var fullExtraPath = "Assets/" + extraAssetFolderName;
if (!Directory.Exists(fullExtraPath))
continue;
if (MainExportPath.ToLower().StartsWith(fullExtraPath.ToLower()))
continue;
// Don't include nested paths
if (!fullExtraPath.ToLower().StartsWith(MainExportPath.ToLower()))
specialFoldersFound.Add(fullExtraPath);
}
if (specialFoldersFound.Count != 0)
PopulateSpecialFoldersBox(specialFoldersFound, serializedToggles);
if (dependencies != null && dependencies.Count != 0)
FindAndPopulateDependencies(dependencies);
// After setting up the main and extra paths, check for missing metas and update validation paths
CheckForMissingMetas();
UpdateValidationPaths();
// Only serialize current selection when no serialized toggles were passed
if (serializeValues)
SerializeSelection?.Invoke();
}
private void InitializeSpecialFoldersSelection()
{
// Dependencies selection
_specialFoldersElement = new VisualElement();
_specialFoldersElement.AddToClassList("selection-box-row");
VisualElement specialFoldersHelpRow = new VisualElement();
specialFoldersHelpRow.AddToClassList("label-help-row");
Label specialFoldersLabel = new Label { text = "Special folders" };
Image specialFoldersLabelTooltip = new Image
{
tooltip =
"If your package content relies on Special Folders (e.g. StreamingAssets), please select which of these folders should be included in the package"
};
_specialFolderTogglesBox = new VisualElement { name = "SpecialFolderToggles" };
_specialFolderTogglesBox.AddToClassList("special-folders-toggles-box");
specialFoldersHelpRow.Add(specialFoldersLabel);
specialFoldersHelpRow.Add(specialFoldersLabelTooltip);
_specialFoldersElement.Add(specialFoldersHelpRow);
_specialFoldersElement.Add(_specialFolderTogglesBox);
Add(_specialFoldersElement);
}
private void PopulateSpecialFoldersBox(List<string> specialFoldersFound, List<string> checkedToggles)
{
InitializeSpecialFoldersSelection();
EventCallback<ChangeEvent<bool>, string> toggleChangeCallback = OnSpecialFolderPathToggledAsset;
foreach (var path in specialFoldersFound)
{
var toggle = new Toggle { value = false, text = path };
toggle.AddToClassList("special-folder-toggle");
if (checkedToggles != null && checkedToggles.Contains(toggle.text))
{
toggle.SetValueWithoutNotify(true);
ExtraExportPaths.Add(toggle.text);
}
toggle.RegisterCallback(toggleChangeCallback, toggle.text);
_specialFolderTogglesBox.Add(toggle);
}
}
private void OnSpecialFolderPathToggledAsset(ChangeEvent<bool> evt, string folderPath)
{
switch (evt.newValue)
{
case true when !ExtraExportPaths.Contains(folderPath):
ExtraExportPaths.Add(folderPath);
break;
case false when ExtraExportPaths.Contains(folderPath):
ExtraExportPaths.Remove(folderPath);
break;
}
UpdateValidationPaths();
SerializeSelection?.Invoke();
}
private void UpdateValidationPaths()
{
var validationPaths = new List<string>() { MainExportPath };
validationPaths.AddRange(ExtraExportPaths);
ValidationElement.SetValidationPaths(validationPaths.ToArray());
}
private void OnToggleDependency(ChangeEvent<bool> evt, string dependency)
{
switch (evt.newValue)
{
case true when !_includedDependencies.Contains(dependency):
_includedDependencies.Add(dependency);
break;
case false when _includedDependencies.Contains(dependency):
_includedDependencies.Remove(dependency);
break;
}
FilterPackageSelection(_packagesFilter);
SerializeSelection?.Invoke();
}
private void OnDependencyToggleValueChange(ChangeEvent<bool> evt)
{
CheckDependencyBoxState();
}
private void OnASTSettingsChange()
{
CheckDependencyBoxState();
}
private void CheckDependencyBoxState()
{
if (_dependenciesToggle.value && !ASToolsPreferences.Instance.UseLegacyExporting)
{
FindAndPopulateDependencies(_includedDependencies);
}
else
{
_packageDependencyBox.style.display = DisplayStyle.None;
}
}
private void FindAndPopulateDependencies(List<string> checkedToggles)
{
_packageDependencyBox?.Clear();
var registryPackages = PackageUtility.GetAllRegistryPackages();
if (registryPackages == null)
{
ASDebug.LogWarning("Package Manifest was not found or could not be parsed.");
return;
}
List<string> packagesFound = new List<string>(registryPackages.Select(x => x.name));
PopulatePackagesSelectionBox(packagesFound, checkedToggles);
}
private void PopulatePackagesSelectionBox(List<string> packagesFound, List<string> checkedToggles)
{
InitializePackageSelection();
EventCallback<ChangeEvent<bool>, string> toggleChangeCallback = OnToggleDependency;
if (packagesFound.Count == 0 || ASToolsPreferences.Instance.UseLegacyExporting)
{
_packageDependencyBox.style.display = DisplayStyle.None;
return;
}
_packageDependencyBox.style.display = DisplayStyle.Flex;
foreach (var path in packagesFound)
{
var toggle = new Toggle { value = false, text = path };
toggle.AddToClassList("extra-packages-toggle");
if (checkedToggles != null && checkedToggles.Contains(toggle.text))
{
toggle.SetValueWithoutNotify(true);
if (!_includedDependencies.Contains(toggle.text))
_includedDependencies.Add(toggle.text);
}
toggle.RegisterCallback(toggleChangeCallback, toggle.text);
_packagesTogglesBox.Add(toggle);
}
}
private void InitializePackageSelection()
{
VisualElement dependenciesHelpRow = new VisualElement();
dependenciesHelpRow.AddToClassList("label-help-row");
Label allPackagesLabel = new Label { text = "All Packages" };
Image allPackagesLabelTooltip = new Image
{
tooltip =
"Select UPM dependencies you would like to include with your package."
};
VisualElement fullPackageSelectionBox = new VisualElement();
fullPackageSelectionBox.AddToClassList("extra-packages-box");
_packagesTogglesBox = new ScrollView { name = "DependencyToggles" };
_packagesTogglesBox.AddToClassList("extra-packages-scroll-view");
_noPackagesLabel = new Label("No packages were found that match this criteria.");
_noPackagesLabel.AddToClassList("no-packages-label");
var scrollContainer = _packagesTogglesBox.Q<VisualElement>("unity-content-viewport");
scrollContainer.Add(_noPackagesLabel);
VisualElement packagesFilteringBox = new VisualElement();
packagesFilteringBox.AddToClassList("packages-filtering-box");
// Select - deselect buttons
VisualElement selectingPackagesBox = new VisualElement();
selectingPackagesBox.AddToClassList("filtering-packages-buttons-box");
Button selectAllButton = new Button(() => SelectAllPackages(true))
{
text = "Select All"
};
Button deSelectAllButton = new Button(() => SelectAllPackages(false))
{
text = "Deselect All"
};
selectAllButton.AddToClassList("filter-packages-button");
deSelectAllButton.AddToClassList("filter-packages-button");
selectingPackagesBox.Add(selectAllButton);
selectingPackagesBox.Add(deSelectAllButton);
// Filtering dropdown
VisualElement filteringDropdownBox = new VisualElement();
filteringDropdownBox.AddToClassList("filtering-packages-dropdown-box");
_filteringDropdown = new ToolbarMenu {text = _packagesFilter = _packageSelectionFilters[0]};
_filteringDropdown.AddToClassList("filter-packages-dropdown");
foreach (var filter in _packageSelectionFilters)
_filteringDropdown.menu.AppendAction(filter, delegate { FilterPackageSelection(filter);});
filteringDropdownBox.Add(_filteringDropdown);
VisualElement packageSelectionButtonsBox = new VisualElement();
packageSelectionButtonsBox.AddToClassList("extra-packages-buttons-box");
// Final adding
packagesFilteringBox.Add(filteringDropdownBox);
packagesFilteringBox.Add(selectingPackagesBox);
fullPackageSelectionBox.Add(_packagesTogglesBox);
fullPackageSelectionBox.Add(packagesFilteringBox);
dependenciesHelpRow.Add(allPackagesLabel);
dependenciesHelpRow.Add(allPackagesLabelTooltip);
_packageDependencyBox.Add(dependenciesHelpRow);
_packageDependencyBox.Add(fullPackageSelectionBox);
}
private void FilterPackageSelection(string filter)
{
var allToggles = _packagesTogglesBox.Children().Cast<Toggle>().ToArray();
var selectedIndex = _packageSelectionFilters.FindIndex(x => x == filter);
switch (selectedIndex)
{
case 0:
foreach (var toggle in allToggles)
toggle.style.display = DisplayStyle.Flex;
break;
case 1:
foreach (var toggle in allToggles)
toggle.style.display = toggle.value ? DisplayStyle.Flex : DisplayStyle.None;
break;
case 2:
foreach (var toggle in allToggles)
toggle.style.display = toggle.value ? DisplayStyle.None : DisplayStyle.Flex;
break;
}
// Check if any toggles are displayed
var count = allToggles.Count(toggle => toggle.style.display == DisplayStyle.Flex);
_noPackagesLabel.style.display = count > 0 ? DisplayStyle.None : DisplayStyle.Flex;
_packagesFilter = filter;
_filteringDropdown.text = filter;
}
private void SelectAllPackages(bool shouldSelect)
{
var allToggles = _packagesTogglesBox.Children().Cast<Toggle>();
foreach (var toggle in allToggles)
toggle.value = shouldSelect;
}
public override async Task<ExportResult> ExportPackage(string outputPath, bool isCompleteProject)
{
var paths = GetAllExportPaths();
if (isCompleteProject)
paths = IncludeProjectSettings(paths);
var includeDependencies = GetIncludeDependenciesToggle();
var dependenciesToInclude = Array.Empty<string>();
if (includeDependencies)
dependenciesToInclude = GetIncludedDependencies().ToArray();
ExporterSettings exportSettings;
if (ASToolsPreferences.Instance.UseLegacyExporting)
exportSettings = new LegacyExporterSettings()
{
ExportPaths = paths,
OutputFilename = outputPath,
IncludeDependencies = includeDependencies,
};
else
exportSettings = new DefaultExporterSettings()
{
ExportPaths = paths,
OutputFilename = outputPath,
Dependencies = dependenciesToInclude,
};
return await PackageExporter.ExportPackage(exportSettings);
}
private string[] IncludeProjectSettings(string[] exportPaths)
{
if (exportPaths.Contains("ProjectSettings"))
return exportPaths;
var updatedExportPaths = new string[exportPaths.Length + 1];
exportPaths.CopyTo(updatedExportPaths, 0);
updatedExportPaths[updatedExportPaths.Length - 1] = "ProjectSettings";
return updatedExportPaths;
}
#endregion
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f8bafd0b9c5b47bc985d17a18fc07978
timeCreated: 1654089523
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/FolderUploadWorkflowView.cs
uploadId: 681981

View File

@@ -0,0 +1,415 @@
using AssetStoreTools.Exporter;
using AssetStoreTools.Utility;
using AssetStoreTools.Utility.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
namespace AssetStoreTools.Uploader.UIElements
{
internal class HybridPackageUploadWorkflowView : UploadWorkflowView
{
public const string WorkflowName = "HybridPackageWorkflow";
public const string WorkflowDisplayName = "Local UPM Package";
public override string Name => WorkflowName;
public override string DisplayName => WorkflowDisplayName;
private VisualElement _extraPackagesBox;
private Label _noExtraPackagesLabel;
private ScrollView _extraPackagesTogglesBox;
private ToolbarMenu _filteringDropdown;
private string _extraPackagesFilter;
private readonly List<string> _extraPackageSelectionFilters = new List<string> { "All", "Selected", "Not Selected" };
private HybridPackageUploadWorkflowView(string category, Action serializeSelection) : base(serializeSelection)
{
Category = category;
SetupWorkflow();
}
public static HybridPackageUploadWorkflowView Create(string category, Action serializeAction)
{
return new HybridPackageUploadWorkflowView(category, serializeAction);
}
protected sealed override void SetupWorkflow()
{
// Path selection
VisualElement folderPathSelectionRow = new VisualElement();
folderPathSelectionRow.AddToClassList("selection-box-row");
VisualElement labelHelpRow = new VisualElement();
labelHelpRow.AddToClassList("label-help-row");
Label folderPathLabel = new Label { text = "Package path" };
Image folderPathLabelTooltip = new Image
{
tooltip = "Select a local Package you would like to export and upload to the Store."
};
labelHelpRow.Add(folderPathLabel);
labelHelpRow.Add(folderPathLabelTooltip);
PathSelectionField = new TextField();
PathSelectionField.AddToClassList("path-selection-field");
PathSelectionField.isReadOnly = true;
Button browsePathButton = new Button(BrowsePath) { name = "BrowsePathButton", text = "Browse" };
browsePathButton.AddToClassList("browse-button");
folderPathSelectionRow.Add(labelHelpRow);
folderPathSelectionRow.Add(PathSelectionField);
folderPathSelectionRow.Add(browsePathButton);
Add(folderPathSelectionRow);
ValidationElement = new AssetValidationElement();
Add(ValidationElement);
ValidationElement.SetCategory(Category);
}
public override void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid)
{
if(!DeserializeMainExportPath(json, out string mainExportPath) || !Directory.Exists(mainExportPath))
{
ASDebug.Log("Unable to restore Hybrid Package workflow paths from local cache");
LoadSerializedWorkflowFallback(lastUploadedGuid, lastUploadedGuid);
return;
}
DeserializeExtraExportPaths(json, out List<string> extraExportPaths);
ASDebug.Log($"Restoring serialized Hybrid Package workflow values from local cache");
LoadSerializedWorkflow(mainExportPath, extraExportPaths);
}
public override void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid)
{
var mainExportPath = AssetDatabase.GUIDToAssetPath(lastUploadedGuid);
if (string.IsNullOrEmpty(mainExportPath))
mainExportPath = lastUploadedPath;
if (!mainExportPath.StartsWith("Packages/") || !Directory.Exists(mainExportPath))
{
ASDebug.Log("Unable to restore Hybrid Package workflow paths from previous upload values");
return;
}
ASDebug.Log($"Restoring serialized Hybrid Package workflow values from previous upload values");
LoadSerializedWorkflow(mainExportPath, null);
}
private void LoadSerializedWorkflow(string relativeAssetDatabasePath, List<string> extraExportPaths)
{
// Expected path is in ADB form, so we need to reconstruct it first
var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
var realPath = Path.GetFullPath(relativeAssetDatabasePath).Replace('\\', '/');
if (realPath.StartsWith(rootProjectPath))
realPath = realPath.Substring(rootProjectPath.Length);
if (!IsValidLocalPackage(realPath, out relativeAssetDatabasePath))
{
ASDebug.Log("Unable to restore Hybrid Package workflow path - package is not a valid UPM package");
return;
}
// Treat this as a manual selection
HandleHybridUploadPathSelection(realPath, relativeAssetDatabasePath, extraExportPaths, false);
}
protected override void BrowsePath()
{
// Path retrieval
string relativeExportPath = string.Empty;
string rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
var absoluteExportPath = EditorUtility.OpenFolderPanel("Select the Package", "Packages/", "");
if (string.IsNullOrEmpty(absoluteExportPath))
return;
if (absoluteExportPath.StartsWith(rootProjectPath))
relativeExportPath = absoluteExportPath.Substring(rootProjectPath.Length);
var workingPath = !string.IsNullOrEmpty(relativeExportPath) ? relativeExportPath : absoluteExportPath;
if (!IsValidLocalPackage(workingPath, out string relativeAssetDatabasePath))
{
EditorUtility.DisplayDialog("Invalid selection", "Selected export path must be a valid local package", "OK");
return;
}
HandleHybridUploadPathSelection(workingPath, relativeAssetDatabasePath, null, true);
}
private void HandleHybridUploadPathSelection(string relativeExportPath, string relativeAssetDatabasePath, List<string> serializedToggles, bool serializeValues)
{
PathSelectionField.value = relativeExportPath + "/";
// Reset and reinitialize the selected export path(s) array
MainExportPath = relativeAssetDatabasePath;
ExtraExportPaths = new List<string>();
// Set additional upload data for the Publisher Portal backend (GUID and Package Path).
// The backend workflow currently accepts only 1 package guid and path, so we'll use the main folder data
LocalPackageGuid = AssetDatabase.AssetPathToGUID(relativeAssetDatabasePath);
LocalPackagePath = relativeAssetDatabasePath;
LocalProjectPath = relativeAssetDatabasePath;
if (_extraPackagesBox != null)
{
_extraPackagesBox.Clear();
Remove(_extraPackagesBox);
_extraPackagesBox = null;
}
List<string> pathsToAdd = new List<string>();
foreach (var package in PackageUtility.GetAllLocalPackages())
{
// Exclude the Asset Store Tools themselves
if (package.name == "com.unity.asset-store-tools")
continue;
var localPackagePath = package.GetConvenientPath();
if (localPackagePath == relativeExportPath)
continue;
pathsToAdd.Add(package.assetPath);
}
pathsToAdd.Sort();
if (pathsToAdd.Count != 0)
PopulateExtraPackagesBox(pathsToAdd, serializedToggles);
// After setting up the main and extra paths, check for missing metas and update validation paths
CheckForMissingMetas();
UpdateValidationPaths();
if (serializeValues)
SerializeSelection?.Invoke();
}
private void PopulateExtraPackagesBox(List<string> otherPackagesFound, List<string> checkedToggles)
{
// Dependencies selection
_extraPackagesBox = new VisualElement();
_extraPackagesBox.AddToClassList("selection-box-row");
InitializeExtraPackageSelection();
EventCallback<ChangeEvent<bool>, string> toggleChangeCallback = OnToggledPackage;
foreach (var path in otherPackagesFound)
{
var toggle = new Toggle { value = false, text = path };
toggle.AddToClassList("extra-packages-toggle");
toggle.tooltip = path;
if (checkedToggles != null && checkedToggles.Contains(toggle.text))
{
toggle.SetValueWithoutNotify(true);
ExtraExportPaths.Add(toggle.text);
}
toggle.RegisterCallback(toggleChangeCallback, toggle.text);
_extraPackagesTogglesBox.Add(toggle);
}
Add(_extraPackagesBox);
}
private void InitializeExtraPackageSelection()
{
VisualElement extraPackagesHelpRow = new VisualElement();
extraPackagesHelpRow.AddToClassList("label-help-row");
Label extraPackagesLabel = new Label { text = "Extra Packages" };
Image extraPackagesLabelTooltip = new Image
{
tooltip = "If your package has dependencies on other local packages, please select which of these packages should also be included in the resulting package"
};
var fullPackageSelectionBox = new VisualElement();
fullPackageSelectionBox.AddToClassList("extra-packages-box");
_extraPackagesTogglesBox = new ScrollView { name = "ExtraPackageToggles" };
_extraPackagesTogglesBox.AddToClassList("extra-packages-scroll-view");
_noExtraPackagesLabel = new Label("No packages were found that match this criteria.");
_noExtraPackagesLabel.AddToClassList("no-packages-label");
var scrollContainer = _extraPackagesTogglesBox.Q<VisualElement>("unity-content-viewport");
scrollContainer.Add(_noExtraPackagesLabel);
VisualElement extraPackagesFilteringBox = new VisualElement();
extraPackagesFilteringBox.AddToClassList("packages-filtering-box");
// Select - deselect buttons
VisualElement selectingPackagesBox = new VisualElement();
selectingPackagesBox.AddToClassList("filtering-packages-buttons-box");
Button selectAllButton = new Button(() => SelectAllPackages(true))
{
text = "Select All"
};
Button deSelectAllButton = new Button(() => SelectAllPackages(false))
{
text = "Deselect All"
};
selectAllButton.AddToClassList("filter-packages-button");
deSelectAllButton.AddToClassList("filter-packages-button");
selectingPackagesBox.Add(selectAllButton);
selectingPackagesBox.Add(deSelectAllButton);
// Filtering dropdown
VisualElement filteringDropdownBox = new VisualElement();
filteringDropdownBox.AddToClassList("filtering-packages-dropdown-box");
_filteringDropdown = new ToolbarMenu { text = _extraPackagesFilter = _extraPackageSelectionFilters[0] };
_filteringDropdown.AddToClassList("filter-packages-dropdown");
foreach (var filter in _extraPackageSelectionFilters)
_filteringDropdown.menu.AppendAction(filter, delegate { FilterPackageSelection(filter); });
filteringDropdownBox.Add(_filteringDropdown);
VisualElement packageSelectionButtonsBox = new VisualElement();
packageSelectionButtonsBox.AddToClassList("extra-packages-buttons-box");
// Final adding
extraPackagesFilteringBox.Add(filteringDropdownBox);
extraPackagesFilteringBox.Add(selectingPackagesBox);
fullPackageSelectionBox.Add(_extraPackagesTogglesBox);
fullPackageSelectionBox.Add(extraPackagesFilteringBox);
extraPackagesHelpRow.Add(extraPackagesLabel);
extraPackagesHelpRow.Add(extraPackagesLabelTooltip);
_extraPackagesBox.Add(extraPackagesHelpRow);
_extraPackagesBox.Add(fullPackageSelectionBox);
}
private void SelectAllPackages(bool shouldSelect)
{
var allToggles = _extraPackagesTogglesBox.Children().Cast<Toggle>();
foreach (var toggle in allToggles)
toggle.value = shouldSelect;
}
private void FilterPackageSelection(string filter)
{
var allToggles = _extraPackagesTogglesBox.Children().Cast<Toggle>().ToArray();
var selectedIndex = _extraPackageSelectionFilters.FindIndex(x => x == filter);
switch (selectedIndex)
{
case 0:
foreach (var toggle in allToggles)
toggle.style.display = DisplayStyle.Flex;
break;
case 1:
foreach (var toggle in allToggles)
toggle.style.display = toggle.value ? DisplayStyle.Flex : DisplayStyle.None;
break;
case 2:
foreach (var toggle in allToggles)
toggle.style.display = toggle.value ? DisplayStyle.None : DisplayStyle.Flex;
break;
}
// Check if any toggles are displayed
var count = allToggles.Count(toggle => toggle.style.display == DisplayStyle.Flex);
_noExtraPackagesLabel.style.display = count > 0 ? DisplayStyle.None : DisplayStyle.Flex;
_extraPackagesFilter = filter;
_filteringDropdown.text = filter;
}
private void OnToggledPackage(ChangeEvent<bool> evt, string folderPath)
{
switch (evt.newValue)
{
case true when !ExtraExportPaths.Contains(folderPath):
ExtraExportPaths.Add(folderPath);
break;
case false when ExtraExportPaths.Contains(folderPath):
ExtraExportPaths.Remove(folderPath);
break;
}
FilterPackageSelection(_extraPackagesFilter);
UpdateValidationPaths();
SerializeSelection?.Invoke();
}
private void UpdateValidationPaths()
{
var validationPaths = new List<string>() { MainExportPath };
validationPaths.AddRange(ExtraExportPaths);
ValidationElement.SetValidationPaths(validationPaths.ToArray());
}
private bool IsValidLocalPackage(string packageFolderPath, out string assetDatabasePackagePath)
{
assetDatabasePackagePath = string.Empty;
string packageManifestPath = $"{packageFolderPath}/package.json";
if (!File.Exists(packageManifestPath))
return false;
try
{
var localPackages = PackageUtility.GetAllLocalPackages();
if (localPackages == null || localPackages.Length == 0)
return false;
foreach (var package in localPackages)
{
var localPackagePath = package.GetConvenientPath();
if (localPackagePath != packageFolderPath)
continue;
assetDatabasePackagePath = package.assetPath;
return true;
}
}
catch
{
return false;
}
return false;
}
public override async Task<ExportResult> ExportPackage(string outputPath, bool _)
{
var paths = GetAllExportPaths();
var exportSettings = new DefaultExporterSettings()
{
ExportPaths = paths,
OutputFilename = outputPath
};
return await PackageExporter.ExportPackage(exportSettings);
}
}
}

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b5472a1f8e8745779ee82d18b63ec19c
timeCreated: 1654112492
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/HybridPackageUploadWorkflowView.cs
uploadId: 681981

View File

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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a9e30749a5784d18a1a2644cc44dda29
timeCreated: 1654112475
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/UnityPackageUploadWorkflowView.cs
uploadId: 681981

View File

@@ -0,0 +1,210 @@
using AssetStoreTools.Exporter;
using AssetStoreTools.Utility;
using AssetStoreTools.Utility.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine.UIElements;
namespace AssetStoreTools.Uploader.UIElements
{
internal abstract class UploadWorkflowView : VisualElement
{
protected TextField PathSelectionField;
// Upload data
protected List<string> ExtraExportPaths = new List<string>();
protected string MainExportPath = String.Empty;
protected string LocalPackageGuid;
protected string LocalPackagePath;
protected string LocalProjectPath;
protected string Category;
protected ValidationElement ValidationElement;
protected readonly Action SerializeSelection;
public abstract string Name { get; }
public abstract string DisplayName { get; }
protected UploadWorkflowView(Action serializeSelection)
{
SerializeSelection = serializeSelection;
style.display = DisplayStyle.None;
}
public string[] GetAllExportPaths()
{
var allPaths = new List<string>(ExtraExportPaths);
if (!string.IsNullOrEmpty(MainExportPath))
allPaths.Insert(0, MainExportPath);
return allPaths.ToArray();
}
public string GetLocalPackageGuid()
{
return LocalPackageGuid;
}
public string GetLocalPackagePath()
{
return LocalPackagePath;
}
public string GetLocalProjectPath()
{
return LocalProjectPath;
}
protected abstract void SetupWorkflow();
public virtual JsonValue SerializeWorkflow()
{
var workflowDict = JsonValue.NewDict();
var mainExportPathDict = JsonValue.NewDict();
mainExportPathDict["path"] = MainExportPath;
if (MainExportPath != null && !MainExportPath.StartsWith("Assets/") && !MainExportPath.StartsWith("Packages/"))
mainExportPathDict["guid"] = new JsonValue("");
else
mainExportPathDict["guid"] = AssetDatabase.AssetPathToGUID(MainExportPath);
workflowDict["mainPath"] = mainExportPathDict;
var extraExportPathsList = new List<JsonValue>();
foreach (var path in ExtraExportPaths)
{
var pathDict = JsonValue.NewDict();
pathDict["path"] = path;
pathDict["guid"] = AssetDatabase.AssetPathToGUID(path);
extraExportPathsList.Add(pathDict);
}
workflowDict["extraPaths"] = extraExportPathsList;
return workflowDict;
}
protected bool DeserializeMainExportPath(JsonValue json, out string mainExportPath)
{
mainExportPath = string.Empty;
try
{
var mainPathDict = json["mainPath"];
if (!mainPathDict.ContainsKey("path") || !mainPathDict["path"].IsString())
return false;
mainExportPath = DeserializePath(mainPathDict);
return true;
}
catch
{
return false;
}
}
protected void DeserializeExtraExportPaths(JsonValue json, out List<string> extraExportPaths)
{
extraExportPaths = new List<string>();
try
{
var extraPathsList = json["extraPaths"].AsList();
extraExportPaths.AddRange(extraPathsList.Select(DeserializePath));
}
catch
{
ASDebug.LogWarning($"Deserializing extra export paths for {Name} failed");
extraExportPaths.Clear();
}
}
protected void DeserializeDependencies(JsonValue json, out List<string> dependencies)
{
dependencies = new List<string>();
try
{
var packageJsonList = json["dependenciesNames"].AsList();
dependencies.AddRange(packageJsonList.Select(package => package.AsString()));
}
catch
{
ASDebug.LogWarning($"Deserializing dependencies for {Name} failed");
dependencies.Clear();
}
}
protected void DeserializeDependenciesToggle(JsonValue json, out bool dependenciesBool)
{
bool includeDependencies;
try
{
includeDependencies = json["dependencies"].AsBool();
}
catch
{
ASDebug.LogWarning($"Deserializing dependencies toggle for {Name} failed");
includeDependencies = false;
}
dependenciesBool = includeDependencies;
}
private string DeserializePath(JsonValue pathDict)
{
// First pass - retrieve from GUID
var exportPath = AssetDatabase.GUIDToAssetPath(pathDict["guid"].AsString());
// Second pass - retrieve directly
if (string.IsNullOrEmpty(exportPath))
exportPath = pathDict["path"].AsString();
return exportPath;
}
protected void CheckForMissingMetas()
{
if (ASToolsPreferences.Instance.DisplayHiddenMetaDialog && FileUtility.IsMissingMetaFiles(GetAllExportPaths()))
{
var selectedOption = EditorUtility.DisplayDialogComplex(
"Notice",
"Your package includes hidden folders which do not contain meta files. " +
"Hidden folders will not be exported unless they contain meta files.\n\nWould you like meta files to be generated?",
"Yes", "No", "No and do not display this again");
switch (selectedOption)
{
case 0:
FileUtility.GenerateMetaFiles(GetAllExportPaths());
EditorUtility.DisplayDialog(
"Success",
"Meta files have been generated. Please note that further manual tweaking may be required to set up correct references",
"OK");
break;
case 1:
// Do nothing
return;
case 2:
ASToolsPreferences.Instance.DisplayHiddenMetaDialog = false;
ASToolsPreferences.Instance.Save();
return;
}
}
}
public bool GetValidationSummary(out string validationSummary)
{
return ValidationElement.GetValidationSummary(out validationSummary);
}
public abstract void LoadSerializedWorkflow(JsonValue json, string lastUploadedPath, string lastUploadedGuid);
public abstract void LoadSerializedWorkflowFallback(string lastUploadedPath, string lastUploadedGuid);
protected abstract void BrowsePath();
public abstract Task<ExportResult> ExportPackage(string outputPath, bool isCompleteProject);
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f84b47b78aca74c4db1e9b753d41422f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI Elements/Upload/Workflows/UploadWorkflowView.cs
uploadId: 681981