Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions Localizer.Desktop/ViewModels/MainViewModel.CatalogLifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,12 @@ await RunIoAsync(async token =>
[RelayCommand]
private async Task SaveCatalogAsync(CancellationToken cancellationToken)
{
if (_catalog is null)
if (_catalog is null || string.IsNullOrWhiteSpace(_catalogPath) || !IsDirty)
{
return;
}

var path = _catalogPath;
if (string.IsNullOrWhiteSpace(path))
{
path = await _dialogs.PickSaveCatalogPathAsync(
suggestedFileName: $"{_catalog.CatalogId.Value}.json",
cancellationToken).ConfigureAwait(true);
if (path is null)
{
return;
}
}

await SaveToPathAsync(path, cancellationToken).ConfigureAwait(true);
await SaveToPathAsync(_catalogPath, cancellationToken).ConfigureAwait(true);
}

[RelayCommand]
Expand Down Expand Up @@ -112,6 +100,7 @@ await RunIoAsync(async token =>

_catalogPath = path;
IsDirty = false;
RefreshSaveCommandState();
UpdateWindowTitle();
StatusText = $"Saved {path}";
}, cancellationToken).ConfigureAwait(true);
Expand Down
16 changes: 15 additions & 1 deletion Localizer.Desktop/ViewModels/MainViewModel.EntryEditing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ private async Task HandleSelectedEntryChangedAsync(EntryRowViewModel? value)

IsDirty = true;
StatusText = $"Saved pending edits for '{previousKey}'.";
// Grid locale statuses already synced inside TryApplyPendingEditorChangesAsync.
}

LoadSelectedEntry(value);
Expand Down Expand Up @@ -188,9 +189,22 @@ private async Task<bool> TryApplyPendingEditorChangesAsync(bool refreshUi)
}

IsDirty = true;
SyncEntriesFromCatalog();
if (refreshUi)
{
RefreshFromCatalog(selectKey: key);
var row = Entries.FirstOrDefault(entry => entry.Key == key);
if (row is not null)
{
if (!ReferenceEquals(SelectedEntry, row))
{
_suppressSelectionLoad = true;
SelectedEntry = row;
_suppressSelectionLoad = false;
}

LoadSelectedEntry(row);
}

RunValidation();
}

Expand Down
118 changes: 102 additions & 16 deletions Localizer.Desktop/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public MainViewModel(
[ObservableProperty]
public partial bool IsDirty { get; set; }

[ObservableProperty]
public partial bool CanSave { get; set; }

[ObservableProperty]
public partial bool CanSaveAs { get; set; }

[ObservableProperty]
public partial string EditorKey { get; set; } = string.Empty;

Expand Down Expand Up @@ -134,13 +140,26 @@ partial void OnIsBusyChanged(bool value)
partial void OnHasCatalogChanged(bool value)
{
OnPropertyChanged(nameof(CanRunCatalogCommands));
RefreshSaveCommandState();
}

partial void OnIsDirtyChanged(bool value)
{
RefreshSaveCommandState();
UpdateWindowTitle();
}

partial void OnHasProjectFolderChanged(bool value)
{
OnPropertyChanged(nameof(CanRunProjectCommands));
}

private void RefreshSaveCommandState()
{
CanSaveAs = HasCatalog;
CanSave = HasCatalog && IsDirty && !string.IsNullOrWhiteSpace(_catalogPath);
}

private void UpdateBusyStatusAnimation(bool isBusy)
{
if (isBusy)
Expand Down Expand Up @@ -219,12 +238,13 @@ private void SetCatalog(Catalog catalog, string? path, bool dirty)
_catalogPath = path;
HasCatalog = true;
IsDirty = dirty;
RefreshSaveCommandState();
RefreshFromCatalog(selectKey: catalog.Entries.Keys.Select(key => key.Value).OrderBy(key => key, StringComparer.Ordinal).FirstOrDefault());
RunValidation();
UpdateWindowTitle();
}

private void RefreshFromCatalog(string? selectKey)
private void RefreshFromCatalog(string? selectKey, bool reloadEditor = true)
{
if (_catalog is null)
{
Expand All @@ -240,31 +260,97 @@ private void RefreshFromCatalog(string? selectKey)
SummaryText =
$"Entries {summary.EntryCount} | Missing {summary.MissingCount} | Stale {summary.StaleCount} | Draft {summary.DraftCount} | Approved {summary.ApprovedCount}";

SyncEntriesFromCatalog();

var previousKey = selectKey ?? SelectedEntry?.Key;
_suppressSelectionLoad = true;
Entries.Clear();
var target = Entries.FirstOrDefault(entry => entry.Key == previousKey) ?? Entries.FirstOrDefault();

foreach (var entry in _catalog.Entries.Values.OrderBy(entry => entry.Key.Value, StringComparer.Ordinal))
if (!ReferenceEquals(SelectedEntry, target))
{
var statuses = _catalog.RequiredLocales
.OrderBy(locale => locale.Value, StringComparer.Ordinal)
.Select(locale => $"{locale.Value}:{_catalog.GetEffectiveStatus(entry, locale)}")
.ToArray();

Entries.Add(new EntryRowViewModel(
entry.Key.Value,
entry.SourceText,
string.Join(" | ", statuses)));
_suppressSelectionLoad = true;
SelectedEntry = target;
_suppressSelectionLoad = false;
}

_suppressSelectionLoad = false;
SelectedEntry = Entries.FirstOrDefault(entry => entry.Key == previousKey) ?? Entries.FirstOrDefault();
if (SelectedEntry is null)
if (target is null)
{
ClearEditor();
return;
}

if (reloadEditor)
{
LoadSelectedEntry(target);
}
}

// Updates existing row view-models in place so the entry grid keeps scroll position and selection.
private void SyncEntriesFromCatalog()
{
if (_catalog is null)
{
Entries.Clear();
return;
}

var ordered = _catalog.Entries.Values
.OrderBy(entry => entry.Key.Value, StringComparer.Ordinal)
.ToList();

var desiredKeys = new HashSet<string>(
ordered.Select(entry => entry.Key.Value),
StringComparer.Ordinal);

for (var index = Entries.Count - 1; index >= 0; index--)
{
if (!desiredKeys.Contains(Entries[index].Key))
{
Entries.RemoveAt(index);
}
}

var rowsByKey = Entries.ToDictionary(row => row.Key, StringComparer.Ordinal);

for (var index = 0; index < ordered.Count; index++)
{
var entry = ordered[index];
var key = entry.Key.Value;
var localeStatuses = FormatLocaleStatuses(entry);

if (rowsByKey.TryGetValue(key, out var existing))
{
existing.SourceText = entry.SourceText;
existing.LocaleStatuses = localeStatuses;

var currentIndex = Entries.IndexOf(existing);
if (currentIndex != index && currentIndex >= 0)
{
Entries.Move(currentIndex, index);
}
}
else
{
var row = new EntryRowViewModel(key, entry.SourceText, localeStatuses);
Entries.Insert(index, row);
rowsByKey[key] = row;
}
}
}

private string FormatLocaleStatuses(CatalogEntry entry)
{
if (_catalog is null)
{
return string.Empty;
}

var statuses = _catalog.RequiredLocales
.OrderBy(locale => locale.Value, StringComparer.Ordinal)
.Select(locale => $"{locale.Value}:{_catalog.GetEffectiveStatus(entry, locale)}");

return string.Join(" | ", statuses);
}

private void UpdateWindowTitle()
{
if (_catalog is null)
Expand Down
4 changes: 2 additions & 2 deletions Localizer.Desktop/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="New" Command="{Binding NewCatalogCommand}" />
<Button Content="Open" Command="{Binding OpenCatalogCommand}" />
<Button Content="Save" Command="{Binding SaveCatalogCommand}" IsEnabled="{Binding HasCatalog}" />
<Button Content="Save As" Command="{Binding SaveCatalogAsCommand}" IsEnabled="{Binding HasCatalog}" />
<Button Content="Save" Command="{Binding SaveCatalogCommand}" IsEnabled="{Binding CanSave}" />
<Button Content="Save As" Command="{Binding SaveCatalogAsCommand}" IsEnabled="{Binding CanSaveAs}" />
<Button Content="Add Entry" Command="{Binding AddEntryCommand}" IsEnabled="{Binding HasCatalog}" />
<Button Content="Remove Entry" Command="{Binding RemoveEntryCommand}" IsEnabled="{Binding CanEditEntry}" />
<Button Content="Apply Edits" Command="{Binding ApplyEntryEditsCommand}" IsEnabled="{Binding CanEditEntry}" />
Expand Down
Loading