desk view

This commit is contained in:
2025-10-14 10:27:13 +02:00
parent 9aa43d2753
commit 4d5f7b4ccd
2 changed files with 84 additions and 57 deletions
+38 -2
View File
@@ -12,7 +12,7 @@
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
position: relative; position: relative;
background: #7a7c7c; background-color: var(--surface-subtle);
} }
.skeuo-header { .skeuo-header {
@@ -21,7 +21,7 @@
justify-content: space-between; justify-content: space-between;
gap: 1rem; gap: 1rem;
padding: 0.75rem 1rem 0.5rem; padding: 0.75rem 1rem 0.5rem;
background-color: var(--surface-subtle, rgba(255, 255, 255, 0.08)); background-color: var(--surface-subtle);
} }
.skeuo-header__meta { .skeuo-header__meta {
@@ -277,6 +277,20 @@ body.skeuo-cursor-remove * {
overflow: hidden; overflow: hidden;
} }
.skeuo-item__card--empty {
background:
radial-gradient(circle at 28% 24%, rgba(255, 255, 255, 0.32), transparent 60%),
radial-gradient(circle at 72% 78%, rgba(0, 0, 0, 0.08), transparent 65%),
linear-gradient(135deg, #e6e1d6 0%, #d2cdc2 100%);
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.skeuo-item__card img { .skeuo-item__card img {
} }
@@ -286,3 +300,25 @@ body.skeuo-cursor-remove * {
letter-spacing: normal; letter-spacing: normal;
color: var(--muted); color: var(--muted);
} }
.skeuo-item__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
gap: 0.5rem;
padding: 1rem;
text-align: center;
}
.skeuo-item__title {
font-size: 0.95rem;
font-weight: 500;
color: #3b3b3b;
max-width: 90%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
+46 -55
View File
@@ -22,13 +22,10 @@ const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag'];
const resolveSizeKey = (doc) => const resolveSizeKey = (doc) =>
doc?.id || doc?.document_id || doc?.uuid || doc?.original_name || doc?.title || 'doc'; doc?.id || doc?.document_id || doc?.uuid || doc?.original_name || doc?.title || 'doc';
const CARD_WIDTH_MIN = 200; const CARD_MIN = 240;
const CARD_WIDTH_MAX = 260; const CARD_MAX = 340;
const CARD_HEIGHT_MIN = 240; const EMPTY_CARD_ASPECT = 1.4;
const CARD_HEIGHT_MAX = 340; const ZOOM_FILL_RATIO = 0.99;
const CARD_ASPECT_MIN = 0.8; // height / width
const CARD_ASPECT_MAX = 1.35;
const ZOOM_FILL_RATIO = 0.97;
const ZOOM_MIN_SCALE = 1.05; const ZOOM_MIN_SCALE = 1.05;
const ZOOM_MAX_SCALE = 5; const ZOOM_MAX_SCALE = 5;
const TAG_REMOVE_DISTANCE = 160; const TAG_REMOVE_DISTANCE = 160;
@@ -554,54 +551,40 @@ const SkeuomorphicWorkspace = ({
const intrinsic = resolvePreviewDimensions(doc); const intrinsic = resolvePreviewDimensions(doc);
if (intrinsic?.width && intrinsic?.height) { if (intrinsic?.width && intrinsic?.height) {
const ratio = intrinsic.height / intrinsic.width || 1;
width = intrinsic.width; width = intrinsic.width;
height = intrinsic.height; height = intrinsic.height;
// scale down to fit within max bounds
if (width > CARD_WIDTH_MAX || height > CARD_HEIGHT_MAX) {
const scale = Math.min(CARD_WIDTH_MAX / width, CARD_HEIGHT_MAX / height);
width *= scale;
height *= scale;
}
// scale up to satisfy minimum bounds while respecting ratio
if (width < CARD_WIDTH_MIN || height < CARD_HEIGHT_MIN) {
const scale = Math.max(CARD_WIDTH_MIN / width, CARD_HEIGHT_MIN / height);
width *= scale;
height *= scale;
if (width > CARD_WIDTH_MAX || height > CARD_HEIGHT_MAX) {
const downScale = Math.min(CARD_WIDTH_MAX / width, CARD_HEIGHT_MAX / height);
width *= downScale;
height *= downScale;
}
}
width = clamp(width, CARD_WIDTH_MIN, CARD_WIDTH_MAX);
height = clamp(height, CARD_HEIGHT_MIN, CARD_HEIGHT_MAX);
} else { } else {
const widthSeed = seededRandom(`${key}:width`); const seed = seededRandom(`${key}:size`);
const ratioSeed = seededRandom(`${key}:ratio`); width = CARD_MIN + seed * (Math.min(CARD_MAX, CARD_MAX / EMPTY_CARD_ASPECT) - CARD_MIN);
const ratio = CARD_ASPECT_MIN + ratioSeed * (CARD_ASPECT_MAX - CARD_ASPECT_MIN); width = clamp(width, CARD_MIN, Math.min(CARD_MAX, CARD_MAX / EMPTY_CARD_ASPECT));
width = CARD_WIDTH_MIN + widthSeed * (CARD_WIDTH_MAX - CARD_WIDTH_MIN); height = width * EMPTY_CARD_ASPECT;
height = width * ratio;
if (width > CARD_WIDTH_MAX || height > CARD_HEIGHT_MAX) {
const scale = Math.min(CARD_WIDTH_MAX / width, CARD_HEIGHT_MAX / height);
width *= scale;
height *= scale;
}
if (width < CARD_WIDTH_MIN || height < CARD_HEIGHT_MIN) {
const scale = Math.max(CARD_WIDTH_MIN / width, CARD_HEIGHT_MIN / height);
width *= scale;
height *= scale;
}
width = clamp(width, CARD_WIDTH_MIN, CARD_WIDTH_MAX);
height = clamp(height, CARD_HEIGHT_MIN, CARD_HEIGHT_MAX);
} }
if (!Number.isFinite(width) || width <= 0) {
width = CARD_MIN;
}
if (!Number.isFinite(height) || height <= 0) {
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 }; const size = { width, height };
docSizeMapRef.current.set(key, size); docSizeMapRef.current.set(key, size);
return size; return size;
@@ -636,8 +619,8 @@ const SkeuomorphicWorkspace = ({
(doc, cardWidth, cardHeight) => { (doc, cardWidth, cardHeight) => {
const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH; const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH;
const canvasHeight = canvasSize.height || DEFAULT_CANVAS_HEIGHT; const canvasHeight = canvasSize.height || DEFAULT_CANVAS_HEIGHT;
const safeCardWidth = cardWidth || CARD_WIDTH_MIN; const safeCardWidth = cardWidth || CARD_MIN;
const safeCardHeight = cardHeight || CARD_HEIGHT_MIN; const safeCardHeight = cardHeight || CARD_MIN;
const usableWidth = Math.max(canvasWidth - CANVAS_PADDING * 2, safeCardWidth); const usableWidth = Math.max(canvasWidth - CANVAS_PADDING * 2, safeCardWidth);
const usableHeight = Math.max(canvasHeight - CANVAS_PADDING * 2, safeCardHeight); const usableHeight = Math.max(canvasHeight - CANVAS_PADDING * 2, safeCardHeight);
const viewportTargetWidth = usableWidth * ZOOM_FILL_RATIO; const viewportTargetWidth = usableWidth * ZOOM_FILL_RATIO;
@@ -1406,6 +1389,7 @@ const SkeuomorphicWorkspace = ({
const tagsStyle = { '--tag-scale': inverseTagScale }; const tagsStyle = { '--tag-scale': inverseTagScale };
const previewUrl = resolvePreviewUrl(doc); const previewUrl = resolvePreviewUrl(doc);
const imageUrl = previewUrl; const imageUrl = previewUrl;
const hasPreview = Boolean(imageUrl);
const title = doc.title || doc.original_name || 'Document'; const title = doc.title || doc.original_name || 'Document';
const dragging = draggingId === doc.id; const dragging = draggingId === doc.id;
const tags = Array.isArray(doc.tags) ? doc.tags : []; const tags = Array.isArray(doc.tags) ? doc.tags : [];
@@ -1422,6 +1406,8 @@ const SkeuomorphicWorkspace = ({
if (dropActive) itemClasses.push('is-tag-target'); if (dropActive) itemClasses.push('is-tag-target');
if (dropPending) itemClasses.push('is-tag-pending'); if (dropPending) itemClasses.push('is-tag-pending');
if (!matchesFilter) itemClasses.push('is-filtered-out'); if (!matchesFilter) itemClasses.push('is-filtered-out');
const cardClasses = ['skeuo-item__card'];
if (!hasPreview) cardClasses.push('skeuo-item__card--empty');
const docTagTokens = docTagKeys.join(' '); const docTagTokens = docTagKeys.join(' ');
return ( return (
<div <div
@@ -1458,11 +1444,16 @@ const SkeuomorphicWorkspace = ({
}} }}
> >
<div className="skeuo-item__body" style={bodyStyle}> <div className="skeuo-item__body" style={bodyStyle}>
<div className="skeuo-item__card"> <div className={cardClasses.join(' ')}>
{imageUrl ? ( {hasPreview ? (
<img src={imageUrl} alt={title} /> <img src={imageUrl} alt={title} />
) : ( ) : (
<div className="skeuo-item__placeholder">DOC</div> <div className="skeuo-item__empty">
<div className="skeuo-item__placeholder">DOC</div>
<div className="skeuo-item__title" title={title}>
{title}
</div>
</div>
)} )}
</div> </div>
{tags.length > 0 && ( {tags.length > 0 && (