mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-08-29 10:35:25 +00:00
add revisions sidesheet
This commit is contained in:
1058
package-lock.json
generated
1058
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@
|
|||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lexical": "^0.12.5",
|
"lexical": "^0.12.5",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"node-sql-parser": "^5.3.11",
|
"node-sql-parser": "^5.3.11",
|
||||||
"oracle-sql-parser": "^0.1.0",
|
"oracle-sql-parser": "^0.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@@ -32,3 +32,17 @@ export async function get(gistId) {
|
|||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCommits(gistId, perPage = 20, page = 1) {
|
||||||
|
const res = await octokit.request(
|
||||||
|
`GET /gists/${gistId}/commits?per_page=${perPage}&page=${page}`,
|
||||||
|
{
|
||||||
|
gist_id: gistId,
|
||||||
|
headers: {
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
@@ -1,17 +1,40 @@
|
|||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { IdContext } from "../../Workspace";
|
import { IdContext } from "../../Workspace";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, Spin } from "@douyinfe/semi-ui";
|
import { Button, IconButton, Spin, Steps, Tag, Toast } from "@douyinfe/semi-ui";
|
||||||
import { IconPlus } from "@douyinfe/semi-icons";
|
import {
|
||||||
|
IconPlus,
|
||||||
|
IconChevronRight,
|
||||||
|
IconChevronLeft,
|
||||||
|
} from "@douyinfe/semi-icons";
|
||||||
|
import * as gists from "../../../api/gists";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
export default function Revisions() {
|
export default function Revisions({ open }) {
|
||||||
const { gistId } = useContext(IdContext);
|
const { gistId } = useContext(IdContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [revisions, setRevisions] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(false);
|
const getRevisions = async (gistId) => {
|
||||||
}, []);
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const gist = await gists.getCommits(gistId);
|
||||||
|
setRevisions(gist);
|
||||||
|
console.log(gist);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
Toast.error(t("oops_smth_went_wrong"));
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (gistId && open) {
|
||||||
|
getRevisions(gistId);
|
||||||
|
}
|
||||||
|
}, [gistId, t, open]);
|
||||||
|
|
||||||
if (gistId && isLoading) {
|
if (gistId && isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -23,11 +46,44 @@ export default function Revisions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-5">
|
<div className="mx-5 relative h-full">
|
||||||
<Button icon={<IconPlus />} block onClick={() => {}}>
|
<div className="sticky top-0 z-10 sidesheet-theme pb-2 flex gap-2">
|
||||||
{t("record_version")}
|
<IconButton icon={<IconChevronLeft />} />
|
||||||
</Button>
|
<Button icon={<IconPlus />} block onClick={() => {}}>
|
||||||
|
{t("record_version")}
|
||||||
|
</Button>
|
||||||
|
<IconButton icon={<IconChevronRight />} />
|
||||||
|
</div>
|
||||||
{!gistId && <div className="my-3">{t("no_saved_revisions")}</div>}
|
{!gistId && <div className="my-3">{t("no_saved_revisions")}</div>}
|
||||||
|
{gistId && (
|
||||||
|
<div className="my-3 overflow-y-auto">
|
||||||
|
<Steps
|
||||||
|
direction="vertical"
|
||||||
|
type="basic"
|
||||||
|
onChange={(i) => console.log(i)}
|
||||||
|
>
|
||||||
|
{revisions.map((r, i) => (
|
||||||
|
<Steps.Step
|
||||||
|
key={r.version}
|
||||||
|
title={
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
|
<span>{`${t("version")} ${revisions.length - i}`}</span>
|
||||||
|
<Tag>{r.version.substring(0, 7)}</Tag>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<div>
|
||||||
|
<div>{`Commited on ${moment(
|
||||||
|
new Date(r.committed_at),
|
||||||
|
).format("MMMM Do YYYY, h:mm")}`}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
icon={<i className="text-sm fa-solid fa-asterisk" />}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ export default function Sidesheet({ type, onClose }) {
|
|||||||
case SIDESHEET.TODO:
|
case SIDESHEET.TODO:
|
||||||
return <Todo />;
|
return <Todo />;
|
||||||
case SIDESHEET.REVISIONS:
|
case SIDESHEET.REVISIONS:
|
||||||
return <Revisions />;
|
return <Revisions open={type !== SIDESHEET.NONE} />;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
195
src/components/EditorHeader/SideSheet/Versions.jsx
Normal file
195
src/components/EditorHeader/SideSheet/Versions.jsx
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import { useCallback, useContext, useEffect, useState } 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 {
|
||||||
|
create,
|
||||||
|
getCommitsWithFile,
|
||||||
|
getVersion,
|
||||||
|
patch,
|
||||||
|
VERSION_FILENAME,
|
||||||
|
} from "../../../api/gists";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import {
|
||||||
|
useAreas,
|
||||||
|
useDiagram,
|
||||||
|
useEnums,
|
||||||
|
useLayout,
|
||||||
|
useNotes,
|
||||||
|
useTransform,
|
||||||
|
useTypes,
|
||||||
|
} from "../../../hooks";
|
||||||
|
import { databases } from "../../../data/databases";
|
||||||
|
|
||||||
|
export default function Versions({ open, title, setTitle }) {
|
||||||
|
const { gistId, setVersion } = useContext(IdContext);
|
||||||
|
const { areas, setAreas } = useAreas();
|
||||||
|
const { setLayout } = useLayout();
|
||||||
|
const { database, tables, relationships, setTables, setRelationships } =
|
||||||
|
useDiagram();
|
||||||
|
const { notes, setNotes } = useNotes();
|
||||||
|
const { types, setTypes } = useTypes();
|
||||||
|
const { enums, setEnums } = useEnums();
|
||||||
|
const { transform } = useTransform();
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [versions, setVersions] = useState([]);
|
||||||
|
|
||||||
|
const diagramToString = useCallback(() => {
|
||||||
|
return JSON.stringify({
|
||||||
|
title,
|
||||||
|
tables,
|
||||||
|
relationships: relationships,
|
||||||
|
notes: notes,
|
||||||
|
subjectAreas: areas,
|
||||||
|
database: database,
|
||||||
|
...(databases[database].hasTypes && { types: types }),
|
||||||
|
...(databases[database].hasEnums && { enums: enums }),
|
||||||
|
transform: transform,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
areas,
|
||||||
|
notes,
|
||||||
|
tables,
|
||||||
|
relationships,
|
||||||
|
database,
|
||||||
|
title,
|
||||||
|
enums,
|
||||||
|
types,
|
||||||
|
transform,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const loadVersion = useCallback(
|
||||||
|
async (sha) => {
|
||||||
|
try {
|
||||||
|
const version = await getVersion(gistId, sha);
|
||||||
|
setVersion(sha);
|
||||||
|
setLayout((prev) => ({ ...prev, readOnly: true }));
|
||||||
|
|
||||||
|
const content = version.data.files[VERSION_FILENAME].content;
|
||||||
|
|
||||||
|
const parsedDiagram = JSON.parse(content);
|
||||||
|
|
||||||
|
setTables(parsedDiagram.tables);
|
||||||
|
setRelationships(parsedDiagram.relationships);
|
||||||
|
setAreas(parsedDiagram.subjectAreas);
|
||||||
|
setNotes(parsedDiagram.notes);
|
||||||
|
setTitle(parsedDiagram.title);
|
||||||
|
|
||||||
|
if (databases[database].hasTypes) {
|
||||||
|
setTypes(parsedDiagram.types);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databases[database].hasEnums) {
|
||||||
|
setEnums(parsedDiagram.enums);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
Toast.error("failed_to_load_diagram");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
gistId,
|
||||||
|
setTables,
|
||||||
|
setRelationships,
|
||||||
|
setAreas,
|
||||||
|
setVersion,
|
||||||
|
setLayout,
|
||||||
|
database,
|
||||||
|
setNotes,
|
||||||
|
setTypes,
|
||||||
|
setEnums,
|
||||||
|
setTitle,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getRevisions = useCallback(
|
||||||
|
async (gistId) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const { data } = await getCommitsWithFile(gistId, VERSION_FILENAME);
|
||||||
|
setVersions(
|
||||||
|
data.filter((version) => version.change_status.total !== 0),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
Toast.error(t("oops_smth_went_wrong"));
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordVersion = async () => {
|
||||||
|
try {
|
||||||
|
if (gistId) {
|
||||||
|
await patch(gistId, VERSION_FILENAME, diagramToString());
|
||||||
|
} else {
|
||||||
|
await create(VERSION_FILENAME, diagramToString());
|
||||||
|
}
|
||||||
|
await getRevisions(gistId);
|
||||||
|
} catch (e) {
|
||||||
|
Toast.error("failed_to_record_version");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (gistId && open) {
|
||||||
|
getRevisions(gistId);
|
||||||
|
}
|
||||||
|
}, [gistId, getRevisions, open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-5 relative h-full">
|
||||||
|
<div className="sticky top-0 z-10 sidesheet-theme pb-2 flex gap-2">
|
||||||
|
<IconButton icon={<IconChevronLeft />} title="Previous" />
|
||||||
|
<Button icon={<IconPlus />} block onClick={recordVersion}>
|
||||||
|
{t("record_version")}
|
||||||
|
</Button>
|
||||||
|
<IconButton icon={<IconChevronRight />} title="Next" />
|
||||||
|
</div>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="text-blue-500 text-center mt-3">
|
||||||
|
<Spin size="middle" />
|
||||||
|
<div>{t("loading")}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{(!gistId || !versions.length) && (
|
||||||
|
<div className="my-3">{t("no_saved_versions")}</div>
|
||||||
|
)}
|
||||||
|
{gistId && (
|
||||||
|
<div className="my-3 overflow-y-auto">
|
||||||
|
<Steps direction="vertical" type="basic">
|
||||||
|
{versions.map((r, i) => (
|
||||||
|
<Steps.Step
|
||||||
|
key={r.version}
|
||||||
|
onClick={() => loadVersion(r.version)}
|
||||||
|
title={
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
|
<span>{`${t("version")} ${versions.length - i}`}</span>
|
||||||
|
<Tag>{r.version.substring(0, 7)}</Tag>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
description={`${t("commited_at")} ${DateTime.fromISO(
|
||||||
|
r.committed_at,
|
||||||
|
)
|
||||||
|
.setLocale(i18n.language)
|
||||||
|
.toLocaleString(DateTime.DATETIME_MED)}`}
|
||||||
|
icon={<i className="text-sm fa-solid fa-asterisk" />}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -260,6 +260,8 @@ const en = {
|
|||||||
many_side_label: "Many(n) side label",
|
many_side_label: "Many(n) side label",
|
||||||
revisions: "Revisions",
|
revisions: "Revisions",
|
||||||
no_saved_revisions: "No saved revisions",
|
no_saved_revisions: "No saved revisions",
|
||||||
|
version: "Version",
|
||||||
|
record_version: "Record version",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -67,7 +67,12 @@
|
|||||||
background-color: rgba(var(--semi-blue-6), 1);
|
background-color: rgba(var(--semi-blue-6), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.semi-spin-wrapper {
|
.semi-steps-item-content,
|
||||||
|
.semi-steps-item-title {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.semi-spin-wrapper {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user