feat: Introduce useCardPointer for card-specific pointer events, add spatial capabilities to LayoutSystem
This commit is contained in:
@@ -10,6 +10,7 @@ import PreviewZoomOverlay from '../detail/PreviewZoomOverlay';
|
|||||||
import DesktopDocumentCard from './DesktopDocumentCard';
|
import DesktopDocumentCard from './DesktopDocumentCard';
|
||||||
import usePreviewMetadata from './hooks/usePreviewMetadata';
|
import usePreviewMetadata from './hooks/usePreviewMetadata';
|
||||||
import useDeskTagInteractions from './tags/useDeskTagInteractions';
|
import useDeskTagInteractions from './tags/useDeskTagInteractions';
|
||||||
|
import { useCardPointer } from './useCardPointer';
|
||||||
import '../styles/workspace/workspace-layout.css';
|
import '../styles/workspace/workspace-layout.css';
|
||||||
import '../styles/workspace/workspace-items.css';
|
import '../styles/workspace/workspace-items.css';
|
||||||
import '../styles/workspace/workspace-cards.css';
|
import '../styles/workspace/workspace-cards.css';
|
||||||
@@ -54,6 +55,21 @@ export interface DesktopWorkspaceProps {
|
|||||||
onDocumentTagDrop?: (docId: Identifier, tag: any) => void;
|
onDocumentTagDrop?: (docId: Identifier, tag: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper to handle hooks per card
|
||||||
|
const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocumentCard>> = React.memo((props) => {
|
||||||
|
const { layoutCard, selected } = props;
|
||||||
|
// We assume layoutCard is always present in this context
|
||||||
|
const cardPointerHandlers = useCardPointer(layoutCard!, !!selected);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DesktopDocumentCard
|
||||||
|
{...props}
|
||||||
|
cardPointerHandlers={cardPointerHandlers}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
DesktopDocumentContainer.displayName = 'DesktopDocumentContainer';
|
||||||
|
|
||||||
const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||||
entries,
|
entries,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
@@ -149,18 +165,18 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="desk-shell"
|
className="desk-shell"
|
||||||
onPointerDown={(e) => {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
onClearSelection();
|
|
||||||
focusShell();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="desk-canvas"
|
className="desk-canvas"
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={handleShellKeyDown}
|
onKeyDown={handleShellKeyDown}
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClearSelection();
|
||||||
|
focusShell();
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{items.map((doc, index) => {
|
{items.map((doc, index) => {
|
||||||
const docId = doc.id ? String(doc.id) : `temp-${index}`;
|
const docId = doc.id ? String(doc.id) : `temp-${index}`;
|
||||||
@@ -173,7 +189,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DesktopDocumentCard
|
<DesktopDocumentContainer
|
||||||
key={docId}
|
key={docId}
|
||||||
doc={doc}
|
doc={doc}
|
||||||
style={{
|
style={{
|
||||||
@@ -191,7 +207,6 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
ensureAssetUrl={ensureAssetUrl}
|
ensureAssetUrl={ensureAssetUrl}
|
||||||
getDocumentAsset={getDocumentAsset}
|
getDocumentAsset={getDocumentAsset}
|
||||||
handleNavigatorSnapshot={() => { }}
|
handleNavigatorSnapshot={() => { }}
|
||||||
cardPointerHandlers={undefined}
|
|
||||||
onDocumentActivate={onDocumentActivate}
|
onDocumentActivate={onDocumentActivate}
|
||||||
layoutCard={layoutCard}
|
layoutCard={layoutCard}
|
||||||
onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
|
onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ export class LayoutCard implements LayoutCardState {
|
|||||||
height: number = 0;
|
height: number = 0;
|
||||||
ref: HTMLElement | null = null;
|
ref: HTMLElement | null = null;
|
||||||
|
|
||||||
|
private _innerRadius: number = 0;
|
||||||
|
private _outerRadius: number = 0;
|
||||||
|
|
||||||
constructor(id: string, initialData: Partial<LayoutCardState> = {}, ref: HTMLElement | null = null) {
|
constructor(id: string, initialData: Partial<LayoutCardState> = {}, ref: HTMLElement | null = null) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
Object.assign(this, initialData);
|
Object.assign(this, initialData);
|
||||||
this.ref = ref;
|
this.ref = ref;
|
||||||
|
this.recalculateRadii();
|
||||||
}
|
}
|
||||||
|
|
||||||
setRef(ref: HTMLElement | null) {
|
setRef(ref: HTMLElement | null) {
|
||||||
@@ -33,9 +37,17 @@ export class LayoutCard implements LayoutCardState {
|
|||||||
|
|
||||||
update(changes: Partial<LayoutCardState>) {
|
update(changes: Partial<LayoutCardState>) {
|
||||||
Object.assign(this, changes);
|
Object.assign(this, changes);
|
||||||
|
if (changes.width !== undefined || changes.height !== undefined) {
|
||||||
|
this.recalculateRadii();
|
||||||
|
}
|
||||||
this.applyTransform();
|
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() {
|
private applyTransform() {
|
||||||
if (this.ref) {
|
if (this.ref) {
|
||||||
this.ref.style.transform =
|
this.ref.style.transform =
|
||||||
@@ -57,6 +69,14 @@ export class LayoutCard implements LayoutCardState {
|
|||||||
height: this.height
|
height: this.height
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get innerRadius(): number {
|
||||||
|
return this._innerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
get outerRadius(): number {
|
||||||
|
return this._outerRadius;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LayoutStore {
|
export class LayoutStore {
|
||||||
@@ -117,6 +137,19 @@ export class LayoutStore {
|
|||||||
this.update(id, { z: ++this.zCounter });
|
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() {
|
getSnapshot() {
|
||||||
return Array.from(this.items.values()).map(card => card.toSnapshot());
|
return Array.from(this.items.values()).map(card => card.toSnapshot());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user