From 2faba0388f93d8e4fb86219eba7883b331d501ff Mon Sep 17 00:00:00 2001 From: soryu Date: Wed, 24 Dec 2025 05:45:22 +0000 Subject: Add versioning to files --- makima/frontend/src/hooks/useVersionHistory.ts | 137 +++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 makima/frontend/src/hooks/useVersionHistory.ts (limited to 'makima/frontend/src/hooks') diff --git a/makima/frontend/src/hooks/useVersionHistory.ts b/makima/frontend/src/hooks/useVersionHistory.ts new file mode 100644 index 0000000..f9d4122 --- /dev/null +++ b/makima/frontend/src/hooks/useVersionHistory.ts @@ -0,0 +1,137 @@ +import { useState, useCallback, useEffect } from "react"; +import { + listFileVersions, + getFileVersion, + restoreFileVersion, + type FileVersionSummary, + type FileVersion, + type FileDetail, + VersionConflictError, +} from "../lib/api"; + +export interface UseVersionHistoryOptions { + fileId: string | null; + currentVersion: number; +} + +export interface VersionHistoryState { + versions: FileVersionSummary[]; + loading: boolean; + error: string | null; + selectedVersion: FileVersion | null; + loadingVersion: boolean; +} + +export function useVersionHistory(options: UseVersionHistoryOptions) { + const { fileId, currentVersion } = options; + const [versions, setVersions] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedVersion, setSelectedVersion] = useState(null); + const [loadingVersion, setLoadingVersion] = useState(false); + const [restoring, setRestoring] = useState(false); + const [conflict, setConflict] = useState<{ expected: number; actual: number } | null>(null); + + // Fetch version list + const fetchVersions = useCallback(async () => { + if (!fileId) return; + + setLoading(true); + setError(null); + try { + const response = await listFileVersions(fileId); + setVersions(response.versions); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to fetch versions"); + } finally { + setLoading(false); + } + }, [fileId]); + + // Fetch a specific version's content + const fetchVersion = useCallback( + async (version: number): Promise => { + if (!fileId) return null; + + setLoadingVersion(true); + setError(null); + try { + const versionData = await getFileVersion(fileId, version); + setSelectedVersion(versionData); + return versionData; + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to fetch version"); + return null; + } finally { + setLoadingVersion(false); + } + }, + [fileId] + ); + + // Restore to a specific version (creates a new version with that content) + const restoreToVersion = useCallback( + async (targetVersion: number): Promise => { + if (!fileId) return null; + + setRestoring(true); + setError(null); + setConflict(null); + try { + const result = await restoreFileVersion(fileId, targetVersion, currentVersion); + // Refresh version list after restore + await fetchVersions(); + setSelectedVersion(null); + return result; + } catch (e) { + if (e instanceof VersionConflictError) { + setConflict({ + expected: e.expectedVersion, + actual: e.actualVersion, + }); + return null; + } + setError(e instanceof Error ? e.message : "Failed to restore version"); + return null; + } finally { + setRestoring(false); + } + }, + [fileId, currentVersion, fetchVersions] + ); + + // Clear selected version + const clearSelectedVersion = useCallback(() => { + setSelectedVersion(null); + }, []); + + // Clear conflict + const clearConflict = useCallback(() => { + setConflict(null); + }, []); + + // Fetch versions when fileId changes + useEffect(() => { + if (fileId) { + fetchVersions(); + } else { + setVersions([]); + setSelectedVersion(null); + } + }, [fileId, fetchVersions]); + + return { + versions, + loading, + error, + selectedVersion, + loadingVersion, + restoring, + conflict, + fetchVersions, + fetchVersion, + restoreToVersion, + clearSelectedVersion, + clearConflict, + }; +} -- cgit v1.2.3