443 lines
15 KiB
C#
443 lines
15 KiB
C#
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|