Add the Collect page
The collector becomes usable. The page is a close copy of the parse page's shape - same proxy gate, same batched flush to the observable collection, same truncation cap that reports rather than truncates silently - because that shape was built for streaming outcomes and the collector produces exactly those. Two differences that are not cosmetic. The batch drops from 512 to 64: items arrive at network speed, roughly one a second, and a batch of five hundred would mean the list never visibly moved. And progress is explicitly indeterminate until a source finishes listing, because a paginated listing genuinely does not know its total until the last page - a bar pretending otherwise would be lying. The input swaps shape with the source: an endpoint source wants one address, a pasted-list source wants many lines. The proxy gate is unchanged in substance - only network sources are gated, and the banner keeps its x:Name because the headless tests find it that way. Rendering the page caught a defect the tests could not: IsVisible sat on the caption inside the header border rather than on the border, so hiding the text left its padding and divider behind as an empty bar above the input. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6909884851
commit
70fb3a1df3
@@ -0,0 +1,49 @@
|
||||
using AvParser.Core.Collecting;
|
||||
using AvParser.UI.Localization;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace AvParser.UI.ViewModels;
|
||||
|
||||
/// <summary>A media source paired with its translated name and description.</summary>
|
||||
/// <remarks>
|
||||
/// The domain knows nothing about languages, so <see cref="IMediaSource"/> carries English text.
|
||||
/// This looks the id up as <c>Source.{id}.Name</c> and falls back to what the source itself says,
|
||||
/// which keeps the "add a source = one registration line" promise intact: a new source works
|
||||
/// untranslated rather than rendering a missing-key marker.
|
||||
/// </remarks>
|
||||
public sealed class MediaSourceViewModel : ReactiveObject
|
||||
{
|
||||
/// <summary>Wraps a source.</summary>
|
||||
public MediaSourceViewModel(IMediaSource source)
|
||||
{
|
||||
Source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
Localizer.Instance.LanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
|
||||
/// <summary>The source itself.</summary>
|
||||
public IMediaSource Source { get; }
|
||||
|
||||
/// <summary>Stable identifier.</summary>
|
||||
public string Id => Source.Id;
|
||||
|
||||
/// <summary>Translated name, or the source's own when untranslated.</summary>
|
||||
public string Name => Localizer.Instance.GetOrDefault($"Source.{Id}.Name", Source.DisplayName);
|
||||
|
||||
/// <summary>Translated description, or the source's own when untranslated.</summary>
|
||||
public string Description => Localizer.Instance.GetOrDefault($"Source.{Id}.Description", Source.Description);
|
||||
|
||||
/// <summary>Whether this source needs a working proxy before it may run.</summary>
|
||||
public bool RequiresNetwork => Source.RequiresNetwork;
|
||||
|
||||
/// <summary>Whether this source reads a listing endpoint rather than pasted text.</summary>
|
||||
public bool UsesEndpoint => Source.RequiresNetwork;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Name;
|
||||
|
||||
private void OnLanguageChanged(object? sender, EventArgs e)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(Name));
|
||||
this.RaisePropertyChanged(nameof(Description));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user