From c242ea8ede67e7bae4a38a2976ae6104d3c29ea4 Mon Sep 17 00:00:00 2001 From: Jelle Buning Date: Fri, 6 Nov 2026 15:08:42 +0100 Subject: [PATCH] Added modified icons instead of letters --- src/Kontext.Presentation/GitChangeRow.cs | 14 +++++++--- .../IssueSidebarViewModel.cs | 25 ++++++++++++++++++ src/Kontext/Views/GitChangesView.xaml | 26 ++++++++++++++----- .../GitChangeRowTests.cs | 24 ++++++++++------- .../IssueSidebarAutoRefreshTests.cs | 19 +++++++++++++- 5 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/Kontext.Presentation/GitChangeRow.cs b/src/Kontext.Presentation/GitChangeRow.cs index 5e1a591..4afc0cf 100644 --- a/src/Kontext.Presentation/GitChangeRow.cs +++ b/src/Kontext.Presentation/GitChangeRow.cs @@ -39,17 +39,23 @@ public bool InitiallyChecked /// Told when the user checks or unchecks the row. public Action? Toggled { get; init; } - public string StatusLetter => Change?.Status switch + public string StatusGlyph => Change?.Status switch { - FileChangeStatus.Modified => "M", // A new file is "+", staged or not. FileChangeStatus.Added or FileChangeStatus.Untracked => "+", - FileChangeStatus.Deleted => "D", - FileChangeStatus.Renamed => "R", + FileChangeStatus.Modified or FileChangeStatus.Renamed => "●", + FileChangeStatus.Deleted => "−", FileChangeStatus.Conflicted => "!", _ => string.Empty, }; + public bool IsAddedGlyph => Change?.Status is FileChangeStatus.Added or FileChangeStatus.Untracked; + + public bool IsChangedGlyph => Change?.Status is FileChangeStatus.Modified or FileChangeStatus.Renamed; + + /// Drawn in the critical colour: deleted files, and conflicted ones since they need attention. + public bool IsRemovedGlyph => Change?.Status is FileChangeStatus.Deleted or FileChangeStatus.Conflicted; + public string FileName => Change is null ? string.Empty : Change.Path[(Change.Path.LastIndexOf('/') + 1)..]; public string Folder => Change is null ? string.Empty : Change.Path[..Math.Max(Change.Path.LastIndexOf('/'), 0)]; diff --git a/src/Kontext.Presentation/IssueSidebarViewModel.cs b/src/Kontext.Presentation/IssueSidebarViewModel.cs index c4653da..8c080b0 100644 --- a/src/Kontext.Presentation/IssueSidebarViewModel.cs +++ b/src/Kontext.Presentation/IssueSidebarViewModel.cs @@ -373,8 +373,33 @@ private void OnGroupExpandedChanged(RepositoryGroupViewModel group) _collapsedRepositories.Save(_collapsed); } + private void ForgetClearedWorkspaces(IReadOnlyList workspaces) + { + var existing = workspaces.Select(workspace => workspace.Issue).ToHashSet(); + + foreach (var item in _all.ToList()) + { + if (existing.Contains(item.Key)) + { + continue; + } + + var state = _tracker.StateOf(item.Key); + if (state.Status is IssueWorkStatus.Ready or IssueWorkStatus.Done && state.WorktreePath is not null) + { + _tracker.Report(item.Key, IssueWorkState.NotStarted); + } + else if (state.Status == IssueWorkStatus.NotStarted) + { + item.HasSavedSession = false; + } + } + } + private void MarkExistingWorkspaces(IReadOnlyList workspaces) { + ForgetClearedWorkspaces(workspaces); + foreach (var workspace in workspaces) { if (!_itemsByKey.TryGetValue(workspace.Issue, out var item)) diff --git a/src/Kontext/Views/GitChangesView.xaml b/src/Kontext/Views/GitChangesView.xaml index 66ead98..93e0016 100644 --- a/src/Kontext/Views/GitChangesView.xaml +++ b/src/Kontext/Views/GitChangesView.xaml @@ -102,12 +102,26 @@ AutomationProperties.AutomationId="GitFileCheck" AutomationProperties.Name="{x:Bind Text}" IsChecked="{x:Bind IsChecked, Mode=TwoWay}" /> - + + + + + new(path, new FileChange(path, null, status, IsStaged: false, additions, deletions, IsBinary: false)); - [TestCase(FileChangeStatus.Modified, "M")] - [TestCase(FileChangeStatus.Added, "+")] - [TestCase(FileChangeStatus.Deleted, "D")] - [TestCase(FileChangeStatus.Renamed, "R")] - [TestCase(FileChangeStatus.Untracked, "+")] - [TestCase(FileChangeStatus.Conflicted, "!")] - public void ShowsTheStatusLetterForEachStatus(FileChangeStatus status, string letter) + [TestCase(FileChangeStatus.Added, "+", true, false, false)] + [TestCase(FileChangeStatus.Untracked, "+", true, false, false)] + [TestCase(FileChangeStatus.Modified, "●", false, true, false)] + [TestCase(FileChangeStatus.Renamed, "●", false, true, false)] + [TestCase(FileChangeStatus.Deleted, "−", false, false, true)] + [TestCase(FileChangeStatus.Conflicted, "!", false, false, true)] + public void ShowsAColouredGlyphForEachStatus(FileChangeStatus status, string glyph, bool added, bool changed, bool removed) { - Assert.That(File(status: status).StatusLetter, Is.EqualTo(letter)); + var row = File(status: status); + + Assert.That(row.StatusGlyph, Is.EqualTo(glyph)); + Assert.That(row.IsAddedGlyph, Is.EqualTo(added)); + Assert.That(row.IsChangedGlyph, Is.EqualTo(changed)); + Assert.That(row.IsRemovedGlyph, Is.EqualTo(removed)); } [Test] @@ -54,7 +59,8 @@ public void AHeaderRowHasNoFile() Assert.That(header.IsHeader, Is.True); Assert.That(header.Text, Is.EqualTo("Staged (1)")); - Assert.That(header.StatusLetter, Is.Empty); + Assert.That(header.StatusGlyph, Is.Empty); + Assert.That(header.IsAddedGlyph || header.IsChangedGlyph || header.IsRemovedGlyph, Is.False); Assert.That(header.HasCounts, Is.False); Assert.That(File().IsHeader, Is.False); } diff --git a/tests/Kontext.Presentation.UnitTests/IssueSidebarAutoRefreshTests.cs b/tests/Kontext.Presentation.UnitTests/IssueSidebarAutoRefreshTests.cs index db0a3d3..81f5f32 100644 --- a/tests/Kontext.Presentation.UnitTests/IssueSidebarAutoRefreshTests.cs +++ b/tests/Kontext.Presentation.UnitTests/IssueSidebarAutoRefreshTests.cs @@ -12,6 +12,7 @@ public class IssueSidebarAutoRefreshTests private FakeTimeProvider _clock = null!; private InMemoryIssueWorkTracker _tracker = null!; private FakeGitHubService _gitHub = null!; + private FakeExistingWorkspaceFinder _workspaces = null!; private IssueSidebarViewModel? _sidebar; [SetUp] @@ -20,6 +21,7 @@ public void Setup() _clock = new FakeTimeProvider(); _tracker = new InMemoryIssueWorkTracker(); _gitHub = new FakeGitHubService { Issues = [Issues.Create(4, "Add notifications", "acme/memento")] }; + _workspaces = new FakeExistingWorkspaceFinder(); } [TearDown] @@ -29,7 +31,7 @@ private IssueSidebarViewModel CreateSidebar(TimeSpan interval) => _sidebar = new( _gitHub, _tracker, - new FakeExistingWorkspaceFinder(), + _workspaces, new WorkspaceIssues(_gitHub), new InMemoryHiddenRepositoryStore(), new InMemoryCollapsedRepositoryStore(), @@ -97,6 +99,21 @@ public async Task DoesNotRefreshWhenTheIntervalIsZero() Assert.That(IssueCount(sidebar), Is.EqualTo(1)); } + [Test] + public async Task NoticesWhenASessionIsClearedElsewhere() + { + var issue = _gitHub.Issues[0]; + _workspaces.Found.Add(new ExistingWorkspace(issue.Repository, issue.Number, new WorktreeInfo(@"C:\wt\4", "kontext/4-add", "abc", IsMain: false))); + var sidebar = CreateSidebar(Interval); + await sidebar.RefreshCommand.ExecuteAsync(null); + Assert.That(_tracker.StateOf(issue.Key).Status, Is.EqualTo(IssueWorkStatus.Ready)); + + _workspaces.Found.Clear(); + _clock.Advance(Interval); + + await AsyncAssert.EventuallyAsync(() => _tracker.StateOf(issue.Key).Status == IssueWorkStatus.NotStarted, "the cleared session to be noticed"); + } + [Test] public async Task StopsRefreshingAfterSignOut() {