diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx
index 9ece8c1..e4c0d4b 100644
--- a/src/components/EditorHeader/ControlPanel.jsx
+++ b/src/components/EditorHeader/ControlPanel.jsx
@@ -51,13 +51,13 @@ import {
useTables,
useUndoRedo,
useSelect,
+ useSaveState,
+ useTypes,
+ useNotes,
+ useAreas,
} from "../../hooks";
import { enterFullscreen } from "../../utils/fullscreen";
import { dataURItoBlob } from "../../utils/utils";
-import useAreas from "../../hooks/useAreas";
-import useNotes from "../../hooks/useNotes";
-import useTypes from "../../hooks/useTypes";
-import useSaveState from "../../hooks/useSaveState";
import { IconAddArea, IconAddNote, IconAddTable } from "../../icons";
import LayoutDropdown from "./LayoutDropdown";
import Sidesheet from "./SideSheet/Sidesheet";
diff --git a/src/components/EditorSidePanel/EnumsTab/EnumDetails.jsx b/src/components/EditorSidePanel/EnumsTab/EnumDetails.jsx
new file mode 100644
index 0000000..96f2e7a
--- /dev/null
+++ b/src/components/EditorSidePanel/EnumsTab/EnumDetails.jsx
@@ -0,0 +1,85 @@
+import { useState } from "react";
+import { Button, Input, TagInput } from "@douyinfe/semi-ui";
+import { IconDeleteStroked } from "@douyinfe/semi-icons";
+import { useEnums, useUndoRedo } from "../../../hooks";
+import { Action, ObjectType } from "../../../data/constants";
+import { useTranslation } from "react-i18next";
+
+export default function EnumDetails({ data, i }) {
+ const { t } = useTranslation();
+ const { deleteEnum, updateEnum } = useEnums();
+ const { setUndoStack, setRedoStack } = useUndoRedo();
+ const [editField, setEditField] = useState({});
+
+ return (
+
+
+
updateEnum(i, { values: v })}
+ onFocus={() => setEditField({ values: data.values })}
+ onBlur={() => {
+ if (JSON.stringify(editField.values) === JSON.stringify(data.values))
+ return;
+ setUndoStack((prev) => [
+ ...prev,
+ {
+ action: Action.EDIT,
+ element: ObjectType.TABLE,
+ component: "field",
+ eid: i,
+ undo: editField,
+ redo: { values: data.values },
+ message: t("edit_enum", {
+ enumName: data.name,
+ extra: "[values]",
+ }),
+ },
+ ]);
+ setRedoStack([]);
+ }}
+ />
+ }
+ type="danger"
+ onClick={() => deleteEnum(i, true)}
+ >
+ {t("delete")}
+
+
+ );
+}
diff --git a/src/components/EditorSidePanel/EnumsTab/EnumsTab.jsx b/src/components/EditorSidePanel/EnumsTab/EnumsTab.jsx
new file mode 100644
index 0000000..69d1ef9
--- /dev/null
+++ b/src/components/EditorSidePanel/EnumsTab/EnumsTab.jsx
@@ -0,0 +1,39 @@
+import { Button, Collapse } from "@douyinfe/semi-ui";
+import { useEnums } from "../../../hooks";
+import { IconPlus } from "@douyinfe/semi-icons";
+import { useTranslation } from "react-i18next";
+import SearchBar from "./SearchBar";
+import EnumDetails from "./EnumDetails";
+
+export default function EnumsTab() {
+ const { enums, addEnum } = useEnums();
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+ } block onClick={() => addEnum()}>
+ {t("add_enum")}
+
+
+
+
+ {enums.map((e, i) => (
+
+ {e.name}
+
+ }
+ itemKey={`${i}`}
+ >
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/EditorSidePanel/EnumsTab/SearchBar.jsx b/src/components/EditorSidePanel/EnumsTab/SearchBar.jsx
new file mode 100644
index 0000000..0d8ac8c
--- /dev/null
+++ b/src/components/EditorSidePanel/EnumsTab/SearchBar.jsx
@@ -0,0 +1,41 @@
+import { useState } from "react";
+import { AutoComplete } from "@douyinfe/semi-ui";
+import { IconSearch } from "@douyinfe/semi-icons";
+import { useEnums } from "../../../hooks";
+import { useTranslation } from "react-i18next";
+
+export default function SearchBar() {
+ const { enums } = useEnums();
+ const [value, setValue] = useState("");
+ const { t } = useTranslation();
+
+ const [filteredResult, setFilteredResult] = useState(
+ enums.map((e) => e.name),
+ );
+
+ const handleStringSearch = (value) => {
+ setFilteredResult(
+ enums.map((e) => e.name).filter((i) => i.includes(value)),
+ );
+ };
+
+ return (
+ }
+ placeholder={t("search")}
+ onSearch={(v) => handleStringSearch(v)}
+ emptyContent={{t("not_found")}
}
+ onChange={(v) => setValue(v)}
+ onSelect={(v) => {
+ const i = enums.findIndex((t) => t.name === v);
+ document
+ .getElementById(`scroll_enum_${i}`)
+ .scrollIntoView({ behavior: "smooth" });
+ }}
+ className="w-full"
+ />
+ );
+}
diff --git a/src/components/EditorSidePanel/SidePanel.jsx b/src/components/EditorSidePanel/SidePanel.jsx
index e5f2e02..6764ebd 100644
--- a/src/components/EditorSidePanel/SidePanel.jsx
+++ b/src/components/EditorSidePanel/SidePanel.jsx
@@ -1,5 +1,5 @@
import { Tabs, TabPane } from "@douyinfe/semi-ui";
-import { DB, Tab } from "../../data/constants";
+import { Tab } from "../../data/constants";
import { useLayout, useSelect, useTables } from "../../hooks";
import RelationshipsTab from "./RelationshipsTab/RelationshipsTab";
import TypesTab from "./TypesTab/TypesTab";
@@ -9,6 +9,8 @@ import NotesTab from "./NotesTab/NotesTab";
import TablesTab from "./TablesTab/TablesTab";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
+import { databases } from "../../data/databases";
+import EnumsTab from "./EnumsTab/EnumsTab";
export default function SidePanel({ width, resize, setResize }) {
const { layout } = useLayout();
@@ -27,13 +29,23 @@ export default function SidePanel({ width, resize, setResize }) {
{ tab: t("subject_areas"), itemKey: Tab.AREAS, component: },
{ tab: t("notes"), itemKey: Tab.NOTES, component: },
];
- if (database === DB.GENERIC || database === DB.POSTGRES) {
+
+ if (databases[database].hasTypes) {
tabs.push({
tab: t("types"),
itemKey: Tab.TYPES,
component: ,
});
}
+
+ if (databases[database].hasEnums) {
+ tabs.push({
+ tab: t("enums"),
+ itemKey: Tab.ENUMS,
+ component: ,
+ });
+ }
+
return tabs;
}, [t, database]);
diff --git a/src/components/Workspace.jsx b/src/components/Workspace.jsx
index cdd4e24..23c5741 100644
--- a/src/components/Workspace.jsx
+++ b/src/components/Workspace.jsx
@@ -15,6 +15,7 @@ import {
useTypes,
useTasks,
useSaveState,
+ useEnums,
} from "../hooks";
import FloatingControls from "./FloatingControls";
import { Modal } from "@douyinfe/semi-ui";
@@ -37,6 +38,7 @@ export default function WorkSpace() {
const { notes, setNotes } = useNotes();
const { saveState, setSaveState } = useSaveState();
const { transform, setTransform } = useTransform();
+ const { enums, setEnums } = useEnums();
const {
tables,
relationships,
@@ -71,12 +73,13 @@ export default function WorkSpace() {
lastModified: new Date(),
tables: tables,
references: relationships,
- types: types,
notes: notes,
areas: areas,
todos: tasks,
pan: transform.pan,
zoom: transform.zoom,
+ ...(databases[database].hasEnums && { enums: enums }),
+ ...(databases[database].hasTypes && { types: types }),
})
.then((id) => {
setId(id);
@@ -92,12 +95,13 @@ export default function WorkSpace() {
lastModified: new Date(),
tables: tables,
references: relationships,
- types: types,
notes: notes,
areas: areas,
todos: tasks,
pan: transform.pan,
zoom: transform.zoom,
+ ...(databases[database].hasEnums && { enums: enums }),
+ ...(databases[database].hasTypes && { types: types }),
})
.then(() => {
setSaveState(State.SAVED);
@@ -111,12 +115,13 @@ export default function WorkSpace() {
title: title,
tables: tables,
relationships: relationships,
- types: types,
notes: notes,
subjectAreas: areas,
todos: tasks,
pan: transform.pan,
zoom: transform.zoom,
+ ...(databases[database].hasEnums && { enums: enums }),
+ ...(databases[database].hasTypes && { types: types }),
})
.then(() => {
setSaveState(State.SAVED);
@@ -138,6 +143,7 @@ export default function WorkSpace() {
transform,
setSaveState,
database,
+ enums,
]);
const load = useCallback(async () => {
@@ -158,9 +164,14 @@ export default function WorkSpace() {
setRelationships(d.references);
setNotes(d.notes);
setAreas(d.areas);
- setTypes(d.types);
setTasks(d.todos ?? []);
setTransform({ pan: d.pan, zoom: d.zoom });
+ if (databases[database].hasTypes) {
+ setTypes(d.types ?? []);
+ }
+ if (databases[database].hasEnums) {
+ setEnums(d.enums ?? []);
+ }
window.name = `d ${d.id}`;
} else {
window.name = "";
@@ -184,7 +195,6 @@ export default function WorkSpace() {
setId(diagram.id);
setTitle(diagram.name);
setTables(diagram.tables);
- setTypes(diagram.types);
setRelationships(diagram.references);
setAreas(diagram.areas);
setNotes(diagram.notes);
@@ -195,6 +205,12 @@ export default function WorkSpace() {
});
setUndoStack([]);
setRedoStack([]);
+ if (databases[database].hasTypes) {
+ setTypes(diagram.types ?? []);
+ }
+ if (databases[database].hasEnums) {
+ setEnums(diagram.enums ?? []);
+ }
window.name = `d ${diagram.id}`;
} else {
window.name = "";
@@ -218,7 +234,6 @@ export default function WorkSpace() {
setId(diagram.id);
setTitle(diagram.title);
setTables(diagram.tables);
- setTypes(diagram.types);
setRelationships(diagram.relationships);
setAreas(diagram.subjectAreas);
setTasks(diagram.todos ?? []);
@@ -229,6 +244,12 @@ export default function WorkSpace() {
});
setUndoStack([]);
setRedoStack([]);
+ if (databases[database].hasTypes) {
+ setTypes(diagram.types ?? []);
+ }
+ if (databases[database].hasEnums) {
+ setEnums(diagram.enums ?? []);
+ }
} else {
setShowSelectDbModal(true);
}
@@ -269,6 +290,8 @@ export default function WorkSpace() {
setTypes,
setTasks,
setDatabase,
+ database,
+ setEnums,
]);
useEffect(() => {
diff --git a/src/context/EnumsContext.jsx b/src/context/EnumsContext.jsx
new file mode 100644
index 0000000..1e2d45d
--- /dev/null
+++ b/src/context/EnumsContext.jsx
@@ -0,0 +1,82 @@
+import { createContext, useState } from "react";
+import { Action, ObjectType } from "../data/constants";
+import { Toast } from "@douyinfe/semi-ui";
+import { useTranslation } from "react-i18next";
+import { useUndoRedo } from "../hooks";
+
+export const EnumsContext = createContext(null);
+
+export default function EnumsContextProvider({ children }) {
+ const { t } = useTranslation();
+ const [enums, setEnums] = useState([]);
+ const { setUndoStack, setRedoStack } = useUndoRedo();
+
+ const addEnum = (data, addToHistory = true) => {
+ if (data) {
+ setEnums((prev) => {
+ const temp = prev.slice();
+ temp.splice(data.id, 0, data);
+ return temp;
+ });
+ } else {
+ setEnums((prev) => [
+ ...prev,
+ {
+ name: `enum_${prev.length}`,
+ values: [],
+ },
+ ]);
+ }
+ if (addToHistory) {
+ setUndoStack((prev) => [
+ ...prev,
+ {
+ action: Action.ADD,
+ element: ObjectType.ENUM,
+ message: t("add_enum"),
+ },
+ ]);
+ setRedoStack([]);
+ }
+ };
+
+ const deleteEnum = (id, addToHistory = true) => {
+ if (addToHistory) {
+ Toast.success(t("enum_deleted"));
+ setUndoStack((prev) => [
+ ...prev,
+ {
+ action: Action.DELETE,
+ element: ObjectType.ENUM,
+ id: id,
+ data: enums[id],
+ message: t("delete_enum", {
+ enumName: enums[id].name,
+ }),
+ },
+ ]);
+ setRedoStack([]);
+ }
+ setEnums((prev) => prev.filter((e, i) => i !== id));
+ };
+
+ const updateEnum = (id, values) => {
+ setEnums((prev) =>
+ prev.map((e, i) => (i === id ? { ...e, ...values } : e)),
+ );
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/context/TablesContext.jsx b/src/context/TablesContext.jsx
index d090b8b..474e282 100644
--- a/src/context/TablesContext.jsx
+++ b/src/context/TablesContext.jsx
@@ -10,7 +10,7 @@ export const TablesContext = createContext(null);
export default function TablesContextProvider({ children }) {
const { t } = useTranslation();
- const [database, setDatabase] = useState("");
+ const [database, setDatabase] = useState(DB.GENERIC);
const [tables, setTables] = useState([]);
const [relationships, setRelationships] = useState([]);
const { transform } = useTransform();
diff --git a/src/data/constants.js b/src/data/constants.js
index 5579c97..c7cb448 100644
--- a/src/data/constants.js
+++ b/src/data/constants.js
@@ -50,6 +50,7 @@ export const Tab = {
AREAS: "3",
NOTES: "4",
TYPES: "5",
+ ENUMS: "6",
};
export const ObjectType = {
@@ -59,6 +60,7 @@ export const ObjectType = {
NOTE: 3,
RELATIONSHIP: 4,
TYPE: 5,
+ ENUM: 6,
};
export const Action = {
diff --git a/src/data/databases.js b/src/data/databases.js
index 78e2e8d..7b710fd 100644
--- a/src/data/databases.js
+++ b/src/data/databases.js
@@ -18,6 +18,7 @@ export const databases = {
label: DB.POSTGRES,
image: postgresImage,
hasTypes: true,
+ hasEnums: true,
},
[DB.SQLITE]: {
name: "SQLite",
diff --git a/src/hooks/index.js b/src/hooks/index.js
index db4384a..310ce42 100644
--- a/src/hooks/index.js
+++ b/src/hooks/index.js
@@ -9,3 +9,4 @@ export { default as useTasks } from "./useTasks";
export { default as useTransform } from "./useTransform";
export { default as useTypes } from "./useTypes";
export { default as useUndoRedo } from "./useUndoRedo";
+export { default as useEnums } from "./useEnums";
diff --git a/src/hooks/useEnums.js b/src/hooks/useEnums.js
new file mode 100644
index 0000000..eb6a5cb
--- /dev/null
+++ b/src/hooks/useEnums.js
@@ -0,0 +1,6 @@
+import { useContext } from "react";
+import { EnumsContext } from "../context/EnumsContext";
+
+export default function useEnums() {
+ return useContext(EnumsContext);
+}
diff --git a/src/pages/Editor.jsx b/src/pages/Editor.jsx
index 5268cf2..dd0ece0 100644
--- a/src/pages/Editor.jsx
+++ b/src/pages/Editor.jsx
@@ -8,6 +8,7 @@ import NotesContextProvider from "../context/NotesContext";
import TypesContextProvider from "../context/TypesContext";
import TasksContextProvider from "../context/TasksContext";
import SaveStateContextProvider from "../context/SaveStateContext";
+import EnumsContextProvider from "../context/EnumsContext";
import WorkSpace from "../components/Workspace";
export default function Editor() {
@@ -20,11 +21,13 @@ export default function Editor() {
-
-
-
-
-
+
+
+
+
+
+
+