cover grid refactor
-split component into several files
This commit is contained in:
@@ -24,7 +24,6 @@ import { PlaylistController } from '@store/controllers/playlist-controller';
|
||||
import { SearchController } from '@store/controllers/search-controller';
|
||||
import '@components/track-info/track-info';
|
||||
import '@components/playlist-picker/playlist-picker.js';
|
||||
import type { PlaylistPicker } from '@components/playlist-picker/playlist-picker.js';
|
||||
import { SelectionController } from '@utils/selection-controller';
|
||||
import type { SelectionHost } from '@utils/selection-controller';
|
||||
import {
|
||||
@@ -41,6 +40,9 @@ import {
|
||||
removeDragImage,
|
||||
} from '@utils/drag-image';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import { ContextMenuController } from '@utils/context-menu-controller.js';
|
||||
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||
import { contextMenuStyles } from '@utils/context-menu-controller.js';
|
||||
import '@components/track-details/track-details.js';
|
||||
import type { TrackDetails } from '@components/track-details/track-details.js';
|
||||
import type { CoverArtUrls } from '@components/track-details/track-details.js';
|
||||
@@ -56,12 +58,23 @@ interface PlaylistEntry {
|
||||
@customElement('playlist-view')
|
||||
export class PlaylistView
|
||||
extends LitElement
|
||||
implements SelectionHost
|
||||
implements SelectionHost, ContextMenuHost
|
||||
{
|
||||
private player = new PlayerController(this);
|
||||
private playlistCtrl = new PlaylistController(this);
|
||||
private searchCtrl = new SearchController(this);
|
||||
private selection = new SelectionController(this);
|
||||
private ctxMenu = new ContextMenuController(this);
|
||||
|
||||
getContextMenuPopup(): HTMLElement | undefined {
|
||||
return this.contextMenuPopup;
|
||||
}
|
||||
|
||||
getPlaylistSubmenuPopup():
|
||||
| HTMLElement
|
||||
| undefined {
|
||||
return this.playlistSubmenuPopup;
|
||||
}
|
||||
private cancelScanComplete?: () => void;
|
||||
private scrollDebounceTimer: ReturnType<
|
||||
typeof setTimeout
|
||||
@@ -162,8 +175,6 @@ export class PlaylistView
|
||||
@state() private refreshing = false;
|
||||
@state() private creating = false;
|
||||
@state() private newPlaylistName = '';
|
||||
@state() private contextMenuOpen = false;
|
||||
@state() private playlistSubmenuOpen = false;
|
||||
@state() private playlistContextMenuOpen = false;
|
||||
@state() private playlistContextMenuIndex = -1;
|
||||
@state() private renamingPlaylistIndex = -1;
|
||||
@@ -198,31 +209,23 @@ export class PlaylistView
|
||||
@query('track-details')
|
||||
private trackDetailsDialog!: TrackDetails;
|
||||
|
||||
private submenuCloseTimer: ReturnType<
|
||||
typeof setTimeout
|
||||
> | null = null;
|
||||
private closePlaylistCtxMenuHandler =
|
||||
() => this.closePlaylistContextMenu();
|
||||
|
||||
private closeContextMenuHandler = () => {
|
||||
this.closeContextMenu();
|
||||
this.closePlaylistContextMenu();
|
||||
};
|
||||
private playlistCtxMenuMousedownHandler =
|
||||
(e: MouseEvent) => {
|
||||
const plPopup =
|
||||
this.playlistContextMenuPopup;
|
||||
|
||||
private mousedownCloseHandler = (
|
||||
e: MouseEvent,
|
||||
) => {
|
||||
const path = e.composedPath();
|
||||
const popup = this.contextMenuPopup;
|
||||
const submenu = this.playlistSubmenuPopup;
|
||||
const plPopup =
|
||||
this.playlistContextMenuPopup;
|
||||
if (
|
||||
plPopup &&
|
||||
e.composedPath().includes(plPopup)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (popup && path.includes(popup)) return;
|
||||
if (submenu && path.includes(submenu)) return;
|
||||
if (plPopup && path.includes(plPopup)) return;
|
||||
|
||||
this.closeContextMenu();
|
||||
this.closePlaylistContextMenu();
|
||||
};
|
||||
this.closePlaylistContextMenu();
|
||||
};
|
||||
|
||||
private clearSelectionHandler = (e: MouseEvent) => {
|
||||
const path = e.composedPath();
|
||||
@@ -321,7 +324,9 @@ export class PlaylistView
|
||||
}
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
static override styles = [
|
||||
contextMenuStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -705,43 +710,6 @@ export class PlaylistView
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#context-menu {
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.context-menu-panel {
|
||||
background-color: var(--yj-bg-elevated, #343a40);
|
||||
border: 1px solid var(--yj-border, #444);
|
||||
border-radius: 6px;
|
||||
padding: 4px 0;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.context-menu-panel wa-dropdown-item {
|
||||
cursor: pointer;
|
||||
--wa-color-text-normal: var(--yj-text-primary, #fff);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.context-menu-panel wa-dropdown-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.submenu-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.submenu-arrow {
|
||||
font-size: 10px;
|
||||
margin-left: auto;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
#playlist-submenu {
|
||||
z-index: 210;
|
||||
}
|
||||
|
||||
#playlist-context-menu {
|
||||
z-index: 200;
|
||||
}
|
||||
@@ -777,7 +745,7 @@ export class PlaylistView
|
||||
border-color: var(--yj-accent, #ffd43b);
|
||||
color: var(--yj-accent, #ffd43b);
|
||||
}
|
||||
`;
|
||||
`];
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -788,15 +756,15 @@ export class PlaylistView
|
||||
);
|
||||
document.addEventListener(
|
||||
'click',
|
||||
this.closeContextMenuHandler,
|
||||
this.closePlaylistCtxMenuHandler,
|
||||
);
|
||||
document.addEventListener(
|
||||
'contextmenu',
|
||||
this.closeContextMenuHandler,
|
||||
this.closePlaylistCtxMenuHandler,
|
||||
);
|
||||
document.addEventListener(
|
||||
'mousedown',
|
||||
this.mousedownCloseHandler,
|
||||
this.playlistCtxMenuMousedownHandler,
|
||||
);
|
||||
document.addEventListener(
|
||||
'click',
|
||||
@@ -815,15 +783,15 @@ export class PlaylistView
|
||||
|
||||
document.removeEventListener(
|
||||
'click',
|
||||
this.closeContextMenuHandler,
|
||||
this.closePlaylistCtxMenuHandler,
|
||||
);
|
||||
document.removeEventListener(
|
||||
'contextmenu',
|
||||
this.closeContextMenuHandler,
|
||||
this.closePlaylistCtxMenuHandler,
|
||||
);
|
||||
document.removeEventListener(
|
||||
'mousedown',
|
||||
this.mousedownCloseHandler,
|
||||
this.playlistCtxMenuMousedownHandler,
|
||||
);
|
||||
document.removeEventListener(
|
||||
'click',
|
||||
@@ -1040,30 +1008,7 @@ export class PlaylistView
|
||||
this.selection.handleContextMenu(
|
||||
String(trackIndex),
|
||||
);
|
||||
this.contextMenuOpen = true;
|
||||
|
||||
// Position at mouse cursor using a virtual anchor.
|
||||
this.updateComplete.then(() => {
|
||||
const popup = this.contextMenuPopup;
|
||||
|
||||
if (popup) {
|
||||
(popup as any).anchor = {
|
||||
getBoundingClientRect() {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
top: e.clientY,
|
||||
left: e.clientX,
|
||||
right: e.clientX,
|
||||
bottom: e.clientY,
|
||||
};
|
||||
},
|
||||
};
|
||||
(popup as any).active = true;
|
||||
}
|
||||
});
|
||||
this.ctxMenu.openAt(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
private onContextMenuAction(action: string) {
|
||||
@@ -1092,7 +1037,8 @@ export class PlaylistView
|
||||
break;
|
||||
}
|
||||
|
||||
this.closeContextMenu(true);
|
||||
this.selection.clear();
|
||||
this.ctxMenu.close();
|
||||
}
|
||||
|
||||
private openTrackDetails(filePath: string) {
|
||||
@@ -1451,82 +1397,7 @@ export class PlaylistView
|
||||
this.onEmptyZoneDrop(e);
|
||||
};
|
||||
|
||||
private closeContextMenu(clearSelection = false) {
|
||||
if (!this.contextMenuOpen) return;
|
||||
|
||||
this.closePlaylistSubmenu();
|
||||
this.contextMenuOpen = false;
|
||||
|
||||
if (clearSelection) {
|
||||
this.selection.clear();
|
||||
}
|
||||
|
||||
const popup = this.contextMenuPopup;
|
||||
|
||||
if (popup) {
|
||||
(popup as any).active = false;
|
||||
}
|
||||
}
|
||||
|
||||
private clearSubmenuCloseTimer() {
|
||||
if (this.submenuCloseTimer !== null) {
|
||||
clearTimeout(this.submenuCloseTimer);
|
||||
this.submenuCloseTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleSubmenuClose = () => {
|
||||
this.clearSubmenuCloseTimer();
|
||||
this.submenuCloseTimer = setTimeout(() => {
|
||||
this.submenuCloseTimer = null;
|
||||
this.closePlaylistSubmenu();
|
||||
}, 150);
|
||||
};
|
||||
|
||||
private async showPlaylistSubmenu() {
|
||||
this.clearSubmenuCloseTimer();
|
||||
|
||||
if (this.playlistSubmenuOpen) return;
|
||||
|
||||
this.playlistSubmenuOpen = true;
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
const submenu = this.playlistSubmenuPopup;
|
||||
const trigger =
|
||||
this.shadowRoot?.querySelector(
|
||||
'.submenu-item',
|
||||
);
|
||||
|
||||
if (submenu && trigger) {
|
||||
(submenu as any).anchor = trigger;
|
||||
(submenu as any).active = true;
|
||||
}
|
||||
|
||||
const picker = this.shadowRoot?.querySelector(
|
||||
'playlist-picker',
|
||||
) as PlaylistPicker | null;
|
||||
|
||||
picker?.reset();
|
||||
}
|
||||
|
||||
private closePlaylistSubmenu() {
|
||||
this.clearSubmenuCloseTimer();
|
||||
|
||||
if (!this.playlistSubmenuOpen) return;
|
||||
|
||||
this.playlistSubmenuOpen = false;
|
||||
|
||||
const submenu = this.playlistSubmenuPopup;
|
||||
|
||||
if (submenu) {
|
||||
(submenu as any).active = false;
|
||||
}
|
||||
}
|
||||
|
||||
private onPlaylistActionComplete = () => {
|
||||
this.closeContextMenu(true);
|
||||
};
|
||||
|
||||
private isActiveTrack(
|
||||
track: playlist.Track,
|
||||
@@ -1549,7 +1420,7 @@ export class PlaylistView
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.closeContextMenu();
|
||||
this.ctxMenu.close();
|
||||
this.playlistContextMenuIndex = index;
|
||||
this.playlistContextMenuOpen = true;
|
||||
|
||||
@@ -1854,9 +1725,10 @@ export class PlaylistView
|
||||
placement="bottom-start"
|
||||
flip
|
||||
shift
|
||||
.active=${this.contextMenuOpen}
|
||||
.active=${this.ctxMenu
|
||||
.contextMenuOpen}
|
||||
>
|
||||
${this.contextMenuOpen
|
||||
${this.ctxMenu.contextMenuOpen
|
||||
? html`
|
||||
<div class="context-menu-panel">
|
||||
<wa-dropdown-item
|
||||
@@ -1865,7 +1737,7 @@ export class PlaylistView
|
||||
'play',
|
||||
)}
|
||||
@mouseenter=${() =>
|
||||
this.closePlaylistSubmenu()}
|
||||
this.ctxMenu.closePlaylistSubmenu()}
|
||||
>
|
||||
<wa-icon
|
||||
slot="icon"
|
||||
@@ -1879,7 +1751,7 @@ export class PlaylistView
|
||||
'add-to-queue',
|
||||
)}
|
||||
@mouseenter=${() =>
|
||||
this.closePlaylistSubmenu()}
|
||||
this.ctxMenu.closePlaylistSubmenu()}
|
||||
>
|
||||
<wa-icon
|
||||
slot="icon"
|
||||
@@ -1893,7 +1765,7 @@ export class PlaylistView
|
||||
'play-next',
|
||||
)}
|
||||
@mouseenter=${() =>
|
||||
this.closePlaylistSubmenu()}
|
||||
this.ctxMenu.closePlaylistSubmenu()}
|
||||
>
|
||||
<wa-icon
|
||||
slot="icon"
|
||||
@@ -1907,7 +1779,7 @@ export class PlaylistView
|
||||
'remove',
|
||||
)}
|
||||
@mouseenter=${() =>
|
||||
this.closePlaylistSubmenu()}
|
||||
this.ctxMenu.closePlaylistSubmenu()}
|
||||
>
|
||||
<wa-icon
|
||||
slot="icon"
|
||||
@@ -1918,14 +1790,15 @@ export class PlaylistView
|
||||
<wa-dropdown-item
|
||||
class="submenu-item"
|
||||
@mouseenter=${() => {
|
||||
this.clearSubmenuCloseTimer();
|
||||
void this.showPlaylistSubmenu();
|
||||
this.ctxMenu.clearSubmenuCloseTimer();
|
||||
void this.ctxMenu.showPlaylistSubmenu(this.getSelectedFilePaths());
|
||||
}}
|
||||
@mouseleave=${this
|
||||
.ctxMenu
|
||||
.scheduleSubmenuClose}
|
||||
@click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
void this.showPlaylistSubmenu();
|
||||
void this.ctxMenu.showPlaylistSubmenu(this.getSelectedFilePaths());
|
||||
}}
|
||||
>
|
||||
<wa-icon
|
||||
@@ -1948,7 +1821,7 @@ export class PlaylistView
|
||||
'track-details',
|
||||
)}
|
||||
@mouseenter=${() =>
|
||||
this.closePlaylistSubmenu()}
|
||||
this.ctxMenu.closePlaylistSubmenu()}
|
||||
>
|
||||
<wa-icon
|
||||
slot="icon"
|
||||
@@ -1969,20 +1842,23 @@ export class PlaylistView
|
||||
placement="right-start"
|
||||
flip
|
||||
shift
|
||||
.active=${this.playlistSubmenuOpen}
|
||||
.active=${this.ctxMenu
|
||||
.playlistSubmenuOpen}
|
||||
>
|
||||
${this.playlistSubmenuOpen &&
|
||||
${this.ctxMenu.playlistSubmenuOpen &&
|
||||
this.selection.hasSelection
|
||||
? html`
|
||||
<div
|
||||
@mouseenter=${() =>
|
||||
this.clearSubmenuCloseTimer()}
|
||||
this.ctxMenu.clearSubmenuCloseTimer()}
|
||||
@mouseleave=${this
|
||||
.ctxMenu
|
||||
.scheduleSubmenuClose}
|
||||
>
|
||||
<playlist-picker
|
||||
.filePaths=${this.getSelectedFilePaths()}
|
||||
@playlist-action-complete=${this
|
||||
.ctxMenu
|
||||
.onPlaylistActionComplete}
|
||||
@click=${(e: Event) =>
|
||||
e.stopPropagation()}
|
||||
|
||||
Reference in New Issue
Block a user