|
|
@@ -35,6 +35,7 @@
|
|
|
Enable Git
|
|
|
</button>
|
|
|
}
|
|
|
+
|
|
|
@if (_errorMessage is not null)
|
|
|
{
|
|
|
<span class="text-red-400 text-xs" data-testid="git-error">@_errorMessage</span>
|
|
|
@@ -45,8 +46,7 @@ else
|
|
|
{
|
|
|
<div class="relative flex items-center gap-2 text-sm" data-testid="git-status-indicator">
|
|
|
|
|
|
- @* Branch name — clickable to open history *@
|
|
|
- @if (!string.IsNullOrEmpty(_branch))
|
|
|
+ @if (!string.IsNullOrEmpty(_branch) && _hasRemote)
|
|
|
{
|
|
|
<button class="text-zinc-400 text-xs hover:text-emerald-400 transition"
|
|
|
data-testid="git-branch"
|
|
|
@@ -55,33 +55,32 @@ else
|
|
|
</button>
|
|
|
}
|
|
|
|
|
|
- @if (_status == GitRepoStatus.Clean)
|
|
|
+ @if (_status == GitRepoStatus.Clean && _hasRemote)
|
|
|
{
|
|
|
<span class="inline-block w-2 h-2 rounded-full bg-emerald-400"
|
|
|
data-testid="git-status-dot-clean"
|
|
|
- title="All changes committed">
|
|
|
- </span>
|
|
|
+ title="All changes committed"></span>
|
|
|
+
|
|
|
<span class="text-zinc-500 text-xs" data-testid="git-status-text">
|
|
|
Saved
|
|
|
</span>
|
|
|
}
|
|
|
- else if (_status == GitRepoStatus.Dirty)
|
|
|
+ else if (_status == GitRepoStatus.Dirty && _hasRemote)
|
|
|
{
|
|
|
<span class="inline-block w-2 h-2 rounded-full bg-amber-400 animate-pulse"
|
|
|
data-testid="git-status-dot-dirty"
|
|
|
- title="Uncommitted changes">
|
|
|
- </span>
|
|
|
+ title="Uncommitted changes"></span>
|
|
|
|
|
|
- @* Save button with dropdown toggle *@
|
|
|
<div class="relative flex" data-testid="git-save-group">
|
|
|
<button class="px-2 py-1 text-xs rounded-l bg-emerald-600 hover:bg-emerald-500 text-white transition disabled:opacity-50"
|
|
|
- disabled="@_isBusy"
|
|
|
+ disabled="@(_isBusy || !_hasRemote)"
|
|
|
data-testid="git-save-button"
|
|
|
@onclick="CommitAsync">
|
|
|
@(_isCommitting ? "Saving..." : "Save")
|
|
|
</button>
|
|
|
+
|
|
|
<button class="px-1.5 py-1 text-xs rounded-r bg-emerald-700 hover:bg-emerald-600 text-white transition border-l border-emerald-800 disabled:opacity-50"
|
|
|
- disabled="@_isBusy"
|
|
|
+ disabled="@(_isBusy || !_hasRemote)"
|
|
|
data-testid="git-save-dropdown"
|
|
|
@onclick="ToggleDropdown">
|
|
|
▾
|
|
|
@@ -91,11 +90,13 @@ else
|
|
|
{
|
|
|
<div class="absolute top-full right-0 mt-1 bg-zinc-800 border border-zinc-700 rounded shadow-lg z-50 min-w-[120px]"
|
|
|
data-testid="git-dropdown-menu">
|
|
|
+
|
|
|
<button class="w-full text-left px-3 py-2 text-xs text-zinc-300 hover:bg-zinc-700 hover:text-white transition"
|
|
|
data-testid="git-diff-button"
|
|
|
@onclick="OpenDiffAsync">
|
|
|
Diff
|
|
|
</button>
|
|
|
+
|
|
|
@if (!_confirmDiscard)
|
|
|
{
|
|
|
<button class="w-full text-left px-3 py-2 text-xs text-zinc-400 hover:bg-zinc-700 hover:text-red-400 transition"
|
|
|
@@ -108,11 +109,13 @@ else
|
|
|
{
|
|
|
<div class="px-3 py-2 flex items-center gap-2">
|
|
|
<span class="text-red-400 text-xs">Sure?</span>
|
|
|
+
|
|
|
<button class="px-2 py-0.5 text-xs rounded bg-red-600 hover:bg-red-500 text-white transition"
|
|
|
data-testid="git-discard-confirm"
|
|
|
@onclick="DiscardAsync">
|
|
|
Yes
|
|
|
</button>
|
|
|
+
|
|
|
<button class="px-2 py-0.5 text-xs rounded text-zinc-400 hover:text-white transition"
|
|
|
data-testid="git-discard-cancel"
|
|
|
@onclick="() => _confirmDiscard = false">
|
|
|
@@ -125,10 +128,10 @@ else
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
- @* Sync / Remote section *@
|
|
|
@if (!_hasRemote)
|
|
|
{
|
|
|
<div class="relative flex items-center gap-1" data-testid="git-remote-group">
|
|
|
+
|
|
|
@if (_showAddRemote)
|
|
|
{
|
|
|
<input type="text"
|
|
|
@@ -137,12 +140,14 @@ else
|
|
|
@bind="_remoteUrl"
|
|
|
@bind:event="oninput"
|
|
|
data-testid="git-remote-url" />
|
|
|
+
|
|
|
<button class="px-2 py-0.5 text-xs rounded bg-emerald-600 hover:bg-emerald-500 text-white transition disabled:opacity-50"
|
|
|
disabled="@(string.IsNullOrWhiteSpace(_remoteUrl))"
|
|
|
data-testid="git-remote-save"
|
|
|
@onclick="AddRemoteAsync">
|
|
|
Add
|
|
|
</button>
|
|
|
+
|
|
|
<button class="px-2 py-0.5 text-xs rounded text-zinc-400 hover:text-white transition"
|
|
|
data-testid="git-remote-cancel"
|
|
|
@onclick="CancelAddRemote">
|
|
|
@@ -157,15 +162,21 @@ else
|
|
|
Add Remote
|
|
|
</button>
|
|
|
}
|
|
|
+
|
|
|
+ <span class="text-xs text-zinc-500">
|
|
|
+ Connect a remote repository to enable saving and sync.
|
|
|
+ </span>
|
|
|
</div>
|
|
|
}
|
|
|
- else if (_hasRemote)
|
|
|
+ else
|
|
|
{
|
|
|
<div class="relative flex" data-testid="git-sync-group">
|
|
|
+
|
|
|
<button class="px-2 py-1 text-xs rounded text-zinc-400 hover:text-white hover:bg-zinc-700 transition disabled:opacity-50"
|
|
|
disabled="@_isSyncing"
|
|
|
data-testid="git-sync-button"
|
|
|
@onclick="ToggleSyncAsync">
|
|
|
+
|
|
|
@if (_isFetching)
|
|
|
{
|
|
|
<span>Checking...</span>
|
|
|
@@ -188,32 +199,14 @@ else
|
|
|
{
|
|
|
<div class="absolute top-full right-0 mt-1 bg-zinc-800 border border-zinc-700 rounded shadow-lg z-50 min-w-[140px]"
|
|
|
data-testid="git-sync-dropdown">
|
|
|
- @if (_syncStatus.Error is not null)
|
|
|
- {
|
|
|
- <div class="px-3 py-2 text-xs text-red-400 border-b border-zinc-700">
|
|
|
- Fetch failed
|
|
|
- </div>
|
|
|
- }
|
|
|
- else if (_syncStatus.Ahead > 0 || _syncStatus.Behind > 0)
|
|
|
- {
|
|
|
- <div class="px-3 py-2 text-xs text-zinc-500 border-b border-zinc-700">
|
|
|
- @if (_syncStatus.Ahead > 0) { <span class="text-emerald-400">↑@_syncStatus.Ahead ahead</span> }
|
|
|
- @if (_syncStatus.Ahead > 0 && _syncStatus.Behind > 0) { <span> · </span> }
|
|
|
- @if (_syncStatus.Behind > 0) { <span class="text-blue-400">↓@_syncStatus.Behind behind</span> }
|
|
|
- </div>
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- <div class="px-3 py-2 text-xs text-zinc-500 border-b border-zinc-700">
|
|
|
- Up to date
|
|
|
- </div>
|
|
|
- }
|
|
|
+
|
|
|
<button class="w-full text-left px-3 py-2 text-xs text-zinc-300 hover:bg-zinc-700 hover:text-white transition disabled:opacity-50"
|
|
|
- disabled="@(_isSyncing)"
|
|
|
+ disabled="@_isSyncing"
|
|
|
data-testid="git-push-button"
|
|
|
@onclick="PushAsync">
|
|
|
@(_isPushing ? "Pushing..." : "Push")
|
|
|
</button>
|
|
|
+
|
|
|
<button class="w-full text-left px-3 py-2 text-xs text-zinc-300 hover:bg-zinc-700 hover:text-white transition disabled:opacity-50"
|
|
|
disabled="@(_isSyncing || _syncStatus.Error is not null)"
|
|
|
data-testid="git-pull-button"
|
|
|
@@ -227,147 +220,39 @@ else
|
|
|
|
|
|
@if (_errorMessage is not null)
|
|
|
{
|
|
|
- <span class="text-red-400 text-xs" data-testid="git-error">
|
|
|
- @_errorMessage
|
|
|
- </span>
|
|
|
+ <span class="text-red-400 text-xs">@_errorMessage</span>
|
|
|
}
|
|
|
</div>
|
|
|
-
|
|
|
- @* Dropdown backdrop — closes all dropdowns on outside click *@
|
|
|
- @if (_showDropdown || _showSyncDropdown)
|
|
|
- {
|
|
|
- <div class="fixed inset-0 z-40" @onclick="CloseAllDropdowns"></div>
|
|
|
- }
|
|
|
-
|
|
|
- @* Diff Modal *@
|
|
|
- @if (_showDiff)
|
|
|
- {
|
|
|
- <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
|
|
|
- data-testid="git-diff-overlay"
|
|
|
- @onclick="CloseDiff">
|
|
|
- <div class="bg-zinc-900 border border-zinc-700 rounded-lg shadow-xl w-[90vw] max-w-4xl max-h-[80vh] flex flex-col"
|
|
|
- @onclick:stopPropagation="true">
|
|
|
-
|
|
|
- <div class="flex items-center justify-between px-4 py-3 border-b border-zinc-700">
|
|
|
- <div class="flex items-center gap-3">
|
|
|
- <span class="text-sm font-semibold text-zinc-200">Changes</span>
|
|
|
- <span class="text-xs text-zinc-500">@_changedFiles.Length file(s)</span>
|
|
|
- </div>
|
|
|
- <button class="text-zinc-400 hover:text-white transition text-lg"
|
|
|
- data-testid="git-diff-close"
|
|
|
- @onclick="CloseDiff">
|
|
|
- ×
|
|
|
- </button>
|
|
|
- </div>
|
|
|
-
|
|
|
- @if (_changedFiles.Length > 0)
|
|
|
- {
|
|
|
- <div class="px-4 py-2 border-b border-zinc-800 flex flex-wrap gap-2">
|
|
|
- @foreach (var file in _changedFiles)
|
|
|
- {
|
|
|
- var status = file.Length >= 2 ? file[..2].Trim() : "?";
|
|
|
- var name = file.Length >= 3 ? file[3..] : file;
|
|
|
- var color = status switch
|
|
|
- {
|
|
|
- "M" => "text-amber-400",
|
|
|
- "A" or "?" => "text-emerald-400",
|
|
|
- "D" => "text-red-400",
|
|
|
- _ => "text-zinc-400"
|
|
|
- };
|
|
|
- <span class="text-xs">
|
|
|
- <span class="@color font-bold">@status</span>
|
|
|
- <span class="text-zinc-300">@name</span>
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- <div class="overflow-auto flex-1 p-4">
|
|
|
- <pre class="text-xs leading-relaxed whitespace-pre-wrap">@((MarkupString)FormatDiff(_diffContent))</pre>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
-
|
|
|
- @* History Modal *@
|
|
|
- @if (_showHistory)
|
|
|
- {
|
|
|
- <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
|
|
|
- data-testid="git-history-overlay"
|
|
|
- @onclick="CloseHistory">
|
|
|
- <div class="bg-zinc-900 border border-zinc-700 rounded-lg shadow-xl w-[90vw] max-w-3xl max-h-[80vh] flex flex-col"
|
|
|
- @onclick:stopPropagation="true">
|
|
|
-
|
|
|
- <div class="flex items-center justify-between px-4 py-3 border-b border-zinc-700">
|
|
|
- <div class="flex items-center gap-3">
|
|
|
- <span class="text-sm font-semibold text-zinc-200">History</span>
|
|
|
- <span class="text-xs text-zinc-500">@_branch</span>
|
|
|
- <span class="text-xs text-zinc-600">@_logEntries.Length commits</span>
|
|
|
- </div>
|
|
|
- <button class="text-zinc-400 hover:text-white transition text-lg"
|
|
|
- data-testid="git-history-close"
|
|
|
- @onclick="CloseHistory">
|
|
|
- ×
|
|
|
- </button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="overflow-auto flex-1">
|
|
|
- @if (_logEntries.Length == 0)
|
|
|
- {
|
|
|
- <div class="p-4 text-zinc-500 text-sm">No commits yet.</div>
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- <div class="divide-y divide-zinc-800">
|
|
|
- @foreach (var entry in _logEntries)
|
|
|
- {
|
|
|
- <div class="px-4 py-3 hover:bg-zinc-800/50 transition" data-testid="git-log-entry">
|
|
|
- <div class="flex items-center gap-3">
|
|
|
- <span class="text-xs text-emerald-400 font-mono shrink-0">@entry.Hash</span>
|
|
|
- <span class="text-sm text-zinc-200 truncate">@entry.Message</span>
|
|
|
- </div>
|
|
|
- <div class="flex items-center gap-3 mt-1">
|
|
|
- <span class="text-xs text-zinc-500">@entry.Author</span>
|
|
|
- <span class="text-xs text-zinc-600">@entry.Date</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
@code {
|
|
|
+
|
|
|
private GitRepoStatus _status = GitRepoStatus.NotAvailable;
|
|
|
- private string _branch = string.Empty;
|
|
|
+ private string _branch = "";
|
|
|
private bool _isCommitting;
|
|
|
private bool _isRestoring;
|
|
|
private bool _confirmDiscard;
|
|
|
private bool _showDropdown;
|
|
|
- private string? _errorMessage;
|
|
|
- private PeriodicTimer? _timer;
|
|
|
- private CancellationTokenSource? _cts;
|
|
|
-
|
|
|
- private bool _showDiff;
|
|
|
- private string _diffContent = string.Empty;
|
|
|
- private string[] _changedFiles = [];
|
|
|
-
|
|
|
+ private bool _showAddRemote;
|
|
|
+ private bool _showSyncDropdown;
|
|
|
private bool _showHistory;
|
|
|
- private GitLogEntry[] _logEntries = [];
|
|
|
-
|
|
|
private bool _hasRemote;
|
|
|
- private GitSyncStatus _syncStatus = new(0, 0, false);
|
|
|
- private bool _showSyncDropdown;
|
|
|
private bool _isFetching;
|
|
|
private bool _isPushing;
|
|
|
private bool _isPulling;
|
|
|
private bool _isInitializing;
|
|
|
private bool _confirmInit;
|
|
|
- private bool _showAddRemote;
|
|
|
- private string _remoteUrl = string.Empty;
|
|
|
+
|
|
|
+ private string? _errorMessage;
|
|
|
+ private string _remoteUrl = "";
|
|
|
+ private string _diffContent = "";
|
|
|
+ private string[] _changedFiles = [];
|
|
|
+ private GitLogEntry[] _logEntries = [];
|
|
|
+
|
|
|
+ private PeriodicTimer? _timer;
|
|
|
+ private CancellationTokenSource? _cts;
|
|
|
+
|
|
|
+ private GitSyncStatus _syncStatus = new(0,0,false);
|
|
|
|
|
|
private bool _isBusy => _isCommitting || _isRestoring || _isSyncing;
|
|
|
private bool _isSyncing => _isPushing || _isPulling || _isFetching;
|
|
|
@@ -382,11 +267,9 @@ else
|
|
|
_branch = GitRepo.GetCurrentBranch();
|
|
|
_hasRemote = GitRepo.HasRemote();
|
|
|
|
|
|
- _cts?.Cancel();
|
|
|
- _timer?.Dispose();
|
|
|
-
|
|
|
_cts = new CancellationTokenSource();
|
|
|
- _timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
|
|
|
+ _timer = new PeriodicTimer(TimeSpan.FromSeconds(15));
|
|
|
+
|
|
|
_ = PollStatusAsync(_cts.Token);
|
|
|
|
|
|
await Task.CompletedTask;
|
|
|
@@ -406,16 +289,11 @@ else
|
|
|
if (newStatus != _status)
|
|
|
{
|
|
|
_status = newStatus;
|
|
|
- _confirmDiscard = false;
|
|
|
- _showDropdown = false;
|
|
|
-
|
|
|
await InvokeAsync(StateHasChanged);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- }
|
|
|
+ catch (OperationCanceledException) {}
|
|
|
}
|
|
|
|
|
|
private async Task InitRepoAsync()
|
|
|
@@ -426,8 +304,7 @@ else
|
|
|
try
|
|
|
{
|
|
|
var error = await InitRepo.ExecuteAsync();
|
|
|
-
|
|
|
- if (error is not null)
|
|
|
+ if (error != null)
|
|
|
{
|
|
|
_errorMessage = error;
|
|
|
return;
|
|
|
@@ -436,14 +313,6 @@ else
|
|
|
_status = GitRepo.GetStatus();
|
|
|
_branch = GitRepo.GetCurrentBranch();
|
|
|
_hasRemote = GitRepo.HasRemote();
|
|
|
-
|
|
|
- _cts?.Cancel();
|
|
|
- _timer?.Dispose();
|
|
|
-
|
|
|
- _cts = new CancellationTokenSource();
|
|
|
- _timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
|
|
|
-
|
|
|
- _ = PollStatusAsync(_cts.Token);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -458,7 +327,7 @@ else
|
|
|
private void CancelAddRemote()
|
|
|
{
|
|
|
_showAddRemote = false;
|
|
|
- _remoteUrl = string.Empty;
|
|
|
+ _remoteUrl = "";
|
|
|
}
|
|
|
|
|
|
private async Task AddRemoteAsync()
|
|
|
@@ -468,7 +337,8 @@ else
|
|
|
try
|
|
|
{
|
|
|
var error = await AddRemoteUseCase.ExecuteAsync(_remoteUrl);
|
|
|
- if (error is not null)
|
|
|
+
|
|
|
+ if (error != null)
|
|
|
{
|
|
|
_errorMessage = error;
|
|
|
return;
|
|
|
@@ -476,7 +346,12 @@ else
|
|
|
|
|
|
_hasRemote = true;
|
|
|
_showAddRemote = false;
|
|
|
- _remoteUrl = string.Empty;
|
|
|
+ _remoteUrl = "";
|
|
|
+
|
|
|
+ _syncStatus = GitRepo.FetchAndGetSyncStatus();
|
|
|
+
|
|
|
+ if (_syncStatus.Behind > 0)
|
|
|
+ await PullAsync();
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -484,26 +359,30 @@ else
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private async Task CommitAsync()
|
|
|
+ private async Task CommitAsync()
|
|
|
{
|
|
|
+ if (!_hasRemote)
|
|
|
+ {
|
|
|
+ _errorMessage = "Add a remote repository before saving.";
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
_errorMessage = null;
|
|
|
_isCommitting = true;
|
|
|
- _confirmDiscard = false;
|
|
|
- _showDropdown = false;
|
|
|
|
|
|
try
|
|
|
{
|
|
|
var error = await CommitAll.ExecuteAsync(
|
|
|
$"rackpeek: save config {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
|
|
|
|
|
|
- if (error is not null)
|
|
|
+ if (error != null)
|
|
|
_errorMessage = error;
|
|
|
|
|
|
_status = GitRepo.GetStatus();
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- _errorMessage = $"Unexpected error: {ex.Message}";
|
|
|
+ _errorMessage = $"Commit error: {ex.Message}";
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
@@ -515,182 +394,147 @@ else
|
|
|
{
|
|
|
_showDropdown = !_showDropdown;
|
|
|
_showSyncDropdown = false;
|
|
|
- _confirmDiscard = false;
|
|
|
}
|
|
|
|
|
|
private void ToggleSyncAsync()
|
|
|
{
|
|
|
+ _showSyncDropdown = !_showSyncDropdown;
|
|
|
+ _showDropdown = false;
|
|
|
+
|
|
|
if (_showSyncDropdown)
|
|
|
{
|
|
|
- _showSyncDropdown = false;
|
|
|
+ _isFetching = true;
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ _syncStatus = GitRepo.FetchAndGetSyncStatus();
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ _isFetching = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task PushAsync()
|
|
|
+ {
|
|
|
+ if (!_hasRemote)
|
|
|
+ {
|
|
|
+ _errorMessage = "Add a remote first.";
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- _showDropdown = false;
|
|
|
_errorMessage = null;
|
|
|
- _isFetching = true;
|
|
|
- _showSyncDropdown = true;
|
|
|
+ _isPushing = true;
|
|
|
|
|
|
try
|
|
|
{
|
|
|
+ var error = await PushUseCase.ExecuteAsync();
|
|
|
+
|
|
|
+ if (error != null)
|
|
|
+ _errorMessage = error;
|
|
|
+
|
|
|
_syncStatus = GitRepo.FetchAndGetSyncStatus();
|
|
|
- if (_syncStatus.Error is not null)
|
|
|
- _errorMessage = _syncStatus.Error;
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- _errorMessage = $"Fetch error: {ex.Message}";
|
|
|
+ _errorMessage = $"Push error: {ex.Message}";
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
- _isFetching = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void CloseAllDropdowns()
|
|
|
- {
|
|
|
- _showDropdown = false;
|
|
|
- _showSyncDropdown = false;
|
|
|
- _confirmDiscard = false;
|
|
|
- }
|
|
|
-
|
|
|
- private void OpenDiffAsync()
|
|
|
- {
|
|
|
- _showDropdown = false;
|
|
|
- _changedFiles = GitRepo.GetChangedFiles();
|
|
|
- _diffContent = GitRepo.GetDiff();
|
|
|
- _showDiff = true;
|
|
|
- }
|
|
|
-
|
|
|
- private void CloseDiff()
|
|
|
- {
|
|
|
- _showDiff = false;
|
|
|
- _diffContent = string.Empty;
|
|
|
- _changedFiles = [];
|
|
|
- }
|
|
|
-
|
|
|
- private void ToggleHistoryAsync()
|
|
|
- {
|
|
|
- if (_showHistory)
|
|
|
- {
|
|
|
- CloseHistory();
|
|
|
- return;
|
|
|
+ _isPushing = false;
|
|
|
}
|
|
|
-
|
|
|
- _showDropdown = false;
|
|
|
- _showSyncDropdown = false;
|
|
|
- _logEntries = GitRepo.GetLog(20);
|
|
|
- _showHistory = true;
|
|
|
}
|
|
|
|
|
|
- private void CloseHistory()
|
|
|
- {
|
|
|
- _showHistory = false;
|
|
|
- _logEntries = [];
|
|
|
- }
|
|
|
-
|
|
|
- private async Task DiscardAsync()
|
|
|
+ private async Task PullAsync()
|
|
|
{
|
|
|
_errorMessage = null;
|
|
|
- _isRestoring = true;
|
|
|
- _confirmDiscard = false;
|
|
|
- _showDropdown = false;
|
|
|
+ _isPulling = true;
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- var error = await RestoreAll.ExecuteAsync();
|
|
|
- if (error is not null)
|
|
|
+ var error = await PullUseCase.ExecuteAsync();
|
|
|
+
|
|
|
+ if (error != null)
|
|
|
_errorMessage = error;
|
|
|
|
|
|
+ _syncStatus = GitRepo.FetchAndGetSyncStatus();
|
|
|
_status = GitRepo.GetStatus();
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- _errorMessage = $"Unexpected error: {ex.Message}";
|
|
|
+ _errorMessage = $"Pull error: {ex.Message}";
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
- _isRestoring = false;
|
|
|
+ _isPulling = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private async Task PushAsync()
|
|
|
+ public void Dispose()
|
|
|
{
|
|
|
- _errorMessage = null;
|
|
|
- _isPushing = true;
|
|
|
+ _cts?.Cancel();
|
|
|
+ _cts?.Dispose();
|
|
|
+ _timer?.Dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OpenDiffAsync()
|
|
|
+ {
|
|
|
+ _showDropdown = false;
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- var error = await PushUseCase.ExecuteAsync();
|
|
|
- if (error is not null)
|
|
|
- _errorMessage = error;
|
|
|
-
|
|
|
- _syncStatus = GitRepo.FetchAndGetSyncStatus();
|
|
|
+ _changedFiles = GitRepo.GetChangedFiles();
|
|
|
+ _diffContent = GitRepo.GetDiff();
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- _errorMessage = $"Push error: {ex.Message}";
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- _isPushing = false;
|
|
|
+ _errorMessage = $"Diff error: {ex.Message}";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private async Task PullAsync()
|
|
|
+ private async Task DiscardAsync()
|
|
|
{
|
|
|
_errorMessage = null;
|
|
|
- _isPulling = true;
|
|
|
+ _isRestoring = true;
|
|
|
+ _confirmDiscard = false;
|
|
|
+ _showDropdown = false;
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- var error = await PullUseCase.ExecuteAsync();
|
|
|
- if (error is not null)
|
|
|
+ var error = await RestoreAll.ExecuteAsync();
|
|
|
+
|
|
|
+ if (error != null)
|
|
|
_errorMessage = error;
|
|
|
|
|
|
- _syncStatus = GitRepo.FetchAndGetSyncStatus();
|
|
|
_status = GitRepo.GetStatus();
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- _errorMessage = $"Pull error: {ex.Message}";
|
|
|
+ _errorMessage = $"Discard error: {ex.Message}";
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
- _isPulling = false;
|
|
|
+ _isRestoring = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static string FormatDiff(string diff)
|
|
|
+ private void ToggleHistoryAsync()
|
|
|
{
|
|
|
- if (string.IsNullOrWhiteSpace(diff))
|
|
|
- return "<span class=\"text-zinc-500\">No diff available</span>";
|
|
|
-
|
|
|
- var lines = diff.Split('\n');
|
|
|
- var sb = new System.Text.StringBuilder();
|
|
|
-
|
|
|
- foreach (var line in lines)
|
|
|
+ if (_showHistory)
|
|
|
{
|
|
|
- var escaped = System.Net.WebUtility.HtmlEncode(line);
|
|
|
- if (line.StartsWith('+') && !line.StartsWith("+++"))
|
|
|
- sb.AppendLine($"<span class=\"text-emerald-400\">{escaped}</span>");
|
|
|
- else if (line.StartsWith('-') && !line.StartsWith("---"))
|
|
|
- sb.AppendLine($"<span class=\"text-red-400\">{escaped}</span>");
|
|
|
- else if (line.StartsWith("@@"))
|
|
|
- sb.AppendLine($"<span class=\"text-blue-400\">{escaped}</span>");
|
|
|
- else if (line.StartsWith("diff "))
|
|
|
- sb.AppendLine($"<span class=\"text-amber-300 font-bold\">{escaped}</span>");
|
|
|
- else
|
|
|
- sb.AppendLine($"<span class=\"text-zinc-400\">{escaped}</span>");
|
|
|
+ _showHistory = false;
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- return sb.ToString();
|
|
|
- }
|
|
|
-
|
|
|
- public void Dispose()
|
|
|
- {
|
|
|
- _cts?.Cancel();
|
|
|
- _cts?.Dispose();
|
|
|
- _timer?.Dispose();
|
|
|
+ try
|
|
|
+ {
|
|
|
+ _logEntries = GitRepo.GetLog(20);
|
|
|
+ _showHistory = true;
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _errorMessage = $"History error: {ex.Message}";
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
+}
|