From 4d5f7b4ccd5a92cf25c2010ba451dceae2a555f2 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Tue, 14 Oct 2025 10:27:13 +0200 Subject: [PATCH] desk view --- frontend/src/skeuomorphic_ws.css | 40 +++++++++++- frontend/src/skeuomorphic_ws.jsx | 101 ++++++++++++++----------------- 2 files changed, 84 insertions(+), 57 deletions(-) diff --git a/frontend/src/skeuomorphic_ws.css b/frontend/src/skeuomorphic_ws.css index 3ddf0a1..625a072 100644 --- a/frontend/src/skeuomorphic_ws.css +++ b/frontend/src/skeuomorphic_ws.css @@ -12,7 +12,7 @@ flex-direction: column; min-height: 0; position: relative; - background: #7a7c7c; + background-color: var(--surface-subtle); } .skeuo-header { @@ -21,7 +21,7 @@ justify-content: space-between; gap: 1rem; 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 { @@ -277,6 +277,20 @@ body.skeuo-cursor-remove * { 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 { } @@ -286,3 +300,25 @@ body.skeuo-cursor-remove * { letter-spacing: normal; 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; +} diff --git a/frontend/src/skeuomorphic_ws.jsx b/frontend/src/skeuomorphic_ws.jsx index 446d7ae..90e63d7 100644 --- a/frontend/src/skeuomorphic_ws.jsx +++ b/frontend/src/skeuomorphic_ws.jsx @@ -22,13 +22,10 @@ const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag']; const resolveSizeKey = (doc) => doc?.id || doc?.document_id || doc?.uuid || doc?.original_name || doc?.title || 'doc'; -const CARD_WIDTH_MIN = 200; -const CARD_WIDTH_MAX = 260; -const CARD_HEIGHT_MIN = 240; -const CARD_HEIGHT_MAX = 340; -const CARD_ASPECT_MIN = 0.8; // height / width -const CARD_ASPECT_MAX = 1.35; -const ZOOM_FILL_RATIO = 0.97; +const CARD_MIN = 240; +const CARD_MAX = 340; +const EMPTY_CARD_ASPECT = 1.4; +const ZOOM_FILL_RATIO = 0.99; const ZOOM_MIN_SCALE = 1.05; const ZOOM_MAX_SCALE = 5; const TAG_REMOVE_DISTANCE = 160; @@ -554,54 +551,40 @@ const SkeuomorphicWorkspace = ({ const intrinsic = resolvePreviewDimensions(doc); if (intrinsic?.width && intrinsic?.height) { - const ratio = intrinsic.height / intrinsic.width || 1; width = intrinsic.width; 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 { - const widthSeed = seededRandom(`${key}:width`); - const ratioSeed = seededRandom(`${key}:ratio`); - const ratio = CARD_ASPECT_MIN + ratioSeed * (CARD_ASPECT_MAX - CARD_ASPECT_MIN); - width = CARD_WIDTH_MIN + widthSeed * (CARD_WIDTH_MAX - CARD_WIDTH_MIN); - 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); + const seed = seededRandom(`${key}:size`); + 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; } + 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 }; docSizeMapRef.current.set(key, size); return size; @@ -636,8 +619,8 @@ const SkeuomorphicWorkspace = ({ (doc, cardWidth, cardHeight) => { const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH; const canvasHeight = canvasSize.height || DEFAULT_CANVAS_HEIGHT; - const safeCardWidth = cardWidth || CARD_WIDTH_MIN; - const safeCardHeight = cardHeight || CARD_HEIGHT_MIN; + const safeCardWidth = cardWidth || CARD_MIN; + const safeCardHeight = cardHeight || CARD_MIN; const usableWidth = Math.max(canvasWidth - CANVAS_PADDING * 2, safeCardWidth); const usableHeight = Math.max(canvasHeight - CANVAS_PADDING * 2, safeCardHeight); const viewportTargetWidth = usableWidth * ZOOM_FILL_RATIO; @@ -1406,6 +1389,7 @@ const SkeuomorphicWorkspace = ({ const tagsStyle = { '--tag-scale': inverseTagScale }; const previewUrl = resolvePreviewUrl(doc); const imageUrl = previewUrl; + const hasPreview = Boolean(imageUrl); const title = doc.title || doc.original_name || 'Document'; const dragging = draggingId === doc.id; const tags = Array.isArray(doc.tags) ? doc.tags : []; @@ -1422,6 +1406,8 @@ const SkeuomorphicWorkspace = ({ if (dropActive) itemClasses.push('is-tag-target'); if (dropPending) itemClasses.push('is-tag-pending'); if (!matchesFilter) itemClasses.push('is-filtered-out'); + const cardClasses = ['skeuo-item__card']; + if (!hasPreview) cardClasses.push('skeuo-item__card--empty'); const docTagTokens = docTagKeys.join(' '); return (
-
- {imageUrl ? ( +
+ {hasPreview ? ( {title} ) : ( -
DOC
+
+
DOC
+
+ {title} +
+
)}
{tags.length > 0 && (