Add enums tab

This commit is contained in:
1ilit 2024-06-30 13:19:21 +03:00
parent 259835892a
commit 810afe4bc0
13 changed files with 313 additions and 18 deletions

View File

@ -51,13 +51,13 @@ import {
useTables, useTables,
useUndoRedo, useUndoRedo,
useSelect, useSelect,
useSaveState,
useTypes,
useNotes,
useAreas,
} from "../../hooks"; } from "../../hooks";
import { enterFullscreen } from "../../utils/fullscreen"; import { enterFullscreen } from "../../utils/fullscreen";
import { dataURItoBlob } from "../../utils/utils"; 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 { IconAddArea, IconAddNote, IconAddTable } from "../../icons";
import LayoutDropdown from "./LayoutDropdown"; import LayoutDropdown from "./LayoutDropdown";
import Sidesheet from "./SideSheet/Sidesheet"; import Sidesheet from "./SideSheet/Sidesheet";

View File

@ -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 (
<div>
<div
className="flex justify-center items-center gap-2"
id={`scroll_enum_${data.id}`}
>
<div className="font-semibold">{t("Name")}: </div>
<Input
value={data.name}
placeholder={t("name")}
onChange={(value) => 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([]);
}}
/>
</div>
<TagInput
separator={[",", ", ", " ,"]}
value={data.values}
addOnBlur
className="my-2"
placeholder={t("values")}
onChange={(v) => 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([]);
}}
/>
<Button
block
icon={<IconDeleteStroked />}
type="danger"
onClick={() => deleteEnum(i, true)}
>
{t("delete")}
</Button>
</div>
);
}

View File

@ -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 (
<div>
<div className="flex gap-2">
<SearchBar />
<div>
<Button icon={<IconPlus />} block onClick={() => addEnum()}>
{t("add_enum")}
</Button>
</div>
</div>
<Collapse>
{enums.map((e, i) => (
<Collapse.Panel
key={e.name + i}
header={
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{e.name}
</div>
}
itemKey={`${i}`}
>
<EnumDetails data={e} i={i} />
</Collapse.Panel>
))}
</Collapse>
</div>
);
}

View File

@ -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 (
<AutoComplete
data={filteredResult}
value={value}
showClear
prefix={<IconSearch />}
placeholder={t("search")}
onSearch={(v) => handleStringSearch(v)}
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
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"
/>
);
}

View File

@ -1,5 +1,5 @@
import { Tabs, TabPane } from "@douyinfe/semi-ui"; 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 { useLayout, useSelect, useTables } from "../../hooks";
import RelationshipsTab from "./RelationshipsTab/RelationshipsTab"; import RelationshipsTab from "./RelationshipsTab/RelationshipsTab";
import TypesTab from "./TypesTab/TypesTab"; import TypesTab from "./TypesTab/TypesTab";
@ -9,6 +9,8 @@ import NotesTab from "./NotesTab/NotesTab";
import TablesTab from "./TablesTab/TablesTab"; import TablesTab from "./TablesTab/TablesTab";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMemo } from "react"; import { useMemo } from "react";
import { databases } from "../../data/databases";
import EnumsTab from "./EnumsTab/EnumsTab";
export default function SidePanel({ width, resize, setResize }) { export default function SidePanel({ width, resize, setResize }) {
const { layout } = useLayout(); const { layout } = useLayout();
@ -27,13 +29,23 @@ export default function SidePanel({ width, resize, setResize }) {
{ tab: t("subject_areas"), itemKey: Tab.AREAS, component: <AreasTab /> }, { tab: t("subject_areas"), itemKey: Tab.AREAS, component: <AreasTab /> },
{ tab: t("notes"), itemKey: Tab.NOTES, component: <NotesTab /> }, { tab: t("notes"), itemKey: Tab.NOTES, component: <NotesTab /> },
]; ];
if (database === DB.GENERIC || database === DB.POSTGRES) {
if (databases[database].hasTypes) {
tabs.push({ tabs.push({
tab: t("types"), tab: t("types"),
itemKey: Tab.TYPES, itemKey: Tab.TYPES,
component: <TypesTab />, component: <TypesTab />,
}); });
} }
if (databases[database].hasEnums) {
tabs.push({
tab: t("enums"),
itemKey: Tab.ENUMS,
component: <EnumsTab />,
});
}
return tabs; return tabs;
}, [t, database]); }, [t, database]);

View File

@ -15,6 +15,7 @@ import {
useTypes, useTypes,
useTasks, useTasks,
useSaveState, useSaveState,
useEnums,
} from "../hooks"; } from "../hooks";
import FloatingControls from "./FloatingControls"; import FloatingControls from "./FloatingControls";
import { Modal } from "@douyinfe/semi-ui"; import { Modal } from "@douyinfe/semi-ui";
@ -37,6 +38,7 @@ export default function WorkSpace() {
const { notes, setNotes } = useNotes(); const { notes, setNotes } = useNotes();
const { saveState, setSaveState } = useSaveState(); const { saveState, setSaveState } = useSaveState();
const { transform, setTransform } = useTransform(); const { transform, setTransform } = useTransform();
const { enums, setEnums } = useEnums();
const { const {
tables, tables,
relationships, relationships,
@ -71,12 +73,13 @@ export default function WorkSpace() {
lastModified: new Date(), lastModified: new Date(),
tables: tables, tables: tables,
references: relationships, references: relationships,
types: types,
notes: notes, notes: notes,
areas: areas, areas: areas,
todos: tasks, todos: tasks,
pan: transform.pan, pan: transform.pan,
zoom: transform.zoom, zoom: transform.zoom,
...(databases[database].hasEnums && { enums: enums }),
...(databases[database].hasTypes && { types: types }),
}) })
.then((id) => { .then((id) => {
setId(id); setId(id);
@ -92,12 +95,13 @@ export default function WorkSpace() {
lastModified: new Date(), lastModified: new Date(),
tables: tables, tables: tables,
references: relationships, references: relationships,
types: types,
notes: notes, notes: notes,
areas: areas, areas: areas,
todos: tasks, todos: tasks,
pan: transform.pan, pan: transform.pan,
zoom: transform.zoom, zoom: transform.zoom,
...(databases[database].hasEnums && { enums: enums }),
...(databases[database].hasTypes && { types: types }),
}) })
.then(() => { .then(() => {
setSaveState(State.SAVED); setSaveState(State.SAVED);
@ -111,12 +115,13 @@ export default function WorkSpace() {
title: title, title: title,
tables: tables, tables: tables,
relationships: relationships, relationships: relationships,
types: types,
notes: notes, notes: notes,
subjectAreas: areas, subjectAreas: areas,
todos: tasks, todos: tasks,
pan: transform.pan, pan: transform.pan,
zoom: transform.zoom, zoom: transform.zoom,
...(databases[database].hasEnums && { enums: enums }),
...(databases[database].hasTypes && { types: types }),
}) })
.then(() => { .then(() => {
setSaveState(State.SAVED); setSaveState(State.SAVED);
@ -138,6 +143,7 @@ export default function WorkSpace() {
transform, transform,
setSaveState, setSaveState,
database, database,
enums,
]); ]);
const load = useCallback(async () => { const load = useCallback(async () => {
@ -158,9 +164,14 @@ export default function WorkSpace() {
setRelationships(d.references); setRelationships(d.references);
setNotes(d.notes); setNotes(d.notes);
setAreas(d.areas); setAreas(d.areas);
setTypes(d.types);
setTasks(d.todos ?? []); setTasks(d.todos ?? []);
setTransform({ pan: d.pan, zoom: d.zoom }); 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}`; window.name = `d ${d.id}`;
} else { } else {
window.name = ""; window.name = "";
@ -184,7 +195,6 @@ export default function WorkSpace() {
setId(diagram.id); setId(diagram.id);
setTitle(diagram.name); setTitle(diagram.name);
setTables(diagram.tables); setTables(diagram.tables);
setTypes(diagram.types);
setRelationships(diagram.references); setRelationships(diagram.references);
setAreas(diagram.areas); setAreas(diagram.areas);
setNotes(diagram.notes); setNotes(diagram.notes);
@ -195,6 +205,12 @@ export default function WorkSpace() {
}); });
setUndoStack([]); setUndoStack([]);
setRedoStack([]); setRedoStack([]);
if (databases[database].hasTypes) {
setTypes(diagram.types ?? []);
}
if (databases[database].hasEnums) {
setEnums(diagram.enums ?? []);
}
window.name = `d ${diagram.id}`; window.name = `d ${diagram.id}`;
} else { } else {
window.name = ""; window.name = "";
@ -218,7 +234,6 @@ export default function WorkSpace() {
setId(diagram.id); setId(diagram.id);
setTitle(diagram.title); setTitle(diagram.title);
setTables(diagram.tables); setTables(diagram.tables);
setTypes(diagram.types);
setRelationships(diagram.relationships); setRelationships(diagram.relationships);
setAreas(diagram.subjectAreas); setAreas(diagram.subjectAreas);
setTasks(diagram.todos ?? []); setTasks(diagram.todos ?? []);
@ -229,6 +244,12 @@ export default function WorkSpace() {
}); });
setUndoStack([]); setUndoStack([]);
setRedoStack([]); setRedoStack([]);
if (databases[database].hasTypes) {
setTypes(diagram.types ?? []);
}
if (databases[database].hasEnums) {
setEnums(diagram.enums ?? []);
}
} else { } else {
setShowSelectDbModal(true); setShowSelectDbModal(true);
} }
@ -269,6 +290,8 @@ export default function WorkSpace() {
setTypes, setTypes,
setTasks, setTasks,
setDatabase, setDatabase,
database,
setEnums,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -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 (
<EnumsContext.Provider
value={{
enums,
setEnums,
addEnum,
updateEnum,
deleteEnum,
}}
>
{children}
</EnumsContext.Provider>
);
}

View File

@ -10,7 +10,7 @@ export const TablesContext = createContext(null);
export default function TablesContextProvider({ children }) { export default function TablesContextProvider({ children }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [database, setDatabase] = useState(""); const [database, setDatabase] = useState(DB.GENERIC);
const [tables, setTables] = useState([]); const [tables, setTables] = useState([]);
const [relationships, setRelationships] = useState([]); const [relationships, setRelationships] = useState([]);
const { transform } = useTransform(); const { transform } = useTransform();

View File

@ -50,6 +50,7 @@ export const Tab = {
AREAS: "3", AREAS: "3",
NOTES: "4", NOTES: "4",
TYPES: "5", TYPES: "5",
ENUMS: "6",
}; };
export const ObjectType = { export const ObjectType = {
@ -59,6 +60,7 @@ export const ObjectType = {
NOTE: 3, NOTE: 3,
RELATIONSHIP: 4, RELATIONSHIP: 4,
TYPE: 5, TYPE: 5,
ENUM: 6,
}; };
export const Action = { export const Action = {

View File

@ -18,6 +18,7 @@ export const databases = {
label: DB.POSTGRES, label: DB.POSTGRES,
image: postgresImage, image: postgresImage,
hasTypes: true, hasTypes: true,
hasEnums: true,
}, },
[DB.SQLITE]: { [DB.SQLITE]: {
name: "SQLite", name: "SQLite",

View File

@ -9,3 +9,4 @@ export { default as useTasks } from "./useTasks";
export { default as useTransform } from "./useTransform"; export { default as useTransform } from "./useTransform";
export { default as useTypes } from "./useTypes"; export { default as useTypes } from "./useTypes";
export { default as useUndoRedo } from "./useUndoRedo"; export { default as useUndoRedo } from "./useUndoRedo";
export { default as useEnums } from "./useEnums";

6
src/hooks/useEnums.js Normal file
View File

@ -0,0 +1,6 @@
import { useContext } from "react";
import { EnumsContext } from "../context/EnumsContext";
export default function useEnums() {
return useContext(EnumsContext);
}

View File

@ -8,6 +8,7 @@ import NotesContextProvider from "../context/NotesContext";
import TypesContextProvider from "../context/TypesContext"; import TypesContextProvider from "../context/TypesContext";
import TasksContextProvider from "../context/TasksContext"; import TasksContextProvider from "../context/TasksContext";
import SaveStateContextProvider from "../context/SaveStateContext"; import SaveStateContextProvider from "../context/SaveStateContext";
import EnumsContextProvider from "../context/EnumsContext";
import WorkSpace from "../components/Workspace"; import WorkSpace from "../components/Workspace";
export default function Editor() { export default function Editor() {
@ -20,11 +21,13 @@ export default function Editor() {
<AreasContextProvider> <AreasContextProvider>
<NotesContextProvider> <NotesContextProvider>
<TypesContextProvider> <TypesContextProvider>
<TablesContextProvider> <EnumsContextProvider>
<SaveStateContextProvider> <TablesContextProvider>
<WorkSpace /> <SaveStateContextProvider>
</SaveStateContextProvider> <WorkSpace />
</TablesContextProvider> </SaveStateContextProvider>
</TablesContextProvider>
</EnumsContextProvider>
</TypesContextProvider> </TypesContextProvider>
</NotesContextProvider> </NotesContextProvider>
</AreasContextProvider> </AreasContextProvider>