diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index feb42149f7..ea4f0399b9 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -3977,6 +3977,7 @@ refresh = "Refresh from server" remove = "Delete" removeVersion = "Remove this version" rename = "Rename" +renamed = "Renamed" renameFolder = "Rename folder" resizeFolderTree = "Resize folder tree (arrow keys, Shift for bigger steps; double-click to auto-fit)" save = "Save" @@ -4079,9 +4080,7 @@ count = "Files" folder = "Folder" labels = "Labels" modified = "Modified" -name = "Name" size = "Size" -toolHistoryAtVersion = "Cumulative tool chain" totalSize = "Total size" type = "Type" versionHistory = "Version journey" diff --git a/frontend/editor/src/core/components/filesPage/FilesPage.css b/frontend/editor/src/core/components/filesPage/FilesPage.css index 9de4a276aa..4815a1b31e 100644 --- a/frontend/editor/src/core/components/filesPage/FilesPage.css +++ b/frontend/editor/src/core/components/filesPage/FilesPage.css @@ -956,178 +956,204 @@ like a commit graph; clicking a row's summary toggles its expanded detail (cumulative tool chain, full meta). Long chains (> 6) collapse the middle behind a "Show N earlier versions" button. */ +/* Version Timeline styling */ .files-page-details-version-timeline { display: flex; flex-direction: column; - gap: 0.4rem; - padding: 0.55rem 0.7rem 0.7rem; - background: var(--c-surface); - border: 1px solid var(--c-border-subtle); - border-radius: 0.5rem; + gap: 0.6rem; + padding: 0.5rem 0; + background: transparent; } + .files-page-details-version-timeline-label { display: flex; align-items: center; - gap: 0.35rem; - font-size: 0.72rem; + gap: 0.4rem; + font-size: 0.75rem; + font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--c-text-subtle); + margin-bottom: 0.85rem; } + .files-page-details-version-timeline-count { margin-left: auto; font-weight: 600; - color: var(--c-text-muted, var(--c-text-subtle)); + color: var(--c-primary); text-transform: none; letter-spacing: 0; } + .files-page-details-version-timeline-list { - list-style: none; - margin: 0; - padding: 0; + list-style: none !important; + margin: 0 !important; + padding: 1.1rem 0 0 0 !important; display: flex; flex-direction: column; + gap: 1.25rem; } + .files-page-details-version-timeline-row, .files-page-details-version-timeline-ellipsis { display: flex; - gap: 0.6rem; - padding: 0.15rem 0; + gap: 0.85rem; + padding: 0; position: relative; + list-style: none !important; } + +.files-page-details-version-timeline-row::before, +.files-page-details-version-timeline-ellipsis::before { + content: none !important; +} + .files-page-details-version-timeline-rail { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; - width: 0.8rem; - padding-top: 0.45rem; + width: 1rem; + padding-top: 0.85rem; } + .files-page-details-version-timeline-rail-dot { - width: 0.55rem; - height: 0.55rem; + width: 0.65rem; + height: 0.65rem; border-radius: 50%; background: var(--c-bg-raised); border: 2px solid var(--c-border-strong, var(--c-border-subtle)); - z-index: 1; + z-index: 2; flex-shrink: 0; + transition: all 0.2s ease; } + .files-page-details-version-timeline-rail-dot.is-active { background: var(--c-primary); border-color: var(--c-primary); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-primary) 25%, transparent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-primary) 30%, transparent); } + .files-page-details-version-timeline-rail-dot.is-ellipsis { - width: 0.35rem; - height: 0.35rem; + width: 0.4rem; + height: 0.4rem; background: var(--c-text-subtle); border-color: transparent; } + .files-page-details-version-timeline-rail-line { width: 2px; flex: 1; background: var(--c-border-subtle); - min-height: 0.6rem; - margin-top: 2px; + min-height: 1.25rem; + margin-top: 6px; } + .files-page-details-version-timeline-body { flex: 1; min-width: 0; display: flex; flex-direction: column; - gap: 0.2rem; - padding: 0.25rem 0.3rem 0.4rem; - border-radius: 0.35rem; - transition: background-color 0.12s ease; + gap: 0.5rem; + padding: 1rem 1.25rem; + border-radius: 0.65rem; + background: var(--c-surface); + border: 1px solid var(--c-border-subtle); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); + transition: + border-color 0.15s ease, + background-color 0.15s ease, + box-shadow 0.15s ease; } + +.files-page-details-version-timeline-body:hover { + border-color: var(--c-border-strong, var(--c-border-subtle)); +} + .files-page-details-version-timeline-row.is-active .files-page-details-version-timeline-body { - background: color-mix(in srgb, var(--c-primary) 10%, transparent); + border-color: color-mix(in srgb, var(--c-primary) 40%, transparent); + background: color-mix(in srgb, var(--c-primary) 5%, var(--c-surface)); + box-shadow: 0 2px 6px color-mix(in srgb, var(--c-primary) 12%, transparent); } -.files-page-details-version-timeline-summary { + +.files-page-details-version-timeline-card-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; +} + +.files-page-details-version-timeline-tool-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--c-text); +} + +.files-page-details-version-timeline-card-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding-top: 0.1rem; +} + +.files-page-details-version-timeline-expand-btn { appearance: none; background: none; border: 0; padding: 0; - margin: 0; + cursor: pointer; display: flex; align-items: center; - gap: 0.4rem; - cursor: pointer; - text-align: left; - color: inherit; - font: inherit; } -.files-page-details-version-timeline-summary:hover - .files-page-details-version-timeline-chevron { + +.files-page-details-version-timeline-expand-btn:hover span { color: var(--c-text); } -.files-page-details-version-timeline-delta { - font-size: 0.82rem; - color: var(--c-text); - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: inline-flex; - align-items: baseline; - gap: 0.25rem; -} -.files-page-details-version-timeline-delta.is-origin { - font-weight: 400; - color: var(--c-text-subtle); - font-style: italic; -} -.files-page-details-version-timeline-delta-plus { - color: var(--c-primary); - font-weight: 700; -} -.files-page-details-version-timeline-spacer { - flex: 1; -} + .files-page-details-version-timeline-chevron { color: var(--c-text-subtle); transition: transform 0.15s ease; } + .files-page-details-version-timeline-chevron.is-expanded { transform: rotate(180deg); - color: var(--c-text); -} -.files-page-details-version-timeline-meta-line { - display: flex; - align-items: center; - gap: 0.35rem; - font-size: 0.7rem; - color: var(--c-text-subtle); + color: var(--c-primary); } + .files-page-details-version-timeline-expanded { display: flex; flex-direction: column; gap: 0.4rem; - margin-top: 0.45rem; + margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px dashed var(--c-border-subtle); } + .files-page-details-version-timeline-toolchain { display: flex; flex-direction: column; gap: 0.2rem; } + .files-page-details-version-timeline-toolchain-label { font-size: 0.65rem; color: var(--c-text-subtle); text-transform: uppercase; letter-spacing: 0.05em; } + .files-page-details-version-timeline-ellipsis-btn, .files-page-details-version-timeline-collapse-btn { appearance: none; background: none; border: 1px dashed var(--c-border-subtle); - border-radius: 0.3rem; - padding: 0.25rem 0.5rem; + border-radius: 0.4rem; + padding: 0.35rem 0.7rem; margin: 0.1rem 0; - font-size: 0.72rem; + font-size: 0.75rem; color: var(--c-text-subtle); cursor: pointer; text-align: left; @@ -1135,11 +1161,13 @@ border-color 0.12s ease, color 0.12s ease; } + .files-page-details-version-timeline-ellipsis-btn:hover, .files-page-details-version-timeline-collapse-btn:hover { color: var(--c-text); - border-color: var(--c-border-strong, var(--c-text-subtle)); + border-color: var(--c-primary); } + .files-page-details-version-timeline-collapse-btn { align-self: flex-start; margin-top: 0.2rem; diff --git a/frontend/editor/src/core/components/filesPage/VersionHistoryModal.tsx b/frontend/editor/src/core/components/filesPage/VersionHistoryModal.tsx index 077924155d..2bef30c763 100644 --- a/frontend/editor/src/core/components/filesPage/VersionHistoryModal.tsx +++ b/frontend/editor/src/core/components/filesPage/VersionHistoryModal.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Center, Loader, Modal, Text } from "@mantine/core"; +import HistoryIcon from "@mui/icons-material/History"; +import { Center, Group, Loader, Modal, Text } from "@mantine/core"; import type { FileId } from "@app/types/file"; import type { StirlingFileStub } from "@app/types/fileContext"; @@ -99,7 +100,17 @@ export function VersionHistoryModal({ onClose={onClose} centered size="md" - title={t("filesPage.field.versionHistory", "Version journey")} + title={ + + + + {t("filesPage.field.versionHistory", "Version journey")} + + + } > {loading ? (
@@ -111,6 +122,7 @@ export function VersionHistoryModal({ currentId={file.id} onAddToWorkspace={handleAddToWorkspace} onRemove={handleRemove} + hideHeader /> ) : ( diff --git a/frontend/editor/src/core/components/filesPage/VersionTimeline.tsx b/frontend/editor/src/core/components/filesPage/VersionTimeline.tsx index 47495fe61b..049fc2365a 100644 --- a/frontend/editor/src/core/components/filesPage/VersionTimeline.tsx +++ b/frontend/editor/src/core/components/filesPage/VersionTimeline.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Badge, Menu } from "@mantine/core"; +import { Badge, Group, Menu, Text } from "@mantine/core"; import { Button } from "@app/ui/Button"; import { ActionIcon } from "@app/ui/ActionIcon"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; @@ -8,16 +8,14 @@ import DeleteIcon from "@mui/icons-material/Delete"; import DownloadIcon from "@mui/icons-material/Download"; import HistoryIcon from "@mui/icons-material/History"; import MoreVertIcon from "@mui/icons-material/MoreVert"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import { FileId, ToolOperation } from "@app/types/file"; import { ToolId } from "@app/types/toolId"; import { StirlingFileStub } from "@app/types/fileContext"; import { formatFileSize, getFileDate } from "@app/utils/fileUtils"; import { downloadFileFromStorage } from "@app/utils/downloadUtils"; -import ToolChain from "@app/components/shared/ToolChain"; -/** Small label/value row; shared with FileDetailsPanel. */ +/** Small label/value row with crisp flex alignment and colon separation. */ export function DetailField({ label, value, @@ -26,9 +24,31 @@ export function DetailField({ value: string; }) { return ( -
- {label} - {value} +
+ + {label}: + + + {value} +
); } @@ -60,7 +80,7 @@ export interface VersionTimelineProps { hideHeader?: boolean; } -/** Version timeline with per-row tool deltas and collapse-when-long. */ +/** Clean, spacious version timeline with minimal clutter. */ export function VersionTimeline({ chain, currentId, @@ -69,7 +89,6 @@ export function VersionTimeline({ hideHeader = false, }: VersionTimelineProps) { const { t } = useTranslation(); - const [expandedIds, setExpandedIds] = useState>(new Set()); const [showAllCollapsed, setShowAllCollapsed] = useState(false); // Newest-first ordering. @@ -113,15 +132,6 @@ export function VersionTimeline({ return [...head, { kind: "ellipsis", hidden }, ...tail]; }, [collapsible, showAllCollapsed, ordered]); - const toggleExpand = (id: FileId) => { - setExpandedIds((prev) => { - const next = new Set(prev); - if (next.has(id)) next.delete(id); - else next.add(id); - return next; - }); - }; - return (
{!hideHeader && ( @@ -135,7 +145,10 @@ export function VersionTimeline({
)} -
    +
      {rows.map((row, idx) => { const isLast = idx === rows.length - 1; if (row.kind === "ellipsis") { @@ -143,6 +156,7 @@ export function VersionTimeline({
    • @@ -152,7 +166,7 @@ export function VersionTimeline({
      -
      - {formatFileSize(v.size)} - {v.lastModified ? ( - <> - · - - {getFileDate({ lastModified: v.lastModified })} - - - ) : null} - {/* Kebab on every row - the original/active version also - needs download + open-in-workspace. */} - - - - e.stopPropagation()} - > - - - - - } - onClick={() => onAddToWorkspace([v.id])} - > - {t( - "filesPage.openVersionInWorkspace", - "Open in workspace", - )} - - } - onClick={() => { - void downloadFileFromStorage(v); - }} - > - {t( - "filesPage.downloadVersion", - "Download this version", - )} - - - } - onClick={() => onRemove([v.id])} - > - {t("filesPage.removeVersion", "Remove this version")} - - - -
      - {isExpanded && ( - // Filename + full cumulative tool chain. -
      - - {v.toolHistory && v.toolHistory.length > 0 && ( -
      - - {t( - "filesPage.field.toolHistoryAtVersion", - "Cumulative tool chain", + + {delta ? ( + + ) : ( + t("filesPage.versionOrigin", "Original upload") + )} + + + + {!isActive && ( + + + - -
      - )} -
      + onClick={(e) => e.stopPropagation()} + > + + + + + } + onClick={() => onAddToWorkspace([v.id])} + > + {t( + "filesPage.openVersionInWorkspace", + "Open in workspace", + )} + + } + onClick={() => { + void downloadFileFromStorage(v); + }} + > + {t( + "filesPage.downloadVersion", + "Download this version", + )} + + + } + onClick={() => onRemove([v.id])} + > + {t("filesPage.removeVersion", "Remove this version")} + + + + )} +
+ + {/* Quiet Meta Line: File Size · Date */} + + {formatFileSize(v.size)} + {v.lastModified && ( + <> · {getFileDate({ lastModified: v.lastModified })} + )} + + + {/* Show filename ONLY if original upload or if name changed */} + {(isOriginal || nameChanged) && ( + + {nameChanged + ? `${t("filesPage.renamed", "Renamed")}: ` + : `${t("filesPage.file", "File")}: `} + + {v.name} + + )} ); })} - + {collapsible && showAllCollapsed && (