Merge pull request #423 from afonsojramos/rework-titlebar-drag-region
refactor: rework window drag regions and text selection model
This commit is contained in:
54 files changed
+135
-150
No files matched your search
+22
@@ -1547,6 +1547,28 @@ body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
/* App-wide non-selection. Music app text (titles, artists, headers) is
|
||||
not meant to be selected — and selectable text breaks window drag
|
||||
regions, since native text selection preempts the drag gesture.
|
||||
Exceptions below opt specific elements back into selectability. */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* Text-input controls always get native selection back. */
|
||||
input,
|
||||
textarea,
|
||||
[contenteditable]:not([contenteditable='false']) {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
/* Escape hatch: add class="selectable" to any element whose text users
|
||||
should be able to copy (error messages, tokens, release notes, etc.). */
|
||||
.selectable,
|
||||
.selectable * {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
/* Scrollbar Styling */
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- 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()}>
|
||||
<div class="modal selectable" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<div class="app-branding">
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<!-- 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()}>
|
||||
<div class="credits-modal selectable" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()}>
|
||||
{#if loading}
|
||||
<div class="loading-state">
|
||||
<LoaderCircle size={32} class="spinner" />
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="booklet-content" bind:this={containerEl}>
|
||||
<div class="booklet-content selectable" bind:this={containerEl}>
|
||||
{#if isLoading}
|
||||
<div class="booklet-loading">
|
||||
<div class="spinner"></div>
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--text-secondary, var(--text-muted));
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -490,7 +490,6 @@
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-muted);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.option {
|
||||
|
||||
@@ -461,7 +461,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="diagnostics-panel">
|
||||
<div class="diagnostics-panel selectable">
|
||||
<button class="section-toggle panel-toggle" onclick={togglePanel}>
|
||||
{#if panelOpen}<ChevronDown size={14} />{:else}<ChevronRight size={14} />{/if}
|
||||
<h4 class="subsection-title" style="margin:0">{$t('settings.developer.diagnostics.title')}</h4>
|
||||
|
||||
@@ -243,7 +243,6 @@
|
||||
.playlist-card {
|
||||
width: 140px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.artwork-container {
|
||||
|
||||
@@ -189,12 +189,6 @@
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
cursor: grab;
|
||||
/* Never select text inside carousels: drag-to-scroll would otherwise
|
||||
highlight titles/artists in the first few pixels before the drag
|
||||
threshold kicks in. Text is still selectable in the dedicated
|
||||
album/artist/playlist views. */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.scroll-container.dragging {
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="modal"
|
||||
class="modal selectable"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="shortcuts-modal-title"
|
||||
|
||||
@@ -326,7 +326,6 @@
|
||||
min-height: 22px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
user-select: none;
|
||||
color: var(--text-primary);
|
||||
transition: background 100ms ease;
|
||||
/* Extend to natural content width when names are long; fill the
|
||||
|
||||
@@ -294,7 +294,6 @@
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.error-list {
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
aria-labelledby="musician-modal-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal">
|
||||
<div class="modal selectable">
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<div class="musician-info">
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
const TERMS_URL = 'https://www.qobuz.com/us-en/legal/terms';
|
||||
</script>
|
||||
|
||||
<div class={`qobuz-legal ${className}`}>
|
||||
<div class={`qobuz-legal selectable ${className}`}>
|
||||
{#if showBrandingLine}
|
||||
<p class="branding-line">
|
||||
{$t('legal.brandingLine')}
|
||||
@@ -74,7 +74,6 @@
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tos-line input {
|
||||
|
||||
@@ -242,7 +242,6 @@
|
||||
.playlist-card {
|
||||
width: 162px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.artwork-container {
|
||||
|
||||
@@ -1650,9 +1650,14 @@
|
||||
|
||||
<svelte:window onclick={handleGlobalClick} />
|
||||
|
||||
<aside class="sidebar" class:collapsed={!isExpanded} class:no-titlebar={!showTitleBar}>
|
||||
<aside
|
||||
class="sidebar"
|
||||
class:collapsed={!isExpanded}
|
||||
class:no-titlebar={!showTitleBar}
|
||||
data-tauri-drag-region={!showTitleBar ? '' : undefined}
|
||||
>
|
||||
<!-- Scrollable Content Area -->
|
||||
<div class="content">
|
||||
<div class="content" data-tauri-drag-region="false">
|
||||
<!-- Search Bar (hidden when search is in titlebar) -->
|
||||
{#if !searchInTitlebar}
|
||||
<div
|
||||
@@ -2256,6 +2261,7 @@
|
||||
class="toggle-btn"
|
||||
onclick={onToggle}
|
||||
title={isExpanded ? $t('actions.collapse') : $t('actions.expand')}
|
||||
data-tauri-drag-region="false"
|
||||
>
|
||||
{#if isExpanded}
|
||||
<ChevronLeft size={16} />
|
||||
@@ -2265,7 +2271,7 @@
|
||||
</button>
|
||||
|
||||
<!-- Fixed User Profile at Bottom -->
|
||||
<div class="user-section" class:collapsed={!isExpanded}>
|
||||
<div class="user-section" class:collapsed={!isExpanded} data-tauri-drag-region="false">
|
||||
<UserCard
|
||||
username={userName}
|
||||
{subscription}
|
||||
@@ -2515,7 +2521,12 @@
|
||||
height: calc(100vh - 104px); /* Only 104px NowPlayingBar, no title bar */
|
||||
}
|
||||
|
||||
/* macOS: pad top of sidebar to clear native traffic light buttons */
|
||||
/* macOS: pad top of sidebar to clear native traffic light buttons.
|
||||
This 32px band is also the only visible drag surface: the <aside>
|
||||
carries `data-tauri-drag-region` (set in the template when there's
|
||||
no custom titlebar), while each direct child carries
|
||||
`data-tauri-drag-region="false"` so clicks inside .content / the
|
||||
collapse toggle / the user section never start a window drag. */
|
||||
:global(html.macos) .sidebar.no-titlebar {
|
||||
padding-top: 32px;
|
||||
}
|
||||
@@ -3487,7 +3498,6 @@
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 150ms ease, background-color 150ms ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.my-qbz-parent:hover {
|
||||
|
||||
@@ -305,7 +305,6 @@
|
||||
color: var(--color-success, #22c55e);
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -288,10 +288,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0 0 0;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
/* 3-zone layout: left and right zones are equal width, center is fixed */
|
||||
@@ -321,7 +317,6 @@
|
||||
.drag-region {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Search bar in titlebar */
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
<!-- 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">
|
||||
<div class="track-modal selectable" onclick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" tabindex="-1">
|
||||
{#if loading}
|
||||
<div class="loading-state">
|
||||
<LoaderCircle size={32} class="spinner" />
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="track-info-panel" class:centered-layout={centeredLayout}>
|
||||
<div class="track-info-panel selectable" class:centered-layout={centeredLayout}>
|
||||
{#if loading}
|
||||
<div class="loading-state">
|
||||
<LoaderCircle size={28} class="spinner" />
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</script>
|
||||
|
||||
<aside class="lyrics-sidebar">
|
||||
<div class="header">
|
||||
<div class="header" data-tauri-drag-region="deep">
|
||||
<div class="header-icon">
|
||||
<MicVocal size={18} />
|
||||
</div>
|
||||
@@ -65,7 +65,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel selectable">
|
||||
{#if isLoading}
|
||||
<div class="state">
|
||||
<div class="loading-spinner"></div>
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Album Metadata -->
|
||||
<div class="metadata" class:no-description={!album.description}>
|
||||
<div class="metadata selectable" class:no-description={!album.description}>
|
||||
<h1 class="album-title">{album.title}</h1>
|
||||
<div class="artist-line">
|
||||
{#if album.parentalWarning}
|
||||
@@ -819,8 +819,8 @@
|
||||
<!-- Track List -->
|
||||
<div class="track-list">
|
||||
<!-- Table Header -->
|
||||
<div class="tracklist-toolbar">
|
||||
<div class="tracklist-toolbar-left">
|
||||
<div class="tracklist-toolbar" data-tauri-drag-region="deep">
|
||||
<div class="tracklist-toolbar-left" data-tauri-drag-region="false">
|
||||
<QualityBadgeStatic
|
||||
bare
|
||||
quality={album.quality}
|
||||
@@ -828,7 +828,7 @@
|
||||
samplingRate={album.samplingRate}
|
||||
/>
|
||||
</div>
|
||||
<div class="tracklist-toolbar-search">
|
||||
<div class="tracklist-toolbar-search" data-tauri-drag-region="false">
|
||||
<Search size={14} />
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -1789,7 +1789,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="artist-detail" class:has-art-bg={!!headerColor} style={headerStyle} bind:this={artistDetailEl} onscroll={(e) => saveScrollPosition('artist', (e.target as HTMLElement).scrollTop, artist.id)}>
|
||||
<div class="artist-detail" class:has-art-bg={!!headerColor} style={headerStyle} bind:this={artistDetailEl} onscroll={(e) => saveScrollPosition('artist', (e.target as HTMLElement).scrollTop, artist.id)} data-tauri-drag-region>
|
||||
<!-- Back Navigation -->
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
@@ -1826,7 +1826,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Artist Info -->
|
||||
<div class="artist-info">
|
||||
<div class="artist-info selectable">
|
||||
<h1 class="artist-name">{artist.name}</h1>
|
||||
|
||||
<!-- Biography -->
|
||||
@@ -2145,7 +2145,7 @@
|
||||
<!-- Top Tracks Section -->
|
||||
{#if topTracks.length > 0 || tracksLoading}
|
||||
<div class="top-tracks-section section-anchor" bind:this={topTracksSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.popularTracks')}</h2>
|
||||
</div>
|
||||
@@ -2391,7 +2391,7 @@
|
||||
|
||||
<!-- Discography Section -->
|
||||
<div class="discography section-anchor" bind:this={discographySection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.discography')}</h2>
|
||||
{#if artist.albums.length > 0}
|
||||
@@ -2493,7 +2493,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="discography section-anchor" bind:this={epsSinglesSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.epsSingles')}</h2>
|
||||
<span class="section-count">{artist.epsSingles.length}</span>
|
||||
@@ -2557,7 +2557,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="discography section-anchor" bind:this={liveAlbumsSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.liveAlbums')}</h2>
|
||||
<span class="section-count">{artist.liveAlbums.length}</span>
|
||||
@@ -2621,7 +2621,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="discography section-anchor" bind:this={compilationsSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.compilations')}</h2>
|
||||
<span class="section-count">{artist.compilations.length}</span>
|
||||
@@ -2678,7 +2678,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="discography section-anchor" bind:this={othersSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.others')}</h2>
|
||||
<span class="section-count">{artist.others.length}</span>
|
||||
@@ -2742,7 +2742,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="playlists-section section-anchor" bind:this={playlistsSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.playlists')}</h2>
|
||||
<span class="section-count">{artist.playlists.length}</span>
|
||||
@@ -2789,7 +2789,7 @@
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="discography section-anchor" bind:this={tributesSection}>
|
||||
<div class="section-header">
|
||||
<div class="section-header" data-tauri-drag-region="deep">
|
||||
<div class="section-header-left">
|
||||
<h2 class="section-title">{$t('artist.tributes')}</h2>
|
||||
<span class="section-count">{artist.tributes.length}</span>
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
<div class="scene-view">
|
||||
{#if !loading}
|
||||
<!-- Back button (always visible) -->
|
||||
<div class="top-bar">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
<span>{$t('actions.back')}</span>
|
||||
@@ -431,7 +431,7 @@
|
||||
|
||||
<!-- Compact sticky header (appears when hero scrolls out) -->
|
||||
{#if heroScrolledPast}
|
||||
<div class="compact-header">
|
||||
<div class="compact-header" data-tauri-drag-region="deep">
|
||||
{#if flagUrl}
|
||||
<img src={flagUrl} alt="" class="compact-flag" />
|
||||
{/if}
|
||||
|
||||
@@ -163,8 +163,8 @@
|
||||
</script>
|
||||
|
||||
<div class="award-albums-view">
|
||||
<div class="top-bar">
|
||||
<div class="top-bar-left">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<div class="top-bar-left" data-tauri-drag-region="false">
|
||||
<button class="back-btn" onclick={onBack} title={$t('actions.back')}>
|
||||
<ChevronLeft size={20} />
|
||||
</button>
|
||||
@@ -173,7 +173,7 @@
|
||||
<h1 class="page-title">{$t('award.section.releases')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-bar-right">
|
||||
<div class="top-bar-right" data-tauri-drag-region="false">
|
||||
<div class="search-wrapper">
|
||||
<Search size={16} />
|
||||
<input
|
||||
|
||||
@@ -227,13 +227,13 @@
|
||||
const heroImage = $derived(page?.image || page?.magazine?.image || '');
|
||||
</script>
|
||||
|
||||
<div class="award-detail-view">
|
||||
<div class="award-detail-view" data-tauri-drag-region>
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
<span>{$t('actions.back')}</span>
|
||||
</button>
|
||||
|
||||
<header class="award-header">
|
||||
<header class="award-header" data-tauri-drag-region="deep">
|
||||
<div class="award-image-wrapper">
|
||||
{#if heroImage && !heroImageFailed}
|
||||
<img
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
|
||||
<ViewTransition duration={200} distance={12} direction="down">
|
||||
<div class="blacklist-manager">
|
||||
<div class="top-bar">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
<span>{$t('actions.back')}</span>
|
||||
|
||||
@@ -181,9 +181,9 @@
|
||||
<span>{$t('actions.back')}</span>
|
||||
</button>
|
||||
{/if}
|
||||
<header class="view-header">
|
||||
<header class="view-header" data-tauri-drag-region="deep">
|
||||
<h1>{$t('collections.nav')}</h1>
|
||||
<div class="header-actions">
|
||||
<div class="header-actions" data-tauri-drag-region="false">
|
||||
<button
|
||||
type="button"
|
||||
class="primary-cta"
|
||||
|
||||
@@ -567,7 +567,7 @@
|
||||
</script>
|
||||
|
||||
<div class="builder-view">
|
||||
<div class="builder-scroll">
|
||||
<div class="builder-scroll" data-tauri-drag-region>
|
||||
{#if onBack}
|
||||
<button class="back-btn" onclick={() => onBack?.()}>
|
||||
<ArrowLeft size={16} />
|
||||
@@ -575,7 +575,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<header class="builder-header">
|
||||
<header class="builder-header" data-tauri-drag-region="deep">
|
||||
{#if artistAvatarUrl}
|
||||
<img class="avatar" src={artistAvatarUrl} alt="" />
|
||||
{:else}
|
||||
@@ -1157,7 +1157,6 @@
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -234,14 +234,14 @@
|
||||
|
||||
<div class="discover-browse">
|
||||
<!-- Top bar -->
|
||||
<div class="top-bar">
|
||||
<div class="top-bar-left">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<div class="top-bar-left" data-tauri-drag-region="false">
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ChevronLeft size={20} />
|
||||
</button>
|
||||
<h1 class="page-title">{$t(titleKey)}</h1>
|
||||
</div>
|
||||
<div class="top-bar-right">
|
||||
<div class="top-bar-right" data-tauri-drag-region="false">
|
||||
<div class="search-wrapper">
|
||||
<Search size={16} />
|
||||
<input
|
||||
|
||||
@@ -163,14 +163,14 @@
|
||||
|
||||
<div class="discover-playlists">
|
||||
<!-- Top bar -->
|
||||
<div class="top-bar">
|
||||
<div class="top-bar-left">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<div class="top-bar-left" data-tauri-drag-region="false">
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ChevronLeft size={20} />
|
||||
</button>
|
||||
<h1 class="page-title">{$t('discover.qobuzPlaylists')}</h1>
|
||||
</div>
|
||||
<div class="top-bar-right">
|
||||
<div class="top-bar-right" data-tauri-drag-region="false">
|
||||
<div class="search-wrapper">
|
||||
<Search size={16} />
|
||||
<input
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="playlist-header">
|
||||
<div class="playlist-header" data-tauri-drag-region="deep">
|
||||
<div class="artwork-container">
|
||||
<div class="artwork artwork-daily"></div>
|
||||
</div>
|
||||
@@ -594,7 +594,7 @@
|
||||
<div class="empty">{$t('qobuzMixes.result.empty')}</div>
|
||||
{:else if result}
|
||||
<div class="track-list">
|
||||
<div class="track-list-header">
|
||||
<div class="track-list-header" data-tauri-drag-region="deep">
|
||||
{#if multiSelectMode}
|
||||
<div class="col-select-all">
|
||||
<input
|
||||
|
||||
@@ -442,7 +442,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="playlist-header">
|
||||
<div class="playlist-header" data-tauri-drag-region="deep">
|
||||
<div class="artwork-container">
|
||||
<div class="artwork artwork-favq"></div>
|
||||
</div>
|
||||
@@ -525,7 +525,7 @@
|
||||
<div class="empty">{$t('qobuzMixes.result.empty')}</div>
|
||||
{:else if tracks.length > 0}
|
||||
<div class="track-list">
|
||||
<div class="track-list-header">
|
||||
<div class="track-list-header" data-tauri-drag-region="deep">
|
||||
{#if multiSelectMode}
|
||||
<div class="col-select-all">
|
||||
<input
|
||||
|
||||
@@ -1439,7 +1439,7 @@
|
||||
|
||||
<ViewTransition duration={200} distance={12} direction="down">
|
||||
<div class="favorites-view" class:no-outer-scroll={(activeTab === 'tracks' && !loading && filteredTracks.length > 0) || (activeTab === 'albums' && !loading && filteredAlbums.length > 0) || (activeTab === 'artists' && !loading && filteredArtists.length > 0)} bind:this={scrollContainer} onscroll={handleFavoritesScroll}>
|
||||
<div class="top-bar">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
{#if onBack}
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
@@ -1451,10 +1451,10 @@
|
||||
</button>
|
||||
</div>
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header" data-tauri-drag-region="deep">
|
||||
<h1>{$t('favorites.title')}</h1>
|
||||
{#if activeTab === 'tracks' && !loading && filteredTracks.length > 0}
|
||||
<div class="header-actions">
|
||||
<div class="header-actions" data-tauri-drag-region="false">
|
||||
<button class="action-btn-circle" onclick={handlePlayAllTracks} title={$t('actions.playAll')}>
|
||||
<Play size={18} fill="currentColor" color="currentColor" />
|
||||
</button>
|
||||
@@ -1483,20 +1483,20 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if activeTab === 'albums' && !loading && filteredAlbums.length > 0}
|
||||
<div class="header-actions">
|
||||
<div class="header-actions" data-tauri-drag-region="false">
|
||||
<button class="action-btn-circle" onclick={handleRandomAlbum} title={$t('favorites.randomAlbum')}>
|
||||
<Shuffle size={18} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if activeTab === 'artists' && !loading && filteredArtists.length > 0}
|
||||
<div class="header-actions">
|
||||
<div class="header-actions" data-tauri-drag-region="false">
|
||||
<button class="action-btn-circle" onclick={handleRandomArtist} title={$t('favorites.randomArtist')}>
|
||||
<Shuffle size={18} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="header-search">
|
||||
<div class="header-search" data-tauri-drag-region="false">
|
||||
{#if !searchExpanded}
|
||||
<button class="search-icon-btn" onclick={() => searchExpanded = true} title={$t('nav.search')}>
|
||||
<Search size={16} />
|
||||
@@ -1522,8 +1522,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Navigation Bar (Artist-style) -->
|
||||
<div class="favorites-nav">
|
||||
<div class="nav-left">
|
||||
<div class="favorites-nav" data-tauri-drag-region="deep">
|
||||
<div class="nav-left" data-tauri-drag-region="false">
|
||||
{#each favoritesPreferences.tab_order as tab}
|
||||
{@const Icon = getTabIcon(tab as TabType)}
|
||||
<button
|
||||
@@ -1536,7 +1536,7 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<div class="nav-right" data-tauri-drag-region="false">
|
||||
<span class="results-count">
|
||||
{#if activeTab === 'tracks'}
|
||||
{filteredTracks.length}{trackSearch ? ` / ${favoriteTracks.length}` : ''} {$t('favorites.tracks').toLowerCase()}
|
||||
|
||||
@@ -345,7 +345,7 @@
|
||||
|
||||
<div class="label-view" onscroll={handleScroll}>
|
||||
<!-- Back button -->
|
||||
<div class="top-bar">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
<span>{$t('actions.back')}</span>
|
||||
@@ -353,7 +353,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="label-header">
|
||||
<header class="label-header" data-tauri-drag-region="deep">
|
||||
<div class="label-image-wrapper">
|
||||
{#if getLabelImage()}
|
||||
<img src={getLabelImage()} alt={label?.name || labelName} class="label-image" loading="lazy" decoding="async" />
|
||||
|
||||
@@ -862,7 +862,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="label-detail-view" bind:this={labelDetailEl}>
|
||||
<div class="label-detail-view" bind:this={labelDetailEl} data-tauri-drag-region>
|
||||
{#if loading}
|
||||
<div class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
@@ -882,7 +882,7 @@
|
||||
</button>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="label-header section-anchor" bind:this={headerSection}>
|
||||
<header class="label-header section-anchor" bind:this={headerSection} data-tauri-drag-region="deep">
|
||||
<div class="label-image-wrapper">
|
||||
{#if getLabelImage(pageData)}
|
||||
<img
|
||||
|
||||
@@ -5740,7 +5740,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Main Library View -->
|
||||
<div class="header">
|
||||
<div class="header" data-tauri-drag-region="deep">
|
||||
<div class="header-content">
|
||||
<h1>{$t('library.title')}</h1>
|
||||
{#if stats}
|
||||
@@ -5753,7 +5753,7 @@
|
||||
<p class="subtitle">{$t('library.yourCollection')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div class="header-actions" data-tauri-drag-region="false">
|
||||
{#if hasPlexConfig()}
|
||||
<button
|
||||
class="icon-btn plex-sync-btn"
|
||||
@@ -8188,7 +8188,6 @@
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.select-all-checkbox input[type='checkbox'] {
|
||||
@@ -9288,9 +9287,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* Block selection bleed on the handle itself; the global override
|
||||
on <body> during drag handles the rest of the page. */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-sidebar-resize-handle:hover,
|
||||
|
||||
@@ -1412,7 +1412,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Header -->
|
||||
<header class="detail-header">
|
||||
<header class="detail-header" data-tauri-drag-region="deep">
|
||||
{#if onBack}
|
||||
<button class="back-btn" onclick={() => onBack()}>
|
||||
<ArrowLeft size={16} />
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
<span>{$t('actions.back')}</span>
|
||||
</button>
|
||||
{/if}
|
||||
<header class="view-header">
|
||||
<header class="view-header" data-tauri-drag-region="deep">
|
||||
<h1>{$t('mixtapes.nav')}</h1>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
<div class="musician-page">
|
||||
<!-- Header -->
|
||||
<header class="page-header">
|
||||
<header class="page-header" data-tauri-drag-region="deep">
|
||||
<button class="back-btn" onclick={onBack} title={ $t('actions.back') }>
|
||||
<ArrowLeft size={20} />
|
||||
</button>
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
</script>
|
||||
|
||||
<div class="offline-cache-manager">
|
||||
<header class="ocm-header">
|
||||
<header class="ocm-header" data-tauri-drag-region="deep">
|
||||
<button type="button" class="back-btn" onclick={onBack} aria-label={$t('actions.back')}>
|
||||
<ArrowLeft size={18} />
|
||||
</button>
|
||||
|
||||
@@ -2420,7 +2420,7 @@
|
||||
{#if playlist.description}
|
||||
<p class="playlist-description">{@html sanitizeHtml(playlist.description)}</p>
|
||||
{/if}
|
||||
<div class="playlist-info">
|
||||
<div class="playlist-info selectable">
|
||||
<span class="owner">{playlist.owner.name}</span>
|
||||
<span class="separator">•</span>
|
||||
<span>{totalTrackCount} {$t('playlist.tracks')}{#if hasLocalTracks} <span class="local-count">({localTracks.length} local)</span>{/if}</span>
|
||||
@@ -2518,9 +2518,9 @@
|
||||
tall as the virtualized content, so the header pins for the whole
|
||||
scroll range. -->
|
||||
<div class="track-list" bind:this={trackListEl}>
|
||||
<div class="tracks-sticky-header">
|
||||
<div class="tracks-sticky-header" data-tauri-drag-region="deep">
|
||||
<!-- Track List Controls -->
|
||||
<div class="track-controls">
|
||||
<div class="track-controls" data-tauri-drag-region="false">
|
||||
<!-- Search -->
|
||||
<div class="search-container">
|
||||
<Search size={16} class="search-icon" />
|
||||
@@ -3450,7 +3450,6 @@
|
||||
.track-row-wrapper.unavailable {
|
||||
opacity: 0.5;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.track-row-wrapper.unavailable :global(.track-row) {
|
||||
|
||||
@@ -890,14 +890,14 @@
|
||||
<span>{$t('actions.back')}</span>
|
||||
</button>
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header" data-tauri-drag-region="deep">
|
||||
<h1>{$t('playlistManager.heading')}</h1>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb Navigation (when inside a folder, only in folder mode and not tree) -->
|
||||
{#if folderMode && viewMode !== 'tree' && currentFolderId && currentFolder}
|
||||
{@const breadcrumbFolder = currentFolder}
|
||||
<div class="breadcrumb">
|
||||
<div class="breadcrumb" data-tauri-drag-region="deep">
|
||||
<button class="breadcrumb-item" onclick={navigateToRoot}>
|
||||
{$t('playlist.allPlaylists')}
|
||||
</button>
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
</script>
|
||||
|
||||
<ViewTransition duration={200} distance={12} direction="up">
|
||||
<div class="purchase-album-detail">
|
||||
<div class="purchase-album-detail" data-tauri-drag-region>
|
||||
<!-- Back Navigation -->
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
@@ -235,7 +235,7 @@
|
||||
</div>
|
||||
{:else if album}
|
||||
<!-- Album Header -->
|
||||
<div class="album-header">
|
||||
<div class="album-header" data-tauri-drag-region="deep">
|
||||
<div class="artwork" class:unavailable={!album.downloadable}>
|
||||
<img src={getQobuzImage(album.image)} alt={album.title} />
|
||||
{#if !album.downloadable}
|
||||
|
||||
@@ -502,7 +502,7 @@
|
||||
|
||||
<div class="purchases-view">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header" data-tauri-drag-region="deep">
|
||||
<div class="header-icon">
|
||||
<ShoppingBag size={32} color="var(--accent-primary)" />
|
||||
</div>
|
||||
@@ -521,8 +521,8 @@
|
||||
{/if}
|
||||
|
||||
<!-- Sticky Navigation Bar -->
|
||||
<div class="purchases-nav">
|
||||
<div class="nav-left">
|
||||
<div class="purchases-nav" data-tauri-drag-region="deep">
|
||||
<div class="nav-left" data-tauri-drag-region="false">
|
||||
<button
|
||||
class="nav-link"
|
||||
class:active={activeTab === 'albums'}
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
</script>
|
||||
|
||||
<div class="release-watch">
|
||||
<div class="top-bar">
|
||||
<div class="top-bar" data-tauri-drag-region="deep">
|
||||
<button class="back-btn" onclick={onBack} title={$t('actions.back')}>
|
||||
<ChevronLeft size={20} />
|
||||
</button>
|
||||
@@ -169,7 +169,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tabs" data-tauri-drag-region="deep">
|
||||
{#each tabs as tab}
|
||||
{@const Icon = tab.icon}
|
||||
<button
|
||||
|
||||
@@ -1136,9 +1136,9 @@
|
||||
{/if}
|
||||
|
||||
<!-- Sticky Header Container -->
|
||||
<div class="sticky-header" class:scrolled={isScrolled}>
|
||||
<div class="sticky-header" class:scrolled={isScrolled} data-tauri-drag-region="deep">
|
||||
{#if !searchInTitlebar}
|
||||
<div class="search-input-container" class:compact={isScrolled}>
|
||||
<div class="search-input-container" class:compact={isScrolled} data-tauri-drag-region="false">
|
||||
<Search size={isScrolled ? 18 : 20} class="search-icon" />
|
||||
<input
|
||||
type="text"
|
||||
@@ -1157,7 +1157,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Tabs and Filters Row -->
|
||||
<div class="tabs-row">
|
||||
<div class="tabs-row" data-tauri-drag-region="false">
|
||||
<div class="tabs">
|
||||
<button
|
||||
class="tab"
|
||||
|
||||
@@ -4181,7 +4181,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header" data-tauri-drag-region="deep">
|
||||
{#if onBack}
|
||||
<button class="back-btn" onclick={onBack}>
|
||||
<ArrowLeft size={16} />
|
||||
@@ -7157,7 +7157,6 @@ flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.discord-rpc-override > summary::-webkit-details-marker {
|
||||
|
||||
@@ -570,7 +570,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="playlist-header">
|
||||
<div class="playlist-header" data-tauri-drag-region="deep">
|
||||
<div class="artwork-container">
|
||||
<div class="artwork artwork-topq"></div>
|
||||
</div>
|
||||
@@ -653,7 +653,7 @@
|
||||
<div class="empty">{$t('qobuzMixes.result.empty')}</div>
|
||||
{:else if result}
|
||||
<div class="track-list">
|
||||
<div class="track-list-header">
|
||||
<div class="track-list-header" data-tauri-drag-region="deep">
|
||||
{#if multiSelectMode}
|
||||
<div class="col-select-all">
|
||||
<input
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="playlist-header">
|
||||
<div class="playlist-header" data-tauri-drag-region="deep">
|
||||
<div class="artwork-container">
|
||||
<div class="artwork artwork-weekly"></div>
|
||||
</div>
|
||||
@@ -589,7 +589,7 @@
|
||||
<div class="empty">{$t('qobuzMixes.result.empty')}</div>
|
||||
{:else if result}
|
||||
<div class="track-list">
|
||||
<div class="track-list-header">
|
||||
<div class="track-list-header" data-tauri-drag-region="deep">
|
||||
{#if multiSelectMode}
|
||||
<div class="col-select-all">
|
||||
<input
|
||||
|
||||
@@ -251,7 +251,5 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 8px 16px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -249,7 +249,5 @@
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -364,8 +364,8 @@
|
||||
</script>
|
||||
|
||||
<div class="discovery">
|
||||
<div class="toolbar">
|
||||
<div class="tabs">
|
||||
<div class="toolbar" data-tauri-drag-region>
|
||||
<div class="tabs" data-tauri-drag-region="false">
|
||||
{#each tabs as tab (tab.id)}
|
||||
<button
|
||||
class="tab"
|
||||
@@ -377,7 +377,7 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="genre-slot">
|
||||
<div class="genre-slot" data-tauri-drag-region="false">
|
||||
<GenreFilterButton
|
||||
onFilterChange={handleGenreFilterChange}
|
||||
context="home"
|
||||
|
||||
+9
-26
@@ -5910,10 +5910,15 @@
|
||||
class:match-chrome={matchSystemChrome && showTitleBar && windowTransparent}
|
||||
style="--chrome-radius: {chromeRadiusPx}px;"
|
||||
>
|
||||
<!-- macOS: drag region for window movement (overlay title bar has no native drag area) -->
|
||||
{#if !showTitleBar && platform === 'macos'}
|
||||
<div class="macos-drag-region" data-tauri-drag-region></div>
|
||||
{/if}
|
||||
<!--
|
||||
macOS overlay title bar: drag handling lives on the existing
|
||||
.sidebar element. It already has a 32px top padding band (to
|
||||
clear the traffic lights) — that's the area we reuse as the
|
||||
window drag surface, by tagging the <aside> as a drag region
|
||||
and tagging each of its direct children as no-drag. No new
|
||||
elements added, no spacing added — just attributes.
|
||||
-->
|
||||
|
||||
<!-- Custom Title Bar (CSD) -->
|
||||
{#if showTitleBar}
|
||||
<TitleBar
|
||||
@@ -7382,28 +7387,6 @@
|
||||
height: calc(100vh - var(--player-bar-height, 104px));
|
||||
}
|
||||
|
||||
/* macOS: pad main content to clear native overlay title bar */
|
||||
:global(html.macos) .main-content {
|
||||
padding-top: 16px;
|
||||
height: calc(100vh - 104px - 16px);
|
||||
}
|
||||
|
||||
/* macOS: home view handles its own spacing */
|
||||
:global(html.macos) .main-content :global(.home-view) {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
/* macOS: invisible drag region for window movement (overlay title bar) */
|
||||
:global(html.macos) .macos-drag-region {
|
||||
height: 28px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.view-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in new issue
Block a user