From 810afe4bc083e3c45c91ee4366d19cefab4b0253 Mon Sep 17 00:00:00 2001 From: 1ilit Date: Sun, 30 Jun 2024 13:19:21 +0300 Subject: [PATCH] Add enums tab --- src/components/EditorHeader/ControlPanel.jsx | 8 +- .../EditorSidePanel/EnumsTab/EnumDetails.jsx | 85 +++++++++++++++++++ .../EditorSidePanel/EnumsTab/EnumsTab.jsx | 39 +++++++++ .../EditorSidePanel/EnumsTab/SearchBar.jsx | 41 +++++++++ src/components/EditorSidePanel/SidePanel.jsx | 16 +++- src/components/Workspace.jsx | 35 ++++++-- src/context/EnumsContext.jsx | 82 ++++++++++++++++++ src/context/TablesContext.jsx | 2 +- src/data/constants.js | 2 + src/data/databases.js | 1 + src/hooks/index.js | 1 + src/hooks/useEnums.js | 6 ++ src/pages/Editor.jsx | 13 +-- 13 files changed, 313 insertions(+), 18 deletions(-) create mode 100644 src/components/EditorSidePanel/EnumsTab/EnumDetails.jsx create mode 100644 src/components/EditorSidePanel/EnumsTab/EnumsTab.jsx create mode 100644 src/components/EditorSidePanel/EnumsTab/SearchBar.jsx create mode 100644 src/context/EnumsContext.jsx create mode 100644 src/hooks/useEnums.js 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 ( +
+
+
{t("Name")}:
+ updateEnum(data.id, { name: value })} + onFocus={(e) => setEditField({ name: e.target.value })} + onBlur={(e) => { + if (e.target.value === editField.name) return; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.ENUM, + aid: i, + undo: editField, + redo: { name: e.target.value }, + message: t("edit_enum", { + enumName: e.target.value, + extra: "[name]", + }), + }, + ]); + setRedoStack([]); + }} + /> +
+ 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([]); + }} + /> + +
+ ); +} 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 ( +
+
+ +
+ +
+
+ + {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() { - - - - - + + + + + + +