mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
d6f6e6466c
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
/**
|
||
* Creates a slim, themed drag ghost and registers it via setDragImage.
|
||
* The element is cleaned up when the drag completes (dragend).
|
||
*
|
||
* On Linux (WebKitGTK), the GTK drag subsystem captures the drag image
|
||
* asynchronously — removing the element in the same tick or via
|
||
* setTimeout(…, 0) causes the image to be gone before GTK reads it,
|
||
* which makes GTK treat the entire drag as invalid (forbidden cursor).
|
||
* Using a dragend listener ensures the element persists for the full
|
||
* duration of the drag operation on all platforms.
|
||
*
|
||
* @param dataTransfer - the event's dataTransfer object
|
||
* @param label - primary text (track/album title)
|
||
* @param opts.coverUrl - optional thumbnail URL (shown as 24×24 image)
|
||
*/
|
||
export function setDragGhost(
|
||
dataTransfer: DataTransfer,
|
||
label: string,
|
||
opts: { coverUrl?: string } = {},
|
||
): void {
|
||
const el = document.createElement('div');
|
||
|
||
el.style.cssText = [
|
||
'position:fixed',
|
||
'left:-9999px',
|
||
'top:-9999px',
|
||
'display:flex',
|
||
'align-items:center',
|
||
'gap:8px',
|
||
`padding:0 14px 0 ${opts.coverUrl ? '6px' : '10px'}`,
|
||
'height:34px',
|
||
'max-width:240px',
|
||
'border-radius:17px',
|
||
'background:var(--bg-card,#fff)',
|
||
'border:1px solid var(--border,rgba(0,0,0,.12))',
|
||
'border-left:3px solid var(--accent,#888)',
|
||
'box-shadow:0 4px 20px rgba(0,0,0,.22)',
|
||
'font-family:var(--font-ui,sans-serif)',
|
||
'font-size:13px',
|
||
'font-weight:500',
|
||
'color:var(--text-primary,#222)',
|
||
'pointer-events:none',
|
||
'white-space:nowrap',
|
||
'overflow:hidden',
|
||
'z-index:99999',
|
||
].join(';');
|
||
|
||
if (opts.coverUrl) {
|
||
const img = document.createElement('img');
|
||
img.src = opts.coverUrl;
|
||
img.style.cssText = 'width:24px;height:24px;border-radius:4px;object-fit:cover;flex-shrink:0;';
|
||
el.appendChild(img);
|
||
} else {
|
||
const dot = document.createElement('span');
|
||
dot.style.cssText = 'width:6px;height:6px;border-radius:50%;background:var(--accent,#888);flex-shrink:0;';
|
||
el.appendChild(dot);
|
||
}
|
||
|
||
const text = document.createElement('span');
|
||
text.textContent = label;
|
||
text.style.cssText = 'overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0;';
|
||
el.appendChild(text);
|
||
|
||
document.body.appendChild(el);
|
||
dataTransfer.setDragImage(el, 20, 17);
|
||
|
||
// Clean up the ghost element when the drag ends, not immediately.
|
||
// This is critical for Linux/WebKitGTK where the drag image is
|
||
// captured asynchronously by GTK — removing it sooner causes
|
||
// the "forbidden" cursor and blocks the entire drop operation.
|
||
const cleanup = () => {
|
||
el.remove();
|
||
document.removeEventListener('dragend', cleanup);
|
||
};
|
||
document.addEventListener('dragend', cleanup, { once: true });
|
||
}
|