refactor(wpf): decouple the view model from the window and localize its output

Introduce IShellView and IFileDialogs so MainWindowVm no longer holds the
concrete window or reaches through MainWindow.Instance, and file pickers are
injectable.

Run Restore off the UI thread like Patch and gate both buttons on IsBusy.
Gate the log commands on a non-empty log.

Move runtime log messages into the locale dictionaries and add the 14 new keys
to all 12 languages.
Track the injected locale dictionary so switching language replaces it instead
of appending a new one each time.

Remove the unused InfoItem control, ToVisibilityInvertedConverter, mw_title and
the popup placeholder title.
This commit is contained in:
kitbyte
2026-08-29 16:56:36 +03:00
parent 572b61ac25
commit e04e313fa3
30 changed files with 467 additions and 330 deletions
-1
View File
@@ -15,7 +15,6 @@
<FontFamily x:Key="Inter" >pack://application:,,,/Style/#Inter 18pt 18pt</FontFamily>
<converters:ToVisibilityConverter x:Key="ToVisibilityConverter"/>
<converters:ToVisibilityInvertedConverter x:Key="ToVisibilityInvertedConverter"/>
</ResourceDictionary>
</Application.Resources>
</Application>
@@ -18,29 +18,7 @@ namespace WandEnhancer.Converters
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch (value)
{
case null:
return False;
case bool booleanValue:
return booleanValue ? True : False;
}
if (!(value is int intValue))
{
return True;
}
switch (parameter)
{
case null:
return intValue == 0 ? False : True;
case int param:
return intValue > param ? True : False;
default:
//Because object not null
return True;
}
return value is bool booleanValue && booleanValue ? True : False;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
@@ -9,10 +9,4 @@ namespace WandEnhancer.Converters
{ }
}
internal sealed class ToVisibilityInvertedConverter : BaseBooleanConverter<Visibility>
{
public ToVisibilityInvertedConverter() :
base(Visibility.Collapsed, Visibility.Visible)
{ }
}
}
@@ -28,6 +28,22 @@ namespace WandEnhancer.Core.Services
private static CultureInfo _currentLanguage;
private static ResourceDictionary _englishBaseDictionary;
private static ResourceDictionary _activeLocaleDictionary;
/// <summary>
/// Localized string for <paramref name="key"/>, falling back to the key itself so a
/// missing entry is visible rather than silently blank.
/// </summary>
public static string Get(string key)
{
return Application.Current?.TryFindResource(key) as string ?? key;
}
/// <summary>Localized format string filled with <paramref name="args"/>.</summary>
public static string Format(string key, params object[] args)
{
return string.Format(Get(key), args);
}
public static CultureInfo CurrentLanguage
{
@@ -104,20 +120,19 @@ namespace WandEnhancer.Core.Services
localeDict[entry.Key] = targetDict[entry.Key];
}
// Find and replace the old locale dictionary
var oldDict = Application.Current.Resources.MergedDictionaries
.FirstOrDefault(d => d.Source != null && d.Source.OriginalString.StartsWith("Locale/lang."));
if (oldDict != null)
// Track the dictionary we injected: it is built by merging entries, so its Source is
// null and a Source-based lookup never finds it - every switch used to append another.
var merged = Application.Current.Resources.MergedDictionaries;
if (_activeLocaleDictionary != null && merged.Contains(_activeLocaleDictionary))
{
var index = Application.Current.Resources.MergedDictionaries.IndexOf(oldDict);
Application.Current.Resources.MergedDictionaries.Remove(oldDict);
Application.Current.Resources.MergedDictionaries.Insert(index, localeDict);
merged[merged.IndexOf(_activeLocaleDictionary)] = localeDict;
}
else
{
Application.Current.Resources.MergedDictionaries.Add(localeDict);
merged.Add(localeDict);
}
_activeLocaleDictionary = localeDict;
if (saveSettings)
{
@@ -27,8 +27,7 @@ namespace WandEnhancer.Core.Services
}
catch (Exception)
{
// Settings loading is non-critical - silently fall back to defaults
// This can fail due to file permissions, corrupted JSON, etc.
// Unreadable or corrupt settings must not block startup; defaults apply.
}
return null;
}
@@ -42,8 +41,7 @@ namespace WandEnhancer.Core.Services
}
catch (Exception)
{
// Settings saving is non-critical - silently ignore errors
// This can fail due to file permissions or read-only directories
// A read-only install directory must not break the app; the choice is lost, not fatal.
}
}
}
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Ordnerpfad</s:String>
<s:String x:Key="mw_folder_not_found">Ordner nicht gefunden</s:String>
<s:String x:Key="mw_patch">Anwenden</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Ausgewählte .js-Dateien werden in Wand gepackt und im Renderer geladen.</s:String>
<s:String x:Key="pv_no_custom_scripts">Keine Skripte ausgewählt</s:String>
<s:String x:Key="pv_start">Starten</s:String>
<s:String x:Key="pv_auto_apply">Nach Updates automatisch anwenden</s:String>
<s:String x:Key="pv_popup_title">Was werden wir verbessern?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">WeMod-Verzeichnis unter {0} ({1}) gefunden</s:String>
<s:String x:Key="log_already_patched">WeMod ist bereits gepatcht. Wenn Sie erneut patchen möchten, stellen Sie bitte zuerst das Backup wieder her.</s:String>
<s:String x:Key="log_ready">Bereit zum Patchen.</s:String>
<s:String x:Key="log_install_not_found">WeMod-Verzeichnis nicht gefunden.</s:String>
<s:String x:Key="log_no_directory">Vorgang nicht möglich. Bitte geben Sie zuerst das Verzeichnis an.</s:String>
<s:String x:Key="log_invalid_directory">Der ausgewählte Ordner {0} ist kein gültiges WeMod-Verzeichnis.</s:String>
<s:String x:Key="log_restore_failed">Fehler beim Wiederherstellen des Backups: {0}</s:String>
<s:String x:Key="log_patch_failed">Fehler beim Patchen: {0}</s:String>
<s:String x:Key="log_copied">Protokolle in die Zwischenablage kopiert.</s:String>
<s:String x:Key="log_copy_failed">Fehler beim Kopieren der Protokolle: {0}</s:String>
<s:String x:Key="log_exported">Protokolle nach {0} exportiert.</s:String>
<s:String x:Key="log_export_failed">Fehler beim Exportieren der Protokolle: {0}</s:String>
<s:String x:Key="log_open_link_failed">{0} konnte nicht in einem Browser geöffnet werden.</s:String>
<s:String x:Key="dialog_pick_install">Wählen Sie das WeMod-Verzeichnis aus</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Folder path</s:String>
<s:String x:Key="mw_folder_not_found">Folder not found</s:String>
<s:String x:Key="mw_patch">Enhance</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Selected .js files are packed into Wand and loaded in the renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">No scripts selected</s:String>
<s:String x:Key="pv_start">Start</s:String>
<s:String x:Key="pv_auto_apply">Auto-apply after updates</s:String>
<s:String x:Key="pv_popup_title">What are we gonna enhance?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">WeMod directory found at {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod already patched. If you want to patch again, please restore the backup first.</s:String>
<s:String x:Key="log_ready">Ready for patching.</s:String>
<s:String x:Key="log_install_not_found">WeMod directory not found.</s:String>
<s:String x:Key="log_no_directory">Cant be done. Please specify the directory first.</s:String>
<s:String x:Key="log_invalid_directory">The selected folder {0} is not a valid WeMod directory.</s:String>
<s:String x:Key="log_restore_failed">Failed to restore backup: {0}</s:String>
<s:String x:Key="log_patch_failed">Failed to patch: {0}</s:String>
<s:String x:Key="log_copied">Logs copied to clipboard.</s:String>
<s:String x:Key="log_copy_failed">Failed to copy logs: {0}</s:String>
<s:String x:Key="log_exported">Logs exported to {0}.</s:String>
<s:String x:Key="log_export_failed">Failed to export logs: {0}</s:String>
<s:String x:Key="log_open_link_failed">Could not open {0} in a browser.</s:String>
<s:String x:Key="dialog_pick_install">Select the WeMod directory</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Ruta de la carpeta</s:String>
<s:String x:Key="mw_folder_not_found">Carpeta no encontrada</s:String>
<s:String x:Key="mw_patch">Aplicar</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Los archivos .js seleccionados se empaquetan en Wand y se cargan en el renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">No hay scripts seleccionados</s:String>
<s:String x:Key="pv_start">Iniciar</s:String>
<s:String x:Key="pv_auto_apply">Aplicar automáticamente tras actualizar</s:String>
<s:String x:Key="pv_popup_title">¿Qué vamos a mejorar?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Directorio de WeMod encontrado en {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod ya está parcheado. Si quieres parchear de nuevo, restaura la copia de seguridad primero.</s:String>
<s:String x:Key="log_ready">Listo para parchear.</s:String>
<s:String x:Key="log_install_not_found">Directorio de WeMod no encontrado.</s:String>
<s:String x:Key="log_no_directory">No se puede realizar. Por favor, especifica el directorio primero.</s:String>
<s:String x:Key="log_invalid_directory">La carpeta seleccionada {0} no es un directorio de WeMod válido.</s:String>
<s:String x:Key="log_restore_failed">Error al restaurar la copia de seguridad: {0}</s:String>
<s:String x:Key="log_patch_failed">Error al parchear: {0}</s:String>
<s:String x:Key="log_copied">Registros copiados al portapapeles.</s:String>
<s:String x:Key="log_copy_failed">Error al copiar los registros: {0}</s:String>
<s:String x:Key="log_exported">Registros exportados a {0}.</s:String>
<s:String x:Key="log_export_failed">Error al exportar los registros: {0}</s:String>
<s:String x:Key="log_open_link_failed">No se pudo abrir {0} en un navegador.</s:String>
<s:String x:Key="dialog_pick_install">Selecciona el directorio de WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Chemin du dossier</s:String>
<s:String x:Key="mw_folder_not_found">Dossier non trouvé</s:String>
<s:String x:Key="mw_patch">Appliquer</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Les fichiers .js sélectionnés sont intégrés dans Wand et chargés dans le renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Aucun script sélectionné</s:String>
<s:String x:Key="pv_start">Démarrer</s:String>
<s:String x:Key="pv_auto_apply">Appliquer automatiquement après les mises à jour</s:String>
<s:String x:Key="pv_popup_title">Qu'allons-nous modifier ?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Répertoire WeMod trouvé à {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod est déjà patché. Si vous souhaitez le patcher à nouveau, veuillez d'abord restaurer la sauvegarde.</s:String>
<s:String x:Key="log_ready">Prêt pour le patch.</s:String>
<s:String x:Key="log_install_not_found">Répertoire WeMod introuvable.</s:String>
<s:String x:Key="log_no_directory">Impossible. Veuillez d'abord spécifier le répertoire.</s:String>
<s:String x:Key="log_invalid_directory">Le dossier sélectionné {0} n'est pas un répertoire WeMod valide.</s:String>
<s:String x:Key="log_restore_failed">Échec de la restauration de la sauvegarde : {0}</s:String>
<s:String x:Key="log_patch_failed">Échec du patch : {0}</s:String>
<s:String x:Key="log_copied">Journaux copiés dans le presse-papiers.</s:String>
<s:String x:Key="log_copy_failed">Échec de la copie des journaux : {0}</s:String>
<s:String x:Key="log_exported">Journaux exportés vers {0}.</s:String>
<s:String x:Key="log_export_failed">Échec de l'exportation des journaux : {0}</s:String>
<s:String x:Key="log_open_link_failed">Impossible d'ouvrir {0} dans un navigateur.</s:String>
<s:String x:Key="dialog_pick_install">Sélectionnez le répertoire WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Percorso cartella</s:String>
<s:String x:Key="mw_folder_not_found">Cartella non trovata</s:String>
<s:String x:Key="mw_patch">Applica</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">I file .js selezionati vengono inseriti in Wand e caricati nel renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Nessuno script selezionato</s:String>
<s:String x:Key="pv_start">Avvia</s:String>
<s:String x:Key="pv_auto_apply">Applica automaticamente dopo gli aggiornamenti</s:String>
<s:String x:Key="pv_popup_title">Cosa modificheremo?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Directory di WeMod trovata in {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod è già stato patchato. Se vuoi patchare di nuovo, ripristina prima il backup.</s:String>
<s:String x:Key="log_ready">Pronto per il patching.</s:String>
<s:String x:Key="log_install_not_found">Directory di WeMod non trovata.</s:String>
<s:String x:Key="log_no_directory">Impossibile procedere. Specifica prima la directory.</s:String>
<s:String x:Key="log_invalid_directory">La cartella selezionata {0} non è una directory valida di WeMod.</s:String>
<s:String x:Key="log_restore_failed">Impossibile ripristinare il backup: {0}</s:String>
<s:String x:Key="log_patch_failed">Impossibile eseguire il patch: {0}</s:String>
<s:String x:Key="log_copied">Log copiati negli appunti.</s:String>
<s:String x:Key="log_copy_failed">Impossibile copiare i log: {0}</s:String>
<s:String x:Key="log_exported">Log esportati in {0}.</s:String>
<s:String x:Key="log_export_failed">Impossibile esportare i log: {0}</s:String>
<s:String x:Key="log_open_link_failed">Impossibile aprire {0} in un browser.</s:String>
<s:String x:Key="dialog_pick_install">Seleziona la directory di WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">フォルダパス</s:String>
<s:String x:Key="mw_folder_not_found">フォルダが見つかりません</s:String>
<s:String x:Key="mw_patch">適用</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">選択した .js ファイルは Wand に組み込まれ、レンダラーで読み込まれます。</s:String>
<s:String x:Key="pv_no_custom_scripts">スクリプトが選択されていません</s:String>
<s:String x:Key="pv_start">開始</s:String>
<s:String x:Key="pv_auto_apply">更新後に自動適用</s:String>
<s:String x:Key="pv_popup_title">何を改善しますか?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">WeModディレクトリが {0} ({1}) に見つかりました</s:String>
<s:String x:Key="log_already_patched">WeModは既にパッチが適用されています。もう一度パッチを適用する場合は、まずバックアップを復元してください。</s:String>
<s:String x:Key="log_ready">パッチ適用の準備ができました。</s:String>
<s:String x:Key="log_install_not_found">WeModディレクトリが見つかりません。</s:String>
<s:String x:Key="log_no_directory">実行できません。先にディレクトリを指定してください。</s:String>
<s:String x:Key="log_invalid_directory">選択したフォルダ {0} は有効なWeModディレクトリではありません。</s:String>
<s:String x:Key="log_restore_failed">バックアップの復元に失敗しました: {0}</s:String>
<s:String x:Key="log_patch_failed">パッチの適用に失敗しました: {0}</s:String>
<s:String x:Key="log_copied">ログをクリップボードにコピーしました。</s:String>
<s:String x:Key="log_copy_failed">ログのコピーに失敗しました: {0}</s:String>
<s:String x:Key="log_exported">ログを {0} にエクスポートしました。</s:String>
<s:String x:Key="log_export_failed">ログのエクスポートに失敗しました: {0}</s:String>
<s:String x:Key="log_open_link_failed">{0} をブラウザで開くことができませんでした。</s:String>
<s:String x:Key="dialog_pick_install">WeModディレクトリを選択してください</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Ścieżka folderu</s:String>
<s:String x:Key="mw_folder_not_found">Folder nie znaleziony</s:String>
<s:String x:Key="mw_patch">Zastosuj</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Wybrane pliki .js są pakowane do Wand i ładowane w rendererze.</s:String>
<s:String x:Key="pv_no_custom_scripts">Nie wybrano skryptów</s:String>
<s:String x:Key="pv_start">Rozpocznij</s:String>
<s:String x:Key="pv_auto_apply">Zastosuj automatycznie po aktualizacji</s:String>
<s:String x:Key="pv_popup_title">Co będziemy ulepszać?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Katalog WeMod znaleziony w {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod został już zaktualizowany. Jeśli chcesz zaktualizować ponownie, najpierw przywróć kopię zapasową.</s:String>
<s:String x:Key="log_ready">Gotowy do aktualizacji (patchowania).</s:String>
<s:String x:Key="log_install_not_found">Nie znaleziono katalogu WeMod.</s:String>
<s:String x:Key="log_no_directory">Nie można tego zrobić. Proszę najpierw określić katalog.</s:String>
<s:String x:Key="log_invalid_directory">Wybrany folder {0} nie jest prawidłowym katalogiem WeMod.</s:String>
<s:String x:Key="log_restore_failed">Nie udało się przywrócić kopii zapasowej: {0}</s:String>
<s:String x:Key="log_patch_failed">Nie udało się zaktualizować: {0}</s:String>
<s:String x:Key="log_copied">Logi skopiowane do schowka.</s:String>
<s:String x:Key="log_copy_failed">Nie udało się skopiować logów: {0}</s:String>
<s:String x:Key="log_exported">Logi wyeksportowane do {0}.</s:String>
<s:String x:Key="log_export_failed">Nie udało się wyeksportować logów: {0}</s:String>
<s:String x:Key="log_open_link_failed">Nie można otworzyć {0} w przeglądarce.</s:String>
<s:String x:Key="dialog_pick_install">Wybierz katalog WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Caminho da pasta</s:String>
<s:String x:Key="mw_folder_not_found">Pasta não encontrada</s:String>
<s:String x:Key="mw_patch">Aplicar</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Os arquivos .js selecionados são empacotados no Wand e carregados no renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Nenhum script selecionado</s:String>
<s:String x:Key="pv_start">Iniciar</s:String>
<s:String x:Key="pv_auto_apply">Aplicar automaticamente após atualizações</s:String>
<s:String x:Key="pv_popup_title">O que vamos melhorar?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Diretório do WeMod encontrado em {0} ({1})</s:String>
<s:String x:Key="log_already_patched">O WeMod já foi modificado. Se quiser modificar novamente, restaure o backup primeiro.</s:String>
<s:String x:Key="log_ready">Pronto para modificar.</s:String>
<s:String x:Key="log_install_not_found">Diretório do WeMod não encontrado.</s:String>
<s:String x:Key="log_no_directory">Não é possível fazer isso. Por favor, especifique o diretório primeiro.</s:String>
<s:String x:Key="log_invalid_directory">A pasta selecionada {0} não é um diretório válido do WeMod.</s:String>
<s:String x:Key="log_restore_failed">Falha ao restaurar o backup: {0}</s:String>
<s:String x:Key="log_patch_failed">Falha ao modificar: {0}</s:String>
<s:String x:Key="log_copied">Logs copiados para a área de transferência.</s:String>
<s:String x:Key="log_copy_failed">Falha ao copiar logs: {0}</s:String>
<s:String x:Key="log_exported">Logs exportados para {0}.</s:String>
<s:String x:Key="log_export_failed">Falha ao exportar logs: {0}</s:String>
<s:String x:Key="log_open_link_failed">Não foi possível abrir {0} no navegador.</s:String>
<s:String x:Key="dialog_pick_install">Selecione o diretório do WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Путь к папке</s:String>
<s:String x:Key="mw_folder_not_found">Папка не найдена</s:String>
<s:String x:Key="mw_patch">Применить</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Выбранные .js попадут в Wand и загрузятся в renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Скрипты не выбраны</s:String>
<s:String x:Key="pv_start">Начать</s:String>
<s:String x:Key="pv_auto_apply">Авто-патч после обновлений</s:String>
<s:String x:Key="pv_popup_title">Что будем улучшать?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Директория WeMod найдена в {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod уже пропатчен. Если вы хотите пропатчить снова, сначала восстановите резервную копию.</s:String>
<s:String x:Key="log_ready">Готово к патчингу.</s:String>
<s:String x:Key="log_install_not_found">Директория WeMod не найдена.</s:String>
<s:String x:Key="log_no_directory">Невозможно выполнить. Пожалуйста, сначала укажите директорию.</s:String>
<s:String x:Key="log_invalid_directory">Выбранная папка {0} не является допустимой директорией WeMod.</s:String>
<s:String x:Key="log_restore_failed">Не удалось восстановить резервную копию: {0}</s:String>
<s:String x:Key="log_patch_failed">Не удалось пропатчить: {0}</s:String>
<s:String x:Key="log_copied">Логи скопированы в буфер обмена.</s:String>
<s:String x:Key="log_copy_failed">Не удалось скопировать логи: {0}</s:String>
<s:String x:Key="log_exported">Логи экспортированы в {0}.</s:String>
<s:String x:Key="log_export_failed">Не удалось экспортировать логи: {0}</s:String>
<s:String x:Key="log_open_link_failed">Не удалось открыть {0} в браузере.</s:String>
<s:String x:Key="dialog_pick_install">Выберите директорию WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Klasör yolu</s:String>
<s:String x:Key="mw_folder_not_found">Klasör bulunamadı</s:String>
<s:String x:Key="mw_patch">Uygula</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Seçilen .js dosyaları Wand içine paketlenir ve renderer'da yüklenir.</s:String>
<s:String x:Key="pv_no_custom_scripts">Betik seçilmedi</s:String>
<s:String x:Key="pv_start">Başlat</s:String>
<s:String x:Key="pv_auto_apply">Güncellemelerden sonra otomatik uygula</s:String>
<s:String x:Key="pv_popup_title">Neyi geliştireceğiz?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">WeMod dizini {0} konumunda bulundu ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod zaten yamanmış. Tekrar yamamak istiyorsanız, lütfen önce yedeği geri yükleyin.</s:String>
<s:String x:Key="log_ready">Yama işlemi için hazır.</s:String>
<s:String x:Key="log_install_not_found">WeMod dizini bulunamadı.</s:String>
<s:String x:Key="log_no_directory">İşlem yapılamıyor. Lütfen önce dizini belirtin.</s:String>
<s:String x:Key="log_invalid_directory">Seçilen {0} klasörü geçerli bir WeMod dizini değil.</s:String>
<s:String x:Key="log_restore_failed">Yedek geri yüklenemedi: {0}</s:String>
<s:String x:Key="log_patch_failed">Yama yapılamadı: {0}</s:String>
<s:String x:Key="log_copied">Günlükler panoya kopyalandı.</s:String>
<s:String x:Key="log_copy_failed">Günlükler kopyalanamadı: {0}</s:String>
<s:String x:Key="log_exported">Günlükler {0} konumuna dışa aktarıldı.</s:String>
<s:String x:Key="log_export_failed">Günlükler dışa aktarılamadı: {0}</s:String>
<s:String x:Key="log_open_link_failed">{0} bir tarayıcıda açılamadı.</s:String>
<s:String x:Key="dialog_pick_install">WeMod dizinini seçin</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">Шлях до папки</s:String>
<s:String x:Key="mw_folder_not_found">Папку не знайдено</s:String>
<s:String x:Key="mw_patch">Застосувати</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">Вибрані файли .js пакуються у Wand і завантажуються в рендерері.</s:String>
<s:String x:Key="pv_no_custom_scripts">Скрипти не вибрано</s:String>
<s:String x:Key="pv_start">Почати</s:String>
<s:String x:Key="pv_auto_apply">Автоматично застосовувати після оновлень</s:String>
<s:String x:Key="pv_popup_title">Що будемо покращувати?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">Директорію WeMod знайдено в {0} ({1})</s:String>
<s:String x:Key="log_already_patched">WeMod вже пропатчено. Якщо ви хочете пропатчити знову, спершу відновіть резервну копію.</s:String>
<s:String x:Key="log_ready">Готово до патчингу.</s:String>
<s:String x:Key="log_install_not_found">Директорію WeMod не знайдено.</s:String>
<s:String x:Key="log_no_directory">Не вдається виконати. Будь ласка, спочатку вкажіть директорію.</s:String>
<s:String x:Key="log_invalid_directory">Вибрана папка {0} не є дійсною директорією WeMod.</s:String>
<s:String x:Key="log_restore_failed">Не вдалося відновити резервну копію: {0}</s:String>
<s:String x:Key="log_patch_failed">Не вдалося пропатчити: {0}</s:String>
<s:String x:Key="log_copied">Логи скопійовано в буфер обміну.</s:String>
<s:String x:Key="log_copy_failed">Не вдалося скопіювати логи: {0}</s:String>
<s:String x:Key="log_exported">Логи експортовано до {0}.</s:String>
<s:String x:Key="log_export_failed">Не вдалося експортувати логи: {0}</s:String>
<s:String x:Key="log_open_link_failed">Не вдалося відкрити {0} у браузері.</s:String>
<s:String x:Key="dialog_pick_install">Виберіть директорію WeMod</s:String>
<!--#endregion -->
</ResourceDictionary>
+18 -1
View File
@@ -7,7 +7,6 @@
<!--#endregion -->
<!--#region MainWindow -->
<s:String x:Key="mw_title">WandEnhancer</s:String>
<s:String x:Key="mw_folder_path">文件夹路径</s:String>
<s:String x:Key="mw_folder_not_found">未找到文件夹</s:String>
<s:String x:Key="mw_patch">增强</s:String>
@@ -35,7 +34,25 @@
<s:String x:Key="pv_custom_scripts_hint">选中的 .js 文件会打包到 Wand 并在渲染器中加载。</s:String>
<s:String x:Key="pv_no_custom_scripts">未选择脚本</s:String>
<s:String x:Key="pv_start">开始</s:String>
<s:String x:Key="pv_auto_apply">更新后自动应用</s:String>
<s:String x:Key="pv_popup_title">我们要增强什么?</s:String>
<!--#endregion -->
<!--#region Runtime log -->
<s:String x:Key="log_install_found">在 {0} ({1}) 找到 WeMod 目录</s:String>
<s:String x:Key="log_already_patched">WeMod 已经修补过。如果想再次修补,请先恢复备份。</s:String>
<s:String x:Key="log_ready">准备修补。</s:String>
<s:String x:Key="log_install_not_found">未找到 WeMod 目录。</s:String>
<s:String x:Key="log_no_directory">无法执行。请先指定目录。</s:String>
<s:String x:Key="log_invalid_directory">选择的文件夹 {0} 不是有效的 WeMod 目录。</s:String>
<s:String x:Key="log_restore_failed">恢复备份失败: {0}</s:String>
<s:String x:Key="log_patch_failed">修补失败: {0}</s:String>
<s:String x:Key="log_copied">日志已复制到剪贴板。</s:String>
<s:String x:Key="log_copy_failed">复制日志失败: {0}</s:String>
<s:String x:Key="log_exported">日志已导出至 {0}。</s:String>
<s:String x:Key="log_export_failed">导出日志失败: {0}</s:String>
<s:String x:Key="log_open_link_failed">无法在浏览器中打开 {0}。</s:String>
<s:String x:Key="dialog_pick_install">选择 WeMod 目录</s:String>
<!--#endregion -->
</ResourceDictionary>
@@ -1,47 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WandEnhancer.ReactiveUICore
{
public sealed class AsyncRelayCommand : ICommand
{
private readonly Func<object, Task> _execute;
private readonly Func<object, bool> _canExecute;
private long _isExecuting;
public AsyncRelayCommand(Func<object, Task> execute, Func<object, bool> canExecute = null)
{
this._execute = execute;
this._canExecute = canExecute ?? (o => true);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
private static void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
public bool CanExecute(object parameter) => Interlocked.Read(ref _isExecuting) == 0 && _canExecute(parameter);
public async void Execute(object parameter)
{
Interlocked.Exchange(ref _isExecuting, 1);
RaiseCanExecuteChanged();
try
{
await _execute(parameter);
}
finally
{
Interlocked.Exchange(ref _isExecuting, 0);
RaiseCanExecuteChanged();
}
}
}
}
-20
View File
@@ -1,20 +0,0 @@
<UserControl x:Class="WandEnhancer.View.Controls.InfoItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WandEnhancer.View.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox Width="20" Height="20" VerticalAlignment="Top">
<Path Fill="{Binding IconColor}" Data="{Binding IconData}"/>
</Viewbox>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="5 0 5 0" TextWrapping="Wrap"
FontSize="12" Text="{Binding Text}"/>
</Grid>
</UserControl>
@@ -1,42 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WandEnhancer.View.Controls
{
public partial class InfoItem : UserControl
{
public static readonly DependencyProperty IconDataProperty =
DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(InfoItem));
public static readonly DependencyProperty IconColorProperty =
DependencyProperty.Register(nameof(IconColor), typeof(Brush), typeof(InfoItem));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(InfoItem));
public Geometry IconData
{
get => (Geometry)GetValue(IconDataProperty);
set => SetValue(IconDataProperty, value);
}
public Brush IconColor
{
get => (Brush)GetValue(IconColorProperty);
set => SetValue(IconColorProperty, value);
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public InfoItem()
{
InitializeComponent();
this.DataContext = this;
}
}
}
+1 -1
View File
@@ -38,7 +38,7 @@
</Button>
<StackPanel Grid.Row="0" x:Name="TitleContainer" Orientation="Horizontal">
<TextBlock x:Name="Title" Text="This is title" Foreground="{DynamicResource Foreground}"
<TextBlock x:Name="Title" Foreground="{DynamicResource Foreground}"
HorizontalAlignment="Left" FontWeight="Bold" FontSize="16"
VerticalAlignment="Bottom"/>
</StackPanel>
@@ -0,0 +1,26 @@
using System.Windows;
namespace WandEnhancer.View.MainWindow
{
/// <summary>
/// What the view model needs from the shell window. Exists so the view model does not
/// hold the concrete window or reach through a static Instance, which made every command
/// untestable and crashed whenever the singleton was not set yet.
/// </summary>
public interface IShellView
{
void OpenPopup(FrameworkElement content, string title);
void ClosePopup();
void ScrollLogIntoView(LogEntry entry);
}
/// <summary>Modal file/folder pickers, kept behind a seam so commands stay headless-testable.</summary>
public interface IFileDialogs
{
/// <summary>Chosen folder, or null when cancelled.</summary>
string PickFolder(string description, string initialPath);
/// <summary>Chosen file path, or null when cancelled.</summary>
string PickSaveFile(string filter, string suggestedFileName);
}
}
+3 -2
View File
@@ -183,9 +183,10 @@
Content="{DynamicResource mw_patch}"/>
</Grid>
<Button HorizontalAlignment="Right"
Command="{Binding RestoreBackupCommand }"
FontWeight="Bold" FontSize="16" Width="200"
Command="{Binding RestoreBackupCommand}"
FontWeight="Bold" FontSize="16" Width="200"
Style="{StaticResource ColoredButton}"
IsEnabled="{Binding IsIdle}"
Visibility="{Binding AlreadyPatched, Converter={StaticResource ToVisibilityConverter}}"
Content="{DynamicResource mw_restore}"/>
</Grid>
@@ -8,21 +8,20 @@ namespace WandEnhancer.View.MainWindow
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
public partial class MainWindow : IShellView
{
public static MainWindow Instance;
public readonly MainWindowVm ViewModel;
public MainWindow()
{
InitializeComponent();
this.ViewModel = new MainWindowVm(this);
this.ViewModel = new MainWindowVm(this, new WindowsFileDialogs());
this.DataContext = ViewModel;
VersionLabel.Text = Constants.Version.ToString();
Instance = this;
}
public void OpenPopup(FrameworkElement content, string title = null)
{
this.PopupHost.PopupContent = content;
@@ -30,6 +29,11 @@ namespace WandEnhancer.View.MainWindow
PopupHost.IsOpen = true;
}
public void ScrollLogIntoView(LogEntry entry)
{
this.LogList.ScrollIntoView(entry);
}
private void OnDragMove(object sender, MouseButtonEventArgs e)
{
this.DragMove();
@@ -47,7 +51,15 @@ namespace WandEnhancer.View.MainWindow
private void OpenSourceClicked(object sender, MouseButtonEventArgs e)
{
System.Diagnostics.Process.Start(Constants.RepositoryUrl);
// No browser association, or the shell refuses the URL: not worth killing the app.
try
{
System.Diagnostics.Process.Start(Constants.RepositoryUrl);
}
catch (Exception)
{
ViewModel.ReportRepositoryLinkFailure(Constants.RepositoryUrl);
}
}
}
}
+121 -118
View File
@@ -3,8 +3,8 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using WandEnhancer.Core;
using WandEnhancer.Core.Services;
using WandEnhancer.Models;
using WandEnhancer.ReactiveUICore;
using WandEnhancer.Utils;
@@ -15,33 +15,33 @@ namespace WandEnhancer.View.MainWindow
{
public class MainWindowVm : ObservableObject
{
private readonly MainWindow _view;
public ObservableCollection<LogEntry> LogList { get; set; } = new ObservableCollection<LogEntry>();
private const string LogExportFilter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
private readonly IShellView _shell;
private readonly IFileDialogs _dialogs;
public ObservableCollection<LogEntry> LogList { get; } = new ObservableCollection<LogEntry>();
private WeModConfig _weModConfig;
public WeModConfig WeModInfo
{
get => _weModConfig;
set
set => SetProperty(ref _weModConfig, value);
}
private void UseInstall(WeModConfig config)
{
WeModInfo = config;
if (config == null)
{
SetProperty(ref _weModConfig, value);
if (value == null) return;
Log($"WeMod directory found at '{_weModConfig}' ({_weModConfig.ExecutableName})", ELogType.Success);
var resourcesPath = Path.Combine(_weModConfig.RootDirectory, "resources");
if (File.Exists(Path.Combine(resourcesPath, "app.asar.backup")) ||
Directory.Exists(Path.Combine(resourcesPath, "app.asar.unpacked.backup")))
{
Log("WeMod already patched. If you want to patch again, please restore the backup first.",
ELogType.Warn);
IsPatchEnabled = false;
AlreadyPatched = true;
return;
}
Log("Ready for patching.", ELogType.Info);
IsPatchEnabled = true;
return;
}
Log(LocalizationManager.Format("log_install_found", config, config.ExecutableName), ELogType.Success);
AlreadyPatched = Enhancer.IsPatched(config.RootDirectory);
IsPatchEnabled = !AlreadyPatched;
Log(LocalizationManager.Get(AlreadyPatched ? "log_already_patched" : "log_ready"),
AlreadyPatched ? ELogType.Warn : ELogType.Info);
}
private bool _isPatchEnabled;
@@ -60,6 +60,24 @@ namespace WandEnhancer.View.MainWindow
set => SetProperty(ref _alreadyPatched, value);
}
private bool _isBusy;
/// <summary>True while a patch or restore runs; both are long file operations.</summary>
public bool IsBusy
{
get => _isBusy;
set
{
if (SetProperty(ref _isBusy, value))
{
OnPropertyChanged(nameof(IsIdle));
}
}
}
/// <summary>Bound by buttons that must not be clickable a second time mid-run.</summary>
public bool IsIdle => !_isBusy;
public RelayCommand SetFolderPathCommand { get; }
public RelayCommand ApplyPatchCommand { get; }
public RelayCommand RestoreBackupCommand { get; }
@@ -69,87 +87,69 @@ namespace WandEnhancer.View.MainWindow
private void OnFolderPathSelection(object obj)
{
using (var dialog = new FolderBrowserDialog())
string selectedPath = _dialogs.PickFolder(
LocalizationManager.Get("dialog_pick_install"),
Environment.GetEnvironmentVariable("LOCALAPPDATA"));
if (selectedPath == null)
{
dialog.SelectedPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
dialog.Description = "Select the WeMod directory";
dialog.ShowNewFolderButton = false;
if (dialog.ShowDialog() != DialogResult.OK) return;
string selectedPath = dialog.SelectedPath;
string fileName = Path.GetFileName(selectedPath);
var info = Extensions.CheckWeModPath(selectedPath);
if (info != null)
{
WeModInfo = info;
return;
}
LogList.Add(new LogEntry
{
LogType = ELogType.Error,
Message = $"The selected folder '{fileName}' is not a valid WeMod directory."
});
return;
}
var info = WeModInstalls.CheckWeModPath(selectedPath);
if (info == null)
{
Log(LocalizationManager.Format("log_invalid_directory", Path.GetFileName(selectedPath)), ELogType.Error);
return;
}
UseInstall(info);
}
private void OnBackupRestoring(object param)
// Restore does the same heavy file IO as Patch, so it runs off the UI thread too.
private async void OnBackupRestoring(object param)
{
var resourcesPath = Path.Combine(WeModInfo.RootDirectory, "resources");
var backupPath = Path.Combine(resourcesPath, "app.asar.backup");
var unpackedBackupPath = Path.Combine(resourcesPath, "app.asar.unpacked.backup");
if (!File.Exists(backupPath) || !Directory.Exists(unpackedBackupPath))
if (WeModInfo == null)
{
Log("Backup is incomplete. Restore the original Wand installation files or reinstall Wand.", ELogType.Error);
Log(LocalizationManager.Get("log_no_directory"), ELogType.Warn);
return;
}
try
IsBusy = true;
bool restored = await Task.Run(() =>
{
var asarPath = Path.Combine(resourcesPath, "app.asar");
var unpackedPath = Path.Combine(resourcesPath, "app.asar.unpacked");
File.Copy(backupPath, asarPath, true);
if (Directory.Exists(unpackedPath))
try
{
Directory.Delete(unpackedPath, true);
new Enhancer(WeModInfo, Log).Restore();
return true;
}
Enhancer.CopyDirectory(unpackedBackupPath, unpackedPath);
var proxyDllPath = Path.Combine(WeModInfo.RootDirectory, "version.dll");
if (File.Exists(proxyDllPath))
catch (Exception e)
{
File.Delete(proxyDllPath);
Log(LocalizationManager.Format("log_restore_failed", e.Message), ELogType.Error);
return false;
}
});
File.Delete(backupPath);
Directory.Delete(unpackedBackupPath, true);
}
catch (Exception e)
IsBusy = false;
if (restored)
{
Log($"Failed to restore backup: {e.Message}", ELogType.Error);
return;
AlreadyPatched = false;
IsPatchEnabled = true;
}
Log("Backup restored successfully.", ELogType.Success);
AlreadyPatched = false;
IsPatchEnabled = true;
}
private void OnPatching(object param)
{
if (WeModInfo == null)
{
Log("Can't be done. Please specify the directory first.", ELogType.Warn);
Log(LocalizationManager.Get("log_no_directory"), ELogType.Warn);
return;
}
MainWindow.Instance.OpenPopup(new PatchVectorsPopup(async config =>
_shell.OpenPopup(new PatchVectorsPopup(async config =>
{
MainWindow.Instance.ClosePopup();
_shell.ClosePopup();
IsPatchEnabled = false;
IsBusy = true;
await Task.Run(() =>
{
try
@@ -159,32 +159,34 @@ namespace WandEnhancer.View.MainWindow
}
catch (Exception e)
{
Log($"Failed to patch: {e.Message}", ELogType.Error);
Log(LocalizationManager.Format("log_patch_failed", e.Message), ELogType.Error);
IsPatchEnabled = true;
}
});
}), Application.Current.FindResource("pv_popup_title") as string);
IsBusy = false;
}), LocalizationManager.Get("pv_popup_title"));
}
private void Log(string message, ELogType logType)
{
Application.Current.Dispatcher.Invoke(() =>
{
message = $"[{logType.ToString().ToUpper()}] {message}";
var entry = new LogEntry
{
LogType = logType,
Message = message
Message = $"[{logType.ToString().ToUpper()}] {message}"
};
LogList.Add(entry);
_view.LogList.ScrollIntoView(entry);
_shell.ScrollLogIntoView(entry);
// The log commands are disabled while the list is empty, and appending a line
// is not user input, so nothing else would re-evaluate CanExecute.
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
});
}
private void OnOpenSettings(object param)
{
MainWindow.Instance.OpenPopup(new SettingsPopup(), Application.Current.FindResource("settings_title") as string);
_shell.OpenPopup(new SettingsPopup(), LocalizationManager.Get("settings_title"));
}
private string BuildLogReport()
@@ -199,67 +201,68 @@ namespace WandEnhancer.View.MainWindow
private void OnCopyLogs(object param)
{
if (LogList.Count == 0)
try
{
System.Windows.Clipboard.SetText(BuildLogReport());
Log(LocalizationManager.Get("log_copied"), ELogType.Success);
}
catch (Exception e)
{
Log(LocalizationManager.Format("log_copy_failed", e.Message), ELogType.Error);
}
}
private void OnExportLogs(object param)
{
string path = _dialogs.PickSaveFile(
LogExportFilter,
$"wand-enhancer-log-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
if (path == null)
{
return;
}
try
{
System.Windows.Clipboard.SetText(BuildLogReport());
Log("Logs copied to clipboard.", ELogType.Success);
File.WriteAllText(path, BuildLogReport());
Log(LocalizationManager.Format("log_exported", path), ELogType.Success);
}
catch (Exception e)
{
Log($"Failed to copy logs: {e.Message}", ELogType.Error);
Log(LocalizationManager.Format("log_export_failed", e.Message), ELogType.Error);
}
}
private void OnExportLogs(object param)
private bool HasLogs(object param) => LogList.Count > 0;
/// <summary>The shell could not hand the repository URL to a browser; show it instead.</summary>
public void ReportRepositoryLinkFailure(string url)
{
if (LogList.Count == 0)
{
return;
}
using (var dialog = new SaveFileDialog
{
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*",
FileName = $"wand-enhancer-log-{DateTime.Now:yyyyMMdd-HHmmss}.txt"
})
{
if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
File.WriteAllText(dialog.FileName, BuildLogReport());
Log($"Logs exported to '{dialog.FileName}'.", ELogType.Success);
}
catch (Exception e)
{
Log($"Failed to export logs: {e.Message}", ELogType.Error);
}
}
Log(LocalizationManager.Format("log_open_link_failed", url), ELogType.Warn);
}
public MainWindowVm(MainWindow view)
public MainWindowVm(IShellView shell, IFileDialogs dialogs)
{
_view = view;
_shell = shell;
_dialogs = dialogs;
SetFolderPathCommand = new RelayCommand(OnFolderPathSelection);
ApplyPatchCommand = new RelayCommand(OnPatching);
RestoreBackupCommand = new RelayCommand(OnBackupRestoring);
OpenSettingsCommand = new RelayCommand(OnOpenSettings);
CopyLogsCommand = new RelayCommand(OnCopyLogs);
ExportLogsCommand = new RelayCommand(OnExportLogs);
CopyLogsCommand = new RelayCommand(OnCopyLogs, HasLogs);
ExportLogsCommand = new RelayCommand(OnExportLogs, HasLogs);
WeModInfo = Extensions.FindWeMod();
UseInstall(WeModInstalls.FindWeMod());
if (WeModInfo == null)
{
Log("WeMod directory not found.", ELogType.Error);
Log(LocalizationManager.Get("log_install_not_found"), ELogType.Error);
}
foreach (var entry in Program.StartupLog)
{
Log(entry.Key, entry.Value);
}
Program.StartupLog.Clear();
}
}
}
@@ -0,0 +1,28 @@
using System.Windows.Forms;
namespace WandEnhancer.View.MainWindow
{
internal sealed class WindowsFileDialogs : IFileDialogs
{
public string PickFolder(string description, string initialPath)
{
using (var dialog = new FolderBrowserDialog
{
SelectedPath = initialPath,
Description = description,
ShowNewFolderButton = false,
})
{
return dialog.ShowDialog() == DialogResult.OK ? dialog.SelectedPath : null;
}
}
public string PickSaveFile(string filter, string suggestedFileName)
{
using (var dialog = new SaveFileDialog { Filter = filter, FileName = suggestedFileName })
{
return dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
}
}
}
}
@@ -18,6 +18,7 @@
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
@@ -39,7 +40,11 @@
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Text="{DynamicResource pv_remote_web_panel_preview}" />
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="RemoteWebPanelPreviewBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<Border Grid.Row="4" Grid.ColumnSpan="2" Margin="0 14 0 0" Padding="10"
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Text="{DynamicResource pv_auto_apply}" />
<CheckBox Grid.Row="4" Grid.Column="1" x:Name="AutoApplyBox" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="True" />
<Border Grid.Row="5" Grid.ColumnSpan="2" Margin="0 14 0 0" Padding="10"
BorderBrush="{DynamicResource Border}" BorderThickness="1" CornerRadius="4"
Background="{DynamicResource Muted}">
<StackPanel>
@@ -91,7 +96,7 @@
</StackPanel>
</Border>
<Button Grid.Row="5" Grid.ColumnSpan="2" Padding="0 5 0 5" Margin="0 15 0 0" Content="{DynamicResource pv_start}"
<Button Grid.Row="6" Grid.ColumnSpan="2" Padding="0 5 0 5" Margin="0 15 0 0" Content="{DynamicResource pv_start}"
Click="OnPatchButtonClick" />
</Grid>
</Grid>
@@ -7,13 +7,13 @@ using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using WandEnhancer.Models;
using WandEnhancer.Utils;
namespace WandEnhancer.View.Popups
{
public partial class PatchVectorsPopup : UserControl
{
private const string JavaScriptDialogFilter = "JavaScript files (*.js)|*.js";
private const string JavaScriptFileExtension = ".js";
private readonly Action<PatchConfig> _onApply;
private readonly ObservableCollection<SelectedScript> _selectedScripts = new ObservableCollection<SelectedScript>();
@@ -40,7 +40,7 @@ namespace WandEnhancer.View.Popups
return;
}
foreach (var path in dialog.FileNames.Where(IsJavaScriptFile))
foreach (var path in dialog.FileNames.Where(WeModInstalls.IsJavaScriptFile))
{
AddScript(path);
}
@@ -99,7 +99,7 @@ namespace WandEnhancer.View.Popups
{
PatchTypes = result,
CustomScriptPaths = _selectedScripts.Select(script => script.FullPath).ToList(),
AutoApplyPatches = false
AutoApplyAfterUpdate = AutoApplyBox.IsChecked == true
});
}
@@ -114,11 +114,6 @@ namespace WandEnhancer.View.Popups
_selectedScripts.Add(new SelectedScript(fullPath));
}
private static bool IsJavaScriptFile(string path)
{
return File.Exists(path) && string.Equals(Path.GetExtension(path), JavaScriptFileExtension, StringComparison.OrdinalIgnoreCase);
}
private void UpdateScriptsEmptyState()
{
NoScriptsText.Visibility = _selectedScripts.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
@@ -56,7 +56,7 @@ namespace WandEnhancer.View.Popups
LocalizationManager.CurrentLanguage = _selectedLanguage;
}
MainWindow.MainWindow.Instance.ClosePopup();
MainWindow.MainWindow.Instance?.ClosePopup();
}
private class LanguageItem
+16 -28
View File
@@ -39,12 +39,6 @@
</PropertyGroup>
<PropertyGroup>
<StartupObject>WandEnhancer.Program</StartupObject>
<CMakeSourceDir>..\tools\asar-fuses-bypass</CMakeSourceDir>
<NativeBuildRoot>..\.tmp\cmake</NativeBuildRoot>
<NativeBuildConfiguration Condition="'$(Configuration)' == 'Debug'">Debug</NativeBuildConfiguration>
<NativeBuildConfiguration Condition="'$(NativeBuildConfiguration)' == ''">Release</NativeBuildConfiguration>
<CMakeBuildDir>$(NativeBuildRoot)\asar-fuses-bypass</CMakeBuildDir>
<ProxyDllPath>$(CMakeBuildDir)\$(NativeBuildConfiguration)\version.dll</ProxyDllPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -72,24 +66,27 @@
<Compile Include="Converters\ToVisibilityConverter.cs" />
<Compile Include="Core\Enhancer.cs" />
<Compile Include="Core\EnhancerConfig.cs" />
<Compile Include="Core\FuseLauncher.cs" />
<Compile Include="Core\JavaScriptPatchApplier.cs" />
<Compile Include="Core\Js\JsCursor.cs" />
<Compile Include="Core\Js\JsFunction.cs" />
<Compile Include="Core\Js\PatchPayload.cs" />
<Compile Include="Core\Services\LocalizationManager.cs" />
<Compile Include="Core\Services\SettingsManager.cs" />
<Compile Include="Models\WeModConfig.cs" />
<Compile Include="Models\PatchConfig.cs" />
<Compile Include="Models\Signature.cs" />
<Compile Include="Program.cs" />
<Compile Include="ReactiveUICore\AsyncRelayCommand.cs" />
<Compile Include="ReactiveUICore\ObservableObject.cs" />
<Compile Include="ReactiveUICore\RelayCommand.cs" />
<Compile Include="Utils\Common.cs" />
<Compile Include="Utils\Extensions.cs" />
<Compile Include="Utils\Win32\Shortcut.cs" />
<Compile Include="View\Controls\InfoItem.xaml.cs">
<DependentUpon>InfoItem.xaml</DependentUpon>
<Compile Include="Utils\ProcessTerminator.cs" />
<Compile Include="Utils\WeModInstalls.cs" />
<Compile Include="View\Controls\PopupHost.xaml.cs">
<DependentUpon>PopupHost.xaml</DependentUpon>
</Compile>
<Compile Include="View\Controls\PopupHost.xaml.cs" />
<Compile Include="View\MainWindow\Logs.cs" />
<Compile Include="View\MainWindow\MainWindow.xaml.cs" />
<Compile Include="View\MainWindow\IShellView.cs" />
<Compile Include="View\MainWindow\WindowsFileDialogs.cs" />
<Compile Include="View\MainWindow\MainWindowVm.cs" />
<Compile Include="View\Popups\PatchVectorsPopup.xaml.cs">
<DependentUpon>PatchVectorsPopup.xaml</DependentUpon>
@@ -116,7 +113,6 @@
<Page Include="Style\ColorScheme.xaml" />
<Page Include="Style\Icons.xaml" />
<Page Include="Style\Styles.xaml" />
<Page Include="View\Controls\InfoItem.xaml" />
<Page Include="View\Controls\PopupHost.xaml" />
<Page Include="View\MainWindow\MainWindow.xaml" />
<Page Include="View\Popups\PatchVectorsPopup.xaml" />
@@ -152,16 +148,16 @@
<Name>AsarSharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(ProxyDllPath)">
<LogicalName>proxydll</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\web-panel\dist\**\*.*" Condition="Exists('..\web-panel\dist\index.html')">
<LogicalName>remote-panel/dist/%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Patches\*.js">
<LogicalName>patches/%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -171,14 +167,6 @@
<Error Condition="!Exists('..\packages\ILRepack.2.0.41\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.41\build\ILRepack.props'))" />
</Target>
<Target Name="ValidateNativeArtifacts" BeforeTargets="BeforeBuild">
<Error Text="Proxy DLL not found: $(ProxyDllPath)"
Condition="!Exists('$(ProxyDllPath)')" />
<Message Text="Embedding Proxy DLL as resource from $(ProxyDllPath)"
Importance="high" />
</Target>
<Target Name="ILRepack" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<PropertyGroup>
<ILRepackExe>..\packages\ILRepack.2.0.41\tools\ILRepack.exe</ILRepackExe>