Files
2025-11-30 08:35:03 +02:00

412 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Needle.Engine.Utils;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
using Task = System.Threading.Tasks.Task;
namespace Needle.Engine.ProjectBundle
{
internal static class ProjectWindowActions
{
private const string BaseMenuPath = "Assets/Create/";
private const int BaseMenuPriority = Engine.Constants.MenuItemOrder;
private class CreateTypescriptAction : EndNameEditAction
{
public string Directory;
public string Template;
/// <summary>
/// optional codegen dir
/// </summary>
public string CodeGenDirectory;
/// <summary>
/// optional path to npmdef
/// </summary>
public string NpmDefPath;
public override async void Action(int instanceId, string pathName, string resourceFile)
{
var fileName = Path.GetFileNameWithoutExtension(pathName);
SanitizeName(ref fileName);
var targetPath = Directory + "/" + fileName + ".ts";
if (File.Exists(targetPath))
{
Debug.LogWarning("Script already exists at " + targetPath);
// fileName = ObjectNames.GetUniqueName(new[] { fileName }, fileName);
// SantizeName(ref fileName);
// targetPath = Directory + "/" + fileName + ".ts";
}
else
{
var code = Template.Replace("component_name", fileName);
File.WriteAllText(targetPath, code);
if (!string.IsNullOrWhiteSpace(CodeGenDirectory))
System.IO.Directory.CreateDirectory(CodeGenDirectory);
}
// TODO: select the new typescript sub asset
if (!string.IsNullOrEmpty(NpmDefPath) && File.Exists(NpmDefPath))
{
// AssetDatabase.AssetPathToGUID(NpmDefPath)
}
var workspaceDirectory = Directory;
if (!Actions.OpenWorkspace(workspaceDirectory, targetPath))
{
Debug.LogWarning("Failed opening workspace: " + Directory);
}
await Task.Delay(100);
EditorUtility.OpenWithDefaultApp(targetPath);
BundleRegistry.Instance.RunCodeGen(targetPath);
var fp = Path.GetFullPath(Directory);
var bundle = BundleRegistry.Instance.Bundles.FirstOrDefault(b => b.PackageDirectory == fp);
if (bundle != null)
{
BundleImporter.MarkDirty(bundle);
}
}
private static void SanitizeName(ref string str)
{
str = Regex.Replace(str, "[\\W]", "");
}
}
[MenuItem(BaseMenuPath + "Typescript", true, BaseMenuPriority + 1)]
private static bool CreateTypescript_Validate()
{
var obj = Selection.activeObject;
var selectionPath = AssetDatabase.GetAssetPath(obj);
if (selectionPath.EndsWith(Constants.Extension) || selectionPath.EndsWith(".codegen")) return true;
var exportInfo = ExportInfo.Get();
if (exportInfo && exportInfo.Exists()) return true;
return false;
}
[MenuItem(BaseMenuPath + "Typescript", false, BaseMenuPriority + 1)]
private static void CreateTypescript()
{
var obj = Selection.activeObject;
var selectionPath = AssetDatabase.GetAssetPath(obj);
var path = AssetDatabase.GUIDToAssetPath("921c8f326fb84c89b6cb2cee80d99e31");
var template = File.ReadAllText(path);
var creator = ScriptableObject.CreateInstance<CreateTypescriptAction>();
creator.Template = template;
if (selectionPath.EndsWith(Constants.Extension) || selectionPath.EndsWith(".codegen"))
{
var projectPath = selectionPath.Substring(0, selectionPath.LastIndexOf(".", StringComparison.Ordinal)) + "~";
if (Directory.Exists(projectPath))
{
creator.NpmDefPath = selectionPath.Substring(0, selectionPath.LastIndexOf(".", StringComparison.Ordinal)) + Constants.Extension;
creator.Directory = projectPath;
creator.CodeGenDirectory = selectionPath.Substring(0, selectionPath.LastIndexOf(".", StringComparison.Ordinal)) + ".codegen";
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, creator, "New Typescript", null, null);
}
}
else
{
var exportInfo = ExportInfo.Get();
var dir = Path.GetFullPath(exportInfo.GetProjectDirectory() + "/src/scripts");
if (Directory.Exists(dir))
{
creator.Directory = dir;
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, creator, "New Typescript", null, null);
}
}
}
[MenuItem(BaseMenuPath + "NPM Definition", false, BaseMenuPriority + 2)]
private static void CreateBundleContextMenu()
{
var obj = Selection.activeObject;
if (obj)
{
CreateBundle(true);
}
}
[MenuItem(BaseMenuPath + "NPM Definition (Link to external package)", false, BaseMenuPriority + 2)]
private static void CreateLinkedBundleContextMenu()
{
var obj = Selection.activeObject;
if (obj)
{
CreateBundle(false);
}
}
private class CreateAssetAction : EndNameEditAction
{
/// <summary>
/// Set to false to show dialogue to link to external package
/// </summary>
public bool Embedded = true;
public override void Action(int instanceId, string pathName, string _)
{
var bundleFilePath = pathName + Constants.Extension;
var fileName = Path.GetFileNameWithoutExtension(bundleFilePath);
var bundle = new Bundle();
bundle.FilePath = bundleFilePath;
var json = JsonConvert.SerializeObject(bundle, Formatting.Indented);
File.WriteAllText(bundleFilePath, json);
AssetDatabase.ImportAsset(bundleFilePath, ImportAssetOptions.ForceSynchronousImport);
var res = AssetDatabase.LoadAssetAtPath<Object>(bundleFilePath);
ProjectWindowUtil.ShowCreatedAsset(res);
if (!Embedded)
{
bundle.type = BundleType.Local;
var selectLocalPath = true;
var locallySelectedPath = "";
var firstTime = true;
do
{
// Only show dialogue before the second time
if (firstTime)
{
var response = EditorUtility.DisplayDialogComplex("New Npm Package",
"Link to a package on a registry (e.g. NPM) or a local package directory (outside of Unity)",
"Link to registry package",
"Abort", "Link to local package");
switch (response)
{
case 0:
bundle.type = BundleType.Remote;
bundle.packageName = res.name;
bundle.packageVersion = "latest";
bundle.Save(bundle.FilePath, false);
break;
case 1:
Debug.Log("Cancelled");
AssetDatabase.DeleteAsset(bundleFilePath);
return;
case 2:
bundle.type = BundleType.Local;
bundle.Save(bundle.FilePath, false);
break;
}
selectLocalPath = response == 2;
}
// TODO: this should happen before the rename action and then take the name of the existing package
firstTime = false;
if (selectLocalPath)
{
locallySelectedPath = EditorUtility.OpenFolderPanel("Select Local npm package location", locallySelectedPath, "");
if (Directory.Exists(locallySelectedPath))
{
if (File.Exists(locallySelectedPath + "/package.json"))
{
bundle.SetLocalPath(locallySelectedPath);
bundle.Save(bundleFilePath);
return;
}
var folderIsEmpty = new DirectoryInfo(locallySelectedPath).EnumerateFileSystemInfos().Any() == false;
if (!folderIsEmpty)
{
locallySelectedPath += "/" + fileName;
if (Directory.Exists(locallySelectedPath))
{
var isEmpty = new DirectoryInfo(locallySelectedPath).EnumerateFileSystemInfos()
.Any() == false;
if (!isEmpty)
{
Debug.LogError("Directory already exists and is not empty, please select a different location: " + locallySelectedPath);
continue;
}
}
Directory.CreateDirectory(locallySelectedPath);
}
if (!DoesUserWantToCreateANewNpmPackageAtSelectedPath(locallySelectedPath))
{
bundle.SetLocalPath(locallySelectedPath);
return;
}
CreateNewNpmPackageForLinkedBundle(bundle, locallySelectedPath);
return;
}
}
} while (selectLocalPath);
}
else if (Embedded)
{
var packageDirectory = pathName + "~";
CreatePackageAt(packageDirectory, fileName);
Actions.InstallBundle(bundle);
}
}
}
internal static bool DoesUserWantToCreateANewNpmPackageAtSelectedPath(string selectedPath)
{
return EditorUtility.DisplayDialog("Create Package",
"The selected directory does not yet contain an npm package.\n\nDo you want to create a new npm package now at \"" +
selectedPath + "\"", "Yes, create a new npm package", "No, select a different location");
}
internal static void CreateNewNpmPackageForLinkedBundle(Bundle bundle, string locallySelectedPath)
{
if (bundle.IsEmbedded)
{
Debug.LogError("Can not create a new npm package for an embedded bundle");
return;
}
Debug.Log("Creating new package at " + locallySelectedPath);
var bundleFilePath = bundle.FilePath;
var fileName = Path.GetFileNameWithoutExtension(bundleFilePath);
bundle.SetLocalPath(locallySelectedPath);
bundle.Save(bundleFilePath);
CreatePackageAt(locallySelectedPath, fileName);
Actions.InstallBundle(bundle);
// Make sure we re-import to update the importer ui
AssetDatabase.ImportAsset(bundleFilePath, ImportAssetOptions.ForceSynchronousImport);
}
private static void CreatePackageAt(string path, string fileName)
{
CreateBasicNpmPackageAt(path, fileName);
CreateGitignore(path);
CreateDefaultWorkspace(path);
CreateTsConfigTemplate(path);
}
public static void CreateBundle(bool embedded)
{
var creator = ScriptableObject.CreateInstance<CreateAssetAction>();
creator.Embedded = embedded;
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, creator, "New NpmDef", null, null);
}
public static void CreateNpmPackageAt(string packageDirectory)
{
Directory.CreateDirectory(packageDirectory);
if (File.Exists(packageDirectory + "/package.json"))
{
return;
}
var si = new ProcessStartInfo();
si.WorkingDirectory = packageDirectory;
si.FileName = "cmd";
si.Arguments = "/c npm init";
Process.Start(si);
}
public static void CreateBasicNpmPackageAt(string packageDirectory, string name)
{
name = Regex.Replace(name, "[\\s]", "-").ToLowerInvariant();
Directory.CreateDirectory(packageDirectory);
var path = packageDirectory + "/package.json";
if (File.Exists(path))
{
Debug.LogError("Can not create new package: package.json already exists at " + path.AsLink());
return;
}
var package = new NpmPackageJson()
{
name = name,
version = "1.0.0"
};
if (NpmUnityEditorVersions.TryGetVersions(out var versions, NpmUnityEditorVersions.Registry.Npm))
{
if (versions.TryGetValue(Engine.Constants.RuntimeNpmPackageName, out var ver))
{
var version = ver.ToString();
package.peerDependencies.Add(Engine.Constants.RuntimeNpmPackageName, version);
package.devDependencies.Add(Engine.Constants.RuntimeNpmPackageName, version);
}
if (versions.TryGetValue("three", out ver))
{
var version = ver.ToString();
package.peerDependencies.Add("three", version);
package.devDependencies.Add("three", version);
}
if (versions.TryGetValue("@types/three", out ver))
{
var version = ver.ToString();
package.devDependencies.Add("@types/three", version);
}
}
// index.ts
const string indexGuid = "3c5c10ecab85408fab4d6492a7895015";
var indexPath = AssetDatabase.GUIDToAssetPath(indexGuid);
if (File.Exists(indexPath) && indexPath.EndsWith(".ts"))
{
File.Copy(indexPath, packageDirectory + "/index.ts");
package.main = "index.ts";
}
// var runtimePackagePath = ExporterProjectSettings.instance.localRuntimePackage;
// if (Directory.Exists(runtimePackagePath))
// {
// runtimePackagePath = Path.GetFullPath(runtimePackagePath);
// runtimePackagePath = new Uri(Path.GetFullPath(packageDirectory + "/")).MakeRelativeUri(new Uri(runtimePackagePath)).ToString();
// package.devDependencies.Add(Tiny.Constants.RuntimeNpmPackageName, "file:" + runtimePackagePath);
// }
var json = JsonConvert.SerializeObject(package, Formatting.Indented);
File.WriteAllText(path, json);
}
private class NpmPackageJson
{
public string name;
public string version;
public string main;
public Dictionary<string, string> dependencies = new Dictionary<string, string>();
public Dictionary<string, string> peerDependencies = new Dictionary<string, string>();
public Dictionary<string, string> devDependencies = new Dictionary<string, string>();
}
public static void CreateDefaultWorkspace(string packageDirectory)
{
var templatePath = AssetDatabase.GUIDToAssetPath("91caacf95048482080bf4b39313474ba");
if (string.IsNullOrEmpty(templatePath)) return;
var template = AssetDatabase.LoadAssetAtPath<TextAsset>(templatePath);
if (!template) return;
var workspacePath = packageDirectory + "/workspace.code-workspace";
if (File.Exists(workspacePath)) return;
File.WriteAllText(workspacePath, template.text);
}
public static void CreateTsConfigTemplate(string packageDirectory)
{
var templatePath = AssetDatabase.GUIDToAssetPath("b5518772aa40416ea40cd882860c24e4");
if (string.IsNullOrEmpty(templatePath)) return;
var template = AssetDatabase.LoadAssetAtPath<TextAsset>(templatePath);
if (!template) return;
var path = packageDirectory + "/tsconfig.json";
if (File.Exists(path)) return;
File.WriteAllText(path, template.text);
}
public static void CreateGitignore(string packageDirectory)
{
var templatePath = AssetDatabase.GUIDToAssetPath("8a0f3f921f064605a4eb0f260ea47acf");
if (string.IsNullOrEmpty(templatePath)) return;
var template = File.ReadAllText(templatePath);
if (string.IsNullOrEmpty(template)) return;
var path = packageDirectory + "/.gitignore";
if (File.Exists(path)) return;
File.WriteAllText(path, template);
}
}
}