summaryrefslogblamecommitdiff
path: root/makima/frontend/src/hooks/useVersionHistory.ts
blob: f9d4122a197d347d695f684d626b83c8054af11c (plain) (tree)








































































































































                                                                                              
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<FileVersionSummary[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [selectedVersion, setSelectedVersion] = useState<FileVersion | null>(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<FileVersion | null> => {
      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<FileDetail | null> => {
      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,
  };
}