mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-08-29 10:35:25 +00:00
load diagram in read only mode from previous version
This commit is contained in:
@@ -43,3 +43,9 @@ export async function getCommits(gistId, perPage = 20, page = 1) {
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getVersion(gistId, sha) {
|
||||
const res = await axios.get(`${baseUrl}/gists/${gistId}/${sha}`);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
@@ -257,6 +257,7 @@ function EditPopoverContent({ data }) {
|
||||
const { updateArea, deleteArea } = useAreas();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { t } = useTranslation();
|
||||
const {layout} = useLayout();
|
||||
const initialColorRef = useRef(data.color);
|
||||
|
||||
const handleColorPick = (color) => {
|
||||
@@ -302,6 +303,7 @@ function EditPopoverContent({ data }) {
|
||||
value={data.name}
|
||||
placeholder={t("name")}
|
||||
className="me-2"
|
||||
readOnly={layout.readOnly}
|
||||
onChange={(value) => updateArea(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -325,6 +327,7 @@ function EditPopoverContent({ data }) {
|
||||
/>
|
||||
<ColorPicker
|
||||
usePopover={true}
|
||||
readOnly={true}
|
||||
value={data.color}
|
||||
onChange={(color) => updateArea(data.id, { color })}
|
||||
onColorPick={(color) => handleColorPick(color)}
|
||||
|
@@ -279,6 +279,7 @@ export default function Canvas() {
|
||||
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
|
||||
if (panning.isPanning) {
|
||||
setTransform((prev) => ({
|
||||
...prev,
|
||||
@@ -294,6 +295,8 @@ export default function Canvas() {
|
||||
return;
|
||||
}
|
||||
|
||||
if(layout.readOnly) return;
|
||||
|
||||
if (linking) {
|
||||
setLinkingLine({
|
||||
...linkingLine,
|
||||
|
@@ -126,7 +126,7 @@ export default function ControlPanel({
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { transform, setTransform } = useTransform();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { setGistId } = useContext(IdContext);
|
||||
const { version, setGistId } = useContext(IdContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const invertLayout = (component) =>
|
||||
@@ -1751,7 +1751,7 @@ export default function ControlPanel({
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="text-xl me-1"
|
||||
className="text-xl flex items-center gap-1 me-1"
|
||||
onPointerEnter={(e) => e.isPrimary && setShowEditName(true)}
|
||||
onPointerLeave={(e) => e.isPrimary && setShowEditName(false)}
|
||||
onPointerDown={(e) => {
|
||||
@@ -1761,12 +1761,20 @@ export default function ControlPanel({
|
||||
}}
|
||||
onClick={() => setModal(MODAL.RENAME)}
|
||||
>
|
||||
{window.name.split(" ")[0] === "t" ? "Templates/" : "Diagrams/"}
|
||||
{title}
|
||||
<span>
|
||||
{(window.name.split(" ")[0] === "t"
|
||||
? "Templates/"
|
||||
: "Diagrams/") + title}
|
||||
</span>
|
||||
{version && (
|
||||
<Tag className="mt-1" color="blue" size="small">
|
||||
{version.substring(0, 7)}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
{(showEditName || modal === MODAL.RENAME) && <IconEdit />}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<div className="flex justify-start text-md select-none me-2">
|
||||
{Object.keys(menu).map((category) => (
|
||||
<Dropdown
|
||||
@@ -1879,17 +1887,25 @@ export default function ControlPanel({
|
||||
</Dropdown>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
{layout.readOnly && (
|
||||
<Tag size="small">
|
||||
{t("read_only")}
|
||||
</Tag>
|
||||
)}
|
||||
{!layout.readOnly && (
|
||||
<Tag
|
||||
size="small"
|
||||
type="tertiary"
|
||||
icon={
|
||||
saveState === State.LOADING || saveState === State.SAVING ? (
|
||||
type="light"
|
||||
prefixIcon={
|
||||
saveState === State.LOADING ||
|
||||
saveState === State.SAVING ? (
|
||||
<Spin size="small" />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{getState()}
|
||||
</Button>
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import { Input } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLayout } from "../../../hooks";
|
||||
|
||||
export default function Rename({ title, setTitle }) {
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
|
||||
return (
|
||||
<Input
|
||||
placeholder={t("name")}
|
||||
defaultValue={title}
|
||||
onChange={(v) => setTitle(v)}
|
||||
readonly={layout.readOnly}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import { InputNumber } from "@douyinfe/semi-ui";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import { useLayout, useSettings } from "../../../hooks";
|
||||
|
||||
export default function SetTableWidth() {
|
||||
const { layout } = useLayout();
|
||||
const { settings, setSettings } = useSettings();
|
||||
|
||||
return (
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
value={settings.tableWidth}
|
||||
readonly={layout.readOnly}
|
||||
onChange={(c) => {
|
||||
if (c < 180) return;
|
||||
setSettings((prev) => ({ ...prev, tableWidth: c }));
|
||||
|
@@ -116,7 +116,7 @@ export default function Share({ title, setModal }) {
|
||||
{!error && (
|
||||
<>
|
||||
<div className="flex gap-3">
|
||||
<Input value={url} size="large" />
|
||||
<Input value={url} size="large" readonly />
|
||||
</div>
|
||||
<div className="text-xs mt-2">{t("share_info")}</div>
|
||||
<div className="flex gap-2 mt-3">
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
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";
|
||||
@@ -7,15 +7,41 @@ import {
|
||||
IconChevronRight,
|
||||
IconChevronLeft,
|
||||
} from "@douyinfe/semi-icons";
|
||||
import { getCommits } from "../../../api/gists";
|
||||
import { getCommits, getVersion } from "../../../api/gists";
|
||||
import { DateTime } from "luxon";
|
||||
import { useAreas, useDiagram, useLayout } from "../../../hooks";
|
||||
|
||||
export default function Revisions({ open }) {
|
||||
const { gistId } = useContext(IdContext);
|
||||
const { gistId, setVersion } = useContext(IdContext);
|
||||
const { setAreas } = useAreas();
|
||||
const { setLayout } = useLayout();
|
||||
const { setTables, setRelationships } = useDiagram();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [revisions, setRevisions] = useState([]);
|
||||
|
||||
const loadVersion = useCallback(
|
||||
async (sha) => {
|
||||
try {
|
||||
const version = await getVersion(gistId, sha);
|
||||
setVersion(sha);
|
||||
setLayout((prev) => ({ ...prev, readOnly: true }));
|
||||
|
||||
const content = version.data.files["share.json"].content;
|
||||
|
||||
const parsedDiagram = JSON.parse(content);
|
||||
|
||||
setTables(parsedDiagram.tables);
|
||||
setRelationships(parsedDiagram.relationships);
|
||||
setAreas(parsedDiagram.subjectAreas);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
Toast.error("failed_to_load_diagram");
|
||||
}
|
||||
},
|
||||
[gistId, setTables, setRelationships, setAreas, setVersion, setLayout],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const getRevisions = async (gistId) => {
|
||||
try {
|
||||
@@ -62,9 +88,7 @@ export default function Revisions({ open }) {
|
||||
{revisions.map((r, i) => (
|
||||
<Steps.Step
|
||||
key={r.version}
|
||||
onClick={() => {
|
||||
alert(r.version);
|
||||
}}
|
||||
onClick={() => loadVersion(r.version)}
|
||||
title={
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span>{`${t("version")} ${revisions.length - i}`}</span>
|
||||
|
@@ -2,12 +2,13 @@ import { useState, useRef } from "react";
|
||||
import { Button, Input } from "@douyinfe/semi-ui";
|
||||
import ColorPicker from "../ColorPicker";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useAreas, useUndoRedo } from "../../../hooks";
|
||||
import { useAreas, useLayout, useUndoRedo } from "../../../hooks";
|
||||
import { Action, ObjectType } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AreaInfo({ data, i }) {
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
const { deleteArea, updateArea } = useAreas();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
@@ -53,6 +54,7 @@ export default function AreaInfo({ data, i }) {
|
||||
<Input
|
||||
value={data.name}
|
||||
placeholder={t("name")}
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) => updateArea(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -77,12 +79,14 @@ export default function AreaInfo({ data, i }) {
|
||||
<ColorPicker
|
||||
usePopover={true}
|
||||
value={data.color}
|
||||
readOnly={layout.readOnly}
|
||||
onChange={(color) => updateArea(i, { color })}
|
||||
onColorPick={(color) => handleColorPick(color)}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => deleteArea(i, true)}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -2,8 +2,9 @@ import { ColorPicker as SemiColorPicker } from "@douyinfe/semi-ui";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ColorPicker({
|
||||
children,
|
||||
value,
|
||||
readOnly,
|
||||
children,
|
||||
onChange,
|
||||
onColorPick,
|
||||
...props
|
||||
@@ -25,6 +26,7 @@ export default function ColorPicker({
|
||||
{...props}
|
||||
value={SemiColorPicker.colorStringToValue(value)}
|
||||
onChange={({ hex: color }) => {
|
||||
if (readOnly) return;
|
||||
setPickedColor(color);
|
||||
onChange(color);
|
||||
}}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { Button, Input, TagInput } from "@douyinfe/semi-ui";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useDiagram, useEnums, useUndoRedo } from "../../../hooks";
|
||||
import { useDiagram, useEnums, useLayout, useUndoRedo } from "../../../hooks";
|
||||
import { Action, ObjectType } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function EnumDetails({ data, i }) {
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
const { deleteEnum, updateEnum } = useEnums();
|
||||
const { tables, updateField } = useDiagram();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
@@ -18,6 +19,7 @@ export default function EnumDetails({ data, i }) {
|
||||
<div className="font-semibold">{t("Name")}: </div>
|
||||
<Input
|
||||
value={data.name}
|
||||
readonly={layout.readOnly}
|
||||
placeholder={t("name")}
|
||||
validateStatus={data.name.trim() === "" ? "error" : "default"}
|
||||
onChange={(value) => {
|
||||
@@ -71,7 +73,11 @@ export default function EnumDetails({ data, i }) {
|
||||
className="my-2"
|
||||
placeholder={t("values")}
|
||||
validateStatus={data.values.length === 0 ? "error" : "default"}
|
||||
onChange={(v) => updateEnum(i, { values: v })}
|
||||
onChange={(v) => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
updateEnum(i, { values: v });
|
||||
}}
|
||||
onFocus={() => setEditField({ values: data.values })}
|
||||
onBlur={() => {
|
||||
if (JSON.stringify(editField.values) === JSON.stringify(data.values))
|
||||
@@ -95,8 +101,9 @@ export default function EnumDetails({ data, i }) {
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
icon={<IconDeleteStroked />}
|
||||
disabled={layout.readOnly}
|
||||
onClick={() => deleteEnum(i, true)}
|
||||
>
|
||||
{t("delete")}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Button, Collapse } from "@douyinfe/semi-ui";
|
||||
import { useEnums } from "../../../hooks";
|
||||
import { useEnums, useLayout } from "../../../hooks";
|
||||
import { IconPlus } from "@douyinfe/semi-icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SearchBar from "./SearchBar";
|
||||
@@ -8,6 +8,7 @@ import Empty from "../Empty";
|
||||
|
||||
export default function EnumsTab() {
|
||||
const { enums, addEnum } = useEnums();
|
||||
const { layout } = useLayout();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -15,7 +16,12 @@ export default function EnumsTab() {
|
||||
<div className="flex gap-2">
|
||||
<SearchBar />
|
||||
<div>
|
||||
<Button icon={<IconPlus />} block onClick={() => addEnum()}>
|
||||
<Button
|
||||
block
|
||||
icon={<IconPlus />}
|
||||
onClick={() => addEnum()}
|
||||
disabled={layout.readOnly}
|
||||
>
|
||||
{t("add_enum")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@@ -3,10 +3,11 @@ import { Button, Collapse, TextArea, Input } from "@douyinfe/semi-ui";
|
||||
import ColorPicker from "../ColorPicker";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { Action, ObjectType } from "../../../data/constants";
|
||||
import { useNotes, useUndoRedo } from "../../../hooks";
|
||||
import { useLayout, useNotes, useUndoRedo } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function NoteInfo({ data, nid }) {
|
||||
const { layout } = useLayout();
|
||||
const { updateNote, deleteNote } = useNotes();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
@@ -62,6 +63,7 @@ export default function NoteInfo({ data, nid }) {
|
||||
<div className="font-semibold me-2 break-keep">{t("title")}:</div>
|
||||
<Input
|
||||
value={data.title}
|
||||
readonly={layout.readOnly}
|
||||
placeholder={t("title")}
|
||||
onChange={(value) => updateNote(data.id, { title: value })}
|
||||
onFocus={(e) => setEditField({ title: e.target.value })}
|
||||
@@ -90,6 +92,7 @@ export default function NoteInfo({ data, nid }) {
|
||||
placeholder={t("content")}
|
||||
value={data.content}
|
||||
autosize
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) => {
|
||||
const textarea = document.getElementById(`note_${data.id}`);
|
||||
textarea.style.height = "0";
|
||||
@@ -127,13 +130,15 @@ export default function NoteInfo({ data, nid }) {
|
||||
<div className="ms-2 flex flex-col gap-2">
|
||||
<ColorPicker
|
||||
usePopover={true}
|
||||
readOnly={layout.readOnly}
|
||||
value={data.color}
|
||||
onChange={(color) => updateNote(data.id, { color })}
|
||||
onColorPick={(color) => handleColorPick(color)}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => deleteNote(nid, true)}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Button, Collapse } from "@douyinfe/semi-ui";
|
||||
import { IconPlus } from "@douyinfe/semi-icons";
|
||||
import { useNotes, useSelect } from "../../../hooks";
|
||||
import { useLayout, useNotes, useSelect } from "../../../hooks";
|
||||
import Empty from "../Empty";
|
||||
import SearchBar from "./SearchBar";
|
||||
import NoteInfo from "./NoteInfo";
|
||||
@@ -10,6 +10,7 @@ export default function NotesTab() {
|
||||
const { notes, addNote } = useNotes();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -23,7 +24,12 @@ export default function NotesTab() {
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Button icon={<IconPlus />} block onClick={() => addNote()}>
|
||||
<Button
|
||||
block
|
||||
icon={<IconPlus />}
|
||||
onClick={() => addNote()}
|
||||
disabled={layout.readOnly}
|
||||
>
|
||||
{t("add_note")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@@ -18,7 +18,7 @@ import {
|
||||
Action,
|
||||
ObjectType,
|
||||
} from "../../../data/constants";
|
||||
import { useDiagram, useUndoRedo } from "../../../hooks";
|
||||
import { useDiagram, useLayout, useUndoRedo } from "../../../hooks";
|
||||
import i18n from "../../../i18n/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo, useState } from "react";
|
||||
@@ -38,6 +38,7 @@ export default function RelationshipInfo({ data }) {
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { tables, deleteRelationship, updateRelationship } = useDiagram();
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
const [editField, setEditField] = useState({});
|
||||
|
||||
const relValues = useMemo(() => {
|
||||
@@ -98,6 +99,8 @@ export default function RelationshipInfo({ data }) {
|
||||
};
|
||||
|
||||
const changeCardinality = (value) => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@@ -117,6 +120,8 @@ export default function RelationshipInfo({ data }) {
|
||||
};
|
||||
|
||||
const changeConstraint = (key, value) => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
const undoKey = `${key}Constraint`;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -145,6 +150,7 @@ export default function RelationshipInfo({ data }) {
|
||||
validateStatus={data.name.trim() === "" ? "error" : "default"}
|
||||
placeholder={t("name")}
|
||||
className="ms-2"
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) => updateRelationship(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -196,9 +202,10 @@ export default function RelationshipInfo({ data }) {
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
icon={<IconLoopTextStroked />}
|
||||
block
|
||||
icon={<IconLoopTextStroked />}
|
||||
onClick={swapKeys}
|
||||
disabled={layout.readOnly}
|
||||
>
|
||||
{t("swap")}
|
||||
</Button>
|
||||
@@ -285,9 +292,10 @@ export default function RelationshipInfo({ data }) {
|
||||
</Col>
|
||||
</Row>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
block
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => deleteRelationship(data.id)}
|
||||
>
|
||||
{t("delete")}
|
||||
|
@@ -9,13 +9,14 @@ import {
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { Action, ObjectType } from "../../../data/constants";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useDiagram, useUndoRedo } from "../../../hooks";
|
||||
import { useDiagram, useLayout, useUndoRedo } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { dbToTypes } from "../../../data/datatypes";
|
||||
import { databases } from "../../../data/databases";
|
||||
|
||||
export default function FieldDetails({ data, tid }) {
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
const { tables, database } = useDiagram();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { updateField, deleteField } = useDiagram();
|
||||
@@ -29,6 +30,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
className="my-2"
|
||||
placeholder={t("default_value")}
|
||||
value={data.default}
|
||||
readonly={layout.readOnly}
|
||||
disabled={dbToTypes[database][data.type].noDefault || data.increment}
|
||||
onChange={(value) => updateField(tid, data.id, { default: value })}
|
||||
onFocus={(e) => setEditField({ default: e.target.value })}
|
||||
@@ -67,7 +69,10 @@ export default function FieldDetails({ data, tid }) {
|
||||
addOnBlur
|
||||
className="my-2"
|
||||
placeholder={t("use_for_batch_input")}
|
||||
onChange={(v) => updateField(tid, data.id, { values: v })}
|
||||
onChange={(v) => {
|
||||
if (layout.readOnly) return;
|
||||
updateField(tid, data.id, { values: v });
|
||||
}}
|
||||
onFocus={() => setEditField({ values: data.values })}
|
||||
onBlur={() => {
|
||||
if (
|
||||
@@ -102,6 +107,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
className="my-2 w-full"
|
||||
placeholder={t("size")}
|
||||
value={data.size}
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) => updateField(tid, data.id, { size: value })}
|
||||
onFocus={(e) => setEditField({ size: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -138,6 +144,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
? "default"
|
||||
: "error"
|
||||
}
|
||||
readonly={layout.readOnly}
|
||||
value={data.size}
|
||||
onChange={(value) => updateField(tid, data.id, { size: value })}
|
||||
onFocus={(e) => setEditField({ size: e.target.value })}
|
||||
@@ -172,6 +179,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
placeholder={t("check")}
|
||||
value={data.check}
|
||||
disabled={data.increment}
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) => updateField(tid, data.id, { check: value })}
|
||||
onFocus={(e) => setEditField({ check: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -203,6 +211,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
<Checkbox
|
||||
value="unique"
|
||||
checked={data.unique}
|
||||
disabled={layout.readOnly}
|
||||
onChange={(checkedValues) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -233,7 +242,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
value="increment"
|
||||
checked={data.increment}
|
||||
disabled={
|
||||
!dbToTypes[database][data.type].canIncrement || data.isArray
|
||||
!dbToTypes[database][data.type].canIncrement || data.isArray || layout.readOnly
|
||||
}
|
||||
onChange={(checkedValues) => {
|
||||
setUndoStack((prev) => [
|
||||
@@ -270,6 +279,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
<Checkbox
|
||||
value="isArray"
|
||||
checked={data.isArray}
|
||||
disabled={layout.readOnly}
|
||||
onChange={(checkedValues) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -307,6 +317,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
<Checkbox
|
||||
value="unsigned"
|
||||
checked={data.unsigned}
|
||||
disabled={layout.readOnly}
|
||||
onChange={(checkedValues) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -343,6 +354,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
className="my-2"
|
||||
placeholder={t("comment")}
|
||||
value={data.comment}
|
||||
readonly={layout.readOnly}
|
||||
autosize
|
||||
rows={2}
|
||||
onChange={(value) => updateField(tid, data.id, { comment: value })}
|
||||
@@ -372,6 +384,7 @@ export default function FieldDetails({ data, tid }) {
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
block
|
||||
disabled={layout.readOnly}
|
||||
onClick={() => deleteField(data, tid)}
|
||||
>
|
||||
{t("delete")}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Action, ObjectType } from "../../../data/constants";
|
||||
import { Input, Button, Popover, Checkbox, Select } from "@douyinfe/semi-ui";
|
||||
import { IconMore, IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useDiagram, useUndoRedo } from "../../../hooks";
|
||||
import { useDiagram, useLayout, useUndoRedo } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
const { tables, updateTable } = useDiagram();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
@@ -22,6 +23,8 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
className="w-full"
|
||||
value={data.fields}
|
||||
onChange={(value) => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@@ -62,6 +65,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
<Input
|
||||
value={data.name}
|
||||
placeholder={t("name")}
|
||||
readonly={layout.readOnly}
|
||||
validateStatus={data.name.trim() === "" ? "error" : "default"}
|
||||
onFocus={() =>
|
||||
setEditField({
|
||||
@@ -106,6 +110,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
<Checkbox
|
||||
value="unique"
|
||||
checked={data.unique}
|
||||
disabled={layout.readOnly}
|
||||
onChange={(checkedValues) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -145,9 +150,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
></Checkbox>
|
||||
</div>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
block
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
|
@@ -2,7 +2,13 @@ import { useMemo, useState } from "react";
|
||||
import { Action, ObjectType } from "../../../data/constants";
|
||||
import { Input, Button, Popover, Select } from "@douyinfe/semi-ui";
|
||||
import { IconMore, IconKeyStroked } from "@douyinfe/semi-icons";
|
||||
import { useEnums, useDiagram, useTypes, useUndoRedo } from "../../../hooks";
|
||||
import {
|
||||
useEnums,
|
||||
useDiagram,
|
||||
useTypes,
|
||||
useUndoRedo,
|
||||
useLayout,
|
||||
} from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { dbToTypes } from "../../../data/datatypes";
|
||||
import { DragHandle } from "../../SortableList/DragHandle";
|
||||
@@ -12,6 +18,7 @@ export default function TableField({ data, tid, index, inherited }) {
|
||||
const { updateField } = useDiagram();
|
||||
const { types } = useTypes();
|
||||
const { enums } = useEnums();
|
||||
const { layout } = useLayout();
|
||||
const { tables, database } = useDiagram();
|
||||
const { t } = useTranslation();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
@@ -20,7 +27,7 @@ export default function TableField({ data, tid, index, inherited }) {
|
||||
|
||||
return (
|
||||
<div className="hover-1 my-2 flex gap-2 items-center">
|
||||
<DragHandle id={data.id} />
|
||||
<DragHandle readOnly={layout.readOnly} id={data.id} />
|
||||
|
||||
<div className="min-w-20 flex-1/3">
|
||||
<Input
|
||||
@@ -29,6 +36,7 @@ export default function TableField({ data, tid, index, inherited }) {
|
||||
validateStatus={
|
||||
data.name.trim() === "" || inherited ? "error" : "default"
|
||||
}
|
||||
readonly={layout.readOnly}
|
||||
placeholder={t("name")}
|
||||
onChange={(value) => updateField(tid, data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
@@ -77,6 +85,8 @@ export default function TableField({ data, tid, index, inherited }) {
|
||||
validateStatus={data.type === "" ? "error" : "default"}
|
||||
placeholder={t("type")}
|
||||
onChange={(value) => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
if (value === data.type) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -142,10 +152,12 @@ export default function TableField({ data, tid, index, inherited }) {
|
||||
|
||||
<div>
|
||||
<Button
|
||||
type={data.notNull ? "tertiary" : "primary"}
|
||||
title={t("nullable")}
|
||||
type={data.notNull ? "tertiary" : "primary"}
|
||||
theme={data.notNull ? "light" : "solid"}
|
||||
onClick={() => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@@ -172,11 +184,13 @@ export default function TableField({ data, tid, index, inherited }) {
|
||||
|
||||
<div>
|
||||
<Button
|
||||
type={data.primary ? "primary" : "tertiary"}
|
||||
title={t("primary")}
|
||||
theme={data.primary ? "solid" : "light"}
|
||||
type={data.primary ? "primary" : "tertiary"}
|
||||
icon={<IconKeyStroked />}
|
||||
onClick={() => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
|
@@ -9,7 +9,12 @@ import {
|
||||
} from "@douyinfe/semi-ui";
|
||||
import ColorPicker from "../ColorPicker";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useDiagram, useSaveState, useUndoRedo } from "../../../hooks";
|
||||
import {
|
||||
useDiagram,
|
||||
useLayout,
|
||||
useSaveState,
|
||||
useUndoRedo,
|
||||
} from "../../../hooks";
|
||||
import { Action, ObjectType, State, DB } from "../../../data/constants";
|
||||
import TableField from "./TableField";
|
||||
import IndexDetails from "./IndexDetails";
|
||||
@@ -21,6 +26,7 @@ export default function TableInfo({ data }) {
|
||||
const { tables, database } = useDiagram();
|
||||
const { t } = useTranslation();
|
||||
const [indexActiveKey, setIndexActiveKey] = useState("");
|
||||
const { layout } = useLayout();
|
||||
const { deleteTable, updateTable, setTables } = useDiagram();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { setSaveState } = useSaveState();
|
||||
@@ -82,6 +88,7 @@ export default function TableInfo({ data }) {
|
||||
validateStatus={data.name.trim() === "" ? "error" : "default"}
|
||||
placeholder={t("name")}
|
||||
className="ms-2"
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) => updateTable(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -139,6 +146,8 @@ export default function TableInfo({ data }) {
|
||||
.filter((t) => t.id !== data.id)
|
||||
.map((t) => ({ label: t.name, value: t.name }))}
|
||||
onChange={(value) => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@@ -204,6 +213,7 @@ export default function TableInfo({ data }) {
|
||||
<TextArea
|
||||
field="comment"
|
||||
value={data.comment}
|
||||
readonly={layout.readOnly}
|
||||
autosize
|
||||
placeholder={t("comment")}
|
||||
rows={1}
|
||||
@@ -238,6 +248,7 @@ export default function TableInfo({ data }) {
|
||||
<div className="flex justify-between items-center gap-1 mb-2">
|
||||
<ColorPicker
|
||||
usePopover={true}
|
||||
readOnly={layout.readOnly}
|
||||
value={data.color}
|
||||
onChange={(color) => updateTable(data.id, { color })}
|
||||
onColorPick={(color) => handleColorPick(color)}
|
||||
@@ -245,6 +256,7 @@ export default function TableInfo({ data }) {
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
block
|
||||
disabled={layout.readOnly}
|
||||
onClick={() => {
|
||||
setIndexActiveKey("1");
|
||||
setUndoStack((prev) => [
|
||||
@@ -277,6 +289,8 @@ export default function TableInfo({ data }) {
|
||||
{t("add_index")}
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
disabled={layout.readOnly}
|
||||
onClick={() => {
|
||||
const id = nanoid();
|
||||
setUndoStack((prev) => [
|
||||
@@ -312,13 +326,13 @@ export default function TableInfo({ data }) {
|
||||
],
|
||||
});
|
||||
}}
|
||||
block
|
||||
>
|
||||
{t("add_field")}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => deleteTable(data.id)}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Collapse, Button } from "@douyinfe/semi-ui";
|
||||
import { IconPlus } from "@douyinfe/semi-icons";
|
||||
import { useSelect, useDiagram, useSaveState } from "../../../hooks";
|
||||
import { useSelect, useDiagram, useSaveState, useLayout } from "../../../hooks";
|
||||
import { ObjectType, State } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DragHandle } from "../../SortableList/DragHandle";
|
||||
@@ -13,6 +13,7 @@ export default function TablesTab() {
|
||||
const { tables, addTable, setTables } = useDiagram();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
const { layout } = useLayout();
|
||||
const { setSaveState } = useSaveState();
|
||||
|
||||
return (
|
||||
@@ -20,7 +21,12 @@ export default function TablesTab() {
|
||||
<div className="flex gap-2">
|
||||
<SearchBar tables={tables} />
|
||||
<div>
|
||||
<Button icon={<IconPlus />} block onClick={() => addTable()}>
|
||||
<Button
|
||||
block
|
||||
icon={<IconPlus />}
|
||||
onClick={() => addTable()}
|
||||
disabled={layout.readOnly}
|
||||
>
|
||||
{t("add_table")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -60,6 +66,8 @@ export default function TablesTab() {
|
||||
}
|
||||
|
||||
function TableListItem({ table }) {
|
||||
const { layout } = useLayout();
|
||||
|
||||
return (
|
||||
<div id={`scroll_table_${table.id}`}>
|
||||
<Collapse.Panel
|
||||
@@ -67,7 +75,7 @@ function TableListItem({ table }) {
|
||||
header={
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<DragHandle id={table.id} />
|
||||
<DragHandle readOnly={layout.readOnly} id={table.id} />
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{table.name}
|
||||
</div>
|
||||
|
@@ -11,13 +11,20 @@ import {
|
||||
Popover,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconDeleteStroked, IconMore } from "@douyinfe/semi-icons";
|
||||
import { useUndoRedo, useTypes, useDiagram, useEnums } from "../../../hooks";
|
||||
import {
|
||||
useUndoRedo,
|
||||
useTypes,
|
||||
useDiagram,
|
||||
useEnums,
|
||||
useLayout,
|
||||
} from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { dbToTypes } from "../../../data/datatypes";
|
||||
|
||||
export default function TypeField({ data, tid, fid }) {
|
||||
const { types, updateType } = useTypes();
|
||||
const { enums } = useEnums();
|
||||
const { layout } = useLayout();
|
||||
const { database } = useDiagram();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
@@ -28,6 +35,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
<Col span={10}>
|
||||
<Input
|
||||
value={data.name}
|
||||
readonly={layout.readOnly}
|
||||
validateStatus={data.name === "" ? "error" : "default"}
|
||||
placeholder={t("name")}
|
||||
onChange={(value) =>
|
||||
@@ -86,6 +94,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
validateStatus={data.type === "" ? "error" : "default"}
|
||||
placeholder={t("type")}
|
||||
onChange={(value) => {
|
||||
if (layout.readOnly) return;
|
||||
if (value === data.type) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -160,13 +169,14 @@ export default function TypeField({ data, tid, fid }) {
|
||||
}
|
||||
className="my-2"
|
||||
placeholder={t("use_for_batch_input")}
|
||||
onChange={(v) =>
|
||||
onChange={(v) => {
|
||||
if (layout.readOnly) return;
|
||||
updateType(tid, {
|
||||
fields: types[tid].fields.map((e, id) =>
|
||||
id === fid ? { ...data, values: v } : e,
|
||||
),
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
onFocus={() => setEditField({ values: data.values })}
|
||||
onBlur={() => {
|
||||
if (
|
||||
@@ -202,6 +212,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
className="my-2 w-full"
|
||||
placeholder={t("size")}
|
||||
value={data.size}
|
||||
readonly={layout.readOnly}
|
||||
onChange={(value) =>
|
||||
updateType(tid, {
|
||||
fields: types[tid].fields.map((e, id) =>
|
||||
@@ -239,6 +250,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
<Input
|
||||
className="my-2 w-full"
|
||||
placeholder={t("set_precision")}
|
||||
readonly={layout.readOnly}
|
||||
validateStatus={
|
||||
/^\(\d+,\s*\d+\)$|^$/.test(data.size)
|
||||
? "default"
|
||||
@@ -277,9 +289,10 @@ export default function TypeField({ data, tid, fid }) {
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
block
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
|
@@ -10,11 +10,12 @@ import {
|
||||
Card,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconDeleteStroked, IconPlus } from "@douyinfe/semi-icons";
|
||||
import { useUndoRedo, useTypes, useDiagram } from "../../../hooks";
|
||||
import { useUndoRedo, useTypes, useDiagram, useLayout } from "../../../hooks";
|
||||
import TypeField from "./TypeField";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TypeInfo({ index, data }) {
|
||||
const { layout } = useLayout();
|
||||
const { deleteType, updateType } = useTypes();
|
||||
const { tables, updateField } = useDiagram();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
@@ -35,6 +36,7 @@ export default function TypeInfo({ index, data }) {
|
||||
<div className="text-md font-semibold break-keep">{t("name")}: </div>
|
||||
<Input
|
||||
value={data.name}
|
||||
readonly={layout.readOnly}
|
||||
validateStatus={data.name === "" ? "error" : "default"}
|
||||
placeholder={t("name")}
|
||||
className="ms-2"
|
||||
@@ -97,6 +99,7 @@ export default function TypeInfo({ index, data }) {
|
||||
field="comment"
|
||||
value={data.comment}
|
||||
autosize
|
||||
readonly={layout.readOnly}
|
||||
placeholder={t("comment")}
|
||||
rows={1}
|
||||
onChange={(value) =>
|
||||
@@ -130,6 +133,7 @@ export default function TypeInfo({ index, data }) {
|
||||
<Col span={12}>
|
||||
<Button
|
||||
icon={<IconPlus />}
|
||||
disabled={layout.readOnly}
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -162,10 +166,11 @@ export default function TypeInfo({ index, data }) {
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => deleteType(index)}
|
||||
block
|
||||
type="danger"
|
||||
disabled={layout.readOnly}
|
||||
icon={<IconDeleteStroked />}
|
||||
onClick={() => deleteType(index)}
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Collapse, Button, Popover } from "@douyinfe/semi-ui";
|
||||
import { IconPlus, IconInfoCircle } from "@douyinfe/semi-icons";
|
||||
import { useSelect, useDiagram, useTypes } from "../../../hooks";
|
||||
import { useSelect, useDiagram, useTypes, useLayout } from "../../../hooks";
|
||||
import { DB, ObjectType } from "../../../data/constants";
|
||||
import Searchbar from "./SearchBar";
|
||||
import Empty from "../Empty";
|
||||
@@ -10,6 +10,7 @@ import { useTranslation } from "react-i18next";
|
||||
export default function TypesTab() {
|
||||
const { types, addType } = useTypes();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { layout } = useLayout();
|
||||
const { database } = useDiagram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -18,7 +19,12 @@ export default function TypesTab() {
|
||||
<div className="flex gap-2">
|
||||
<Searchbar />
|
||||
<div>
|
||||
<Button icon={<IconPlus />} block onClick={() => addType()}>
|
||||
<Button
|
||||
block
|
||||
icon={<IconPlus />}
|
||||
onClick={() => addType()}
|
||||
disabled={layout.readOnly}
|
||||
>
|
||||
{t("add_type")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { IconHandle } from "@douyinfe/semi-icons";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
|
||||
export function DragHandle({ id }) {
|
||||
export function DragHandle({ id, readOnly }) {
|
||||
const { listeners } = useSortable({ id });
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex cursor-move items-center justify-center opacity-50 mt-0.5"
|
||||
{...listeners}
|
||||
className={`opacity-50 mt-0.5 ${readOnly ? "cursor-not-allowed" : "cursor-move"}`}
|
||||
{...(!readOnly && listeners)}
|
||||
>
|
||||
<IconHandle />
|
||||
</div>
|
||||
|
@@ -19,26 +19,33 @@ import {
|
||||
useEnums,
|
||||
} from "../hooks";
|
||||
import FloatingControls from "./FloatingControls";
|
||||
import { Modal, Tag } from "@douyinfe/semi-ui";
|
||||
import { Button, Modal, Tag } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { databases } from "../data/databases";
|
||||
import { isRtl } from "../i18n/utils/rtl";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { get } from "../api/gists";
|
||||
|
||||
export const IdContext = createContext({ gistId: "", setGistId: () => {} });
|
||||
export const IdContext = createContext({
|
||||
gistId: "",
|
||||
setGistId: () => {},
|
||||
version: "",
|
||||
setVersion: () => {},
|
||||
});
|
||||
|
||||
const SIDEPANEL_MIN_WIDTH = 384;
|
||||
|
||||
export default function WorkSpace() {
|
||||
const [id, setId] = useState(0);
|
||||
const [gistId, setGistId] = useState("");
|
||||
const [version, setVersion] = useState("");
|
||||
const [loadedFromGistId, setLoadedFromGistId] = useState("");
|
||||
const [title, setTitle] = useState("Untitled Diagram");
|
||||
const [resize, setResize] = useState(false);
|
||||
const [width, setWidth] = useState(SIDEPANEL_MIN_WIDTH);
|
||||
const [lastSaved, setLastSaved] = useState("");
|
||||
const [showSelectDbModal, setShowSelectDbModal] = useState(false);
|
||||
const [showRestoreModal, setShowRestoreModal] = useState(false);
|
||||
const [selectedDb, setSelectedDb] = useState("");
|
||||
const { layout } = useLayout();
|
||||
const { settings } = useSettings();
|
||||
@@ -67,8 +74,6 @@ export default function WorkSpace() {
|
||||
};
|
||||
|
||||
const save = useCallback(async () => {
|
||||
if (saveState !== State.SAVING) return;
|
||||
|
||||
const name = window.name.split(" ");
|
||||
const op = name[0];
|
||||
const saveAsDiagram = window.name === "" || op === "d" || op === "lt";
|
||||
@@ -163,7 +168,6 @@ export default function WorkSpace() {
|
||||
enums,
|
||||
gistId,
|
||||
loadedFromGistId,
|
||||
saveState,
|
||||
]);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
@@ -397,8 +401,12 @@ export default function WorkSpace() {
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (layout.readOnly) return;
|
||||
|
||||
if (saveState !== State.SAVING) return;
|
||||
|
||||
save();
|
||||
}, [saveState, save]);
|
||||
}, [saveState, layout, save]);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Editor | drawDB";
|
||||
@@ -408,7 +416,7 @@ export default function WorkSpace() {
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-hidden theme">
|
||||
<IdContext.Provider value={{ gistId, setGistId }}>
|
||||
<IdContext.Provider value={{ gistId, setGistId, version, setVersion }}>
|
||||
<ControlPanel
|
||||
diagramId={id}
|
||||
setDiagramId={setId}
|
||||
@@ -437,6 +445,18 @@ export default function WorkSpace() {
|
||||
<CanvasContextProvider className="h-full w-full">
|
||||
<Canvas saveState={saveState} setSaveState={setSaveState} />
|
||||
</CanvasContextProvider>
|
||||
{version && (
|
||||
<div
|
||||
className="absolute right-8 top-2"
|
||||
onClick={() => setShowRestoreModal(true)}
|
||||
>
|
||||
<Button
|
||||
icon={<i className="fa-solid fa-rotate-right mt-0.5"></i>}
|
||||
>
|
||||
Restore version
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!(layout.sidebar || layout.toolbar || layout.header) && (
|
||||
<div className="fixed right-5 bottom-4">
|
||||
<FloatingControls />
|
||||
@@ -493,6 +513,12 @@ export default function WorkSpace() {
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
visible={showRestoreModal}
|
||||
centered
|
||||
closable
|
||||
onCancel={() => setShowRestoreModal(false)}
|
||||
></Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ export default function LayoutContextProvider({ children }) {
|
||||
issues: true,
|
||||
toolbar: true,
|
||||
dbmlEditor: false,
|
||||
readOnly: false,
|
||||
});
|
||||
|
||||
return (
|
||||
|
Reference in New Issue
Block a user