feat(12-02): replace config-page library section with full library management UI
- Add library list with name, path, track count per library - Add Library button opens folder picker, auto-creates library - Inline rename with Enter/Escape via overflow menu - Removal confirmation dialog with real impact counts (tracks, playlists, queue) - Toast notification with removal summary after library removal - Add GetAllLibrariesWithTrackCounts + Info type to backend Library struct - Add Wails binding stubs for AddLibrary, RenameLibrary, RemoveLibrary, GetRemovalImpact - Add Info, RemovalImpact, RemovalSummary types to models.ts - Remove old single-directory library config UI (GetLibraryDirectory/SetLibraryDirectory)
This commit is contained in:
@@ -443,3 +443,42 @@ func (l *Library) GetAllGenresWithCounts() (
|
||||
|
||||
return genres, nil
|
||||
}
|
||||
|
||||
// Info contains library metadata enriched with track count
|
||||
// for the frontend settings UI.
|
||||
type Info struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
TrackCount int64 `json:"trackCount"`
|
||||
}
|
||||
|
||||
// GetAllLibrariesWithTrackCounts returns all libraries with their
|
||||
// audio file counts. Typically 1-5 libraries so the loop is trivial.
|
||||
func (l *Library) GetAllLibrariesWithTrackCounts() ([]Info, error) {
|
||||
libs, err := l.db.Queries.GetAllLibraries(l.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get libraries: %w", err)
|
||||
}
|
||||
|
||||
result := make([]Info, 0, len(libs))
|
||||
|
||||
for _, lib := range libs {
|
||||
count, countErr := l.db.Queries.CountAudioFilesByLibrary(l.ctx, lib.ID)
|
||||
if countErr != nil {
|
||||
l.logger.Error("could not count tracks for library",
|
||||
"libraryID", lib.ID, "error", countErr)
|
||||
|
||||
count = 0
|
||||
}
|
||||
|
||||
result = append(result, Info{
|
||||
ID: lib.ID,
|
||||
Name: lib.Name,
|
||||
Path: lib.Path,
|
||||
TrackCount: count,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -8,16 +8,21 @@ import {
|
||||
CancelCurrentScan,
|
||||
CancelAllScans,
|
||||
ScanAllLibraries,
|
||||
ScanLibrary,
|
||||
PauseScan,
|
||||
ResumeScan,
|
||||
AddLibrary,
|
||||
RenameLibrary,
|
||||
RemoveLibrary,
|
||||
GetRemovalImpact,
|
||||
GetAllLibrariesWithTrackCounts,
|
||||
} from '@go/library/Library';
|
||||
import {
|
||||
GetLibraryDirectory,
|
||||
SetLibraryDirectory,
|
||||
GetScanConcurrency,
|
||||
SetScanConcurrency,
|
||||
} from '@go/config/Config';
|
||||
import { DirectoryPicker } from '@go/frontendutil/FrontendUtil';
|
||||
import type { library } from '@go/models';
|
||||
import { ThemeController } from '@store/controllers/theme-controller';
|
||||
import { TrackListController } from '@store/controllers/tracklist-controller';
|
||||
import { FavoritesController } from '@store/controllers/favorites-controller';
|
||||
@@ -337,8 +342,15 @@ export class ConfigPage extends LitElement {
|
||||
@state() private playlists: playlist.Summary[] = [];
|
||||
|
||||
// --- Library state ---
|
||||
@state() private libraryDirectory = '';
|
||||
@state() private selectedDirectory = '';
|
||||
@state() private libraries: library.Info[] = [];
|
||||
@state() private editingLibraryId: number | null = null;
|
||||
@state() private editingName = '';
|
||||
@state() private removingLibraryId: number | null = null;
|
||||
@state() private removalImpact: library.RemovalImpact | null = null;
|
||||
@state() private isRemoving = false;
|
||||
@state() private toastMessage = '';
|
||||
@state() private toastVisible = false;
|
||||
@state() private activeMenuId: number | null = null;
|
||||
@state() private scanning = false;
|
||||
@state() private statusMessage = '';
|
||||
@state() private scanProgress: ScanProgress | null = null;
|
||||
@@ -357,6 +369,7 @@ export class ConfigPage extends LitElement {
|
||||
existingAction: string;
|
||||
} | null = null;
|
||||
|
||||
private toastTimer?: ReturnType<typeof setTimeout>;
|
||||
private cancelScanStarted?: () => void;
|
||||
private cancelScanProgress?: () => void;
|
||||
private cancelScanComplete?: () => void;
|
||||
@@ -365,6 +378,9 @@ export class ConfigPage extends LitElement {
|
||||
private cancelScanCancelled?: () => void;
|
||||
private cancelScanQueued?: () => void;
|
||||
private cancelScanQueueDrained?: () => void;
|
||||
private cancelLibraryAdded?: () => void;
|
||||
private cancelLibraryRenamed?: () => void;
|
||||
private cancelLibraryRemoved?: () => void;
|
||||
|
||||
static override styles = css`
|
||||
:host {
|
||||
@@ -812,6 +828,182 @@ export class ConfigPage extends LitElement {
|
||||
color: var(--yj-accent, #ffd43b);
|
||||
}
|
||||
|
||||
/* Library management */
|
||||
.library-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.library-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75em;
|
||||
padding: 0.6em 0.5em;
|
||||
border-bottom: 1px solid var(--yj-border-subtle, #333);
|
||||
}
|
||||
|
||||
.library-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.library-row:hover {
|
||||
background: var(--yj-bg-elevated, #343a40);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.library-name {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.library-name:hover {
|
||||
color: var(--yj-accent, #ffd43b);
|
||||
}
|
||||
|
||||
.library-path {
|
||||
color: var(--yj-text-tertiary, #868e96);
|
||||
font-size: var(--yj-text-sm, 13px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.library-count {
|
||||
color: var(--yj-text-tertiary, #868e96);
|
||||
font-size: var(--yj-text-sm, 13px);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
flex: 1;
|
||||
padding: 0.3em 0.5em;
|
||||
background: var(--yj-bg-elevated, #343a40);
|
||||
border: 1px solid var(--yj-accent, #ffd43b);
|
||||
border-radius: 4px;
|
||||
color: var(--yj-text-primary, #fff);
|
||||
font-size: 0.9em;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.overflow-wrapper {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.overflow-btn {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--yj-text-tertiary, #868e96);
|
||||
font-size: 1.1em;
|
||||
padding: 0.2em 0.4em;
|
||||
letter-spacing: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.overflow-btn:hover {
|
||||
background: var(--yj-bg-overlay, #495057);
|
||||
color: var(--yj-text-primary, #fff);
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--yj-bg-surface, #2a2a2a);
|
||||
border: 1px solid var(--yj-border, #444);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
z-index: 100;
|
||||
min-width: 120px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.overflow-item {
|
||||
padding: 0.5em 1em;
|
||||
font-size: var(--yj-text-sm, 13px);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.overflow-item:hover {
|
||||
background: var(--yj-bg-elevated, #343a40);
|
||||
}
|
||||
|
||||
.overflow-item--danger {
|
||||
color: var(--yj-error, #e03131);
|
||||
}
|
||||
|
||||
.overflow-item--danger:hover {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--yj-error, #e03131) 10%,
|
||||
var(--yj-bg-elevated, #343a40)
|
||||
);
|
||||
}
|
||||
|
||||
.add-library-btn {
|
||||
margin-top: 0.75em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--yj-bg-surface, #2a2a2a);
|
||||
color: var(--yj-text-primary, #fff);
|
||||
padding: 0.75em 1.5em;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
font-size: var(--yj-text-sm, 13px);
|
||||
z-index: 2000;
|
||||
border: 1px solid var(--yj-border, #444);
|
||||
animation: toast-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes toast-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%)
|
||||
translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%)
|
||||
translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Keyboard shortcuts section */
|
||||
.shortcut-category {
|
||||
margin-bottom: 16px;
|
||||
@@ -880,7 +1072,7 @@ export class ConfigPage extends LitElement {
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.loadLibraryConfig();
|
||||
void this.loadLibraries();
|
||||
void this.loadPlaylists();
|
||||
this.scrollMode =
|
||||
localStorage.getItem(SCROLL_STORAGE_KEY) || 'hover';
|
||||
@@ -917,6 +1109,20 @@ export class ConfigPage extends LitElement {
|
||||
Events.LibraryScanQueueDrained,
|
||||
this.handleScanQueueDrained,
|
||||
);
|
||||
this.cancelLibraryAdded = EventsOn(
|
||||
Events.LibraryAdded,
|
||||
() => void this.loadLibraries(),
|
||||
);
|
||||
this.cancelLibraryRenamed = EventsOn(
|
||||
Events.LibraryRenamed,
|
||||
() => void this.loadLibraries(),
|
||||
);
|
||||
this.cancelLibraryRemoved = EventsOn(
|
||||
Events.LibraryRemoved,
|
||||
() => void this.loadLibraries(),
|
||||
);
|
||||
|
||||
document.addEventListener('click', this.handleDocumentClick);
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
@@ -929,21 +1135,27 @@ export class ConfigPage extends LitElement {
|
||||
this.cancelScanCancelled?.();
|
||||
this.cancelScanQueued?.();
|
||||
this.cancelScanQueueDrained?.();
|
||||
this.cancelLibraryAdded?.();
|
||||
this.cancelLibraryRenamed?.();
|
||||
this.cancelLibraryRemoved?.();
|
||||
|
||||
document.removeEventListener('click', this.handleDocumentClick);
|
||||
|
||||
if (this.toastTimer) clearTimeout(this.toastTimer);
|
||||
}
|
||||
|
||||
private async loadLibraryConfig(): Promise<void> {
|
||||
private async loadLibraries(): Promise<void> {
|
||||
try {
|
||||
const [dir, mode] = await Promise.all([
|
||||
GetLibraryDirectory(),
|
||||
const [libs, mode] = await Promise.all([
|
||||
GetAllLibrariesWithTrackCounts(),
|
||||
GetScanConcurrency(),
|
||||
]);
|
||||
|
||||
this.libraryDirectory = dir;
|
||||
this.selectedDirectory = dir;
|
||||
this.libraries = libs ?? [];
|
||||
this.concurrencyMode = mode;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Failed to load library config:',
|
||||
'Failed to load libraries:',
|
||||
err,
|
||||
);
|
||||
}
|
||||
@@ -1072,37 +1284,121 @@ export class ConfigPage extends LitElement {
|
||||
this.cancelMetrics = null;
|
||||
};
|
||||
|
||||
private handleDirectoryBrowse = async (): Promise<void> => {
|
||||
private handleAddLibrary = async (): Promise<void> => {
|
||||
try {
|
||||
const dir = await DirectoryPicker();
|
||||
|
||||
if (dir) {
|
||||
this.selectedDirectory = dir;
|
||||
await AddLibrary(dir);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Directory picker failed:',
|
||||
'Failed to add library:',
|
||||
err,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private handleSaveDirectory = async (): Promise<void> => {
|
||||
if (!this.selectedDirectory) return;
|
||||
private handleStartRename = (id: number, name: string): void => {
|
||||
this.editingLibraryId = id;
|
||||
this.editingName = name;
|
||||
this.activeMenuId = null;
|
||||
};
|
||||
|
||||
private handleRenameKeyDown = async (e: KeyboardEvent): Promise<void> => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.editingLibraryId !== null && this.editingName.trim()) {
|
||||
try {
|
||||
await SetLibraryDirectory(
|
||||
this.selectedDirectory,
|
||||
);
|
||||
this.libraryDirectory =
|
||||
this.selectedDirectory;
|
||||
this.statusMessage =
|
||||
'Library directory saved. A scan will start automatically if the directory changed.';
|
||||
await RenameLibrary(this.editingLibraryId, this.editingName.trim());
|
||||
} catch (err) {
|
||||
this.statusMessage = `Failed to save directory: ${err}`;
|
||||
console.error('Failed to rename library:', err);
|
||||
}
|
||||
}
|
||||
|
||||
this.editingLibraryId = null;
|
||||
this.editingName = '';
|
||||
} else if (e.key === 'Escape') {
|
||||
this.editingLibraryId = null;
|
||||
this.editingName = '';
|
||||
}
|
||||
};
|
||||
|
||||
private handleRenameInput = (e: InputEvent): void => {
|
||||
this.editingName = (e.target as HTMLInputElement).value;
|
||||
};
|
||||
|
||||
private handleRescanLibrary = (id: number): void => {
|
||||
this.activeMenuId = null;
|
||||
void ScanLibrary(id);
|
||||
};
|
||||
|
||||
private handleRemoveClick = async (id: number): Promise<void> => {
|
||||
this.activeMenuId = null;
|
||||
|
||||
try {
|
||||
const impact = await GetRemovalImpact(id);
|
||||
|
||||
this.removalImpact = impact;
|
||||
this.removingLibraryId = id;
|
||||
} catch (err) {
|
||||
console.error('Failed to get removal impact:', err);
|
||||
}
|
||||
};
|
||||
|
||||
private handleConfirmRemove = async (): Promise<void> => {
|
||||
if (this.removingLibraryId === null) return;
|
||||
|
||||
const id = this.removingLibraryId;
|
||||
const lib = this.libraries.find((l) => l.id === id);
|
||||
const libName = lib?.name ?? 'Library';
|
||||
|
||||
this.isRemoving = true;
|
||||
|
||||
try {
|
||||
const summary = await RemoveLibrary(id);
|
||||
|
||||
this.removingLibraryId = null;
|
||||
this.removalImpact = null;
|
||||
this.isRemoving = false;
|
||||
this.showToast(
|
||||
`Removed '${libName}' (${summary?.tracksDeleted ?? 0} tracks deleted)`,
|
||||
);
|
||||
} catch (err) {
|
||||
this.isRemoving = false;
|
||||
console.error('Failed to remove library:', err);
|
||||
}
|
||||
};
|
||||
|
||||
private handleCancelRemove = (): void => {
|
||||
this.removingLibraryId = null;
|
||||
this.removalImpact = null;
|
||||
this.isRemoving = false;
|
||||
};
|
||||
|
||||
private toggleOverflowMenu = (id: number, e: Event): void => {
|
||||
e.stopPropagation();
|
||||
this.activeMenuId = this.activeMenuId === id ? null : id;
|
||||
};
|
||||
|
||||
private handleDocumentClick = (): void => {
|
||||
if (this.activeMenuId !== null) {
|
||||
this.activeMenuId = null;
|
||||
}
|
||||
};
|
||||
|
||||
private showToast(message: string): void {
|
||||
this.toastMessage = message;
|
||||
this.toastVisible = true;
|
||||
|
||||
if (this.toastTimer) clearTimeout(this.toastTimer);
|
||||
|
||||
this.toastTimer = setTimeout(() => {
|
||||
this.toastVisible = false;
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
private handleConcurrencyChange = (
|
||||
e: CustomEvent<ConfigFieldChangeEvent>,
|
||||
): void => {
|
||||
@@ -1456,13 +1752,6 @@ export class ConfigPage extends LitElement {
|
||||
// COMPUTED
|
||||
// ===================================================================
|
||||
|
||||
private get directoryChanged(): boolean {
|
||||
return (
|
||||
this.selectedDirectory !==
|
||||
this.libraryDirectory
|
||||
);
|
||||
}
|
||||
|
||||
private get hasRescanPhases(): boolean {
|
||||
if (!this.metrics) return false;
|
||||
|
||||
@@ -1901,36 +2190,88 @@ export class ConfigPage extends LitElement {
|
||||
// --- Library section ---
|
||||
|
||||
private renderLibrarySection() {
|
||||
const removingLib = this.libraries.find(
|
||||
(l) => l.id === this.removingLibraryId,
|
||||
);
|
||||
|
||||
return html`
|
||||
<config-section
|
||||
heading="Library"
|
||||
description="Configure your music library location and scanning behaviour."
|
||||
heading="Libraries"
|
||||
description="Manage your music library folders. Each library is scanned independently."
|
||||
>
|
||||
<config-field
|
||||
.schema=${{
|
||||
key: 'libraryDirectory',
|
||||
label: 'Library Directory',
|
||||
description:
|
||||
'The root directory containing your music files.',
|
||||
type: 'directory' as const,
|
||||
}}
|
||||
.value=${this.selectedDirectory}
|
||||
@config-browse=${this.handleDirectoryBrowse}
|
||||
></config-field>
|
||||
|
||||
${this.directoryChanged
|
||||
<div class="library-list">
|
||||
${this.libraries.map(
|
||||
(lib) => html`
|
||||
<div class="library-row">
|
||||
${this.editingLibraryId === lib.id
|
||||
? html`
|
||||
<div class="save-row">
|
||||
<button
|
||||
class="btn-success"
|
||||
?disabled=${this.scanning}
|
||||
@click=${this.handleSaveDirectory}
|
||||
<input
|
||||
class="edit-input"
|
||||
type="text"
|
||||
.value=${this.editingName}
|
||||
@input=${this.handleRenameInput}
|
||||
@keydown=${this.handleRenameKeyDown}
|
||||
@click=${(e: Event) => e.stopPropagation()}
|
||||
/>
|
||||
`
|
||||
: html`
|
||||
<span
|
||||
class="library-name"
|
||||
@click=${() => this.handleStartRename(lib.id, lib.name)}
|
||||
>
|
||||
Save Directory
|
||||
${lib.name}
|
||||
</span>
|
||||
`}
|
||||
<span class="library-path">${lib.path}</span>
|
||||
<span class="library-count">
|
||||
${lib.trackCount} tracks
|
||||
</span>
|
||||
<div class="overflow-wrapper">
|
||||
<button
|
||||
class="overflow-btn"
|
||||
@click=${(e: Event) => this.toggleOverflowMenu(lib.id, e)}
|
||||
>
|
||||
\u22EF
|
||||
</button>
|
||||
${this.activeMenuId === lib.id
|
||||
? html`
|
||||
<div
|
||||
class="overflow-menu"
|
||||
@click=${(e: Event) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
class="overflow-item"
|
||||
@click=${() => this.handleStartRename(lib.id, lib.name)}
|
||||
>
|
||||
Rename
|
||||
</div>
|
||||
<div
|
||||
class="overflow-item"
|
||||
@click=${() => this.handleRescanLibrary(lib.id)}
|
||||
>
|
||||
Rescan
|
||||
</div>
|
||||
<div
|
||||
class="overflow-item overflow-item--danger"
|
||||
@click=${() => void this.handleRemoveClick(lib.id)}
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn-primary add-library-btn"
|
||||
@click=${this.handleAddLibrary}
|
||||
>
|
||||
Add Library
|
||||
</button>
|
||||
|
||||
<config-field
|
||||
.schema=${{
|
||||
@@ -2143,7 +2484,57 @@ export class ConfigPage extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
${this.removingLibraryId !== null && this.removalImpact
|
||||
? html`
|
||||
<div
|
||||
class="cancel-dialog-overlay"
|
||||
@click=${this.handleCancelRemove}
|
||||
>
|
||||
<div
|
||||
class="cancel-dialog"
|
||||
@click=${(e: Event) => e.stopPropagation()}
|
||||
>
|
||||
<div class="cancel-dialog-title">
|
||||
Remove Library
|
||||
</div>
|
||||
<div class="cancel-dialog-message">
|
||||
Remove '${removingLib?.name}'?
|
||||
This will delete
|
||||
${this.removalImpact.trackCount}
|
||||
tracks, affect
|
||||
${this.removalImpact.playlistsAffected}
|
||||
playlists, and remove
|
||||
${this.removalImpact.queueItemCount}
|
||||
queue items.
|
||||
</div>
|
||||
<div class="cancel-dialog-actions">
|
||||
<button
|
||||
class="btn-ghost"
|
||||
?disabled=${this.isRemoving}
|
||||
@click=${this.handleCancelRemove}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn-danger"
|
||||
?disabled=${this.isRemoving}
|
||||
@click=${this.handleConfirmRemove}
|
||||
>
|
||||
${this.isRemoving
|
||||
? html`<span class="spinner"></span> Removing\u2026`
|
||||
: 'Remove'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</config-section>
|
||||
|
||||
${this.toastVisible
|
||||
? html`<div class="toast">${this.toastMessage}</div>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -3,6 +3,8 @@
|
||||
import {library} from '../models';
|
||||
import {context} from '../models';
|
||||
|
||||
export function AddLibrary(arg1:string):Promise<library.Info>;
|
||||
|
||||
export function CancelAllScans():Promise<void>;
|
||||
|
||||
export function CancelCurrentScan():Promise<void>;
|
||||
@@ -13,6 +15,8 @@ export function FullRescan():Promise<library.ScanMetrics>;
|
||||
|
||||
export function GetAlbumTracks(arg1:number):Promise<Array<library.Track>>;
|
||||
|
||||
export function GetAllLibrariesWithTrackCounts():Promise<Array<library.Info>>;
|
||||
|
||||
export function GetAlbumsByArtist(arg1:number):Promise<Array<library.Album>>;
|
||||
|
||||
export function GetAllAlbums():Promise<Array<library.Album>>;
|
||||
@@ -23,6 +27,8 @@ export function GetAllGenresWithCounts():Promise<Array<library.GenreWithCount>>;
|
||||
|
||||
export function GetAllTracks():Promise<Array<library.Track>>;
|
||||
|
||||
export function GetRemovalImpact(arg1:number):Promise<library.RemovalImpact>;
|
||||
|
||||
export function GetScanQueueLength():Promise<number>;
|
||||
|
||||
export function GetTracksByGenre(arg1:string):Promise<Array<library.Track>>;
|
||||
@@ -33,6 +39,10 @@ export function IsScanPaused():Promise<boolean>;
|
||||
|
||||
export function PauseScan():Promise<void>;
|
||||
|
||||
export function RemoveLibrary(arg1:number):Promise<library.RemovalSummary>;
|
||||
|
||||
export function RenameLibrary(arg1:number, arg2:string):Promise<void>;
|
||||
|
||||
export function QueuedLibraryNames():Promise<Array<string>>;
|
||||
|
||||
export function ResumeScan():Promise<void>;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function AddLibrary(arg1) {
|
||||
return window['go']['library']['Library']['AddLibrary'](arg1);
|
||||
}
|
||||
|
||||
export function CancelAllScans() {
|
||||
return window['go']['library']['Library']['CancelAllScans']();
|
||||
}
|
||||
@@ -22,6 +26,10 @@ export function GetAlbumTracks(arg1) {
|
||||
return window['go']['library']['Library']['GetAlbumTracks'](arg1);
|
||||
}
|
||||
|
||||
export function GetAllLibrariesWithTrackCounts() {
|
||||
return window['go']['library']['Library']['GetAllLibrariesWithTrackCounts']();
|
||||
}
|
||||
|
||||
export function GetAlbumsByArtist(arg1) {
|
||||
return window['go']['library']['Library']['GetAlbumsByArtist'](arg1);
|
||||
}
|
||||
@@ -42,6 +50,10 @@ export function GetAllTracks() {
|
||||
return window['go']['library']['Library']['GetAllTracks']();
|
||||
}
|
||||
|
||||
export function GetRemovalImpact(arg1) {
|
||||
return window['go']['library']['Library']['GetRemovalImpact'](arg1);
|
||||
}
|
||||
|
||||
export function GetScanQueueLength() {
|
||||
return window['go']['library']['Library']['GetScanQueueLength']();
|
||||
}
|
||||
@@ -62,6 +74,14 @@ export function PauseScan() {
|
||||
return window['go']['library']['Library']['PauseScan']();
|
||||
}
|
||||
|
||||
export function RemoveLibrary(arg1) {
|
||||
return window['go']['library']['Library']['RemoveLibrary'](arg1);
|
||||
}
|
||||
|
||||
export function RenameLibrary(arg1, arg2) {
|
||||
return window['go']['library']['Library']['RenameLibrary'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function QueuedLibraryNames() {
|
||||
return window['go']['library']['Library']['QueuedLibraryNames']();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,62 @@ export namespace library {
|
||||
this.Name = source["Name"];
|
||||
}
|
||||
}
|
||||
export class Info {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
trackCount: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Info(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.path = source["path"];
|
||||
this.trackCount = source["trackCount"];
|
||||
}
|
||||
}
|
||||
export class RemovalImpact {
|
||||
trackCount: number;
|
||||
playlistsAffected: number;
|
||||
queueItemCount: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new RemovalImpact(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.trackCount = source["trackCount"];
|
||||
this.playlistsAffected = source["playlistsAffected"];
|
||||
this.queueItemCount = source["queueItemCount"];
|
||||
}
|
||||
}
|
||||
export class RemovalSummary {
|
||||
tracksDeleted: number;
|
||||
artistsRemoved: number;
|
||||
albumsRemoved: number;
|
||||
genresRemoved: number;
|
||||
playlistsAffected: number;
|
||||
queueItemsRemoved: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new RemovalSummary(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.tracksDeleted = source["tracksDeleted"];
|
||||
this.artistsRemoved = source["artistsRemoved"];
|
||||
this.albumsRemoved = source["albumsRemoved"];
|
||||
this.genresRemoved = source["genresRemoved"];
|
||||
this.playlistsAffected = source["playlistsAffected"];
|
||||
this.queueItemsRemoved = source["queueItemsRemoved"];
|
||||
}
|
||||
}
|
||||
export class GenreWithCount {
|
||||
Name: string;
|
||||
TrackCount: number;
|
||||
|
||||
Reference in New Issue
Block a user