Public Access
Give the window its v5b chrome and each session surface its own shell
This commit is contained in:
@@ -40,6 +40,15 @@ internal sealed class RemoteEntryRowViewModel(SftpEntry entry)
|
||||
|
||||
internal bool IsFile => entry.Kind is SftpEntryKind.File;
|
||||
|
||||
/// <summary>The row's own Material Icons glyph — folder for a directory, a document for a file.</summary>
|
||||
/// <remarks>
|
||||
/// The design's own sample data asks for "draft", which is a Material Symbols glyph this application's
|
||||
/// embedded classic Material Icons font does not contain — see TransfersScreen.axaml's own remark on the
|
||||
/// substitution. insert_drive_file is the closest classic equivalent: a plain document rather than one
|
||||
/// with a folded corner, but a file rather than a folder is the fact this glyph exists to carry.
|
||||
/// </remarks>
|
||||
internal string IconGlyph => IsNavigable ? "\uE2C7" : "\uE24D";
|
||||
|
||||
/// <remarks>
|
||||
/// A directory shows nothing rather than a zero. Its inode's size is a number no user has ever wanted,
|
||||
/// and a column of zeroes beside real sizes reads as a listing that failed to measure them.
|
||||
@@ -84,6 +93,9 @@ internal sealed class LocalEntryRowViewModel(LocalEntry entry)
|
||||
|
||||
internal bool IsFile => !entry.IsDirectory;
|
||||
|
||||
/// <inheritdoc cref="RemoteEntryRowViewModel.IconGlyph" />
|
||||
internal string IconGlyph => IsNavigable ? "\uE2C7" : "\uE24D";
|
||||
|
||||
internal string Size => entry.IsDirectory ? string.Empty : ByteSize.Format(entry.Length);
|
||||
|
||||
internal string Modified => Timestamps.Format(entry.LastWriteTimeUtc);
|
||||
@@ -132,8 +144,64 @@ internal sealed partial class TransferRowViewModel(TransferSnapshot snapshot) :
|
||||
? Transfer.RemotePath
|
||||
: Transfer.LocalPath;
|
||||
|
||||
/// <summary>
|
||||
/// "source → destination directory", the v5b TRANSFERS strip's own single-line label.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Built from real paths this snapshot already carries rather than a shortened form invented for the
|
||||
/// strip: the source is the file name at whichever end the transfer reads from, and the destination is
|
||||
/// the whole directory it is written into — <see cref="SftpPath.Parent"/> for an upload, and
|
||||
/// <see cref="System.IO.Path.GetDirectoryName(string)"/> for a download, because the two ends are a
|
||||
/// remote path and a local one and this codebase does not run the BCL's path helpers on the former; see
|
||||
/// <see cref="SftpPath"/>'s own remark on why. Neither end is trimmed to a last segment the way the
|
||||
/// design's own sample data is — a directory two levels down would read as the same word as its parent —
|
||||
/// so the strip's own 320-pixel column and <c>TextTrimming="CharacterEllipsis"</c> carry the length
|
||||
/// instead.
|
||||
/// </remarks>
|
||||
internal string Label
|
||||
{
|
||||
get
|
||||
{
|
||||
var (sourcePath, destinationDirectory) = Transfer.Direction is TransferDirection.Upload
|
||||
? (Transfer.LocalPath, SftpPath.Parent(Transfer.RemotePath))
|
||||
: (Transfer.RemotePath, System.IO.Path.GetDirectoryName(Transfer.LocalPath) ?? string.Empty);
|
||||
|
||||
// Path.GetFileName reads either separator on Windows, so a remote path's forward slashes need no
|
||||
// translation to name the file at the end of it.
|
||||
var source = System.IO.Path.GetFileName(sourcePath);
|
||||
|
||||
return $"{source} → {destinationDirectory}";
|
||||
}
|
||||
}
|
||||
|
||||
internal double Percent => Transfer.Fraction * 100;
|
||||
|
||||
/// <summary>
|
||||
/// The v5b TRANSFERS strip's own right-aligned status word: a state, or a live percentage while running.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not <see cref="Progress"/>, which stays for the row's own tooltip: that string carries the bytes and
|
||||
/// the rate together, which is the sentence somebody reads on purpose, and a status column has room for a
|
||||
/// word rather than a sentence. A running transfer's word is its own percentage rather than "running",
|
||||
/// because the strip's slim progress bar already says "in motion" and the number beside it is the fact
|
||||
/// the bar alone cannot state exactly.
|
||||
/// </remarks>
|
||||
internal string StatusWord => Transfer.State switch
|
||||
{
|
||||
TransferState.Queued => "queued",
|
||||
TransferState.Running => string.Create(
|
||||
CultureInfo.InvariantCulture, $"{(int)Percent}%"),
|
||||
TransferState.Completed => "done",
|
||||
TransferState.Cancelled => "stopped",
|
||||
_ => "failed",
|
||||
};
|
||||
|
||||
/// <summary>Whether the status word should read as the strip's own "in motion" colour.</summary>
|
||||
internal bool IsInProgress => Transfer.State is TransferState.Running;
|
||||
|
||||
/// <summary>Whether the status word should read as the strip's own "finished, kept" colour.</summary>
|
||||
internal bool IsDone => Transfer.State is TransferState.Completed;
|
||||
|
||||
/// <summary>
|
||||
/// What the row says about where it has got to.
|
||||
/// </summary>
|
||||
@@ -192,6 +260,9 @@ internal sealed partial class TransferRowViewModel(TransferSnapshot snapshot) :
|
||||
OnPropertyChanged(nameof(Progress));
|
||||
OnPropertyChanged(nameof(Percent));
|
||||
OnPropertyChanged(nameof(StateLabel));
|
||||
OnPropertyChanged(nameof(StatusWord));
|
||||
OnPropertyChanged(nameof(IsInProgress));
|
||||
OnPropertyChanged(nameof(IsDone));
|
||||
OnPropertyChanged(nameof(IsRunning));
|
||||
OnPropertyChanged(nameof(IsFinished));
|
||||
OnPropertyChanged(nameof(CanResume));
|
||||
@@ -297,6 +368,18 @@ internal sealed partial class TransfersViewModel : ObservableObject, IAsyncDispo
|
||||
/// </remarks>
|
||||
private (string Address, string HostLabel, Guid HostId, DateTimeOffset StartedAt)? connected;
|
||||
|
||||
/// <summary>
|
||||
/// When the open connection was made, for the v5b session shell's status bar — or null while nothing is
|
||||
/// connected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A read of <see cref="connected"/> rather than a field of its own: that tuple is already the one place
|
||||
/// this screen keeps "what is open and when it opened", written at the same two call sites — a host and a
|
||||
/// bucket — that set <see cref="ConnectedTo"/>. A second field would be a second fact to keep in step with
|
||||
/// the first, for no reader that could not already reach it here.
|
||||
/// </remarks>
|
||||
internal DateTimeOffset? ConnectedStartedAt => connected?.StartedAt;
|
||||
|
||||
private bool disposed;
|
||||
|
||||
/// <param name="sftp">Opens SFTP sessions.</param>
|
||||
@@ -342,7 +425,11 @@ internal sealed partial class TransfersViewModel : ObservableObject, IAsyncDispo
|
||||
// which is the life of the process.
|
||||
RemoteEntries.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasRemoteEntries));
|
||||
LocalEntries.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasLocalEntries));
|
||||
Transfers.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasTransfers));
|
||||
Transfers.CollectionChanged += (_, _) =>
|
||||
{
|
||||
OnPropertyChanged(nameof(HasTransfers));
|
||||
OnPropertyChanged(nameof(ActiveTransfersLabel));
|
||||
};
|
||||
|
||||
// The fourth, and it follows a list this screen does not own: the buckets are refilled from the
|
||||
// vault on every synchronisation pass, so the invitation these flags decide between has to change
|
||||
@@ -619,6 +706,20 @@ internal sealed partial class TransfersViewModel : ObservableObject, IAsyncDispo
|
||||
/// </remarks>
|
||||
internal int ActiveTransfers => Transfers.Count(row => row.IsRunning);
|
||||
|
||||
/// <summary>
|
||||
/// The v5b TRANSFERS strip's own count chip: "N active", off <see cref="ActiveTransfers"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A string of its own rather than a converter in the markup, for the reason every other label in this
|
||||
/// class is one: the word belongs beside the number it is stated for, in one place, rather than composed
|
||||
/// out of a format string a screen's own XAML would otherwise have to carry. It reads "0 active" rather
|
||||
/// than hiding at zero — the chip only exists at all while <see cref="HasTransfers"/> is true, which a
|
||||
/// queue holding nothing but finished or failed rows still is, and "0 active" is the honest word for
|
||||
/// that shape rather than a chip claiming activity that has already finished.
|
||||
/// </remarks>
|
||||
internal string ActiveTransfersLabel => string.Create(
|
||||
CultureInfo.InvariantCulture, $"{ActiveTransfers} active");
|
||||
|
||||
internal bool HasTransfers => Transfers.Count > 0;
|
||||
|
||||
/// <summary>Whether a download of the chosen remote file would have somewhere to go.</summary>
|
||||
@@ -1653,6 +1754,11 @@ internal sealed partial class TransfersViewModel : ObservableObject, IAsyncDispo
|
||||
Transfers.Add(new TransferRowViewModel(e.Transfer));
|
||||
}
|
||||
|
||||
// Not covered by the CollectionChanged subscription above: a transfer that finishes changes which
|
||||
// rows count as active without the collection itself gaining or losing a row, so the strip's own
|
||||
// count chip needs its own raise here.
|
||||
OnPropertyChanged(nameof(ActiveTransfersLabel));
|
||||
|
||||
ActivityChanged?.Invoke(this, EventArgs.Empty);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user