145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
/**
|
|
* Shared drag-and-drop coordination for track items.
|
|
*
|
|
* Uses the HTML5 Drag and Drop API with a custom MIME type so that
|
|
* drag sources and drop targets across different shadow roots can
|
|
* communicate. A global custom event ("yj-drag-active") is
|
|
* dispatched on `document` so that non-participating components
|
|
* (sidebar, queue button) can react to the drag lifecycle.
|
|
*/
|
|
|
|
/** MIME type used in dataTransfer for in-app track drags. */
|
|
export const DRAG_MIME = 'application/x-yj-tracks';
|
|
|
|
/** Sources that can originate a drag. */
|
|
export type DragSource =
|
|
| 'track-list'
|
|
| 'cover-grid'
|
|
| 'queue'
|
|
| 'playlist';
|
|
|
|
/** Serialized payload stored in dataTransfer. */
|
|
export interface DragPayload {
|
|
filePaths: string[];
|
|
source: DragSource;
|
|
sourcePlaylistId?: number;
|
|
}
|
|
|
|
// =====================================================================
|
|
// Active drag source tracking
|
|
// =====================================================================
|
|
|
|
let activeDragSource: DragSource | null = null;
|
|
let activeDragPlaylistId: number | undefined;
|
|
|
|
/** Return the source of the in-progress drag, or null. */
|
|
export function getActiveDragSource(): DragSource | null {
|
|
return activeDragSource;
|
|
}
|
|
|
|
/** Return the playlist ID of the in-progress drag, if any. */
|
|
export function getActiveDragPlaylistId():
|
|
| number
|
|
| undefined {
|
|
return activeDragPlaylistId;
|
|
}
|
|
|
|
// =====================================================================
|
|
// Global drag-active event
|
|
// =====================================================================
|
|
|
|
export interface DragActiveDetail {
|
|
active: boolean;
|
|
}
|
|
|
|
/**
|
|
* Notify the entire document that a track drag has started or ended.
|
|
* Non-participating components listen for this to show/hide drop
|
|
* affordances (e.g. sidebar hover-to-navigate, queue button glow).
|
|
*/
|
|
export function emitDragActive(active: boolean): void {
|
|
if (!active) {
|
|
activeDragSource = null;
|
|
activeDragPlaylistId = undefined;
|
|
}
|
|
|
|
document.dispatchEvent(
|
|
new CustomEvent<DragActiveDetail>(
|
|
'yj-drag-active',
|
|
{
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: { active },
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Helpers for drag sources
|
|
// =====================================================================
|
|
|
|
/**
|
|
* Populate a DragEvent's dataTransfer with the standard payload.
|
|
* Returns false if dataTransfer is unavailable.
|
|
*/
|
|
export function setDragPayload(
|
|
e: DragEvent,
|
|
payload: DragPayload,
|
|
): boolean {
|
|
if (!e.dataTransfer) return false;
|
|
|
|
activeDragSource = payload.source;
|
|
activeDragPlaylistId = payload.sourcePlaylistId;
|
|
|
|
e.dataTransfer.effectAllowed = 'copyMove';
|
|
e.dataTransfer.setData(
|
|
DRAG_MIME,
|
|
JSON.stringify(payload),
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
// =====================================================================
|
|
// Helpers for drop targets
|
|
// =====================================================================
|
|
|
|
/** Check whether a dragover event carries our custom MIME type. */
|
|
export function hasTrackPayload(e: DragEvent): boolean {
|
|
return (
|
|
e.dataTransfer?.types.includes(DRAG_MIME) ?? false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extract the DragPayload from a drop event.
|
|
* Returns null if the data is missing or malformed.
|
|
*/
|
|
export function getDragPayload(
|
|
e: DragEvent,
|
|
): DragPayload | null {
|
|
const raw = e.dataTransfer?.getData(DRAG_MIME);
|
|
|
|
if (!raw) return null;
|
|
|
|
try {
|
|
const parsed: unknown = JSON.parse(raw);
|
|
|
|
if (
|
|
typeof parsed === 'object' &&
|
|
parsed !== null &&
|
|
'filePaths' in parsed &&
|
|
Array.isArray(
|
|
(parsed as DragPayload).filePaths,
|
|
)
|
|
) {
|
|
return parsed as DragPayload;
|
|
}
|
|
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|