Per organized-ideas.md §8: the storage layer (DiagramRepository etc.) was already renamed in an earlier phase; this finishes it everywhere else. - domain/types.ts: Project -> Diagram. domain/project.ts -> domain/diagram.ts (createEmptyProject -> createEmptyDiagram, default name "Untitled Diagram"). - domain/compatibility.ts, domain/bom.ts: Project param/type -> Diagram. - data/exportImport.ts: ProjectImportError -> DiagramImportError, projectToJson/downloadProjectFile/readProjectFile/normalizeProject -> their Diagram equivalents. - state/projectStore.ts -> state/diagramStore.ts: useProjectStore -> useDiagramStore, the `project` field -> `diagram`, newProject/renameProject/ importProject/applyRestoredProject -> *Diagram, restoredProjectUpdatedAt -> restoredDiagramUpdatedAt. - Every component updated to match, compiler-guided (tsc -b enumerated each remaining call site after the core rename, the same approach used for the earlier catalog-parameter refactor). - README updated for the terminology, and to match the repository class names (which had already been renamed but the README hadn't caught up). No backend/schema changes: the JSON shape stored in diagrams.data never changed, only TypeScript-side identifiers, so existing diagrams are unaffected. One SQL comment fixed for accuracy (no migration needed). Verified: tsc -b and oxlint clean; grepped src/ for any remaining Project/project reference (none) after the sweep.
75 lines
3.2 KiB
TypeScript
75 lines
3.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { useDiagramSnapshotStore } from '../../state/diagramSnapshotStore'
|
|
import { useDiagramStore } from '../../state/diagramStore'
|
|
import Modal from '../common/Modal'
|
|
|
|
function formatDateTime(iso: string): string {
|
|
return new Date(iso).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })
|
|
}
|
|
|
|
export default function VersionHistoryModal({ onClose }: { onClose: () => void }) {
|
|
const diagram = useDiagramStore((s) => s.diagram)
|
|
const access = useDiagramStore((s) => s.access)
|
|
const canRestore = access?.myPermission === 'owner' || access?.myPermission === 'edit'
|
|
const diagramId = diagram.id
|
|
|
|
const snapshots = useDiagramSnapshotStore((s) => s.snapshots)
|
|
const isLoaded = useDiagramSnapshotStore((s) => s.isLoaded)
|
|
const load = useDiagramSnapshotStore((s) => s.load)
|
|
const restore = useDiagramSnapshotStore((s) => s.restore)
|
|
const [restoringId, setRestoringId] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
load(diagramId)
|
|
}, [diagramId, load])
|
|
|
|
const handleRestore = async (snapshotId: string, createdAt: string) => {
|
|
const confirmed = window.confirm(
|
|
`Restore the diagram to how it was on ${formatDateTime(createdAt)}? If you've made changes since your last restore, the current state is saved as a new checkpoint first, so this can be undone.`,
|
|
)
|
|
if (!confirmed) return
|
|
setRestoringId(snapshotId)
|
|
try {
|
|
await restore(diagramId, snapshotId)
|
|
} finally {
|
|
setRestoringId(null)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Modal title={`Version History — "${diagram.name}"`} onClose={onClose} width="max-w-md">
|
|
<div className="space-y-1.5">
|
|
<p className="text-[11px] text-slate-500">
|
|
Checkpoints saved periodically while editing (up to the 50 most recent).
|
|
</p>
|
|
{!isLoaded && <p className="py-2 text-center text-xs italic text-slate-400">Loading…</p>}
|
|
{isLoaded && snapshots.length === 0 && (
|
|
<p className="py-2 text-center text-xs italic text-slate-400">No checkpoints yet.</p>
|
|
)}
|
|
{snapshots.map((s) => (
|
|
<div key={s.id} className="flex items-center justify-between gap-2 rounded border border-slate-200 bg-white px-2 py-1.5 text-xs">
|
|
<div className="min-w-0">
|
|
<div className="font-medium text-slate-700">{formatDateTime(s.createdAt)}</div>
|
|
{s.savedByUsername && <div className="text-[10px] text-slate-400">by {s.savedByUsername}</div>}
|
|
</div>
|
|
{canRestore && (
|
|
<button
|
|
onClick={() => handleRestore(s.id, s.createdAt)}
|
|
disabled={restoringId === s.id}
|
|
className="shrink-0 rounded bg-slate-100 px-2 py-1 text-[11px] font-medium text-slate-600 hover:bg-indigo-100 hover:text-indigo-700 disabled:cursor-not-allowed disabled:opacity-40"
|
|
>
|
|
Restore
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-4 flex justify-end border-t border-slate-100 pt-3">
|
|
<button onClick={onClose} className="rounded px-3 py-1.5 text-xs text-slate-600 hover:bg-slate-100">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</Modal>
|
|
)
|
|
}
|