feat: Introduce new LayoutSystem and spatial workspace architecture design document, refactor document card and workspace to use it, and simplify pointer event handling.
This commit is contained in:
@@ -16,6 +16,8 @@ interface PendingRemovalTag {
|
|||||||
tagId?: string;
|
tagId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { LayoutCard } from './LayoutSystem';
|
||||||
|
|
||||||
interface DesktopDocumentCardProps {
|
interface DesktopDocumentCardProps {
|
||||||
doc: Document;
|
doc: Document;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
@@ -39,7 +41,7 @@ interface DesktopDocumentCardProps {
|
|||||||
onDocTagDrag?: (event: React.DragEvent<HTMLElement>) => void;
|
onDocTagDrag?: (event: React.DragEvent<HTMLElement>) => void;
|
||||||
onDocTagDragEnd?: (event: React.DragEvent<HTMLElement>) => void;
|
onDocTagDragEnd?: (event: React.DragEvent<HTMLElement>) => void;
|
||||||
pendingRemovalTag?: PendingRemovalTag | null;
|
pendingRemovalTag?: PendingRemovalTag | null;
|
||||||
registerNode?: (node: HTMLDivElement | null) => void;
|
layoutCard?: LayoutCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
|
const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
|
||||||
@@ -65,7 +67,7 @@ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
|
|||||||
onDocTagDrag,
|
onDocTagDrag,
|
||||||
onDocTagDragEnd,
|
onDocTagDragEnd,
|
||||||
pendingRemovalTag,
|
pendingRemovalTag,
|
||||||
registerNode,
|
layoutCard,
|
||||||
}) => {
|
}) => {
|
||||||
const correspondents = useMemo(() => resolveCorrespondents(doc), [doc]);
|
const correspondents = useMemo(() => resolveCorrespondents(doc), [doc]);
|
||||||
const tags = Array.isArray(doc?.tags) ? doc.tags : [];
|
const tags = Array.isArray(doc?.tags) ? doc.tags : [];
|
||||||
@@ -88,7 +90,7 @@ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
|
|||||||
data-doc-id={doc.id}
|
data-doc-id={doc.id}
|
||||||
data-tag-ids={dataTagIds}
|
data-tag-ids={dataTagIds}
|
||||||
aria-hidden={ariaHidden}
|
aria-hidden={ariaHidden}
|
||||||
ref={registerNode ?? undefined}
|
ref={(node) => layoutCard?.setRef(node)}
|
||||||
{...cardPointerHandlers}
|
{...cardPointerHandlers}
|
||||||
onDragEnter={(event) => {
|
onDragEnter={(event) => {
|
||||||
if (doc?.id == null) {
|
if (doc?.id == null) {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { globalLayout, LayoutItem } from './LayoutSystem';
|
import { LayoutStore, LayoutCard } from './LayoutSystem';
|
||||||
import PreviewZoomOverlay from '../detail/PreviewZoomOverlay';
|
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 '../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';
|
||||||
@@ -42,7 +42,7 @@ interface DocumentSizeInfo {
|
|||||||
|
|
||||||
// Fallback size computation
|
// Fallback size computation
|
||||||
const computeFallbackCardSize = (_doc: DeskDocument): DocumentSizeInfo => {
|
const computeFallbackCardSize = (_doc: DeskDocument): DocumentSizeInfo => {
|
||||||
return { width: 200, height: 280, source: 'fallback' };
|
return { width: 200, height: 200, source: 'fallback' };
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DesktopWorkspaceProps {
|
export interface DesktopWorkspaceProps {
|
||||||
@@ -51,7 +51,7 @@ export interface DesktopWorkspaceProps {
|
|||||||
getDocumentAsset?: (...args: any[]) => unknown;
|
getDocumentAsset?: (...args: any[]) => unknown;
|
||||||
onDocumentActivate?: (doc: DeskDocument, event?: unknown) => void;
|
onDocumentActivate?: (doc: DeskDocument, event?: unknown) => void;
|
||||||
onSelectionChange?: (selectedIds: Identifier[]) => void;
|
onSelectionChange?: (selectedIds: Identifier[]) => void;
|
||||||
layout?: any[];
|
onDocumentTagDrop?: (docId: Identifier, tag: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||||
@@ -60,10 +60,9 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
onDocumentActivate,
|
onDocumentActivate,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
layout: initialLayout,
|
onDocumentTagDrop,
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const itemRefs = useRef<Map<string, HTMLElement>>(new Map());
|
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
return entries
|
return entries
|
||||||
@@ -74,7 +73,8 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
}, [entries]);
|
}, [entries]);
|
||||||
|
|
||||||
// Layout System Initialization
|
// Layout System Initialization
|
||||||
const layoutRef = useRef<Map<string, LayoutItem>>(new Map());
|
const layoutStore = useMemo(() => new LayoutStore(), []);
|
||||||
|
const layoutRef = useRef<Map<string, LayoutCard>>(new Map());
|
||||||
|
|
||||||
// Selection Context
|
// Selection Context
|
||||||
const {
|
const {
|
||||||
@@ -101,33 +101,46 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
|
|
||||||
const metadataMap = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl);
|
const metadataMap = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl);
|
||||||
|
|
||||||
const ensureDocumentSize = useCallback((doc: DeskDocument | null): DocumentSizeInfo | null => {
|
const ensureDocumentSize = useCallback((doc: DeskDocument): DocumentSizeInfo => {
|
||||||
if (!doc) return null;
|
|
||||||
if (doc.id) {
|
if (doc.id) {
|
||||||
const meta = metadataMap.get(String(doc.id));
|
const meta = metadataMap.get(String(doc.id));
|
||||||
if (meta) return { width: meta.width, height: meta.height, source: 'metadata' };
|
if (meta && meta.width && meta.height)
|
||||||
|
return { width: meta.width, height: meta.height, source: 'metadata' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return computeFallbackCardSize(doc);
|
return computeFallbackCardSize(doc);
|
||||||
}, [metadataMap]);
|
}, [metadataMap]);
|
||||||
|
|
||||||
// Initialize Layout System
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (initialLayout) {
|
|
||||||
// Seeding logic if needed, but registration happens in render loop
|
|
||||||
}
|
|
||||||
}, [initialLayout]);
|
|
||||||
|
|
||||||
// Sync LayoutStore to layoutRef
|
// Sync LayoutStore to layoutRef
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
layoutRef.current = globalLayout.items;
|
layoutRef.current = layoutStore.items;
|
||||||
};
|
};
|
||||||
sync();
|
sync();
|
||||||
}, []);
|
}, [layoutStore.items]);
|
||||||
|
|
||||||
const handleShellKeyDown = useCallback(() => { }, []);
|
const handleShellKeyDown = useCallback(() => { }, []);
|
||||||
const focusShell = useCallback(() => { }, []);
|
const focusShell = useCallback(() => { }, []);
|
||||||
|
|
||||||
|
// Tag Interactions
|
||||||
|
const [pendingRemovalTag, setPendingRemovalTag] = useState<{ docId?: string; tagId?: string } | null>(null);
|
||||||
|
const [tagDropTargetId, setTagDropTargetId] = useState<string | null>(null);
|
||||||
|
const [pendingTagDocId, setPendingTagDocId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const tagEngine = useMemo(() => ({
|
||||||
|
setPendingRemovalTag,
|
||||||
|
setTagDropTargetId,
|
||||||
|
setPendingTagDocId,
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const tagInteractions = useDeskTagInteractions({
|
||||||
|
engine: tagEngine,
|
||||||
|
onAssignTagToDocument: (docId: string, tag: any) => {
|
||||||
|
onDocumentTagDrop?.(docId, tag);
|
||||||
|
},
|
||||||
|
requestCanvasFocus: focusShell,
|
||||||
|
});
|
||||||
|
|
||||||
// Overlay State
|
// Overlay State
|
||||||
const [overlayDisplay, setOverlayDisplay] = useState<OverlayDisplay | null>(null);
|
const [overlayDisplay, setOverlayDisplay] = useState<OverlayDisplay | null>(null);
|
||||||
const closeOverlay = useCallback(() => setOverlayDisplay(null), []);
|
const closeOverlay = useCallback(() => setOverlayDisplay(null), []);
|
||||||
@@ -154,25 +167,10 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
const isSelected = selectedDocumentIds.includes(docId);
|
const isSelected = selectedDocumentIds.includes(docId);
|
||||||
const size = ensureDocumentSize(doc);
|
const size = ensureDocumentSize(doc);
|
||||||
|
|
||||||
// Register with LayoutStore
|
const layoutCard = layoutStore.initialize(docId, null, {
|
||||||
const registerNode = (node: HTMLDivElement) => {
|
width: size.width,
|
||||||
if (node) {
|
height: size.height
|
||||||
itemRefs.current.set(docId, node);
|
});
|
||||||
const initItem = initialLayout?.find((i: any) => i.id === docId);
|
|
||||||
globalLayout.initialize(docId, node, {
|
|
||||||
x: initItem?.x,
|
|
||||||
y: initItem?.y,
|
|
||||||
rotation: initItem?.rotation,
|
|
||||||
z: index,
|
|
||||||
width: size?.width ?? 200,
|
|
||||||
height: size?.height ?? 200
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
itemRefs.current.delete(docId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// const cardPointerHandlers = getCardPointerHandlers(doc);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DesktopDocumentCard
|
<DesktopDocumentCard
|
||||||
@@ -187,8 +185,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
}}
|
}}
|
||||||
shouldLoad={true}
|
shouldLoad={true}
|
||||||
matchesFilter={true}
|
matchesFilter={true}
|
||||||
tagTargetActive={false}
|
|
||||||
tagTargetPending={false}
|
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
docTagTokens=""
|
docTagTokens=""
|
||||||
ensureAssetUrl={ensureAssetUrl}
|
ensureAssetUrl={ensureAssetUrl}
|
||||||
@@ -196,7 +193,18 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
|||||||
handleNavigatorSnapshot={() => { }}
|
handleNavigatorSnapshot={() => { }}
|
||||||
cardPointerHandlers={undefined}
|
cardPointerHandlers={undefined}
|
||||||
onDocumentActivate={onDocumentActivate}
|
onDocumentActivate={onDocumentActivate}
|
||||||
registerNode={registerNode}
|
layoutCard={layoutCard}
|
||||||
|
onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
|
||||||
|
onTagDragOver={tagInteractions.handleTagDragOverDoc}
|
||||||
|
onTagDragLeave={tagInteractions.handleTagDragLeaveDoc}
|
||||||
|
onTagDrop={tagInteractions.handleTagDropOnDoc}
|
||||||
|
onDocTagPointerDown={tagInteractions.handleDocTagPointerDown}
|
||||||
|
onDocTagDragStart={tagInteractions.handleDocTagDragStart}
|
||||||
|
onDocTagDrag={tagInteractions.handleDocTagDrag}
|
||||||
|
onDocTagDragEnd={tagInteractions.handleDocTagDragEnd}
|
||||||
|
tagTargetActive={tagDropTargetId === docId}
|
||||||
|
tagTargetPending={pendingTagDocId === docId}
|
||||||
|
pendingRemovalTag={pendingRemovalTag}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export interface LayoutItem {
|
import { constrainDimensions } from './utils/layoutUtils';
|
||||||
|
|
||||||
|
export interface LayoutCardState {
|
||||||
id: string;
|
id: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -6,79 +8,108 @@ export interface LayoutItem {
|
|||||||
rotation: number;
|
rotation: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
ref: HTMLElement;
|
}
|
||||||
|
|
||||||
|
export class LayoutCard implements LayoutCardState {
|
||||||
|
id: string;
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
z: number = 0;
|
||||||
|
rotation: number = 0;
|
||||||
|
width: number = 0;
|
||||||
|
height: number = 0;
|
||||||
|
ref: HTMLElement | null = null;
|
||||||
|
|
||||||
|
constructor(id: string, initialData: Partial<LayoutCardState> = {}, ref: HTMLElement | null = null) {
|
||||||
|
this.id = id;
|
||||||
|
Object.assign(this, initialData);
|
||||||
|
this.ref = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef(ref: HTMLElement | null) {
|
||||||
|
this.ref = ref;
|
||||||
|
this.applyTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
update(changes: Partial<LayoutCardState>) {
|
||||||
|
Object.assign(this, changes);
|
||||||
|
this.applyTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyTransform() {
|
||||||
|
if (this.ref) {
|
||||||
|
this.ref.style.transform =
|
||||||
|
`translate3d(${this.x}px, ${this.y}px, 0) rotate(${this.rotation}deg)`;
|
||||||
|
this.ref.style.zIndex = String(this.z);
|
||||||
|
this.ref.style.width = `${this.width}px`;
|
||||||
|
this.ref.style.height = `${this.height}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toSnapshot(): LayoutCardState {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
x: this.x,
|
||||||
|
y: this.y,
|
||||||
|
z: this.z,
|
||||||
|
rotation: this.rotation,
|
||||||
|
width: this.width,
|
||||||
|
height: this.height
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LayoutStore {
|
export class LayoutStore {
|
||||||
items = new Map<string, LayoutItem>();
|
items = new Map<string, LayoutCard>();
|
||||||
zCounter = 100;
|
zCounter = 100;
|
||||||
|
|
||||||
register(id: string, ref: HTMLElement, initialData: Partial<LayoutItem>) {
|
initialize(id: string, ref: HTMLElement | null, config: {
|
||||||
const existing = this.items.get(id);
|
|
||||||
this.items.set(id, {
|
|
||||||
id,
|
|
||||||
ref,
|
|
||||||
x: existing?.x ?? 0,
|
|
||||||
y: existing?.y ?? 0,
|
|
||||||
z: existing?.z ?? 0,
|
|
||||||
rotation: existing?.rotation ?? 0,
|
|
||||||
width: 200,
|
|
||||||
height: 200,
|
|
||||||
...initialData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(id: string, ref: HTMLElement, config: {
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
rotation?: number;
|
|
||||||
z: number;
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}) {
|
}) {
|
||||||
if (this.items.has(id)) {
|
let card = this.items.get(id);
|
||||||
// Update ref if it changed
|
|
||||||
const item = this.items.get(id)!;
|
const { width, height } = constrainDimensions(
|
||||||
if (item.ref !== ref) {
|
config.width,
|
||||||
item.ref = ref;
|
config.height,
|
||||||
this.update(id, {}); // Re-apply styles
|
280
|
||||||
}
|
);
|
||||||
return;
|
|
||||||
|
if (!card) {
|
||||||
|
// Apply defaults if not provided
|
||||||
|
const x = Math.random() * 500;
|
||||||
|
const y = Math.random() * 500;
|
||||||
|
const rotation = Math.random() * 10 - 5;
|
||||||
|
|
||||||
|
card = new LayoutCard(id, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
rotation,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}, ref);
|
||||||
|
this.items.set(id, card);
|
||||||
|
} else {
|
||||||
|
card.update({ width, height });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply defaults if not provided
|
// Always update ref and ensure transform is applied
|
||||||
const x = config.x ?? Math.random() * 500;
|
if (card.ref !== ref) {
|
||||||
const y = config.y ?? Math.random() * 500;
|
card.setRef(ref);
|
||||||
const rotation = config.rotation ?? (Math.random() * 10 - 5);
|
}
|
||||||
|
|
||||||
this.register(id, ref, {
|
return card;
|
||||||
...config,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
rotation
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply immediately
|
|
||||||
this.update(id, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unregister(id: string) {
|
unregister(id: string) {
|
||||||
this.items.delete(id);
|
this.items.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast Update: Updates internal state AND applies CSS transform immediately
|
update(id: string, updates: Partial<LayoutCardState>) {
|
||||||
update(id: string, updates: Partial<LayoutItem>) {
|
const card = this.items.get(id);
|
||||||
const item = this.items.get(id);
|
if (card) {
|
||||||
if (!item) return;
|
if (updates.z) this.zCounter = Math.max(this.zCounter, updates.z);
|
||||||
|
card.update(updates);
|
||||||
Object.assign(item, updates);
|
|
||||||
if (updates.z) this.zCounter = Math.max(this.zCounter, updates.z);
|
|
||||||
|
|
||||||
// Direct DOM manipulation (The "Engine" part)
|
|
||||||
if (item.ref) {
|
|
||||||
item.ref.style.transform =
|
|
||||||
`translate3d(${item.x}px, ${item.y}px, 0) rotate(${item.rotation}deg)`;
|
|
||||||
item.ref.style.zIndex = String(item.z);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,10 +118,6 @@ export class LayoutStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSnapshot() {
|
getSnapshot() {
|
||||||
// Return serializable data for persistence
|
return Array.from(this.items.values()).map(card => card.toSnapshot());
|
||||||
return Array.from(this.items.values()).map(({ ref: _ref, ...data }) => data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton or Context-provided instance
|
|
||||||
export const globalLayout = new LayoutStore();
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const usePreviewMetadata = (
|
|||||||
const docId = String(doc.id);
|
const docId = String(doc.id);
|
||||||
const resolveAsset = (type: string) => getDocumentAsset?.(doc, type) ?? null;
|
const resolveAsset = (type: string) => getDocumentAsset?.(doc, type) ?? null;
|
||||||
|
|
||||||
let asset = resolveAsset('preview') || resolveAsset('thumbnail');
|
let asset = resolveAsset('thumbnail');
|
||||||
let metadata = (asset?.metadata as { width?: number; height?: number } | null) || null;
|
let metadata = (asset?.metadata as { width?: number; height?: number } | null) || null;
|
||||||
|
|
||||||
const hasDimensions = (meta: { width?: number | string; height?: number | string } | null) =>
|
const hasDimensions = (meta: { width?: number | string; height?: number | string } | null) =>
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ const preventAll = (event) => {
|
|||||||
if (typeof event.stopPropagation === 'function') event.stopPropagation();
|
if (typeof event.stopPropagation === 'function') event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Restore getPointerPosition
|
|
||||||
const getPointerPosition = (event) => {
|
|
||||||
return { x: event.clientX, y: event.clientY };
|
|
||||||
};
|
|
||||||
import { TAG_REMOVE_DISTANCE } from '../../constants/desktop';
|
import { TAG_REMOVE_DISTANCE } from '../../constants/desktop';
|
||||||
|
|
||||||
const createDragPreview = (node, clientX, clientY) => {
|
const createDragPreview = (node, clientX, clientY) => {
|
||||||
@@ -150,7 +146,8 @@ export const useDeskTagInteractions = ({
|
|||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
writeTagTransferData(event.dataTransfer, tag, doc.id);
|
writeTagTransferData(event.dataTransfer, tag, doc.id);
|
||||||
|
|
||||||
const { x: pointerX, y: pointerY } = getPointerPosition(event, { fallbackToPage: false });
|
const pointerX = event.clientX;
|
||||||
|
const pointerY = event.clientY;
|
||||||
const { clone, offsetX, offsetY } = createDragPreview(event.currentTarget, pointerX, pointerY) || {};
|
const { clone, offsetX, offsetY } = createDragPreview(event.currentTarget, pointerX, pointerY) || {};
|
||||||
if (clone) {
|
if (clone) {
|
||||||
event.dataTransfer.setDragImage(clone, offsetX || 0, offsetY || 0);
|
event.dataTransfer.setDragImage(clone, offsetX || 0, offsetY || 0);
|
||||||
@@ -181,7 +178,8 @@ export const useDeskTagInteractions = ({
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { x, y } = getPointerPosition(event);
|
const x = event.clientX;
|
||||||
|
const y = event.clientY;
|
||||||
const dx = x - (state.initialX || 0);
|
const dx = x - (state.initialX || 0);
|
||||||
const dy = y - (state.initialY || 0);
|
const dy = y - (state.initialY || 0);
|
||||||
state.distance = Math.sqrt(dx * dx + dy * dy);
|
state.distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|||||||
@@ -33,3 +33,22 @@ export const computeCardBounds = ({
|
|||||||
maxY: Math.max(padding + halfH, canvasHeight - padding - halfH),
|
maxY: Math.max(padding + halfH, canvasHeight - padding - halfH),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const constrainDimensions = (width: number, height: number, maxDimension: number) => {
|
||||||
|
if (width <= maxDimension && height <= maxDimension) {
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
const aspect = width / height;
|
||||||
|
if (width > height) {
|
||||||
|
return {
|
||||||
|
width: maxDimension,
|
||||||
|
height: maxDimension / aspect
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
width: maxDimension * aspect,
|
||||||
|
height: maxDimension
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user