feat: restore legacy modal and drag behavior
This commit is contained in:
@@ -3374,18 +3374,19 @@ public sealed class LegacyOperatorController
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
var selectedRevision = selected.NamedPlaylist?.Revision ?? -1;
|
||||
|
||||
var saved = await SaveCurrentNamedPlaylistAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var succeeded =
|
||||
(saved.NamedPlaylist?.Revision ?? -1) > selectedRevision &&
|
||||
string.Equals(
|
||||
saved.NamedPlaylist?.SelectedDefinitionId,
|
||||
definitionId,
|
||||
StringComparison.Ordinal) &&
|
||||
saved.NamedPlaylist?.LastMutationOutcome is
|
||||
LegacyNamedPlaylistMutationOutcome.CommittedFresh or
|
||||
LegacyNamedPlaylistMutationOutcome.CommittedOptimistic &&
|
||||
saved.StatusKind == LegacyOperatorStatusKind.Information;
|
||||
LegacyNamedPlaylistMutationOutcome.CommittedOptimistic;
|
||||
CompleteCommand(command, definitionId, succeeded);
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
@@ -3441,6 +3442,88 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes a correlated named-playlist command when the native dispatch
|
||||
/// boundary rejects the request or encounters an unexpected exception.
|
||||
/// The terminal receipt prevents the WebView modal from waiting indefinitely;
|
||||
/// the named-playlist workflow remains the authority for any write quarantine
|
||||
/// that was already latched before the exception escaped.
|
||||
/// </summary>
|
||||
public LegacyOperatorSnapshot ReportNamedPlaylistCommandFailure(
|
||||
string command,
|
||||
string targetId,
|
||||
string message)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(command);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(targetId);
|
||||
if (command is not "refresh-named-playlists" and
|
||||
not "select-named-playlist" and
|
||||
not "create-named-playlist" and
|
||||
not "delete-selected-named-playlist" and
|
||||
not "load-named-playlist-by-id" and
|
||||
not "save-current-named-playlist-to")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(command),
|
||||
command,
|
||||
"Only correlated named-playlist commands can be completed here.");
|
||||
}
|
||||
|
||||
CompleteCommand(command, targetId, succeeded: false);
|
||||
return ReportError(message);
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot CompleteNamedPlaylistDispatch(
|
||||
string command,
|
||||
string requestId,
|
||||
bool succeeded)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(command);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(requestId);
|
||||
if (command is not "refresh-named-playlists" and
|
||||
not "select-named-playlist" and
|
||||
not "create-named-playlist" and
|
||||
not "delete-selected-named-playlist")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(command),
|
||||
command,
|
||||
"The command does not use a named-playlist dispatch id.");
|
||||
}
|
||||
|
||||
CompleteCommand(command, requestId, succeeded);
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminates an accepted save command without making its database outcome
|
||||
/// retryable. This is intentionally separate from a known pre-dispatch
|
||||
/// rejection: an escaped post-dispatch exception permanently quarantines every
|
||||
/// later named-playlist write in this process.
|
||||
/// </summary>
|
||||
public LegacyOperatorSnapshot ReportNamedPlaylistWriteOutcomeUnknown(
|
||||
string command,
|
||||
string targetId,
|
||||
string message)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(command);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(targetId);
|
||||
if (command is not "create-named-playlist" and
|
||||
not "delete-selected-named-playlist" and
|
||||
not "save-current-named-playlist-to")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(command),
|
||||
command,
|
||||
"The command is not a named-playlist write.");
|
||||
}
|
||||
|
||||
_namedPlaylistWorkflow?.QuarantineAmbiguousWriteOutcome();
|
||||
CompleteCommand(command, targetId, succeeded: false);
|
||||
return ReportError(message);
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot ReportInformation(string message)
|
||||
{
|
||||
_statusMessage = message ?? string.Empty;
|
||||
|
||||
@@ -297,6 +297,28 @@ public sealed class LegacyNamedPlaylistWorkflowController
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conservatively latches the no-retry barrier when an exception escapes the
|
||||
/// native dispatch boundary after a named-playlist write was accepted. The
|
||||
/// caller cannot prove whether the database observed the mutation, so every
|
||||
/// later write in this workflow instance must remain blocked until restart.
|
||||
/// </summary>
|
||||
public LegacyNamedPlaylistWorkflowSnapshot QuarantineAmbiguousWriteOutcome()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_isWriteQuarantined = true;
|
||||
_lastMutationOutcome = LegacyNamedPlaylistMutationOutcome.OutcomeUnknown;
|
||||
_statusMessage = QuarantineMessage;
|
||||
_statusKind = LegacyOperatorStatusKind.Error;
|
||||
++_mutationEpoch;
|
||||
++_listGeneration;
|
||||
++_loadGeneration;
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LegacyNamedPlaylistWorkflowSnapshot> RefreshAsync(
|
||||
int maximumResults = DefaultMaximumDefinitions,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -854,9 +876,7 @@ public sealed class LegacyNamedPlaylistWorkflowController
|
||||
definitions,
|
||||
list.IsTruncated,
|
||||
suggested,
|
||||
LegacyNamedPlaylistFreshness.Fresh,
|
||||
programCode);
|
||||
ApplyDocument(document, LegacyNamedPlaylistFreshness.Fresh);
|
||||
LegacyNamedPlaylistFreshness.Fresh);
|
||||
Touch();
|
||||
}
|
||||
|
||||
@@ -968,10 +988,9 @@ public sealed class LegacyNamedPlaylistWorkflowController
|
||||
IReadOnlyList<NamedPlaylistSummary> summaries,
|
||||
bool isTruncated,
|
||||
string suggestedProgramCode,
|
||||
LegacyNamedPlaylistFreshness freshness,
|
||||
string? preferredSelectionCode = null)
|
||||
LegacyNamedPlaylistFreshness freshness)
|
||||
{
|
||||
var selectedCode = preferredSelectionCode ?? _selectedProgramCode;
|
||||
var previousSelectedCode = _selectedProgramCode;
|
||||
_definitions = summaries.Select(summary => new DefinitionState(
|
||||
NextOpaqueId("named-definition"),
|
||||
summary)).ToList();
|
||||
@@ -979,12 +998,11 @@ public sealed class LegacyNamedPlaylistWorkflowController
|
||||
_suggestedProgramCode = suggestedProgramCode;
|
||||
_listFreshness = freshness;
|
||||
|
||||
var selected = selectedCode is null
|
||||
? null
|
||||
: _definitions.FirstOrDefault(definition => string.Equals(
|
||||
definition.Summary.ProgramCode,
|
||||
selectedCode,
|
||||
StringComparison.Ordinal));
|
||||
// WinForms assigns a new DataSource on every PList refresh. A non-empty
|
||||
// ListBox therefore selects index zero, including after add and delete.
|
||||
// Keep that operator-visible reset instead of preserving a Web selection
|
||||
// or selecting the newly created definition.
|
||||
var selected = _definitions.FirstOrDefault();
|
||||
if (selected is null)
|
||||
{
|
||||
_selectedProgramCode = null;
|
||||
@@ -999,6 +1017,15 @@ public sealed class LegacyNamedPlaylistWorkflowController
|
||||
_selectedProgramCode = selected.Summary.ProgramCode;
|
||||
_selectedDefinitionId = selected.DefinitionId;
|
||||
_selectedTitle = selected.Summary.Title;
|
||||
if (!string.Equals(
|
||||
previousSelectedCode,
|
||||
selected.Summary.ProgramCode,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
_rows = [];
|
||||
_documentFreshness = LegacyNamedPlaylistFreshness.None;
|
||||
++_loadGeneration;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDocument(
|
||||
|
||||
Reference in New Issue
Block a user