Files
psysonic/src/hooks/useGlobalDndAndSelectionBlockers.ts
T
Frank Stellmacher b591a1cb5f refactor(app-shell): I.1 — split AppShell.tsx 691 → 248 LOC across 12 files (#671)
* refactor(app-shell): extract appShellHelpers.ts

Move SIDEBAR_COLLAPSED_STORAGE_KEY + read/persist helpers and the
shouldSuppressQueueResizerMouseDown geometry helper out of AppShell.tsx
into utils/appShellHelpers.ts.

AppShell.tsx: 691 → 639 LOC.

* refactor(app-shell): extract usePlatformShellSetup hook

Bundle the one-shot platform/window-shell effects (tiling-WM detection,
no-compositing class, data-platform attr, custom titlebar sync, kinetic
scroll toggle, logging mode push) into hooks/usePlatformShellSetup.ts.
Returns isTilingWm so AppShell can still gate the custom titlebar.

AppShell.tsx: 639 → 604 LOC.

* refactor(app-shell): extract 5 lifecycle hooks

Pull these effect islands into hooks/:
- useOrbitBodyAttrs       — orbit role/phase → <html data-orbit-*> attrs
- useWindowFullscreenState — Tauri isFullscreen() tracker
- useNowPlayingTrayTitle  — title + tray tooltip sync
- useTrayMenuI18n         — tray menu labels via i18n
- useServerCapabilitiesProbe — music folders + rating support + orbit
                              orphan sweep on login

AppShell.tsx: 604 → 497 LOC.

* refactor(app-shell): extract useQueueResizer hook

Bundle queueWidth state, drag listeners, sidebar-aligned handle position,
and the click-vs-drag mousedown handler into hooks/useQueueResizer.ts.
AppShell drops shouldSuppressQueueResizerMouseDown wiring and 4 local
state pieces.

AppShell.tsx: 497 → 403 LOC.

* refactor(app-shell): extract 4 misc lifecycle hooks

- useGlobalDndAndSelectionBlockers — document-level DnD/select-all/
  selectstart blockers (Linux/WebKitGTK + Wayland workarounds).
- useAppActivityTracking — <html data-app-hidden> + <html data-app-blurred>
  so CSS can pause cosmetic animation when the app isn't being looked at.
- useMainScrollingIndicator — scroll-idle tracker for main + np viewports.
- useOfflineAutoNav — connStatus transitions push to /offline or back.

AppShell.tsx: 403 → 282 LOC.

* refactor(app-shell): extract AppShellQueueResizerSeam subcomponent

The 6px resizer strip and the round resize/toggle handle (~50 LOC of JSX
with embedded scrollbar-collision suppression + self-heal logic) move
into components/AppShellQueueResizerSeam.tsx. Desktop-only.

AppShell.tsx: 282 → 248 LOC.
2026-05-13 23:48:03 +02:00

66 lines
2.6 KiB
TypeScript

import { useEffect } from 'react';
/**
* Globally tame Linux/WebKitGTK + Wayland behaviour that the page-level
* CSS / Tauri config can't reach:
*
* - dragover/dragenter: WebKitGTK shows a permanent "forbidden" cursor
* for external drops unless we preventDefault and force dropEffect.
* - drop on document: block so an OS-file-manager drag doesn't navigate
* the webview away from the app.
* - dragstart (capture): cancel native drags from in-page elements (e.g.
* SVG grips) — on Wayland these can leave a stuck GTK drag-proxy.
* In-app moves go through psy-drag (mouse events), unaffected.
* - Ctrl/Cmd+A: WebKit ignores `user-select: none` for keyboard
* shortcuts. Still allow select-all inside real text fields.
* - selectstart: same story for mouse-drag selection. Allow inside
* inputs / textareas / contentEditable and explicit `[data-selectable]`
* regions (cover info, etc.).
*
* Harmless on Windows/macOS.
*/
export function useGlobalDndAndSelectionBlockers(): void {
useEffect(() => {
const allow = (e: DragEvent) => {
e.preventDefault();
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
};
const blockDrop = (e: DragEvent) => { e.preventDefault(); };
const blockSelectAll = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return;
e.preventDefault();
}
};
const blockSelectStart = (e: Event) => {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return;
if ((target as HTMLElement).closest('[data-selectable]')) return;
e.preventDefault();
};
const blockDragStart = (e: DragEvent) => {
e.preventDefault();
};
document.addEventListener('dragover', allow);
document.addEventListener('dragenter', allow);
document.addEventListener('drop', blockDrop);
document.addEventListener('dragstart', blockDragStart, true);
document.addEventListener('keydown', blockSelectAll, true);
document.addEventListener('selectstart', blockSelectStart);
return () => {
document.removeEventListener('dragover', allow);
document.removeEventListener('dragenter', allow);
document.removeEventListener('drop', blockDrop);
document.removeEventListener('dragstart', blockDragStart, true);
document.removeEventListener('keydown', blockSelectAll, true);
document.removeEventListener('selectstart', blockSelectStart);
};
}, []);
}