skeuo aspect ratio

This commit is contained in:
2025-10-27 01:55:37 +01:00
parent b260065245
commit 84a3a9a5b5
3 changed files with 143 additions and 63 deletions
+1
View File
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import {
DownloadIcon,
EditIcon,
-1
View File
@@ -55,7 +55,6 @@
display: flex;
flex-direction: column;
align-items: center;
transition: width 0.28s ease, height 0.28s ease;
}
.skeuo-item:focus-visible {
+142 -62
View File
@@ -60,9 +60,12 @@ const SkeuoPreviewCard = ({
prefetch,
});
const { currentUrl, cardinality, canGoPrev, canGoNext } = navigator;
const { currentUrl, cardinality, canGoPrev, canGoNext, currentMetadata, ordinal } = navigator;
const docId = doc?.id ?? null;
const metadataWidth = Number(currentMetadata?.width);
const metadataHeight = Number(currentMetadata?.height);
useEffect(() => {
if (!onNavigatorSnapshot || !docId) {
return undefined;
@@ -74,6 +77,9 @@ const SkeuoPreviewCard = ({
canGoNext,
goPrev: navigator.goPrev,
goNext: navigator.goNext,
ordinal,
width: Number.isFinite(metadataWidth) && metadataWidth > 0 ? metadataWidth : null,
height: Number.isFinite(metadataHeight) && metadataHeight > 0 ? metadataHeight : null,
};
onNavigatorSnapshot(docId, snapshot);
return () => onNavigatorSnapshot(docId, null);
@@ -83,6 +89,9 @@ const SkeuoPreviewCard = ({
title,
canGoPrev,
canGoNext,
ordinal,
metadataWidth,
metadataHeight,
navigator.goPrev,
navigator.goNext,
onNavigatorSnapshot,
@@ -555,6 +564,37 @@ const clamp = (value, min, max) => {
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) =>
`translate3d(${x}px, ${y}px, 0) rotate(${rotation}deg) scale(${scale})`;
@@ -602,43 +642,90 @@ const SkeuomorphicWorkspace = ({
const [overlayOriginRect, setOverlayOriginRect] = useState(null);
const [overlayOriginTransform, setOverlayOriginTransform] = useState(null);
const [previewSnapshots, setPreviewSnapshots] = useState(() => new Map());
const [docSizeVersion, setDocSizeVersion] = useState(0);
const [tagDropTargetId, setTagDropTargetId] = useState(null);
const [pendingTagDocId, setPendingTagDocId] = useState(null);
const [pendingRemovalTag, setPendingRemovalTag] = useState(null);
const draggingTagRef = useRef(null);
const pendingDocTagDragRef = useRef(null);
const docSizeMapRef = useRef(new Map());
const documentLookupRef = useRef(new Map());
const removalCursorActiveRef = useRef(false);
const handleNavigatorSnapshot = useCallback((docId, snapshot) => {
if (!docId) {
return;
}
setPreviewSnapshots((previous) => {
const prevSnapshot = previous.get(docId);
if (!snapshot) {
if (!previous.has(docId)) {
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) {
return;
}
setPreviewSnapshots((previous) => {
const prevSnapshot = previous.get(docId);
if (!snapshot) {
if (!previous.has(docId)) {
return previous;
}
const next = new Map(previous);
next.delete(docId);
return next;
}
const next = new Map(previous);
const sameSnapshot =
prevSnapshot &&
prevSnapshot.url === snapshot.url &&
prevSnapshot.alt === snapshot.alt &&
prevSnapshot.canGoPrev === snapshot.canGoPrev &&
prevSnapshot.canGoNext === snapshot.canGoNext &&
prevSnapshot.goPrev === snapshot.goPrev &&
prevSnapshot.goNext === snapshot.goNext &&
prevSnapshot.ordinal === snapshot.ordinal &&
prevSnapshot.width === snapshot.width &&
prevSnapshot.height === snapshot.height;
if (sameSnapshot) {
return previous;
}
const next = new Map(previous);
next.delete(docId);
next.set(docId, snapshot);
return next;
});
if (snapshot) {
applySnapshotDimensions(docId, snapshot);
}
const next = new Map(previous);
const sameSnapshot =
prevSnapshot &&
prevSnapshot.url === snapshot.url &&
prevSnapshot.alt === snapshot.alt &&
prevSnapshot.canGoPrev === snapshot.canGoPrev &&
prevSnapshot.canGoNext === snapshot.canGoNext &&
prevSnapshot.goPrev === snapshot.goPrev &&
prevSnapshot.goNext === snapshot.goNext;
if (sameSnapshot) {
return previous;
}
next.set(docId, snapshot);
return next;
});
}, []);
},
[applySnapshotDimensions],
);
const activeTagSet = useMemo(() => {
if (!Array.isArray(activeTagIds) || activeTagIds.length === 0) {
return new Set();
@@ -663,6 +750,15 @@ const SkeuomorphicWorkspace = ({
const resolvePreviewDimensions = useCallback(
(doc) => {
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 view = createAssetView(asset);
const metadata = view.getPrimaryMetadata() || {};
@@ -673,7 +769,7 @@ const SkeuomorphicWorkspace = ({
}
return null;
},
[resolvePreviewAsset],
[previewSnapshots, resolvePreviewAsset],
);
useEffect(() => {
@@ -780,48 +876,26 @@ const SkeuomorphicWorkspace = ({
if (cache) {
return cache;
}
let width;
let height;
const intrinsic = resolvePreviewDimensions(doc);
let normalized = null;
if (intrinsic?.width && intrinsic?.height) {
width = intrinsic.width;
height = intrinsic.height;
} else {
normalized = clampCardDimensions(intrinsic.width, intrinsic.height);
}
if (!normalized) {
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));
height = width * EMPTY_CARD_ASPECT;
const height = width * EMPTY_CARD_ASPECT;
normalized = clampCardDimensions(width, height);
}
if (!Number.isFinite(width) || width <= 0) {
width = CARD_MIN;
}
if (!Number.isFinite(height) || height <= 0) {
height = CARD_MIN;
if (!normalized) {
normalized = { width: CARD_MIN, height: CARD_MIN };
}
const scaleDown = Math.min(1, CARD_MAX / width, CARD_MAX / height);
width *= scaleDown;
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;
docSizeMapRef.current.set(key, normalized);
return normalized;
}, [resolvePreviewDimensions]);
const documentLookup = useMemo(() => {
@@ -834,8 +908,13 @@ const SkeuomorphicWorkspace = ({
return map;
}, [items]);
useEffect(() => {
documentLookupRef.current = documentLookup;
}, [documentLookup]);
useEffect(() => {
docSizeMapRef.current = new Map();
setDocSizeVersion((value) => value + 1);
}, [items]);
const overlayDisplay = useMemo(() => {
@@ -1020,6 +1099,7 @@ const SkeuomorphicWorkspace = ({
items,
canvasSize.width,
canvasSize.height,
docSizeVersion,
ensureDocumentSize,
syncLayoutSnapshot,
]);