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; /// /// optional codegen dir /// public string CodeGenDirectory; /// /// optional path to npmdef /// 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(); 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 { /// /// Set to false to show dialogue to link to external package /// 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(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(); 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 dependencies = new Dictionary(); public Dictionary peerDependencies = new Dictionary(); public Dictionary devDependencies = new Dictionary(); } public static void CreateDefaultWorkspace(string packageDirectory) { var templatePath = AssetDatabase.GUIDToAssetPath("91caacf95048482080bf4b39313474ba"); if (string.IsNullOrEmpty(templatePath)) return; var template = AssetDatabase.LoadAssetAtPath(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(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); } } }