skeuo aspect ratio
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import {
|
import {
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
|
|||||||
@@ -55,7 +55,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: width 0.28s ease, height 0.28s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeuo-item:focus-visible {
|
.skeuo-item:focus-visible {
|
||||||
|
|||||||
@@ -60,9 +60,12 @@ const SkeuoPreviewCard = ({
|
|||||||
prefetch,
|
prefetch,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { currentUrl, cardinality, canGoPrev, canGoNext } = navigator;
|
const { currentUrl, cardinality, canGoPrev, canGoNext, currentMetadata, ordinal } = navigator;
|
||||||
const docId = doc?.id ?? null;
|
const docId = doc?.id ?? null;
|
||||||
|
|
||||||
|
const metadataWidth = Number(currentMetadata?.width);
|
||||||
|
const metadataHeight = Number(currentMetadata?.height);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!onNavigatorSnapshot || !docId) {
|
if (!onNavigatorSnapshot || !docId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -74,6 +77,9 @@ const SkeuoPreviewCard = ({
|
|||||||
canGoNext,
|
canGoNext,
|
||||||
goPrev: navigator.goPrev,
|
goPrev: navigator.goPrev,
|
||||||
goNext: navigator.goNext,
|
goNext: navigator.goNext,
|
||||||
|
ordinal,
|
||||||
|
width: Number.isFinite(metadataWidth) && metadataWidth > 0 ? metadataWidth : null,
|
||||||
|
height: Number.isFinite(metadataHeight) && metadataHeight > 0 ? metadataHeight : null,
|
||||||
};
|
};
|
||||||
onNavigatorSnapshot(docId, snapshot);
|
onNavigatorSnapshot(docId, snapshot);
|
||||||
return () => onNavigatorSnapshot(docId, null);
|
return () => onNavigatorSnapshot(docId, null);
|
||||||
@@ -83,6 +89,9 @@ const SkeuoPreviewCard = ({
|
|||||||
title,
|
title,
|
||||||
canGoPrev,
|
canGoPrev,
|
||||||
canGoNext,
|
canGoNext,
|
||||||
|
ordinal,
|
||||||
|
metadataWidth,
|
||||||
|
metadataHeight,
|
||||||
navigator.goPrev,
|
navigator.goPrev,
|
||||||
navigator.goNext,
|
navigator.goNext,
|
||||||
onNavigatorSnapshot,
|
onNavigatorSnapshot,
|
||||||
@@ -555,6 +564,37 @@ const clamp = (value, min, max) => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clampCardDimensions = (width, height) => {
|
||||||
|
let nextWidth = Number(width);
|
||||||
|
let nextHeight = Number(height);
|
||||||
|
if (!Number.isFinite(nextWidth) || nextWidth <= 0 || !Number.isFinite(nextHeight) || nextHeight <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scaleDown = Math.min(1, CARD_MAX / nextWidth, CARD_MAX / nextHeight);
|
||||||
|
nextWidth *= scaleDown;
|
||||||
|
nextHeight *= scaleDown;
|
||||||
|
|
||||||
|
const minDim = Math.min(nextWidth, nextHeight);
|
||||||
|
if (minDim > 0 && minDim < CARD_MIN) {
|
||||||
|
const scaleUp = CARD_MIN / minDim;
|
||||||
|
nextWidth *= scaleUp;
|
||||||
|
nextHeight *= scaleUp;
|
||||||
|
|
||||||
|
const adjust = Math.min(1, CARD_MAX / nextWidth, CARD_MAX / nextHeight);
|
||||||
|
nextWidth *= adjust;
|
||||||
|
nextHeight *= adjust;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextWidth = clamp(nextWidth, CARD_MIN, CARD_MAX);
|
||||||
|
nextHeight = clamp(nextHeight, CARD_MIN, CARD_MAX);
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const formatTransform = (x, y, rotation = 0, scale = 1) =>
|
const formatTransform = (x, y, rotation = 0, scale = 1) =>
|
||||||
`translate3d(${x}px, ${y}px, 0) rotate(${rotation}deg) scale(${scale})`;
|
`translate3d(${x}px, ${y}px, 0) rotate(${rotation}deg) scale(${scale})`;
|
||||||
|
|
||||||
@@ -602,17 +642,54 @@ const SkeuomorphicWorkspace = ({
|
|||||||
const [overlayOriginRect, setOverlayOriginRect] = useState(null);
|
const [overlayOriginRect, setOverlayOriginRect] = useState(null);
|
||||||
const [overlayOriginTransform, setOverlayOriginTransform] = useState(null);
|
const [overlayOriginTransform, setOverlayOriginTransform] = useState(null);
|
||||||
const [previewSnapshots, setPreviewSnapshots] = useState(() => new Map());
|
const [previewSnapshots, setPreviewSnapshots] = useState(() => new Map());
|
||||||
|
const [docSizeVersion, setDocSizeVersion] = useState(0);
|
||||||
const [tagDropTargetId, setTagDropTargetId] = useState(null);
|
const [tagDropTargetId, setTagDropTargetId] = useState(null);
|
||||||
const [pendingTagDocId, setPendingTagDocId] = useState(null);
|
const [pendingTagDocId, setPendingTagDocId] = useState(null);
|
||||||
const [pendingRemovalTag, setPendingRemovalTag] = useState(null);
|
const [pendingRemovalTag, setPendingRemovalTag] = useState(null);
|
||||||
const draggingTagRef = useRef(null);
|
const draggingTagRef = useRef(null);
|
||||||
const pendingDocTagDragRef = useRef(null);
|
const pendingDocTagDragRef = useRef(null);
|
||||||
const docSizeMapRef = useRef(new Map());
|
const docSizeMapRef = useRef(new Map());
|
||||||
|
const documentLookupRef = useRef(new Map());
|
||||||
const removalCursorActiveRef = useRef(false);
|
const removalCursorActiveRef = useRef(false);
|
||||||
const handleNavigatorSnapshot = useCallback((docId, snapshot) => {
|
const applySnapshotDimensions = useCallback(
|
||||||
|
(docId, snapshot) => {
|
||||||
|
if (!docId || !snapshot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = documentLookupRef.current.get(docId);
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = Number(snapshot.width);
|
||||||
|
const height = Number(snapshot.height);
|
||||||
|
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = clampCardDimensions(width, height);
|
||||||
|
if (!normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = resolveSizeKey(doc);
|
||||||
|
const existing = docSizeMapRef.current.get(key);
|
||||||
|
if (existing && existing.width === normalized.width && existing.height === normalized.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
docSizeMapRef.current.set(key, normalized);
|
||||||
|
setDocSizeVersion((value) => value + 1);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const handleNavigatorSnapshot = useCallback(
|
||||||
|
(docId, snapshot) => {
|
||||||
if (!docId) {
|
if (!docId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreviewSnapshots((previous) => {
|
setPreviewSnapshots((previous) => {
|
||||||
const prevSnapshot = previous.get(docId);
|
const prevSnapshot = previous.get(docId);
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
@@ -623,6 +700,7 @@ const SkeuomorphicWorkspace = ({
|
|||||||
next.delete(docId);
|
next.delete(docId);
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = new Map(previous);
|
const next = new Map(previous);
|
||||||
const sameSnapshot =
|
const sameSnapshot =
|
||||||
prevSnapshot &&
|
prevSnapshot &&
|
||||||
@@ -631,14 +709,23 @@ const SkeuomorphicWorkspace = ({
|
|||||||
prevSnapshot.canGoPrev === snapshot.canGoPrev &&
|
prevSnapshot.canGoPrev === snapshot.canGoPrev &&
|
||||||
prevSnapshot.canGoNext === snapshot.canGoNext &&
|
prevSnapshot.canGoNext === snapshot.canGoNext &&
|
||||||
prevSnapshot.goPrev === snapshot.goPrev &&
|
prevSnapshot.goPrev === snapshot.goPrev &&
|
||||||
prevSnapshot.goNext === snapshot.goNext;
|
prevSnapshot.goNext === snapshot.goNext &&
|
||||||
|
prevSnapshot.ordinal === snapshot.ordinal &&
|
||||||
|
prevSnapshot.width === snapshot.width &&
|
||||||
|
prevSnapshot.height === snapshot.height;
|
||||||
if (sameSnapshot) {
|
if (sameSnapshot) {
|
||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
next.set(docId, snapshot);
|
next.set(docId, snapshot);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}, []);
|
|
||||||
|
if (snapshot) {
|
||||||
|
applySnapshotDimensions(docId, snapshot);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[applySnapshotDimensions],
|
||||||
|
);
|
||||||
const activeTagSet = useMemo(() => {
|
const activeTagSet = useMemo(() => {
|
||||||
if (!Array.isArray(activeTagIds) || activeTagIds.length === 0) {
|
if (!Array.isArray(activeTagIds) || activeTagIds.length === 0) {
|
||||||
return new Set();
|
return new Set();
|
||||||
@@ -663,6 +750,15 @@ const SkeuomorphicWorkspace = ({
|
|||||||
const resolvePreviewDimensions = useCallback(
|
const resolvePreviewDimensions = useCallback(
|
||||||
(doc) => {
|
(doc) => {
|
||||||
if (!doc) return null;
|
if (!doc) return null;
|
||||||
|
|
||||||
|
const snapshot = previewSnapshots.get(doc.id);
|
||||||
|
if (snapshot && snapshot.width && snapshot.height) {
|
||||||
|
return {
|
||||||
|
width: snapshot.width,
|
||||||
|
height: snapshot.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const asset = resolvePreviewAsset(doc);
|
const asset = resolvePreviewAsset(doc);
|
||||||
const view = createAssetView(asset);
|
const view = createAssetView(asset);
|
||||||
const metadata = view.getPrimaryMetadata() || {};
|
const metadata = view.getPrimaryMetadata() || {};
|
||||||
@@ -673,7 +769,7 @@ const SkeuomorphicWorkspace = ({
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[resolvePreviewAsset],
|
[previewSnapshots, resolvePreviewAsset],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -780,48 +876,26 @@ const SkeuomorphicWorkspace = ({
|
|||||||
if (cache) {
|
if (cache) {
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
let width;
|
|
||||||
let height;
|
|
||||||
|
|
||||||
const intrinsic = resolvePreviewDimensions(doc);
|
const intrinsic = resolvePreviewDimensions(doc);
|
||||||
|
let normalized = null;
|
||||||
if (intrinsic?.width && intrinsic?.height) {
|
if (intrinsic?.width && intrinsic?.height) {
|
||||||
width = intrinsic.width;
|
normalized = clampCardDimensions(intrinsic.width, intrinsic.height);
|
||||||
height = intrinsic.height;
|
}
|
||||||
} else {
|
|
||||||
|
if (!normalized) {
|
||||||
const seed = seededRandom(`${key}:size`);
|
const seed = seededRandom(`${key}:size`);
|
||||||
width = CARD_MIN + seed * (Math.min(CARD_MAX, CARD_MAX / EMPTY_CARD_ASPECT) - CARD_MIN);
|
let width = CARD_MIN + seed * (Math.min(CARD_MAX, CARD_MAX / EMPTY_CARD_ASPECT) - CARD_MIN);
|
||||||
width = clamp(width, CARD_MIN, Math.min(CARD_MAX, CARD_MAX / EMPTY_CARD_ASPECT));
|
width = clamp(width, CARD_MIN, Math.min(CARD_MAX, CARD_MAX / EMPTY_CARD_ASPECT));
|
||||||
height = width * EMPTY_CARD_ASPECT;
|
const height = width * EMPTY_CARD_ASPECT;
|
||||||
|
normalized = clampCardDimensions(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Number.isFinite(width) || width <= 0) {
|
if (!normalized) {
|
||||||
width = CARD_MIN;
|
normalized = { width: CARD_MIN, height: CARD_MIN };
|
||||||
}
|
|
||||||
if (!Number.isFinite(height) || height <= 0) {
|
|
||||||
height = CARD_MIN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const scaleDown = Math.min(1, CARD_MAX / width, CARD_MAX / height);
|
docSizeMapRef.current.set(key, normalized);
|
||||||
width *= scaleDown;
|
return normalized;
|
||||||
height *= scaleDown;
|
|
||||||
|
|
||||||
const minDim = Math.min(width, height);
|
|
||||||
if (minDim < CARD_MIN) {
|
|
||||||
const scaleUp = CARD_MIN / minDim;
|
|
||||||
width *= scaleUp;
|
|
||||||
height *= scaleUp;
|
|
||||||
|
|
||||||
const adjust = Math.min(1, CARD_MAX / width, CARD_MAX / height);
|
|
||||||
width *= adjust;
|
|
||||||
height *= adjust;
|
|
||||||
}
|
|
||||||
|
|
||||||
width = clamp(width, CARD_MIN, CARD_MAX);
|
|
||||||
height = clamp(height, CARD_MIN, CARD_MAX);
|
|
||||||
|
|
||||||
const size = { width, height };
|
|
||||||
docSizeMapRef.current.set(key, size);
|
|
||||||
return size;
|
|
||||||
}, [resolvePreviewDimensions]);
|
}, [resolvePreviewDimensions]);
|
||||||
|
|
||||||
const documentLookup = useMemo(() => {
|
const documentLookup = useMemo(() => {
|
||||||
@@ -834,8 +908,13 @@ const SkeuomorphicWorkspace = ({
|
|||||||
return map;
|
return map;
|
||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
documentLookupRef.current = documentLookup;
|
||||||
|
}, [documentLookup]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
docSizeMapRef.current = new Map();
|
docSizeMapRef.current = new Map();
|
||||||
|
setDocSizeVersion((value) => value + 1);
|
||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
const overlayDisplay = useMemo(() => {
|
const overlayDisplay = useMemo(() => {
|
||||||
@@ -1020,6 +1099,7 @@ const SkeuomorphicWorkspace = ({
|
|||||||
items,
|
items,
|
||||||
canvasSize.width,
|
canvasSize.width,
|
||||||
canvasSize.height,
|
canvasSize.height,
|
||||||
|
docSizeVersion,
|
||||||
ensureDocumentSize,
|
ensureDocumentSize,
|
||||||
syncLayoutSnapshot,
|
syncLayoutSnapshot,
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user