Files
Shi.Ji e31d3560bb 添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
2026-05-18 11:43:09 +08:00

443 lines
15 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MainShell.Common;
using MainShell.Models;
using MainShell.Recipe.Models;
using Newtonsoft.Json.Linq;
using Stylet;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
namespace MainShell.Recipe.ViewModel
{
public abstract class RecipeViewModelBase<TRecipe> : BaseScreen, IRecipeViewModel
where TRecipe : RecipeBase, new()
{
private string _lastSavedSnapshot = string.Empty;
protected string LastSavedSnapshot => _lastSavedSnapshot;
private bool _skipLeaveCheck;
private RecipeWrap _selectedRecipeWrap;
public ObservableCollection<RecipeWrap> RecipeWraps { get; protected set; } = new ObservableCollection<RecipeWrap>();
public RecipeWrap SelectedRecipeWrap
{
get { return _selectedRecipeWrap; }
set
{
if (_selectedRecipeWrap == value)
return;
if (!_skipLeaveCheck && _selectedRecipeWrap != null && value != null && !TryLeaveCurrentRecipeContext())
{
NotifyOfPropertyChange(() => SelectedRecipeWrap);
return;
}
if (SetAndNotify(ref _selectedRecipeWrap, value))
{
LoadRecipeFromWrap(value);
}
}
}
public RecipeButtonGroupViewModel RecipeButtonGroupViewModel { get; protected set; }
public virtual bool CanCopyRecipe => true;
public virtual bool CanImportRecipe => false;
public virtual bool CanExportRecipe => false;
public bool HasUnsavedChanges => IsRecipeDirty();
public string CurrentRecipeDisplayName => CurrentRecipe != null ? CurrentRecipe.RecipeName : EmptyRecipeDisplayName;
public RecipeManager RecipeManager { get; }
protected RecipeViewModelBase(RecipeManager recipeManager)
{
RecipeManager = recipeManager;
}
protected abstract TRecipe CurrentRecipe { get; set; }
protected abstract string RecipeFolderPath { get; }
protected abstract string EmptyRecipeDisplayName { get; }
protected abstract string GetActiveRecipeName();
protected abstract void SwitchActiveRecipe(string recipeName);
protected abstract void ClearActiveRecipe();
protected virtual void OnRecipeLoaded(TRecipe recipe, RecipeWrap recipeWrap) { }
protected virtual string GetUnsavedChangesMessage() => $"<22><EFBFBD><E4B7BD>{CurrentRecipeDisplayName}<7D><><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD>޸ģ<DEB8><C4A3>Ƿ<EFBFBD><C7B7>ȱ<EFBFBD><C8B1>棿";
protected virtual void AfterDeleteCurrentRecipe(string deletedRecipeName) { }
protected virtual void AfterRecipeRenamed(string oldRecipeName, string newRecipeName) { }
public virtual bool TryLeaveCurrentRecipeContext()
{
if (!HasUnsavedChanges)
return true;
var result = MwMessageBox.Show(GetUnsavedChangesMessage(), <><CEB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ", System.Windows.MessageBoxButton.YesNoCancel, System.Windows.MessageBoxImage.Warning);
if (result == System.Windows.MessageBoxResult.Cancel)
return false;
if (result == System.Windows.MessageBoxResult.Yes)
SaveCurrentRecipe(false);
return true;
}
public void SaveCurrentRecipeSilently()
{
SaveCurrentRecipe(false);
}
protected void RegisterButtonGroupEvents()
{
RecipeButtonGroupViewModel = new RecipeButtonGroupViewModel();
RecipeButtonGroupViewModel.CreateNewRequested -= OnCreateNewRecipe;
RecipeButtonGroupViewModel.ApplyRequested -= OnApplyRecipe;
RecipeButtonGroupViewModel.DeleteRequested -= OnDeleteRecipe;
RecipeButtonGroupViewModel.CopyRequested -= OnCopyRecipe;
RecipeButtonGroupViewModel.ImportRequested -= OnImportRecipe;
RecipeButtonGroupViewModel.SaveRequested -= OnSaveRecipe;
RecipeButtonGroupViewModel.ExportRequested -= OnExportRecipe;
RecipeButtonGroupViewModel.ReNameRequested -= OnReNameRecipe;
RecipeButtonGroupViewModel.ClearRequested -= OnClearRecipe;
RecipeButtonGroupViewModel.CreateNewRequested += OnCreateNewRecipe;
RecipeButtonGroupViewModel.ApplyRequested += OnApplyRecipe;
RecipeButtonGroupViewModel.DeleteRequested += OnDeleteRecipe;
RecipeButtonGroupViewModel.CopyRequested += OnCopyRecipe;
RecipeButtonGroupViewModel.ImportRequested += OnImportRecipe;
RecipeButtonGroupViewModel.SaveRequested += OnSaveRecipe;
RecipeButtonGroupViewModel.ExportRequested += OnExportRecipe;
RecipeButtonGroupViewModel.ReNameRequested += OnReNameRecipe;
RecipeButtonGroupViewModel.ClearRequested += OnClearRecipe;
}
public virtual void OnCreateNewRecipe(object sender, EventArgs eventArgs)
{
if (eventArgs is RecipeEventArgs args)
{
if (!TryLeaveCurrentRecipeContext())
return;
var recipeWrap = new RecipeWrap
{
RecipeName = args.RecipeName,
CreateTime = DateTime.UtcNow,
ModifiedTime = DateTime.UtcNow,
};
RecipeWraps.Add(recipeWrap);
SelectRecipeWrapInternal(recipeWrap);
}
}
public virtual void OnDeleteRecipe(object sender, EventArgs eventArgs)
{
if (SelectedRecipeWrap == null)
return;
if (!TryLeaveCurrentRecipeContext())
return;
var deletedRecipeName = SelectedRecipeWrap.RecipeName;
if (!RecipeManager.DeleteRecipeFolder(RecipeFolderPath, deletedRecipeName, out var error))
{
MwMessageBox.Show(error);
return;
}
RecipeWraps.Remove(SelectedRecipeWrap);
AfterDeleteCurrentRecipe(deletedRecipeName);
if (RecipeWraps.Count > 0)
{
SelectRecipeWrapInternal(RecipeWraps[0]);
}
else
{
SelectedRecipeWrap = null;
CurrentRecipe = null;
ClearActiveRecipe();
CaptureSavedSnapshot();
}
}
public virtual void OnCopyRecipe(object sender, EventArgs eventArgs)
{
if (!(eventArgs is RecipeEventArgs args) || SelectedRecipeWrap == null)
return;
var sourceRecipeName = SelectedRecipeWrap.RecipeName;
if (string.IsNullOrWhiteSpace(sourceRecipeName) || string.IsNullOrWhiteSpace(args.RecipeName))
return;
if (CurrentRecipe != null &&
string.Equals(CurrentRecipe.RecipeName, sourceRecipeName, StringComparison.Ordinal) &&
HasUnsavedChanges)
{
SaveCurrentRecipe(false);
}
if (!RecipeManager.CopyRecipeFolder(RecipeFolderPath, sourceRecipeName, args.RecipeName, out var error))
{
MwMessageBox.Show(error);
return;
}
var copiedRecipeWrap = new RecipeWrap
{
RecipeName = args.RecipeName,
CreateTime = DateTime.UtcNow,
ModifiedTime = DateTime.UtcNow,
};
RecipeWraps.Add(copiedRecipeWrap);
SelectRecipeWrapInternal(copiedRecipeWrap);
}
public virtual void OnApplyRecipe(object sender, EventArgs eventArgs)
{
if (eventArgs is RecipeEventArgs args)
{
var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.RecipeName);
if (recipeWrap == null)
return;
if (!ReferenceEquals(SelectedRecipeWrap, recipeWrap))
{
if (!TryLeaveCurrentRecipeContext())
return;
SelectRecipeWrapInternal(recipeWrap);
}
if (SelectedRecipeWrap == null)
return;
if (CurrentRecipe != null && IsRecipeDirty())
{
SaveCurrentRecipe(false);
}
SwitchActiveRecipe(SelectedRecipeWrap.RecipeName);
UpdateInUseState(SelectedRecipeWrap);
}
}
public abstract void OnImportRecipe(object sender, EventArgs eventArgs);
public virtual void OnSaveRecipe(object sender, EventArgs eventArgs)
{
CommonUti.RunOnUi(() =>
{
if (eventArgs is RecipeEventArgs)
{
SaveCurrentRecipe(true);
}
});
}
public abstract void OnExportRecipe(object sender, EventArgs eventArgs);
public abstract void OnReNameRecipe(object sender, EventArgs eventArgs);
public virtual void OnClearRecipe(object sender, EventArgs eventArgs)
{
if (!TryLeaveCurrentRecipeContext())
return;
if (SelectedRecipeWrap == null)
return;
var clearedRecipe = new TRecipe
{
RecipeName = SelectedRecipeWrap.RecipeName
};
CurrentRecipe = clearedRecipe;
OnRecipeLoaded(clearedRecipe, SelectedRecipeWrap);
NotifyOfPropertyChange(() => HasUnsavedChanges);
}
protected bool IsRecipeDirty()
{
if (CurrentRecipe == null)
return false;
var path = Path.Combine(CurrentRecipe.Dir ?? string.Empty, CurrentRecipe.FileName ?? string.Empty);
if (!File.Exists(path))
return true;
return !AreSnapshotsEquivalent(_lastSavedSnapshot, CurrentRecipe.CreateSnapshot());
}
protected void CaptureSavedSnapshot()
{
_lastSavedSnapshot = CurrentRecipe != null ? CurrentRecipe.CreateSnapshot() : string.Empty;
NotifyOfPropertyChange(() => HasUnsavedChanges);
NotifyOfPropertyChange(() => CurrentRecipeDisplayName);
}
protected bool TryRenameRecipe(RecipeRenameEventArgs args)
{
if (args == null)
return false;
var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.OldRecipeName);
if (recipeWrap == null)
return false;
if (!RecipeManager.RenameFolder(RecipeFolderPath, args.OldRecipeName, args.NewRecipeName, out var error))
{
MwMessageBox.Show(error);
return false;
}
recipeWrap.RecipeName = args.NewRecipeName;
recipeWrap.ModifiedTime = DateTime.UtcNow;
if (CurrentRecipe != null && string.Equals(CurrentRecipe.RecipeName, args.OldRecipeName, StringComparison.Ordinal))
{
CurrentRecipe.RecipeName = args.NewRecipeName;
}
if (ReferenceEquals(recipeWrap, SelectedRecipeWrap))
{
SwitchActiveRecipe(args.NewRecipeName);
}
CaptureSavedSnapshot();
AfterRecipeRenamed(args.OldRecipeName, args.NewRecipeName);
return true;
}
protected void SaveCurrentRecipe(bool showSuccessMessage)
{
if (CurrentRecipe == null)
return;
CurrentRecipe.Write();
CaptureSavedSnapshot();
if (SelectedRecipeWrap != null)
{
SelectedRecipeWrap.ModifiedTime = DateTime.UtcNow;
}
if (showSuccessMessage)
{
MwMessageBox.Show("<22><><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD>");
}
}
private static bool AreSnapshotsEquivalent(string leftSnapshot, string rightSnapshot)
{
var left = string.IsNullOrWhiteSpace(leftSnapshot) ? null : ParseSnapshotToken(leftSnapshot);
var right = string.IsNullOrWhiteSpace(rightSnapshot) ? null : ParseSnapshotToken(rightSnapshot);
if (left == null || right == null)
return false;
return JToken.DeepEquals(left, right);
}
private static JToken ParseSnapshotToken(string snapshot)
{
try
{
return JToken.Parse(snapshot);
}
catch
{
return null;
}
}
protected void SelectRecipeWrapInternal(RecipeWrap recipeWrap)
{
try
{
_skipLeaveCheck = true;
SelectedRecipeWrap = recipeWrap;
}
finally
{
_skipLeaveCheck = false;
}
}
protected void InitializeSelection(string activeRecipeName)
{
SyncSelection(activeRecipeName, true);
}
protected virtual void UpdateInUseState(RecipeWrap activeRecipeWrap)
{
foreach (var wrap in RecipeWraps)
{
wrap.IsInUse = ReferenceEquals(wrap, activeRecipeWrap);
}
}
public void SyncSelectionFromActiveRecipe()
{
SyncSelection(GetActiveRecipeName(), false);
}
private void SyncSelection(string recipeName, bool fallbackToFirst)
{
if (RecipeWraps == null || RecipeWraps.Count == 0)
{
UpdateInUseState(null);
return;
}
var target = string.IsNullOrWhiteSpace(recipeName)
? null
: RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, recipeName, StringComparison.Ordinal));
if (target == null)
{
UpdateInUseState(null);
if (!fallbackToFirst)
return;
target = RecipeWraps[0];
}
if (ReferenceEquals(SelectedRecipeWrap, target))
{
UpdateInUseState(target);
return;
}
SelectRecipeWrapInternal(target);
}
private void LoadRecipeFromWrap(RecipeWrap recipeWrap)
{
if (recipeWrap == null)
{
UpdateInUseStateByActiveRecipe();
return;
}
var newRecipe = new TRecipe
{
RecipeName = recipeWrap.RecipeName
};
newRecipe.Read();
CurrentRecipe = newRecipe;
CaptureSavedSnapshot();
UpdateInUseStateByActiveRecipe();
OnRecipeLoaded(newRecipe, recipeWrap);
}
private void UpdateInUseStateByActiveRecipe()
{
var activeRecipeName = GetActiveRecipeName();
var activeRecipeWrap = RecipeWraps == null || string.IsNullOrWhiteSpace(activeRecipeName)
? null
: RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, activeRecipeName, StringComparison.Ordinal));
UpdateInUseState(activeRecipeWrap);
}
}
}