From 6bf3317fae6ee3960a5ff7a31ec8e4b1e504131b Mon Sep 17 00:00:00 2001 From: 1ilit <1ilit@proton.me> Date: Sun, 24 Aug 2025 18:38:23 +0400 Subject: [PATCH] finalize versioning implementation, add pagination --- src/api/gists.js | 11 +- .../EditorHeader/SideSheet/Versions.jsx | 188 +++++++++++++----- src/i18n/locales/en.js | 6 +- 3 files changed, 146 insertions(+), 59 deletions(-) diff --git a/src/api/gists.js b/src/api/gists.js index 7fc1d90..1b7d45b 100644 --- a/src/api/gists.js +++ b/src/api/gists.js @@ -53,13 +53,18 @@ export async function getVersion(gistId, sha) { return res.data; } -export async function getCommitsWithFile(gistId, file, perPage = 20, page = 1) { +export async function getCommitsWithFile( + gistId, + file, + limit = 10, + cursor = null, +) { const res = await axios.get( `${baseUrl}/gists/${gistId}/file-versions/${file}`, { params: { - per_page: perPage, - page, + limit, + cursor, }, }, ); diff --git a/src/components/EditorHeader/SideSheet/Versions.jsx b/src/components/EditorHeader/SideSheet/Versions.jsx index abf415b..7ce9cbf 100644 --- a/src/components/EditorHeader/SideSheet/Versions.jsx +++ b/src/components/EditorHeader/SideSheet/Versions.jsx @@ -1,12 +1,8 @@ -import { useCallback, useContext, useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useState, useMemo } from "react"; import { IdContext } from "../../Workspace"; import { useTranslation } from "react-i18next"; -import { Button, IconButton, Spin, Steps, Tag, Toast } from "@douyinfe/semi-ui"; -import { - IconPlus, - IconChevronRight, - IconChevronLeft, -} from "@douyinfe/semi-icons"; +import { Button, Spin, Steps, Tag, Toast } from "@douyinfe/semi-ui"; +import { IconPlus } from "@douyinfe/semi-icons"; import { create, getCommitsWithFile, @@ -28,8 +24,24 @@ import { } from "../../../hooks"; import { databases } from "../../../data/databases"; +const LIMIT = 10; +const STORAGE_KEY = "versions_cache"; + +function loadCache() { + try { + const saved = localStorage.getItem(STORAGE_KEY); + return saved ? JSON.parse(saved) : {}; + } catch { + return {}; + } +} + +function saveCache(cache) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(cache)); +} + export default function Versions({ open, title, setTitle }) { - const { gistId, setGistId, setVersion } = useContext(IdContext); + const { gistId, setGistId, version, setVersion } = useContext(IdContext); const { areas, setAreas } = useAreas(); const { setLayout } = useLayout(); const { database, tables, relationships, setTables, setRelationships } = @@ -41,6 +53,11 @@ export default function Versions({ open, title, setTitle }) { const { t, i18n } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [versions, setVersions] = useState([]); + const [hasMore, setHasMore] = useState(false); + const [cursor, setCursor] = useState(null); + const [isRecording, setIsRecording] = useState(false); + + const cacheRef = useMemo(() => loadCache(), []); const diagramToString = useCallback(() => { return JSON.stringify({ @@ -66,6 +83,11 @@ export default function Versions({ open, title, setTitle }) { transform, ]); + const currentStep = useMemo(() => { + if (!version) return 0; + return versions.findIndex((v) => v.version === version); + }, [version, versions]); + const loadVersion = useCallback( async (sha) => { try { @@ -74,7 +96,6 @@ export default function Versions({ open, title, setTitle }) { setLayout((prev) => ({ ...prev, readOnly: true })); const content = version.data.files[VERSION_FILENAME].content; - const parsedDiagram = JSON.parse(content); setTables(parsedDiagram.tables); @@ -91,7 +112,6 @@ export default function Versions({ open, title, setTitle }) { setEnums(parsedDiagram.enums); } } catch (e) { - console.log(e); Toast.error("failed_to_load_diagram"); } }, @@ -111,24 +131,52 @@ export default function Versions({ open, title, setTitle }) { ); const getRevisions = useCallback( - async (gistId) => { + async (cursorParam) => { try { + if (!gistId) return; + setIsLoading(true); - const { data } = await getCommitsWithFile(gistId, VERSION_FILENAME); - setVersions( - data.filter((version) => version.change_status.total !== 0), + + const cached = cacheRef[gistId]; + if (cached && !cursorParam) { + setVersions(cached.versions); + setCursor(cached.cursor); + setHasMore(cached.hasMore); + setIsLoading(false); + return; + } + + const res = await getCommitsWithFile( + gistId, + VERSION_FILENAME, + LIMIT, + cursorParam, ); + + const newVersions = cursorParam ? [...versions, ...res.data] : res.data; + + setVersions(newVersions); + setHasMore(res.pagination.hasMore); + setCursor(res.pagination.cursor); + + cacheRef[gistId] = { + versions: newVersions, + cursor: res.pagination.cursor, + hasMore: res.pagination.hasMore, + }; + saveCache(cacheRef); } catch (e) { - console.log(e); Toast.error(t("oops_smth_went_wrong")); } finally { setIsLoading(false); } }, - [t], + [gistId, versions, t, cacheRef], ); const hasDiagramChanged = async () => { + if (!gistId) return true; + const previousVersion = await get(gistId); const previousDiagram = JSON.parse( previousVersion.data.files[VERSION_FILENAME]?.content, @@ -150,6 +198,7 @@ export default function Versions({ open, title, setTitle }) { const recordVersion = async () => { try { + setIsRecording(true); const hasChanges = await hasDiagramChanged(); if (!hasChanges) { Toast.info(t("no_changes_to_record")); @@ -160,63 +209,92 @@ export default function Versions({ open, title, setTitle }) { } else { const id = await create(VERSION_FILENAME, diagramToString()); setGistId(id); + console.log("new gist created", id); } - await getRevisions(gistId); + + delete cacheRef[gistId]; + saveCache(cacheRef); + + await getRevisions(); } catch (e) { Toast.error("failed_to_record_version"); + } finally { + setIsRecording(false); } }; + const onClearCache = () => { + delete cacheRef[gistId]; + saveCache(cacheRef); + Toast.success(t("cache_cleared")); + }; + useEffect(() => { if (gistId && open) { - getRevisions(gistId); + getRevisions(); } - }, [gistId, getRevisions, open]); + }, [gistId, open, getRevisions]); return (