Files
bansonic_beta_main/Assets/EasyChart/Scripts/Editor/EasyChartLibraryWindow.Export.cs
T

825 lines
35 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using EasyChart;
namespace EasyChart.Editor
{
public partial class EasyChartLibraryWindow
{
private const string UXML_ROOT_PATH = "Assets/EasyChart/LibraryUxml";
private const string UXML_BACKUPS_SUBFOLDER = "_Backups";
private const string PROFILE_JSON_EXT = ".chartprofile.json";
private void ImportUxmlMirrorToRestoreProfiles()
{
string projectRoot = Directory.GetCurrentDirectory().Replace('\\', '/');
string defaultAbs = Path.Combine(projectRoot, GetActiveUxmlRootPath().Replace('/', Path.DirectorySeparatorChar));
string picked = EditorUtility.OpenFolderPanel("Import UXML Mirror Root", defaultAbs, string.Empty);
if (string.IsNullOrEmpty(picked)) return;
string pickedNorm = picked.Replace('\\', '/');
if (!pickedNorm.StartsWith(projectRoot + "/", StringComparison.OrdinalIgnoreCase))
{
EditorUtility.DisplayDialog("Import", "Please pick a folder inside this Unity project (under the project root).", "OK");
return;
}
string uxmlRootAssetPath = pickedNorm.Substring(projectRoot.Length + 1);
if (!uxmlRootAssetPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) || !AssetDatabase.IsValidFolder(uxmlRootAssetPath))
{
EditorUtility.DisplayDialog("Import", $"Invalid folder: {uxmlRootAssetPath}", "OK");
return;
}
string profileLibraryRoot = ROOT_PATH;
if (uxmlRootAssetPath.StartsWith(UXML_ROOT_PATH, StringComparison.OrdinalIgnoreCase))
{
string rel = uxmlRootAssetPath.Substring(UXML_ROOT_PATH.Length).TrimStart('/');
if (!string.IsNullOrEmpty(rel))
{
int slash = rel.IndexOf('/');
string libraryName = slash >= 0 ? rel.Substring(0, slash) : rel;
if (!string.IsNullOrEmpty(libraryName) && !string.Equals(libraryName, UXML_BACKUPS_SUBFOLDER, StringComparison.OrdinalIgnoreCase))
{
profileLibraryRoot = $"{ROOT_PATH}/{libraryName}";
}
}
}
string uxmlRootFullPath = Path.Combine(projectRoot, uxmlRootAssetPath.Replace('/', Path.DirectorySeparatorChar));
if (!Directory.Exists(uxmlRootFullPath))
{
EditorUtility.DisplayDialog("Import", "Selected folder does not exist on disk.", "OK");
return;
}
string backupsRoot = (uxmlRootAssetPath + "/" + UXML_BACKUPS_SUBFOLDER).Replace('\\', '/');
string[] files;
try
{
files = Directory.GetFiles(uxmlRootFullPath, "*.uxml", SearchOption.AllDirectories);
}
catch (Exception e)
{
EditorUtility.DisplayDialog("Import", e.Message, "OK");
return;
}
int created = 0;
int createdFromJson = 0;
int skippedExisting = 0;
int skippedInvalid = 0;
for (int i = 0; i < files.Length; i++)
{
string full = files[i];
string fullNorm = full.Replace('\\', '/');
string assetPath;
if (fullNorm.StartsWith(projectRoot + "/", StringComparison.OrdinalIgnoreCase))
{
assetPath = fullNorm.Substring(projectRoot.Length + 1);
}
else
{
continue;
}
if (!assetPath.StartsWith(uxmlRootAssetPath, StringComparison.OrdinalIgnoreCase)) continue;
if (assetPath.StartsWith(backupsRoot, StringComparison.OrdinalIgnoreCase)) continue;
string rel = assetPath.Substring(uxmlRootAssetPath.Length).TrimStart('/');
if (string.IsNullOrEmpty(rel)) continue;
string relDir = Path.GetDirectoryName(rel)?.Replace('\\', '/');
string baseName = Path.GetFileNameWithoutExtension(rel);
if (string.IsNullOrEmpty(baseName)) { skippedInvalid++; continue; }
string destFolder = string.IsNullOrEmpty(relDir) ? profileLibraryRoot : $"{profileLibraryRoot}/{relDir}";
EnsureFolderExists(destFolder);
string destAssetPath = $"{destFolder}/{baseName}.asset";
if (AssetDatabase.LoadAssetAtPath<ChartProfile>(destAssetPath) != null)
{
skippedExisting++;
continue;
}
string jsonFullPath = Path.Combine(Path.GetDirectoryName(full) ?? string.Empty, baseName + PROFILE_JSON_EXT);
var profile = ScriptableObject.CreateInstance<ChartProfile>();
bool restoredFromJson = false;
if (File.Exists(jsonFullPath))
{
try
{
string json = File.ReadAllText(jsonFullPath);
if (!string.IsNullOrEmpty(json))
{
EditorJsonUtility.FromJsonOverwrite(json, profile);
restoredFromJson = true;
}
}
catch
{
restoredFromJson = false;
}
}
if (!restoredFromJson)
{
string text;
try
{
text = File.ReadAllText(full);
}
catch
{
skippedInvalid++;
continue;
}
if (!TryParseChartElementStyle(text, out float w, out float h))
{
w = 300f;
h = 200f;
}
profile.chartWidth = w;
profile.chartHeight = h;
}
if (string.IsNullOrEmpty(profile.chartName)) profile.chartName = baseName;
AssetDatabase.CreateAsset(profile, destAssetPath);
created++;
if (restoredFromJson) createdFromJson++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
RefreshTree();
ScheduleRefreshSeriesList();
ScheduleUpdatePreview();
EditorUtility.DisplayDialog(
"Import",
$"Restored {created} profile(s) ({createdFromJson} from JSON). Skipped {skippedExisting} existing, {skippedInvalid} invalid.",
"OK");
}
private void ImportUxmlRestoreSelectedProfile(string profilePath)
{
if (string.IsNullOrEmpty(profilePath)) return;
var profile = AssetDatabase.LoadAssetAtPath<ChartProfile>(profilePath);
if (profile == null)
{
EditorUtility.DisplayDialog("Import", "No valid ChartProfile selected.", "OK");
return;
}
string profileName = Path.GetFileNameWithoutExtension(profilePath);
string libraryRoot = GetActiveProfileRootPath();
// Calculate relative path
string relativePath = profilePath;
if (relativePath.StartsWith(libraryRoot))
{
relativePath = relativePath.Substring(libraryRoot.Length).TrimStart('/');
}
string relativeFolder = Path.GetDirectoryName(relativePath)?.Replace('\\', '/');
// Build UXML path
string uxmlRoot = GetActiveUxmlRootPath();
string uxmlFolder = string.IsNullOrEmpty(relativeFolder) ? uxmlRoot : $"{uxmlRoot}/{relativeFolder}";
string uxmlPath = $"{uxmlFolder}/{profileName}.uxml";
string jsonPath = $"{uxmlFolder}/{profileName}{PROFILE_JSON_EXT}";
string projectRoot = Directory.GetCurrentDirectory().Replace('\\', '/');
string jsonFullPath = Path.Combine(projectRoot, jsonPath.Replace('/', Path.DirectorySeparatorChar));
string uxmlFullPath = Path.Combine(projectRoot, uxmlPath.Replace('/', Path.DirectorySeparatorChar));
bool restoredFromJson = false;
if (File.Exists(jsonFullPath))
{
try
{
string json = File.ReadAllText(jsonFullPath);
if (!string.IsNullOrEmpty(json))
{
Undo.RecordObject(profile, "Restore Profile from UXML");
EditorJsonUtility.FromJsonOverwrite(json, profile);
EditorUtility.SetDirty(profile);
restoredFromJson = true;
}
}
catch (Exception e)
{
Debug.LogWarning($"[EasyChart] Failed to restore from JSON: {e.Message}");
}
}
if (!restoredFromJson && File.Exists(uxmlFullPath))
{
try
{
string text = File.ReadAllText(uxmlFullPath);
if (TryParseChartElementStyle(text, out float w, out float h))
{
Undo.RecordObject(profile, "Restore Profile from UXML");
profile.chartWidth = w;
profile.chartHeight = h;
EditorUtility.SetDirty(profile);
restoredFromJson = true;
}
}
catch (Exception e)
{
Debug.LogWarning($"[EasyChart] Failed to restore from UXML: {e.Message}");
}
}
if (restoredFromJson)
{
AssetDatabase.SaveAssets();
ScheduleRefreshSeriesList();
ScheduleUpdatePreview();
EditorUtility.DisplayDialog("Import", $"Restored profile '{profileName}' from UXML.", "OK");
}
else
{
EditorUtility.DisplayDialog("Import", $"No UXML/JSON found for profile '{profileName}'.\nExpected: {uxmlPath}", "OK");
}
}
private void ImportUxmlFromBackup()
{
string projectRoot = Directory.GetCurrentDirectory().Replace('\\', '/');
string backupRoot = $"{GetActiveUxmlRootPath()}/{UXML_BACKUPS_SUBFOLDER}";
string defaultAbs = Path.Combine(projectRoot, backupRoot.Replace('/', Path.DirectorySeparatorChar));
string picked = EditorUtility.OpenFolderPanel("Select Backup Folder", defaultAbs, string.Empty);
if (string.IsNullOrEmpty(picked)) return;
string pickedNorm = picked.Replace('\\', '/');
if (!pickedNorm.StartsWith(projectRoot + "/", StringComparison.OrdinalIgnoreCase))
{
EditorUtility.DisplayDialog("Import", "Please pick a folder inside this Unity project.", "OK");
return;
}
string uxmlRootAssetPath = pickedNorm.Substring(projectRoot.Length + 1);
if (!uxmlRootAssetPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) || !AssetDatabase.IsValidFolder(uxmlRootAssetPath))
{
EditorUtility.DisplayDialog("Import", $"Invalid folder: {uxmlRootAssetPath}", "OK");
return;
}
string profileLibraryRoot = GetActiveProfileRootPath();
string uxmlRootFullPath = Path.Combine(projectRoot, uxmlRootAssetPath.Replace('/', Path.DirectorySeparatorChar));
string[] files;
try
{
files = Directory.GetFiles(uxmlRootFullPath, "*.uxml", SearchOption.AllDirectories);
}
catch (Exception e)
{
EditorUtility.DisplayDialog("Import", e.Message, "OK");
return;
}
int created = 0;
int createdFromJson = 0;
int skippedExisting = 0;
int skippedInvalid = 0;
for (int i = 0; i < files.Length; i++)
{
string full = files[i];
string fullNorm = full.Replace('\\', '/');
string assetPath;
if (fullNorm.StartsWith(projectRoot + "/", StringComparison.OrdinalIgnoreCase))
{
assetPath = fullNorm.Substring(projectRoot.Length + 1);
}
else
{
continue;
}
if (!assetPath.StartsWith(uxmlRootAssetPath, StringComparison.OrdinalIgnoreCase)) continue;
string rel = assetPath.Substring(uxmlRootAssetPath.Length).TrimStart('/');
if (string.IsNullOrEmpty(rel)) continue;
string relDir = Path.GetDirectoryName(rel)?.Replace('\\', '/');
string baseName = Path.GetFileNameWithoutExtension(rel);
if (string.IsNullOrEmpty(baseName)) { skippedInvalid++; continue; }
string destFolder = string.IsNullOrEmpty(relDir) ? profileLibraryRoot : $"{profileLibraryRoot}/{relDir}";
EnsureFolderExists(destFolder);
string destAssetPath = $"{destFolder}/{baseName}.asset";
if (AssetDatabase.LoadAssetAtPath<ChartProfile>(destAssetPath) != null)
{
skippedExisting++;
continue;
}
string jsonFullPath = Path.Combine(Path.GetDirectoryName(full) ?? string.Empty, baseName + PROFILE_JSON_EXT);
var profile = ScriptableObject.CreateInstance<ChartProfile>();
bool restoredFromJson = false;
if (File.Exists(jsonFullPath))
{
try
{
string json = File.ReadAllText(jsonFullPath);
if (!string.IsNullOrEmpty(json))
{
EditorJsonUtility.FromJsonOverwrite(json, profile);
restoredFromJson = true;
}
}
catch
{
restoredFromJson = false;
}
}
if (!restoredFromJson)
{
string text;
try
{
text = File.ReadAllText(full);
}
catch
{
skippedInvalid++;
continue;
}
if (!TryParseChartElementStyle(text, out float w, out float h))
{
w = 300f;
h = 200f;
}
profile.chartWidth = w;
profile.chartHeight = h;
}
if (string.IsNullOrEmpty(profile.chartName)) profile.chartName = baseName;
AssetDatabase.CreateAsset(profile, destAssetPath);
created++;
if (restoredFromJson) createdFromJson++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
RefreshTree();
ScheduleRefreshSeriesList();
ScheduleUpdatePreview();
EditorUtility.DisplayDialog(
"Import from Backup",
$"Restored {created} profile(s) ({createdFromJson} from JSON). Skipped {skippedExisting} existing, {skippedInvalid} invalid.",
"OK");
}
private void ImportUnityPackage()
{
string picked = EditorUtility.OpenFilePanel("Import Unity Package", "", "unitypackage");
if (string.IsNullOrEmpty(picked)) return;
AssetDatabase.ImportPackage(picked, true);
}
private static bool TryParseChartElementStyle(string uxml, out float width, out float height)
{
width = 0f;
height = 0f;
if (string.IsNullOrEmpty(uxml)) return false;
var m = Regex.Match(uxml, "style=\"([^\"]*)\"", RegexOptions.IgnoreCase);
if (!m.Success) return false;
string style = m.Groups[1].Value;
bool ok = false;
var mw = Regex.Match(style, "width\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)px", RegexOptions.IgnoreCase);
if (mw.Success && float.TryParse(mw.Groups[1].Value, out var w))
{
width = w;
ok = true;
}
var mh = Regex.Match(style, "height\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)px", RegexOptions.IgnoreCase);
if (mh.Success && float.TryParse(mh.Groups[1].Value, out var h))
{
height = h;
ok = true;
}
return ok;
}
private static string BuildProfileBackupJson(ChartProfile profile)
{
if (profile == null) return string.Empty;
return EditorJsonUtility.ToJson(profile, true);
}
private void ExportSelectedProfileUnityPackage()
{
try
{
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
{
EditorUtility.DisplayDialog("Export Package", "Unity is busy (compiling/updating). Please try again after it finishes.", "OK");
return;
}
if (_selectedProfile == null)
{
EditorUtility.DisplayDialog("Export Package", "Please select a Chart Profile first.", "OK");
return;
}
string profilePath = AssetDatabase.GetAssetPath(_selectedProfile);
if (string.IsNullOrEmpty(profilePath))
{
EditorUtility.DisplayDialog("Export Package", "Selected profile has no asset path.", "OK");
return;
}
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string baseName = SanitizeFileName(Path.GetFileNameWithoutExtension(profilePath));
if (string.IsNullOrEmpty(baseName)) baseName = SanitizeFileName(_selectedProfile.name);
if (string.IsNullOrEmpty(baseName)) baseName = "ChartProfile";
string destFolder = $"{GetActiveUxmlRootPath()}/{UXML_BACKUPS_SUBFOLDER}/UnityPackages";
EnsureFolderExists(destFolder);
string destAssetPath = $"{destFolder}/{baseName}_{timestamp}.unitypackage";
string projectRoot = Directory.GetCurrentDirectory();
string destFullPath = Path.Combine(projectRoot, destAssetPath.Replace('/', Path.DirectorySeparatorChar));
AssetDatabase.ExportPackage(
new[] { profilePath },
destFullPath,
ExportPackageOptions.IncludeDependencies | ExportPackageOptions.Recurse);
EditorUtility.DisplayDialog("Export Package", $"Exported to {destAssetPath}", "OK");
}
catch (Exception ex)
{
Debug.LogError($"[EasyChartLibraryWindow] ExportSelectedProfileUnityPackage failed: {ex.Message}\n{ex.StackTrace}");
EditorUtility.DisplayDialog("Export Package", ex.Message, "OK");
}
}
private void ExportAllProfilesUnityPackage()
{
try
{
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
{
EditorUtility.DisplayDialog("Export Package", "Unity is busy (compiling/updating). Please try again after it finishes.", "OK");
return;
}
var guids = AssetDatabase.FindAssets("t:ChartProfile", new[] { GetActiveProfileRootPath() });
if (guids == null || guids.Length == 0)
{
EditorUtility.DisplayDialog("Export Package", "No ChartProfile found under the library root.", "OK");
return;
}
var assetPaths = new List<string>(guids.Length);
for (int i = 0; i < guids.Length; i++)
{
string p = AssetDatabase.GUIDToAssetPath(guids[i]);
if (string.IsNullOrEmpty(p)) continue;
assetPaths.Add(p);
}
if (assetPaths.Count == 0)
{
EditorUtility.DisplayDialog("Export Package", "No valid ChartProfile asset paths found.", "OK");
return;
}
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string destFolder = $"{GetActiveUxmlRootPath()}/{UXML_BACKUPS_SUBFOLDER}/UnityPackages";
EnsureFolderExists(destFolder);
string destAssetPath = $"{destFolder}/EasyChartLibraryProfiles_{timestamp}.unitypackage";
string projectRoot = Directory.GetCurrentDirectory();
string destFullPath = Path.Combine(projectRoot, destAssetPath.Replace('/', Path.DirectorySeparatorChar));
AssetDatabase.ExportPackage(
assetPaths.ToArray(),
destFullPath,
ExportPackageOptions.IncludeDependencies | ExportPackageOptions.Recurse);
EditorUtility.DisplayDialog("Export Package", $"Exported to {destAssetPath}", "OK");
}
catch (Exception ex)
{
Debug.LogError($"[EasyChartLibraryWindow] ExportAllProfilesUnityPackage failed: {ex.Message}\n{ex.StackTrace}");
EditorUtility.DisplayDialog("Export Package", ex.Message, "OK");
}
}
private void ExportToUxml()
{
try
{
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
{
EditorUtility.DisplayDialog("Export", "Unity is busy (compiling/updating). Please try again after it finishes.", "OK");
return;
}
if (_selectedProfile == null)
{
EditorUtility.DisplayDialog("Error", "Please select a Chart Profile first.", "OK");
return;
}
string profilePath = AssetDatabase.GetAssetPath(_selectedProfile);
string exportFolder = Path.GetDirectoryName(profilePath)?.Replace("\\", "/");
if (string.IsNullOrEmpty(exportFolder) || !AssetDatabase.IsValidFolder(exportFolder))
{
EditorUtility.DisplayDialog("Error", "Invalid export folder for the selected profile.", "OK");
return;
}
string baseName = SanitizeFileName(_selectedProfile.name);
string fileName = baseName + ".uxml";
string path = $"{exportFolder}/{fileName}";
string content = BuildUxmlContentForProfile(_selectedProfile);
string projectRoot = Directory.GetCurrentDirectory();
string fullPath = Path.Combine(projectRoot, path.Replace('/', Path.DirectorySeparatorChar));
WriteTextFileIfChanged(fullPath, content);
EditorApplication.delayCall += () =>
{
if (!EditorApplication.isCompiling && !EditorApplication.isUpdating) AssetDatabase.Refresh();
};
EditorUtility.DisplayDialog("Export Successful", $"Exported to {path}", "OK");
}
catch (Exception ex)
{
Debug.LogError($"[EasyChartLibraryWindow] ExportToUxml failed: {ex.Message}\n{ex.StackTrace}");
EditorUtility.DisplayDialog("Export", ex.Message, "OK");
}
}
private static bool WriteTextFileIfChanged(string fullPath, string content)
{
try
{
if (File.Exists(fullPath))
{
string existing = File.ReadAllText(fullPath);
if (string.Equals(existing, content, StringComparison.Ordinal))
{
return false;
}
}
File.WriteAllText(fullPath, content);
return true;
}
catch (Exception ex)
{
Debug.LogError($"[EasyChartLibraryWindow] WriteTextFileIfChanged failed: path='{fullPath}' err='{ex.Message}'\n{ex.StackTrace}");
return false;
}
}
private string GetBackupRootFolder(string label)
{
label = SanitizeFileName(label);
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
return $"{GetActiveUxmlRootPath()}/{UXML_BACKUPS_SUBFOLDER}/{label}_{timestamp}";
}
private static string BuildUxmlContentForProfile(ChartProfile profile)
{
if (profile == null) return string.Empty;
string profilePath = AssetDatabase.GetAssetPath(profile);
string profileKey = Path.GetFileNameWithoutExtension(profilePath);
if (string.IsNullOrEmpty(profileKey)) profileKey = profile.name;
string profileGuid = AssetDatabase.AssetPathToGUID(profilePath);
return $@"<ui:UXML xmlns:ui=""UnityEngine.UIElements"" xmlns:ec=""EasyChart"" editor-extension-mode=""False"">
<ec:ChartElement profile-name=""{profileKey}"" profile-guid=""{profileGuid}"" style=""width: {profile.chartWidth}px; height: {profile.chartHeight}px;"" />
</ui:UXML>";
}
private void ExportAllToUxmlMirror()
{
ExportFolderToUxmlMirrorInternal(GetActiveProfileRootPath(), GetActiveUxmlRootPath(), pruneOrphans: true);
}
private void ExportAllToUxmlMirrorBackup()
{
string backupRoot = GetBackupRootFolder("ExportAll");
ExportFolderToUxmlMirrorInternal(GetActiveProfileRootPath(), backupRoot, pruneOrphans: false);
}
private void ExportFolderToUxmlMirror(string folderPath)
{
if (string.IsNullOrEmpty(folderPath) || !AssetDatabase.IsValidFolder(folderPath)) return;
string activeProfileRoot = GetActiveProfileRootPath();
string activeUxmlRoot = GetActiveUxmlRootPath();
string rel = string.Empty;
if (folderPath.StartsWith(activeProfileRoot, StringComparison.OrdinalIgnoreCase))
{
rel = folderPath.Substring(activeProfileRoot.Length).TrimStart('/');
}
string pruneRoot = string.IsNullOrEmpty(rel) ? activeUxmlRoot : $"{activeUxmlRoot}/{rel}";
ExportFolderToUxmlMirrorInternal(folderPath, activeUxmlRoot, pruneOrphans: true, pruneRoot: pruneRoot);
}
private void ExportFolderToUxmlMirrorBackup(string folderPath)
{
if (string.IsNullOrEmpty(folderPath) || !AssetDatabase.IsValidFolder(folderPath)) return;
string label = Path.GetFileName(folderPath);
if (string.IsNullOrEmpty(label)) label = "ExportFolder";
string backupRoot = GetBackupRootFolder(label);
ExportFolderToUxmlMirrorInternal(folderPath, backupRoot, pruneOrphans: false);
}
private void ExportFolderToUxmlMirrorInternal(string folderPath, string destRoot, bool pruneOrphans, string pruneRoot = null)
{
try
{
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
{
EditorUtility.DisplayDialog("Export", "Unity is busy (compiling/updating). Please try again after it finishes.", "OK");
return;
}
if (string.IsNullOrEmpty(folderPath) || !AssetDatabase.IsValidFolder(folderPath)) return;
if (string.IsNullOrEmpty(destRoot)) return;
EnsureFolderExists(destRoot);
string activeProfileRoot = GetActiveProfileRootPath();
var guids = AssetDatabase.FindAssets("t:ChartProfile", new[] { folderPath });
if (guids == null || guids.Length == 0)
{
EditorUtility.DisplayDialog("Export", "No ChartProfile found in this folder.", "OK");
return;
}
var expectedAssetPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var usedThisRun = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
int exported = 0;
int skippedConflicts = 0;
for (int i = 0; i < guids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
if (string.IsNullOrEmpty(assetPath)) continue;
var profile = AssetDatabase.LoadAssetAtPath<ChartProfile>(assetPath);
if (profile == null) continue;
string assetFolder = Path.GetDirectoryName(assetPath)?.Replace('\\', '/');
if (string.IsNullOrEmpty(assetFolder)) continue;
string relativeFolder = string.Empty;
if (assetFolder.StartsWith(activeProfileRoot, StringComparison.OrdinalIgnoreCase))
{
relativeFolder = assetFolder.Substring(activeProfileRoot.Length).TrimStart('/');
}
string destFolder = string.IsNullOrEmpty(relativeFolder) ? destRoot : $"{destRoot}/{relativeFolder}";
EnsureFolderExists(destFolder);
string baseName = SanitizeFileName(Path.GetFileNameWithoutExtension(assetPath));
if (string.IsNullOrEmpty(baseName)) continue;
string destAssetPath = $"{destFolder}/{baseName}.uxml";
if (usedThisRun.Contains(destAssetPath))
{
skippedConflicts++;
continue;
}
usedThisRun.Add(destAssetPath);
expectedAssetPaths.Add(destAssetPath);
string projectRoot = Directory.GetCurrentDirectory();
string destFullPath = Path.Combine(projectRoot, destAssetPath.Replace('/', Path.DirectorySeparatorChar));
WriteTextFileIfChanged(destFullPath, BuildUxmlContentForProfile(profile));
string jsonAssetPath = $"{destFolder}/{baseName}{PROFILE_JSON_EXT}";
string jsonFullPath = Path.Combine(projectRoot, jsonAssetPath.Replace('/', Path.DirectorySeparatorChar));
WriteTextFileIfChanged(jsonFullPath, BuildProfileBackupJson(profile));
exported++;
}
if (pruneOrphans)
{
PruneOrphanUxmlFiles(string.IsNullOrEmpty(pruneRoot) ? destRoot : pruneRoot, expectedAssetPaths);
}
EditorApplication.delayCall += () =>
{
if (!EditorApplication.isCompiling && !EditorApplication.isUpdating) AssetDatabase.Refresh();
};
if (skippedConflicts > 0)
{
EditorUtility.DisplayDialog("Export", $"Exported {exported} UXML file(s). Skipped {skippedConflicts} name conflict(s).", "OK");
}
else
{
EditorUtility.DisplayDialog("Export", $"Exported {exported} UXML file(s).", "OK");
}
}
catch (Exception ex)
{
Debug.LogError($"[EasyChartLibraryWindow] ExportFolderToUxmlMirrorInternal failed: folder='{folderPath}' destRoot='{destRoot}' pruneOrphans='{pruneOrphans}' err='{ex.Message}'\n{ex.StackTrace}");
EditorUtility.DisplayDialog("Export", ex.Message, "OK");
}
}
private static void PruneOrphanUxmlFiles(string destRoot, HashSet<string> expectedAssetPaths)
{
try
{
if (string.IsNullOrEmpty(destRoot)) return;
if (destRoot.IndexOf("/" + UXML_BACKUPS_SUBFOLDER, StringComparison.OrdinalIgnoreCase) >= 0) return;
string projectRoot = Directory.GetCurrentDirectory();
string destRootFullPath = Path.Combine(projectRoot, destRoot.Replace('/', Path.DirectorySeparatorChar));
if (!Directory.Exists(destRootFullPath)) return;
string[] files;
try
{
files = Directory.GetFiles(destRootFullPath, "*.*", SearchOption.AllDirectories);
}
catch (Exception e)
{
Debug.LogError($"[EasyChartLibraryWindow] PruneOrphanUxmlFiles scan failed: root='{destRootFullPath}' err='{e.Message}'\n{e.StackTrace}");
return;
}
for (int i = 0; i < files.Length; i++)
{
string full = files[i];
string projectRootNormalized = projectRoot.Replace('\\', '/');
string fullNormalized = full.Replace('\\', '/');
string assetPath;
if (fullNormalized.StartsWith(projectRootNormalized + "/", StringComparison.OrdinalIgnoreCase))
{
assetPath = fullNormalized.Substring(projectRootNormalized.Length + 1);
}
else
{
assetPath = fullNormalized;
}
if (assetPath.IndexOf("/" + UXML_BACKUPS_SUBFOLDER + "/", StringComparison.OrdinalIgnoreCase) >= 0) continue;
bool isUxml = assetPath.EndsWith(".uxml", StringComparison.OrdinalIgnoreCase);
bool isJson = assetPath.EndsWith(PROFILE_JSON_EXT, StringComparison.OrdinalIgnoreCase);
if (!isUxml && !isJson) continue;
if (expectedAssetPaths != null)
{
if (isUxml && expectedAssetPaths.Contains(assetPath)) continue;
if (isJson)
{
string correspondingUxml = assetPath.Substring(0, assetPath.Length - PROFILE_JSON_EXT.Length) + ".uxml";
if (expectedAssetPaths.Contains(correspondingUxml)) continue;
}
}
AssetDatabase.DeleteAsset(assetPath);
}
}
catch (Exception ex)
{
Debug.LogError($"[EasyChartLibraryWindow] PruneOrphanUxmlFiles failed: destRoot='{destRoot}' err='{ex.Message}'\n{ex.StackTrace}");
}
}
}
}