feat(jobs): let a panel answer for every kind, at either density
Three properties the phone's band needs, added here so it is the same panel rather than a second job UI -- which is what keeps `applyJobControl` and its "you will discard hours of downloading" confirmation in the picture. `kinds="*"` is every kind, which is what the header indicator was for. Spelled as a star rather than taken as the meaning of an empty attribute, because empty is what a typo and a dropped binding both produce and "show everything" is the wrong thing to do by accident; empty still shows nothing. `density` is passed to `job-row`, whose `compact` variant its own source calls "the popover density" -- which is exactly what the band replaces. `full` stays the default, so the four settings call sites are untouched. `active-only` drops terminal rows. The band is in the layout, so a finished row there holds the content down after the work is done; Settings keeps them, because that is where "did the last scan work" is asked and a finished row there dismisses itself.
This commit is contained in:
@@ -51,6 +51,14 @@ export class JobPanel extends LitElement {
|
|||||||
* literal in a template, and one of them is inside an HTMX-adjacent
|
* literal in a template, and one of them is inside an HTMX-adjacent
|
||||||
* settings page where a property binding would be one more thing to
|
* settings page where a property binding would be one more thing to
|
||||||
* remember.
|
* remember.
|
||||||
|
*
|
||||||
|
* **`*` means every kind**, which is the phone's band (#62) and
|
||||||
|
* nothing else: there, this panel is standing in for the header
|
||||||
|
* indicator, whose whole job was to be the one view of everything
|
||||||
|
* at once. It is spelled `*` rather than taken as the meaning of an
|
||||||
|
* empty attribute, because empty is what a typo and a missing
|
||||||
|
* binding both produce and "show everything" is the wrong thing to
|
||||||
|
* do by accident. Empty still shows nothing.
|
||||||
*/
|
*/
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
kinds = '';
|
kinds = '';
|
||||||
@@ -59,6 +67,31 @@ export class JobPanel extends LitElement {
|
|||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
heading = '';
|
heading = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row density, passed to `job-row`.
|
||||||
|
*
|
||||||
|
* `full` adds elapsed time and per-job statistics and is what a
|
||||||
|
* settings section wants, so it stays the default and the four
|
||||||
|
* existing call sites are unchanged. `compact` is what `job-row`
|
||||||
|
* itself calls "the popover density", and it is what the phone's
|
||||||
|
* band uses (#62) — there this panel *is* the popover, on a screen
|
||||||
|
* 439 CSS px tall, and the full density spent 259 of them.
|
||||||
|
*/
|
||||||
|
@property({ type: String })
|
||||||
|
density: 'compact' | 'full' = 'full';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop finished rows.
|
||||||
|
*
|
||||||
|
* For the phone's band (#62), which is *in the layout*: a finished
|
||||||
|
* row there is a banner that stays after the work is done and keeps
|
||||||
|
* the content pushed down. Settings keeps them, because that is
|
||||||
|
* where "did the last scan work" is asked, and a finished row there
|
||||||
|
* dismisses itself.
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean, attribute: 'active-only' })
|
||||||
|
activeOnly = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private jobs: Job[] = [];
|
private jobs: Job[] = [];
|
||||||
|
|
||||||
@@ -162,9 +195,14 @@ export class JobPanel extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get mine(): Job[] {
|
private get mine(): Job[] {
|
||||||
const wanted = this.wanted;
|
const ofKind =
|
||||||
|
this.kinds.trim() === '*'
|
||||||
|
? this.jobs
|
||||||
|
: this.jobs.filter((job) =>
|
||||||
|
this.wanted.has(job.kind as JobKind),
|
||||||
|
);
|
||||||
|
|
||||||
return this.jobs.filter((job) => wanted.has(job.kind as JobKind));
|
return this.activeOnly ? ofKind.filter((job) => !isTerminal(job)) : ofKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
private openDetails(id: string) {
|
private openDetails(id: string) {
|
||||||
@@ -196,7 +234,7 @@ export class JobPanel extends LitElement {
|
|||||||
<div class="job-entry">
|
<div class="job-entry">
|
||||||
<job-row
|
<job-row
|
||||||
.job=${job}
|
.job=${job}
|
||||||
variant="full"
|
variant=${this.density}
|
||||||
@job-control=${applyJobControl}
|
@job-control=${applyJobControl}
|
||||||
></job-row>
|
></job-row>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -76,6 +76,68 @@ describe('<job-panel>', () => {
|
|||||||
expect(titles(el)).toEqual(['Building the index', 'Filling in artists']);
|
expect(titles(el)).toEqual(['Building the index', 'Filling in artists']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #62. The phone's band has no kinds to name: it is standing in for
|
||||||
|
* the header indicator, whose whole job was to be the one view of
|
||||||
|
* everything at once.
|
||||||
|
*/
|
||||||
|
it('answers for every kind when asked with a star', async () => {
|
||||||
|
const el = await fixture<LitElement>('job-panel', { kinds: '*' });
|
||||||
|
|
||||||
|
await snapshot([
|
||||||
|
job({ id: 'scan:1', kind: 'library-scan', title: 'Scanning Music' }),
|
||||||
|
job({ id: 'idx', kind: 'index-build', title: 'Building the index' }),
|
||||||
|
job({ id: 'dl:1', kind: 'download', title: 'Downloading Glass Harbour' }),
|
||||||
|
]);
|
||||||
|
await el.updateComplete;
|
||||||
|
|
||||||
|
expect(titles(el)).toEqual([
|
||||||
|
'Scanning Music',
|
||||||
|
'Building the index',
|
||||||
|
'Downloading Glass Harbour',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The other half of that, and the reason it is a star rather than the
|
||||||
|
* meaning of an empty attribute: empty is what a typo and a dropped
|
||||||
|
* binding both produce, and "show everything" is the wrong thing to
|
||||||
|
* do by accident.
|
||||||
|
*/
|
||||||
|
it('still shows nothing when asked for nothing', async () => {
|
||||||
|
const el = await fixture<LitElement>('job-panel', { kinds: '' });
|
||||||
|
|
||||||
|
await snapshot([job({ id: 'scan:1', kind: 'library-scan' })]);
|
||||||
|
await el.updateComplete;
|
||||||
|
|
||||||
|
expect([el.hidden, rows(el)].map(String)).toEqual(['true', '']);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `full` stays the default so the four settings call sites are
|
||||||
|
* untouched; the band asks for the density `job-row` calls "the
|
||||||
|
* popover density", because on the phone this panel *is* the popover.
|
||||||
|
*/
|
||||||
|
it('passes its density to the rows, defaulting to full', async () => {
|
||||||
|
const settings = await fixture<LitElement>('job-panel', { kinds: '*' });
|
||||||
|
|
||||||
|
await snapshot([job()]);
|
||||||
|
await settings.updateComplete;
|
||||||
|
|
||||||
|
const band = await fixture<LitElement>('job-panel', {
|
||||||
|
kinds: '*',
|
||||||
|
density: 'compact',
|
||||||
|
});
|
||||||
|
|
||||||
|
await snapshot([job()]);
|
||||||
|
await band.updateComplete;
|
||||||
|
|
||||||
|
expect([
|
||||||
|
rows(settings)[0]?.getAttribute('variant'),
|
||||||
|
rows(band)[0]?.getAttribute('variant'),
|
||||||
|
]).toEqual(['full', 'compact']);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An idle panel in four places is four pieces of furniture describing
|
* An idle panel in four places is four pieces of furniture describing
|
||||||
* an absence — and `hidden` rather than an empty render, because the
|
* an absence — and `hidden` rather than an empty render, because the
|
||||||
|
|||||||
Reference in New Issue
Block a user