fix(svelte): resolve warnings in 26 components
Phase 1 (real warnings): - SearchView: add $state() to searchInput and scrollContainer - DiscoverBrowseView: use $derived() for genreContext - FavoritesView: replace deprecated svelte:component - CastPicker: replace deprecated svelte:component (2 instances) - NowPlayingBar: remove unused .section-separator CSS - SettingsView: remove unused nested .experimental-badge CSS - PlaylistDetailView: remove unused .playlist-api-image CSS - PurchaseAlbumDetailView: remove unused .download-done CSS - DiscoverPlaylistsBrowseView: fix nested buttons (outer to div) - AlbumCreditsModal: fix nested button (inner to div) Phase 2 (a11y warnings): - Add tabindex="-1" to dialog/menu elements (7 files) - Add svelte-ignore for autofocus (3 files) - Add onkeydown handlers to role="button" elements (5 files) - Add role/svelte-ignore to onclick divs (6 files) - Add role="menu" to dropdown menus (4 files) - Add svelte-ignore for drag handlers (3 files) - Fix label a11y: add aria-label, convert decorative labels to spans
This commit is contained in:
26 files changed
+237
-187
No files matched your search
@@ -45,7 +45,9 @@
|
||||
</script>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="modal-backdrop" onclick={onClose}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="modal-backdrop" onclick={onClose} role="presentation">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="modal" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Play, ChevronDown, ChevronUp, Loader2, X } from 'lucide-svelte';
|
||||
import type { AlbumCredits, QobuzAlbum, Performer, TrackCredits } from '$lib/types';
|
||||
import type { AlbumCredits, QobuzAlbum, Performer, TrackCredits } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
@@ -50,15 +50,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
async function loadAlbumCredits(id: string) {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const album = await invoke<QobuzAlbum>('v2_get_album', { albumId: id });
|
||||
credits = mapAlbumToCredits(album);
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e);
|
||||
credits = null;
|
||||
async function loadAlbumCredits(id: string) {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const album = await invoke<QobuzAlbum>('v2_get_album', { albumId: id });
|
||||
credits = mapAlbumToCredits(album);
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e);
|
||||
credits = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -79,97 +79,99 @@
|
||||
onTrackPlay?.(track);
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function parsePerformers(performersStr?: string): Performer[] {
|
||||
if (!performersStr) return [];
|
||||
|
||||
return performersStr
|
||||
.split(' - ')
|
||||
.map((performerChunk) => {
|
||||
const parts = performerChunk.split(',').map((part) => part.trim()).filter(Boolean);
|
||||
if (parts.length === 0) return null;
|
||||
|
||||
const [name, ...roles] = parts;
|
||||
return { name, roles };
|
||||
})
|
||||
.filter((performer): performer is Performer => performer !== null);
|
||||
}
|
||||
|
||||
function formatTrackDuration(seconds: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(seconds || 0));
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const secs = totalSeconds % 60;
|
||||
return `${minutes}:${String(secs).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatAlbumDuration(seconds: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(seconds || 0));
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
if (hours > 0) return `${hours}h ${minutes}m`;
|
||||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
function formatQuality(bitDepth?: number, samplingRate?: number): string {
|
||||
if (bitDepth && samplingRate) return `${bitDepth}-bit / ${samplingRate} kHz`;
|
||||
if (bitDepth) return `${bitDepth}-bit`;
|
||||
if (samplingRate) return `${samplingRate} kHz`;
|
||||
return '-';
|
||||
}
|
||||
|
||||
function mapAlbumToCredits(album: QobuzAlbum): AlbumCredits {
|
||||
const artwork = album.image?.large || album.image?.thumbnail || album.image?.small || '';
|
||||
const tracks = album.tracks?.items ?? [];
|
||||
const releaseDate = album.release_date_original || '';
|
||||
const releaseYear = releaseDate ? String(new Date(releaseDate).getFullYear()) : '';
|
||||
|
||||
const mappedTracks: TrackCredits[] = tracks.map((trackItem, index) => ({
|
||||
id: trackItem.id,
|
||||
number: trackItem.track_number || index + 1,
|
||||
title: trackItem.title,
|
||||
artist: trackItem.performer?.name || album.artist?.name || 'Unknown Artist',
|
||||
duration: formatTrackDuration(trackItem.duration || 0),
|
||||
duration_seconds: trackItem.duration || 0,
|
||||
performers: parsePerformers(trackItem.performers),
|
||||
copyright: trackItem.copyright,
|
||||
album_id: album.id,
|
||||
artist_id: trackItem.performer?.id
|
||||
}));
|
||||
|
||||
return {
|
||||
album: {
|
||||
id: album.id,
|
||||
artwork,
|
||||
title: album.title,
|
||||
artist: album.artist?.name || 'Unknown Artist',
|
||||
artist_id: album.artist?.id,
|
||||
year: releaseYear,
|
||||
release_date: releaseDate || undefined,
|
||||
label: album.label?.name || '',
|
||||
label_id: album.label?.id,
|
||||
genre: album.genre?.name || '',
|
||||
quality: formatQuality(album.maximum_bit_depth, album.maximum_sampling_rate),
|
||||
track_count: album.tracks_count ?? mappedTracks.length,
|
||||
duration: formatAlbumDuration(album.duration || 0),
|
||||
bit_depth: album.maximum_bit_depth,
|
||||
sampling_rate: album.maximum_sampling_rate,
|
||||
description: album.description
|
||||
},
|
||||
tracks: mappedTracks
|
||||
};
|
||||
}
|
||||
</script>
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function parsePerformers(performersStr?: string): Performer[] {
|
||||
if (!performersStr) return [];
|
||||
|
||||
return performersStr
|
||||
.split(' - ')
|
||||
.map((performerChunk) => {
|
||||
const parts = performerChunk.split(',').map((part) => part.trim()).filter(Boolean);
|
||||
if (parts.length === 0) return null;
|
||||
|
||||
const [name, ...roles] = parts;
|
||||
return { name, roles };
|
||||
})
|
||||
.filter((performer): performer is Performer => performer !== null);
|
||||
}
|
||||
|
||||
function formatTrackDuration(seconds: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(seconds || 0));
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const secs = totalSeconds % 60;
|
||||
return `${minutes}:${String(secs).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatAlbumDuration(seconds: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(seconds || 0));
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
if (hours > 0) return `${hours}h ${minutes}m`;
|
||||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
function formatQuality(bitDepth?: number, samplingRate?: number): string {
|
||||
if (bitDepth && samplingRate) return `${bitDepth}-bit / ${samplingRate} kHz`;
|
||||
if (bitDepth) return `${bitDepth}-bit`;
|
||||
if (samplingRate) return `${samplingRate} kHz`;
|
||||
return '-';
|
||||
}
|
||||
|
||||
function mapAlbumToCredits(album: QobuzAlbum): AlbumCredits {
|
||||
const artwork = album.image?.large || album.image?.thumbnail || album.image?.small || '';
|
||||
const tracks = album.tracks?.items ?? [];
|
||||
const releaseDate = album.release_date_original || '';
|
||||
const releaseYear = releaseDate ? String(new Date(releaseDate).getFullYear()) : '';
|
||||
|
||||
const mappedTracks: TrackCredits[] = tracks.map((trackItem, index) => ({
|
||||
id: trackItem.id,
|
||||
number: trackItem.track_number || index + 1,
|
||||
title: trackItem.title,
|
||||
artist: trackItem.performer?.name || album.artist?.name || 'Unknown Artist',
|
||||
duration: formatTrackDuration(trackItem.duration || 0),
|
||||
duration_seconds: trackItem.duration || 0,
|
||||
performers: parsePerformers(trackItem.performers),
|
||||
copyright: trackItem.copyright,
|
||||
album_id: album.id,
|
||||
artist_id: trackItem.performer?.id
|
||||
}));
|
||||
|
||||
return {
|
||||
album: {
|
||||
id: album.id,
|
||||
artwork,
|
||||
title: album.title,
|
||||
artist: album.artist?.name || 'Unknown Artist',
|
||||
artist_id: album.artist?.id,
|
||||
year: releaseYear,
|
||||
release_date: releaseDate || undefined,
|
||||
label: album.label?.name || '',
|
||||
label_id: album.label?.id,
|
||||
genre: album.genre?.name || '',
|
||||
quality: formatQuality(album.maximum_bit_depth, album.maximum_sampling_rate),
|
||||
track_count: album.tracks_count ?? mappedTracks.length,
|
||||
duration: formatAlbumDuration(album.duration || 0),
|
||||
bit_depth: album.maximum_bit_depth,
|
||||
sampling_rate: album.maximum_sampling_rate,
|
||||
description: album.description
|
||||
},
|
||||
tracks: mappedTracks
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
{#if isOpen}
|
||||
<div class="modal-overlay" onclick={onClose} role="dialog" aria-modal="true">
|
||||
<div class="credits-modal" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div class="modal-overlay" onclick={onClose} role="presentation">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="credits-modal" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()}>
|
||||
{#if loading}
|
||||
<div class="loading-state">
|
||||
<Loader2 size={32} class="spinner" />
|
||||
@@ -296,13 +298,16 @@
|
||||
>
|
||||
<div class="track-number">
|
||||
{#if isHovered && onTrackPlay}
|
||||
<button
|
||||
<div
|
||||
class="play-btn"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={(e) => handleTrackPlay(track, e)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleTrackPlay(track, e); } }}
|
||||
aria-label="Play track"
|
||||
>
|
||||
<Play size={14} fill="currentColor" />
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<span>{track.number}</span>
|
||||
{/if}
|
||||
|
||||
@@ -183,9 +183,10 @@
|
||||
|
||||
<!-- Connected Device Banner -->
|
||||
{#if castState.isConnected && castState.device}
|
||||
{@const ProtocolIcon = getProtocolIcon(castState.protocol!)}
|
||||
<div class="connected-banner">
|
||||
<div class="connected-info">
|
||||
<svelte:component this={getProtocolIcon(castState.protocol!)} size={20} />
|
||||
<ProtocolIcon size={20} />
|
||||
<div class="connected-text">
|
||||
<span class="connected-label">Connected to</span>
|
||||
<span class="connected-name">{castState.device.name}</span>
|
||||
@@ -249,13 +250,14 @@
|
||||
{:else}
|
||||
<div class="devices">
|
||||
{#each devices() as device}
|
||||
{@const CastIcon = getProtocolIcon(activeProtocol)}
|
||||
<button class="device" onclick={() => handleConnect(device)}>
|
||||
<Monitor size={24} />
|
||||
<div class="device-info">
|
||||
<span class="device-name">{device.name}</span>
|
||||
<span class="device-ip">{device.ip}</span>
|
||||
</div>
|
||||
<svelte:component this={getProtocolIcon(activeProtocol)} size={20} class="cast-icon" />
|
||||
<CastIcon size={20} class="cast-icon" />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -80,11 +80,13 @@
|
||||
|
||||
{#if isOpen}
|
||||
<div class="modal-backdrop" onclick={handleBackdropClick} role="presentation">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="keybindings-settings-title"
|
||||
tabindex="-1"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<header class="modal-header">
|
||||
|
||||
@@ -88,11 +88,13 @@
|
||||
onclick={handleBackdropClick}
|
||||
role="presentation"
|
||||
>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="shortcuts-modal-title"
|
||||
tabindex="-1"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<header class="modal-header">
|
||||
|
||||
@@ -61,12 +61,14 @@
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="modal-backdrop"
|
||||
onclick={handleBackdropClick}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="musician-modal-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal">
|
||||
<!-- Header -->
|
||||
|
||||
@@ -673,15 +673,6 @@
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
/* Section Separator */
|
||||
.section-separator {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: var(--border-subtle);
|
||||
margin: 0 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Queue Button & Icon */
|
||||
.queue-btn {
|
||||
width: 32px;
|
||||
|
||||
@@ -328,6 +328,7 @@
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="modal" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="modal-header">
|
||||
<div class="header-title">
|
||||
|
||||
@@ -660,6 +660,7 @@
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="modal" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="modal-header">
|
||||
<h2>
|
||||
@@ -782,7 +783,7 @@
|
||||
|
||||
{#if mode === 'edit'}
|
||||
<div class="form-group">
|
||||
<label>{$t('playlist.customArtwork')}</label>
|
||||
<span class="form-label">{$t('playlist.customArtwork')}</span>
|
||||
<div class="playlist-artwork-editor">
|
||||
<div class="playlist-artwork-preview">
|
||||
{#if customArtworkPath}
|
||||
@@ -1022,7 +1023,8 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
.form-group label,
|
||||
.form-group .form-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Queue Panel -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
|
||||
<aside class="queue-panel" onclick={handlePanelClick}>
|
||||
<!-- Header with Tabs -->
|
||||
<div class="header">
|
||||
@@ -354,6 +355,7 @@
|
||||
ondragend={handleDragEnd}
|
||||
role="button"
|
||||
tabindex={isUnavailable ? -1 : 0}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleTrackClick(queueTrack); } }}
|
||||
>
|
||||
<span class="track-number">{originalIndex + 1}</span>
|
||||
<div class="track-info">
|
||||
@@ -415,6 +417,7 @@
|
||||
onmouseleave={() => hoveredHistoryTrack = null}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleHistoryTrackClick(track); } }}
|
||||
>
|
||||
<img src={track.artwork} alt={track.title} class="history-artwork" />
|
||||
<div class="track-info">
|
||||
|
||||
@@ -1618,6 +1618,8 @@
|
||||
class="dropdown-menu"
|
||||
bind:this={menuEl}
|
||||
style={menuStyle}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
onmouseenter={() => isHoveringDropdown = true}
|
||||
onmouseleave={() => isHoveringDropdown = false}
|
||||
>
|
||||
@@ -1629,6 +1631,7 @@
|
||||
tabindex="0"
|
||||
onmouseenter={openSubmenu}
|
||||
onmouseleave={closeSubmenuDelayed}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openSubmenu(); } }}
|
||||
>
|
||||
<ArrowUpDown size={14} />
|
||||
<span class="menu-label">{$t('library.sortBy')}</span>
|
||||
@@ -1641,6 +1644,8 @@
|
||||
class="submenu"
|
||||
bind:this={submenuEl}
|
||||
style={submenuStyle}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
onmouseenter={openSubmenu}
|
||||
onmouseleave={closeSubmenuDelayed}
|
||||
>
|
||||
@@ -1723,6 +1728,7 @@
|
||||
</span>
|
||||
</button>
|
||||
{:else if item.type === 'folder-playlist'}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="playlist-drag-wrapper"
|
||||
class:dragging={draggedPlaylistId === item.playlist.id}
|
||||
@@ -1744,6 +1750,7 @@
|
||||
</NavigationItem>
|
||||
</div>
|
||||
{:else if item.type === 'root-playlist'}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="playlist-drag-wrapper"
|
||||
class:dragging={draggedPlaylistId === item.playlist.id}
|
||||
@@ -1880,6 +1887,7 @@
|
||||
{@const availableFolders = folders.filter(f => f.id !== contextMenu.currentFolderId)}
|
||||
{@const showSearch = availableFolders.length >= FOLDER_SEARCH_THRESHOLD}
|
||||
{@const contextPlaylistHidden = contextMenu.playlist ? (playlistSettings.get(contextMenu.playlist.id)?.hidden ?? false) : false}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="context-menu"
|
||||
class:has-search={showSearch}
|
||||
@@ -1888,6 +1896,7 @@
|
||||
onmouseenter={() => isHoveringContextMenu = true}
|
||||
onmouseleave={() => isHoveringContextMenu = false}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
{#if contextMenu.folder}
|
||||
{@const contextFolderHidden = contextMenu.folder.is_hidden ?? false}
|
||||
@@ -1974,6 +1983,7 @@
|
||||
|
||||
<!-- Collapsed Folder Popover -->
|
||||
{#if folderPopover.visible}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="folder-popover"
|
||||
style="left: {folderPopover.x}px; top: {folderPopover.y}px;"
|
||||
@@ -1981,6 +1991,7 @@
|
||||
onmouseenter={() => isHoveringFolderPopover = true}
|
||||
onmouseleave={() => isHoveringFolderPopover = false}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="folder-popover-header">
|
||||
<Folder size={14} />
|
||||
@@ -2011,10 +2022,12 @@
|
||||
<!-- Create Folder Modal -->
|
||||
{#if showCreateFolderModal}
|
||||
<div class="modal-overlay" onclick={cancelCreateFolder} role="presentation">
|
||||
<div class="modal-content create-folder-modal" onclick={(e) => e.stopPropagation()} role="dialog" aria-modal="true">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="modal-content create-folder-modal" onclick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" tabindex="-1">
|
||||
<h2 class="modal-title">{$t('playlist.newFolder', { default: 'New Folder' })}</h2>
|
||||
<div class="form-group">
|
||||
<label for="folder-name">{$t('common.name', { default: 'Name' })}</label>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
id="folder-name"
|
||||
type="text"
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
return role.replace(/([A-Z])/g, ' $1').trim().toUpperCase();
|
||||
}
|
||||
|
||||
// Group performers by role and return ordered entries
|
||||
function getGroupedCredits(performers: Performer[]): { role: string; names: string[] }[] {
|
||||
// Group performers by role and return ordered entries
|
||||
function getGroupedCredits(performers: Performer[]): { role: string; names: string[] }[] {
|
||||
const grouped: Record<string, string[]> = {};
|
||||
|
||||
performers.forEach(performer => {
|
||||
@@ -98,25 +98,25 @@
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
return entries.map(([role, names]) => ({ role, names }));
|
||||
}
|
||||
|
||||
// Parse Qobuz performers string: "Name, Role - Name, Role"
|
||||
function parsePerformers(performersStr?: string): Performer[] {
|
||||
if (!performersStr) return [];
|
||||
return performersStr
|
||||
.split(' - ')
|
||||
.map(segment => segment.trim())
|
||||
.filter(Boolean)
|
||||
.map(segment => {
|
||||
const parts = segment.split(', ').map(p => p.trim()).filter(Boolean);
|
||||
return {
|
||||
name: parts[0] || '',
|
||||
roles: parts.slice(1)
|
||||
};
|
||||
})
|
||||
.filter(p => p.name.length > 0);
|
||||
}
|
||||
return entries.map(([role, names]) => ({ role, names }));
|
||||
}
|
||||
|
||||
// Parse Qobuz performers string: "Name, Role - Name, Role"
|
||||
function parsePerformers(performersStr?: string): Performer[] {
|
||||
if (!performersStr) return [];
|
||||
return performersStr
|
||||
.split(' - ')
|
||||
.map(segment => segment.trim())
|
||||
.filter(Boolean)
|
||||
.map(segment => {
|
||||
const parts = segment.split(', ').map(p => p.trim()).filter(Boolean);
|
||||
return {
|
||||
name: parts[0] || '',
|
||||
roles: parts.slice(1)
|
||||
};
|
||||
})
|
||||
.filter(p => p.name.length > 0);
|
||||
}
|
||||
|
||||
// Load track info when modal opens
|
||||
$effect(() => {
|
||||
@@ -128,18 +128,18 @@
|
||||
}
|
||||
});
|
||||
|
||||
async function loadTrackInfo(id: number) {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const track = await invoke<TrackInfo['track']>('v2_get_track', { trackId: id });
|
||||
trackInfo = {
|
||||
track,
|
||||
performers: parsePerformers(track.performers)
|
||||
};
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e);
|
||||
trackInfo = null;
|
||||
async function loadTrackInfo(id: number) {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const track = await invoke<TrackInfo['track']>('v2_get_track', { trackId: id });
|
||||
trackInfo = {
|
||||
track,
|
||||
performers: parsePerformers(track.performers)
|
||||
};
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e);
|
||||
trackInfo = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -160,8 +160,10 @@
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
{#if isOpen}
|
||||
<div class="modal-overlay" onclick={onClose} role="dialog" aria-modal="true">
|
||||
<div class="track-modal" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div class="modal-overlay" onclick={onClose} role="presentation">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div class="track-modal" onclick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" tabindex="-1">
|
||||
{#if loading}
|
||||
<div class="loading-state">
|
||||
<Loader2 size={32} class="spinner" />
|
||||
|
||||
@@ -1632,7 +1632,7 @@
|
||||
Qobuz Radio
|
||||
</button>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="radio-dropdown-backdrop" onclick={() => radioDropdownOpen = false}></div>
|
||||
{/if}
|
||||
{#if isRadioLoading && radioLoadingMessage}
|
||||
@@ -1692,7 +1692,7 @@
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="hide-dropdown-backdrop" onclick={() => showHideDropdown = false}></div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1858,11 +1858,13 @@
|
||||
}}
|
||||
>
|
||||
{#if multiSelectMode}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
|
||||
<label class="track-checkbox-wrap" onclick={(e) => e.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={multiSelectedIds.has(track.id)}
|
||||
onchange={() => toggleMultiSelect(track.id)}
|
||||
aria-label={$t('actions.select')}
|
||||
style="width:15px;height:15px;cursor:pointer;accent-color:var(--accent-primary);"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -239,10 +239,12 @@
|
||||
onclick={() => confirmClearOpen = false}
|
||||
onkeydown={(e) => e.key === 'Escape' && (confirmClearOpen = false)}
|
||||
>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="modal-content"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 class="modal-title">{$t('settings.blacklist.confirmClearTitle')}</h2>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
albumOfTheWeek: 'discover-albums-of-the-week',
|
||||
pressAward: 'discover-press-accolades',
|
||||
};
|
||||
const genreContext = GENRE_CONTEXT_MAP[endpointType];
|
||||
const genreContext = $derived(GENRE_CONTEXT_MAP[endpointType]);
|
||||
|
||||
// State
|
||||
let albums = $state<FavoriteAlbum[]>([]);
|
||||
|
||||
@@ -291,9 +291,12 @@
|
||||
<!-- List View -->
|
||||
<div class="playlist-list">
|
||||
{#each getFilteredPlaylists() as playlist (playlist.id)}
|
||||
<button
|
||||
<div
|
||||
class="list-row"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => onPlaylistClick?.(playlist.id)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPlaylistClick?.(playlist.id); } }}
|
||||
>
|
||||
<div class="list-artwork">
|
||||
{#if playlist.image?.rectangle || playlist.image?.covers?.[0]}
|
||||
@@ -344,7 +347,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1326,6 +1326,7 @@
|
||||
{:else}
|
||||
<div class="search-expanded">
|
||||
<Search size={16} class="search-icon-inline" />
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
placeholder={$t('placeholders.search')}
|
||||
@@ -1346,12 +1347,13 @@
|
||||
<div class="favorites-nav">
|
||||
<div class="nav-left">
|
||||
{#each favoritesPreferences.tab_order as tab}
|
||||
{@const Icon = getTabIcon(tab as TabType)}
|
||||
<button
|
||||
class="nav-link"
|
||||
class:active={activeTab === tab}
|
||||
onclick={() => handleTabChange(tab as TabType)}
|
||||
>
|
||||
<svelte:component this={getTabIcon(tab as TabType)} size={16} />
|
||||
<Icon size={16} />
|
||||
<span>{getTabLabel(tab as TabType)}</span>
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
@@ -263,6 +263,7 @@
|
||||
{#if searchExpanded}
|
||||
<div class="search-expanded">
|
||||
<Search size={16} class="search-icon-inline" />
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
class="search-input-inline"
|
||||
|
||||
@@ -880,11 +880,13 @@
|
||||
}}
|
||||
>
|
||||
{#if multiSelectMode}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
|
||||
<label class="track-checkbox-wrap" onclick={(e) => e.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={multiSelectedIds.has(track.id)}
|
||||
onchange={() => toggleMultiSelect(track.id)}
|
||||
aria-label={$t('actions.select')}
|
||||
style="width:15px;height:15px;cursor:pointer;accent-color:var(--accent-primary);"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -3579,8 +3579,8 @@
|
||||
{:else}
|
||||
<div class="album-list">
|
||||
{#each hiddenAlbums as album (album.id)}
|
||||
<div class="album-row" role="button" tabindex="0">
|
||||
<div class="album-row-art" onclick={() => handleAlbumClick(album)}>
|
||||
<div class="album-row" role="button" tabindex="0" onclick={() => handleAlbumClick(album)} onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAlbumClick(album); } }}>
|
||||
<div class="album-row-art">
|
||||
{#if album.artwork_path}
|
||||
<img src={getArtworkUrl(album.artwork_path)} alt={album.title} loading="lazy" decoding="async" />
|
||||
{:else}
|
||||
@@ -3589,7 +3589,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="album-row-info" onclick={() => handleAlbumClick(album)}>
|
||||
<div class="album-row-info">
|
||||
<div class="album-row-title">{album.title}</div>
|
||||
<div class="album-row-artist">{album.artist}</div>
|
||||
<div class="album-row-meta">
|
||||
@@ -3677,8 +3677,10 @@
|
||||
{/if}
|
||||
</button>
|
||||
{#if showFilterPanel}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="filter-panel"
|
||||
role="region"
|
||||
onmouseenter={clearFilterPanelTimer}
|
||||
onmouseleave={startFilterPanelTimer}
|
||||
onclick={handleFilterPanelActivity}
|
||||
@@ -4108,8 +4110,9 @@
|
||||
|
||||
<!-- Album Settings Modal -->
|
||||
{#if showAlbumEditModal && selectedAlbum}
|
||||
<div class="modal-overlay" onclick={() => showAlbumEditModal = false}>
|
||||
<div class="modal" onclick={(e: MouseEvent) => e.stopPropagation()}>
|
||||
<div class="modal-overlay" onclick={() => showAlbumEditModal = false} role="presentation">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="modal" role="dialog" aria-modal="true" tabindex="-1" onclick={(e: MouseEvent) => e.stopPropagation()}>
|
||||
<div class="modal-header">
|
||||
<h2>Album Settings</h2>
|
||||
<button class="close-btn" onclick={() => showAlbumEditModal = false}>
|
||||
@@ -4150,7 +4153,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<div class="artwork-layout-header" class:discogs-active={discogsFetchSuccessful}>
|
||||
<label>Album Artwork</label>
|
||||
<span class="form-label">Album Artwork</span>
|
||||
{#if discogsFetchSuccessful}
|
||||
<div class="discogs-layout-label">Select Artwork from Discogs</div>
|
||||
{/if}
|
||||
@@ -5825,7 +5828,7 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
.form-group .form-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
@@ -5845,7 +5848,7 @@
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.artwork-layout-header label {
|
||||
.artwork-layout-header .form-label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2268,6 +2268,7 @@
|
||||
{@const available = isTrackAvailable(track)}
|
||||
{@const removedFromQobuz = isTrackRemovedFromQobuz(track)}
|
||||
{@const trackBlacklisted = !track.isLocal && track.artistId ? isArtistBlacklisted(track.artistId) : false}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="track-row-wrapper virtual-track-item"
|
||||
class:unavailable={!available}
|
||||
@@ -2288,21 +2289,25 @@
|
||||
>
|
||||
{#if multiSelectMode && !isCustomOrderMode}
|
||||
{@const trackKey = getTrackKey(track)}
|
||||
<label class="track-checkbox" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
|
||||
<label class="track-checkbox" onclick={(e: MouseEvent) => e.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={multiSelectedKeys.has(trackKey)}
|
||||
onchange={() => toggleMultiSelect(track)}
|
||||
aria-label={$t('actions.select')}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{#if isCustomOrderMode}
|
||||
{@const trackKey = getTrackKey(track)}
|
||||
<label class="track-checkbox" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
|
||||
<label class="track-checkbox" onclick={(e: MouseEvent) => e.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedTrackKeys.has(trackKey)}
|
||||
onchange={() => toggleTrackSelection(track)}
|
||||
aria-label={$t('actions.select')}
|
||||
/>
|
||||
</label>
|
||||
<div class="reorder-controls">
|
||||
@@ -3208,13 +3213,6 @@
|
||||
border-color: var(--alpha-20);
|
||||
}
|
||||
|
||||
/* Playlist API image (pre-made collage from Qobuz) */
|
||||
.playlist-api-image {
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.track-list-bottom-spacer {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
@@ -950,6 +950,8 @@
|
||||
{#if showFilterMenu}
|
||||
<div
|
||||
class="dropdown-menu"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
onmouseenter={() => isHoveringFilterMenu = true}
|
||||
onmouseleave={() => isHoveringFilterMenu = false}
|
||||
>
|
||||
@@ -998,6 +1000,8 @@
|
||||
{#if showSortMenu}
|
||||
<div
|
||||
class="dropdown-menu"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
onmouseenter={() => isHoveringSortMenu = true}
|
||||
onmouseleave={() => isHoveringSortMenu = false}
|
||||
>
|
||||
@@ -1104,6 +1108,7 @@
|
||||
{#if viewMode === 'grid'}
|
||||
<div class="folders-grid">
|
||||
{#each folders as folder (folder.id)}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="folder-card"
|
||||
class:drag-over={dragOverFolderId === folder.id}
|
||||
@@ -1219,6 +1224,7 @@
|
||||
{@const isFavorite = playlistSettings.get(playlist.id)?.is_favorite}
|
||||
{@const localStatus = getLocalContentStatus(playlist.id)}
|
||||
{@const isUnavailable = offlineStatus.isOffline && !isPlaylistAvailableOffline(playlist.id)}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="grid-item"
|
||||
class:hidden={isHidden}
|
||||
@@ -1267,6 +1273,7 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => onPlaylistSelect?.(playlist.id)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPlaylistSelect?.(playlist.id); } }}
|
||||
title={isUnavailable ? $t('offline.viewOnly') : undefined}
|
||||
>
|
||||
<div class="artwork">
|
||||
@@ -1373,6 +1380,7 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => onPlaylistSelect?.(playlist.id)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPlaylistSelect?.(playlist.id); } }}
|
||||
>
|
||||
<div class="tree-item-artwork">
|
||||
<PlaylistCollage artworks={playlist.images ?? []} size={32} />
|
||||
@@ -1433,6 +1441,7 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => onPlaylistSelect?.(playlist.id)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPlaylistSelect?.(playlist.id); } }}
|
||||
>
|
||||
<div class="tree-item-artwork">
|
||||
<PlaylistCollage artworks={playlist.images ?? []} size={32} />
|
||||
@@ -1501,6 +1510,7 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => onPlaylistSelect?.(playlist.id)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPlaylistSelect?.(playlist.id); } }}
|
||||
title={isUnavailable ? $t('offline.viewOnly') : undefined}
|
||||
>
|
||||
{#if sort === 'custom' && !isUnavailable}
|
||||
|
||||
@@ -389,6 +389,7 @@
|
||||
{@const isActive = activeTrackId === track.id}
|
||||
{@const isDownloadedForFormat = selectedFormatId !== null && (track.downloaded_format_ids ?? []).includes(selectedFormatId)}
|
||||
{@const isDownloaded = isDownloadedForFormat || status === 'complete'}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="track-row"
|
||||
class:downloading={status === 'downloading'}
|
||||
@@ -399,6 +400,7 @@
|
||||
onclick={() => track.streamable && onTrackPlay?.(toDisplayTrack(track))}
|
||||
role={track.streamable && onTrackPlay ? 'button' : undefined}
|
||||
tabindex={track.streamable && onTrackPlay ? 0 : undefined}
|
||||
onkeydown={track.streamable && onTrackPlay ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onTrackPlay?.(toDisplayTrack(track)); } } : undefined}
|
||||
>
|
||||
<div class="col-number">
|
||||
{#if isActive && isPlaybackActive}
|
||||
@@ -900,12 +902,6 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.download-done {
|
||||
color: var(--success, #4caf50);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.download-active {
|
||||
color: var(--accent-primary);
|
||||
display: flex;
|
||||
|
||||
@@ -907,6 +907,7 @@
|
||||
{#each group.items as track (track.id)}
|
||||
{@const dlStatus = getTrackDownloadStatus(track.id)}
|
||||
{@const isDownloaded = track.downloaded || dlStatus === 'complete'}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="track-row"
|
||||
class:active={activeTrackId === track.id}
|
||||
@@ -916,6 +917,7 @@
|
||||
onclick={() => track.streamable && onTrackPlay?.(toDisplayTrack(track))}
|
||||
role={track.streamable && onTrackPlay ? 'button' : undefined}
|
||||
tabindex={track.streamable && onTrackPlay ? 0 : undefined}
|
||||
onkeydown={track.streamable && onTrackPlay ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onTrackPlay?.(toDisplayTrack(track)); } } : undefined}
|
||||
>
|
||||
<div class="track-artwork">
|
||||
{#if track.album?.image}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
import { saveScrollPosition, getSavedScrollPosition } from '$lib/stores/navigationStore';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
let searchInput: HTMLInputElement | null = null;
|
||||
let searchInput = $state<HTMLInputElement | null>(null);
|
||||
let albumsCarouselContainer = $state<HTMLDivElement | null>(null);
|
||||
let artistsCarouselContainer = $state<HTMLDivElement | null>(null);
|
||||
let scrollContainer: HTMLDivElement | null = null;
|
||||
let scrollContainer = $state<HTMLDivElement | null>(null);
|
||||
let isScrolled = $state(false);
|
||||
let currentAlbumPage = $state(0);
|
||||
let currentArtistPage = $state(0);
|
||||
@@ -1338,11 +1338,14 @@
|
||||
<MoreHorizontal size={16} />
|
||||
</button>
|
||||
{#if mostPopularMenuOpen}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="popular-menu"
|
||||
bind:this={popularMenuEl}
|
||||
style={popularMenuStyle}
|
||||
use:portal
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button class="menu-item" onclick={() => { onAlbumPlay?.(album.id); mostPopularMenuOpen = false; }}>
|
||||
@@ -1395,6 +1398,7 @@
|
||||
</div>
|
||||
{:else if allResults.most_popular?.type === 'tracks'}
|
||||
{@const track = allResults.most_popular.content}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="popular-card most-popular-card" class:menu-open={mostPopularMenuOpen} onmouseenter={measurePopularOverflow}>
|
||||
<div class="popular-card-artwork">
|
||||
{#if track.album?.image?.large || track.album?.image?.small}
|
||||
@@ -1436,11 +1440,14 @@
|
||||
<MoreHorizontal size={16} />
|
||||
</button>
|
||||
{#if mostPopularMenuOpen}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="popular-menu"
|
||||
bind:this={popularMenuEl}
|
||||
style={popularMenuStyle}
|
||||
use:portal
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button class="menu-item" onclick={() => { handleSearchTrackPlay(track, 0); mostPopularMenuOpen = false; }}>
|
||||
|
||||
@@ -3997,8 +3997,8 @@
|
||||
<!-- Auto-Theme failure modal -->
|
||||
{#if autoThemeFailedModal}
|
||||
<div class="auto-theme-modal-backdrop" role="presentation" onclick={dismissAutoThemeFailedModal}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="auto-theme-modal" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="auto-theme-modal" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()}>
|
||||
<h3>{$t('settings.appearance.autoThemeError')}</h3>
|
||||
<p class="auto-theme-modal-message">{autoThemeFailedMessage}</p>
|
||||
<p class="auto-theme-modal-hint">{$t('settings.appearance.autoThemeFailedHint')}</p>
|
||||
@@ -5947,11 +5947,6 @@ flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.collapsible-section .section-title-btn .experimental-badge {
|
||||
flex-shrink: 0;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
/* Composition subsection (inside Appearance) */
|
||||
.composition-subsection {
|
||||
padding-top: 16px;
|
||||
|
||||
Reference in new issue
Block a user