`;
}
/**
* The catalog download's one permission (plan 016 B4).
*
* It is in this section rather than General because it is about
* *this* download and nothing else, and because the section already
* explains what the catalog is — the toggle would be unreadable
* beside "Default page".
*/
private handleAllowMeteredChange = (
e: CustomEvent,
): void => {
const allow = Boolean(e.detail.value);
const previous = this.allowMeteredCatalogDownload;
this.allowMeteredCatalogDownload = allow;
void SetAllowMeteredCatalogDownload(allow).catch((err: unknown) => {
console.error('failed to save metered download permission', err);
// The visible state reverted, so this is the Transient case:
// a small action the user can simply repeat.
this.allowMeteredCatalogDownload = previous;
notificationStore.transient({
key: 'metered-catalog-setting',
title: 'Setting not saved',
text: describeError(err, 'That setting could not be saved.'),
});
});
};
private tierIcon(state: string): string {
switch (state) {
case 'complete':
case 'skipped':
return '✅';
case 'running':
return '🔄';
case 'error':
return '❌';
case 'pending':
default:
return '⏳';
}
}
private formatCount(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
return `${n}`;
}
private timeAgo(iso: string): string {
const then = new Date(iso).getTime();
if (!then) return '';
const seconds = Math.floor((Date.now() - then) / 1000);
if (seconds < 60) return 'just now';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
if (days === 1) return 'yesterday';
return `${days}d ago`;
}
// --- Now Playing section ---
private renderNowPlayingSection() {
return html`
`;
}
// --- General section ---
private renderGeneralSection() {
return html`
`;
}
// --- Theme section ---
private renderThemeSection() {
return html`
`;
}
// --- Keyboard Shortcuts section ---
private renderShortcutsSection() {
const bindings =
this.shortcutsCtrl.state.bindings;
// All four, from the shared table: the Autotag bindings are
// persisted and rebindable like any other, and listing three of
// four categories is how they came to be written down nowhere.
const categories = SHORTCUT_CATEGORIES;
return html`
${categories.map((cat) => {
const actions = Object.entries(
SHORTCUT_META,
).filter(
([, meta]) =>
meta.category === cat,
);
if (actions.length === 0) return '';
return html`
${this
.shortcutConflict
.newKey}
is already bound to
${SHORTCUT_META[
this
.shortcutConflict
.existingAction
]?.label ??
this
.shortcutConflict
.existingAction}.
${this.editingLibraryId === lib.id
? html`
e.stopPropagation()}
/>
`
: html`
{
// The document handler closes the
// editor, and without this it closes
// the one this very click opened.
e.stopPropagation();
this.handleStartRename(lib.id, lib.name);
}}
>
${lib.name}
`}
${lib.path}
${this.removingLibraryId === lib.id
? 'Removing…'
: html`${lib.trackCount} tracks`}