From d44c33dd69fc418fc49e5609fb80c6e324cef414 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Wed, 26 Nov 2025 14:02:56 +0100 Subject: [PATCH] feat: Introduce `useCardPointer` for card-specific pointer events, add spatial capabilities to `LayoutSystem` --- frontend/src/desktop/DesktopWorkspace.tsx | 31 +++++++++++++++------ frontend/src/desktop/LayoutSystem.ts | 33 +++++++++++++++++++++++ frontend/src/desktop/useCardPointer.ts | 23 ++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 frontend/src/desktop/useCardPointer.ts diff --git a/frontend/src/desktop/DesktopWorkspace.tsx b/frontend/src/desktop/DesktopWorkspace.tsx index 10ff54f..24d07a9 100644 --- a/frontend/src/desktop/DesktopWorkspace.tsx +++ b/frontend/src/desktop/DesktopWorkspace.tsx @@ -10,6 +10,7 @@ import PreviewZoomOverlay from '../detail/PreviewZoomOverlay'; import DesktopDocumentCard from './DesktopDocumentCard'; import usePreviewMetadata from './hooks/usePreviewMetadata'; import useDeskTagInteractions from './tags/useDeskTagInteractions'; +import { useCardPointer } from './useCardPointer'; import '../styles/workspace/workspace-layout.css'; import '../styles/workspace/workspace-items.css'; import '../styles/workspace/workspace-cards.css'; @@ -54,6 +55,21 @@ export interface DesktopWorkspaceProps { onDocumentTagDrop?: (docId: Identifier, tag: any) => void; } +// Wrapper to handle hooks per card +const DesktopDocumentContainer: React.FC> = React.memo((props) => { + const { layoutCard, selected } = props; + // We assume layoutCard is always present in this context + const cardPointerHandlers = useCardPointer(layoutCard!, !!selected); + + return ( + + ); +}); +DesktopDocumentContainer.displayName = 'DesktopDocumentContainer'; + const DesktopWorkspace: React.FC = ({ entries, ensureAssetUrl, @@ -149,18 +165,18 @@ const DesktopWorkspace: React.FC = ({ <>
{ - if (e.target === e.currentTarget) { - onClearSelection(); - focusShell(); - } - }} >
{ + if (e.target === e.currentTarget) { + onClearSelection(); + focusShell(); + } + }} > {items.map((doc, index) => { const docId = doc.id ? String(doc.id) : `temp-${index}`; @@ -173,7 +189,7 @@ const DesktopWorkspace: React.FC = ({ }); return ( - = ({ ensureAssetUrl={ensureAssetUrl} getDocumentAsset={getDocumentAsset} handleNavigatorSnapshot={() => { }} - cardPointerHandlers={undefined} onDocumentActivate={onDocumentActivate} layoutCard={layoutCard} onTagDragEnter={tagInteractions.handleTagDragEnterDoc} diff --git a/frontend/src/desktop/LayoutSystem.ts b/frontend/src/desktop/LayoutSystem.ts index 16b9f9e..a42ae1e 100644 --- a/frontend/src/desktop/LayoutSystem.ts +++ b/frontend/src/desktop/LayoutSystem.ts @@ -20,10 +20,14 @@ export class LayoutCard implements LayoutCardState { height: number = 0; ref: HTMLElement | null = null; + private _innerRadius: number = 0; + private _outerRadius: number = 0; + constructor(id: string, initialData: Partial = {}, ref: HTMLElement | null = null) { this.id = id; Object.assign(this, initialData); this.ref = ref; + this.recalculateRadii(); } setRef(ref: HTMLElement | null) { @@ -33,9 +37,17 @@ export class LayoutCard implements LayoutCardState { update(changes: Partial) { Object.assign(this, changes); + if (changes.width !== undefined || changes.height !== undefined) { + this.recalculateRadii(); + } this.applyTransform(); } + private recalculateRadii() { + this._innerRadius = Math.min(this.width, this.height) / 2; + this._outerRadius = Math.sqrt(this.width * this.width + this.height * this.height) / 2; + } + private applyTransform() { if (this.ref) { this.ref.style.transform = @@ -57,6 +69,14 @@ export class LayoutCard implements LayoutCardState { height: this.height }; } + + get innerRadius(): number { + return this._innerRadius; + } + + get outerRadius(): number { + return this._outerRadius; + } } export class LayoutStore { @@ -117,6 +137,19 @@ export class LayoutStore { this.update(id, { z: ++this.zCounter }); } + getCardsInCircle(x: number, y: number, radius: number): LayoutCard[] { + const result: LayoutCard[] = []; + for (const card of this.items.values()) { + const dx = card.x - x; + const dy = card.y - y; + const distance = Math.sqrt(dx * dx + dy * dy); + if (distance < radius + card.outerRadius) { + result.push(card); + } + } + return result; + } + getSnapshot() { return Array.from(this.items.values()).map(card => card.toSnapshot()); } diff --git a/frontend/src/desktop/useCardPointer.ts b/frontend/src/desktop/useCardPointer.ts new file mode 100644 index 0000000..8ec043f --- /dev/null +++ b/frontend/src/desktop/useCardPointer.ts @@ -0,0 +1,23 @@ +import React, { useCallback } from 'react'; + +import { LayoutCard } from './LayoutSystem'; + +export const useCardPointer = (card: LayoutCard, isSelected: boolean) => { + const onPointerDown = useCallback((e: React.PointerEvent) => { + console.log('Pointer down on card', card.id, card.rotation, card.z, isSelected, e); + }, [card, isSelected]); + + const onPointerMove = useCallback((e: React.PointerEvent) => { + console.log('Pointer move on card', card.id, card.rotation, e); + }, [card]); + + const onPointerUp = useCallback((e: React.PointerEvent) => { + console.log('Pointer up on card', card.id, card.rotation, e); + }, [card]); + + return { + onPointerDown, + onPointerMove, + onPointerUp, + }; +};