From 1132edbbb3e3d7f92810fecb5efb3c088e785882 Mon Sep 17 00:00:00 2001 From: 1ilit Date: Fri, 15 Mar 2024 16:00:23 +0200 Subject: [PATCH] Abstract save state from editor --- src/components/Area.jsx | 10 +- src/components/AreaOverview.jsx | 8 +- src/components/ControlPanel.jsx | 14 +- src/components/Note.jsx | 8 +- src/components/Todo.jsx | 42 ++-- src/components/Workspace.jsx | 312 ++++++++++++++++++++++++++++++ src/context/SaveStateContext.jsx | 14 ++ src/hooks/useSaveState.js | 6 + src/pages/Editor.jsx | 319 +------------------------------ 9 files changed, 379 insertions(+), 354 deletions(-) create mode 100644 src/components/Workspace.jsx create mode 100644 src/context/SaveStateContext.jsx create mode 100644 src/hooks/useSaveState.js diff --git a/src/components/Area.jsx b/src/components/Area.jsx index 28122a5..51b1b83 100644 --- a/src/components/Area.jsx +++ b/src/components/Area.jsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useState } from "react"; import { Button, Popover, Input, Toast } from "@douyinfe/semi-ui"; import { IconEdit, @@ -13,19 +13,19 @@ import { defaultTableTheme, State, } from "../data/data"; -import { StateContext } from "../pages/Editor"; import useLayout from "../hooks/useLayout"; import useSettings from "../hooks/useSettings"; import useUndoRedo from "../hooks/useUndoRedo"; import useSelect from "../hooks/useSelect"; import useAreas from "../hooks/useAreas"; +import useSaveState from "../hooks/useSaveState"; export default function Area(props) { const [hovered, setHovered] = useState(false); const [editField, setEditField] = useState({}); - const { setState } = useContext(StateContext); const { layout } = useLayout(); const { settings } = useSettings(); + const { setSaveState } = useSaveState(); const { updateArea, deleteArea } = useAreas(); const { setUndoStack, setRedoStack } = useUndoRedo(); const { selectedElement, setSelectedElement } = useSelect(); @@ -100,7 +100,7 @@ export default function Area(props) { ...prev, open: false, })); - setState(State.SAVING); + setSaveState(State.SAVING); }} stopPropagation content={ @@ -145,7 +145,7 @@ export default function Area(props) { updateArea(props.areaData.id, { color: defaultTableTheme, }); - setState(State.SAVING); + setSaveState(State.SAVING); }} > Clear diff --git a/src/components/AreaOverview.jsx b/src/components/AreaOverview.jsx index e9f431a..9b32f78 100644 --- a/src/components/AreaOverview.jsx +++ b/src/components/AreaOverview.jsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useState } from "react"; import { Empty, Row, @@ -26,12 +26,12 @@ import { ObjectType, State, } from "../data/data"; -import { StateContext } from "../pages/Editor"; import useUndoRedo from "../hooks/useUndoRedo"; import useAreas from "../hooks/useAreas"; +import useSaveState from "../hooks/useSaveState"; export default function AreaOverview() { - const { setState } = useContext(StateContext); + const { setSaveState } = useSaveState(); const { areas, addArea, deleteArea, updateArea } = useAreas(); const { setUndoStack, setRedoStack } = useUndoRedo(); const [editField, setEditField] = useState({}); @@ -141,7 +141,7 @@ export default function AreaOverview() { size="small" onClick={() => { updateArea(i, { color: defaultTableTheme }); - setState(State.SAVING); + setSaveState(State.SAVING); }} > Clear diff --git a/src/components/ControlPanel.jsx b/src/components/ControlPanel.jsx index 9da1d9c..878e863 100644 --- a/src/components/ControlPanel.jsx +++ b/src/components/ControlPanel.jsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useState } from "react"; import { IconCaretdown, IconChevronRight, @@ -43,7 +43,6 @@ import { jsonToMariaDB, jsonToSQLServer, } from "../utils/toSQL"; -import { StateContext } from "../pages/Editor"; import { IconAddTable, IconAddArea, IconAddNote } from "./CustomIcons"; import { ObjectType, Action, Tab, State, Cardinality } from "../data/data"; import jsPDF from "jspdf"; @@ -68,6 +67,7 @@ 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"; export default function ControlPanel({ diagramId, @@ -117,7 +117,7 @@ export default function ControlPanel({ message: "", }); const [data, setData] = useState(null); - const { state, setState } = useContext(StateContext); + const { saveState, setSaveState } = useSaveState(); const { layout, setLayout } = useLayout(); const { settings, setSettings } = useSettings(); const { @@ -734,7 +734,7 @@ export default function ControlPanel({ copy(); del(); }; - const save = () => setState(State.SAVING); + const save = () => setSaveState(State.SAVING); const open = () => setVisible(MODAL.OPEN); const saveDiagramAs = () => setVisible(MODAL.SAVEAS); const loadDiagram = async (id) => { @@ -1036,7 +1036,7 @@ export default function ControlPanel({ Exit: { function: () => { save(); - if (state === State.SAVED) navigate("/"); + if (saveState === State.SAVED) navigate("/"); }, }, }, @@ -2256,7 +2256,7 @@ export default function ControlPanel({ } function getState() { - switch (state) { + switch (saveState) { case State.NONE: return "No changes"; case State.LOADING: @@ -2378,7 +2378,7 @@ export default function ControlPanel({ size="small" type="tertiary" icon={ - state === State.LOADING || state === State.SAVING ? ( + saveState === State.LOADING || saveState === State.SAVING ? ( ) : null } diff --git a/src/components/Note.jsx b/src/components/Note.jsx index 6d3fbf4..04fd953 100644 --- a/src/components/Note.jsx +++ b/src/components/Note.jsx @@ -1,5 +1,4 @@ -import { useContext, useState } from "react"; -import { StateContext } from "../pages/Editor"; +import { useState } from "react"; import { Action, ObjectType, noteThemes, Tab, State } from "../data/data"; import { Input, Button, Popover, Toast } from "@douyinfe/semi-ui"; import { @@ -11,6 +10,7 @@ import useLayout from "../hooks/useLayout"; import useUndoRedo from "../hooks/useUndoRedo"; import useSelect from "../hooks/useSelect"; import useNotes from "../hooks/useNotes"; +import useSaveState from "../hooks/useSaveState"; export default function Note({ data, onMouseDown }) { const w = 180; @@ -19,7 +19,7 @@ export default function Note({ data, onMouseDown }) { const [editField, setEditField] = useState({}); const [hovered, setHovered] = useState(false); const { layout } = useLayout(); - const { setState } = useContext(StateContext); + const { setSaveState } = useSaveState(); const { updateNote, deleteNote } = useNotes(); const { setUndoStack, setRedoStack } = useUndoRedo(); const { selectedElement, setSelectedElement } = useSelect(); @@ -147,7 +147,7 @@ export default function Note({ data, onMouseDown }) { ...prev, open: false, })); - setState(State.SAVING); + setSaveState(State.SAVING); }} stopPropagation content={ diff --git a/src/components/Todo.jsx b/src/components/Todo.jsx index bfa7573..d574064 100644 --- a/src/components/Todo.jsx +++ b/src/components/Todo.jsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useState } from "react"; import { Checkbox, Input, @@ -19,27 +19,29 @@ import { IconDeleteStroked, IconCaretdown, } from "@douyinfe/semi-icons"; -import { StateContext } from "../pages/Editor"; import { State } from "../data/data"; import useTasks from "../hooks/useTasks"; +import useSaveState from "../hooks/useSaveState"; + +const Priority = { + NONE: 0, + LOW: 1, + MEDIUM: 2, + HIGH: 3, +}; + +const SortOrder = { + ORIGINAL: "My order", + PRIORITY: "Priority", + COMPLETED: "Completed", + ALPHABETICALLY: "Alphabetically", +}; export default function Todo() { - const Priority = { - NONE: 0, - LOW: 1, - MEDIUM: 2, - HIGH: 3, - }; - const SortOrder = { - ORIGINAL: "My order", - PRIORITY: "Priority", - COMPLETED: "Completed", - ALPHABETICALLY: "Alphabetically", - }; const [activeTask, setActiveTask] = useState(-1); const [, setSortOrder] = useState(SortOrder.ORIGINAL); const { tasks, setTasks, updateTask } = useTasks(); - const { setState } = useContext(StateContext); + const { setSaveState } = useSaveState(); const priorityLabel = (p) => { switch (p) { @@ -165,7 +167,7 @@ export default function Todo() { checked={t.complete} onChange={(e) => { updateTask(i, { complete: e.target.checked }); - setState(State.SAVING); + setSaveState(State.SAVING); }} > @@ -174,7 +176,7 @@ export default function Todo() { placeholder="Title" onChange={(v) => updateTask(i, { title: v })} value={t.title} - onBlur={() => setState(State.SAVING)} + onBlur={() => setSaveState(State.SAVING)} > @@ -187,7 +189,7 @@ export default function Todo() { { updateTask(i, { priority: e.target.value }); - setState(State.SAVING); + setSaveState(State.SAVING); }} value={t.priority} direction="vertical" @@ -222,7 +224,7 @@ export default function Todo() { setTasks((prev) => prev.filter((task, j) => i !== j) ); - setState(State.SAVING); + setSaveState(State.SAVING); }} > Delete @@ -245,7 +247,7 @@ export default function Todo() { placeholder="Details" onChange={(v) => updateTask(i, { details: v })} value={t.details} - onBlur={() => setState(State.SAVING)} + onBlur={() => setSaveState(State.SAVING)} > diff --git a/src/components/Workspace.jsx b/src/components/Workspace.jsx new file mode 100644 index 0000000..e3f2c01 --- /dev/null +++ b/src/components/Workspace.jsx @@ -0,0 +1,312 @@ +import { useState, useEffect, useCallback } from "react"; +import ControlPanel from "../components/ControlPanel"; +import Canvas from "../components/Canvas"; +import SidePanel from "../components/SidePanel"; +import { State } from "../data/data"; +import { db } from "../data/db"; +import useLayout from "../hooks/useLayout"; +import useSettings from "../hooks/useSettings"; +import useTransform from "../hooks/useTransform"; +import useTables from "../hooks/useTables"; +import useUndoRedo from "../hooks/useUndoRedo"; +import Controls from "../components/Controls"; +import useAreas from "../hooks/useAreas"; +import useNotes from "../hooks/useNotes"; +import useTypes from "../hooks/useTypes"; +import useTasks from "../hooks/useTasks"; +import useSaveState from "../hooks/useSaveState"; + +export default function WorkSpace() { + const [id, setId] = useState(0); + const [title, setTitle] = useState("Untitled Diagram"); + const [resize, setResize] = useState(false); + const [width, setWidth] = useState(340); + const [lastSaved, setLastSaved] = useState(""); + const { layout } = useLayout(); + const { types, setTypes } = useTypes(); + const { areas, setAreas } = useAreas(); + const { tasks, setTasks } = useTasks(); + const { notes, setNotes } = useNotes(); + const { settings, setSettings } = useSettings(); + const { saveState, setSaveState } = useSaveState(); + const { transform, setTransform } = useTransform(); + const { tables, relationships, setTables, setRelationships } = useTables(); + const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo(); + + const handleResize = (e) => { + if (!resize) return; + const w = e.clientX; + if (w > 340) setWidth(w); + }; + + useEffect(() => { + if ( + tables?.length === 0 && + areas?.length === 0 && + notes?.length === 0 && + types?.length === 0 && + tasks?.length === 0 + ) + return; + + if (settings.autosave) { + setSaveState(State.SAVING); + } + }, [ + undoStack, + redoStack, + settings.autosave, + tables?.length, + areas?.length, + notes?.length, + types?.length, + relationships?.length, + tasks?.length, + transform.zoom, + title, + setSaveState, + ]); + + const save = useCallback( + async (diagram = true) => { + if (saveState !== State.SAVING) { + return; + } + if (diagram) { + if ( + (id === 0 && window.name === "") || + window.name.split(" ")[0] === "lt" + ) { + db.diagrams + .add({ + name: title, + lastModified: new Date(), + tables: tables, + references: relationships, + types: types, + notes: notes, + areas: areas, + todos: tasks, + pan: transform.pan, + zoom: transform.zoom, + }) + .then((id) => { + setId(id); + window.name = `d ${id}`; + setSaveState(State.SAVED); + setLastSaved(new Date().toLocaleString()); + }); + } else { + db.diagrams + .update(id, { + name: title, + lastModified: new Date(), + tables: tables, + references: relationships, + types: types, + notes: notes, + areas: areas, + todos: tasks, + pan: transform.pan, + zoom: transform.zoom, + }) + .then(() => { + setSaveState(State.SAVED); + setLastSaved(new Date().toLocaleString()); + }); + } + } else { + db.templates + .update(id, { + title: title, + tables: tables, + relationships: relationships, + types: types, + notes: notes, + subjectAreas: areas, + todos: tasks, + pan: transform.pan, + zoom: transform.zoom, + }) + .then(() => { + setSaveState(State.SAVED); + setLastSaved(new Date().toLocaleString()); + }) + .catch(() => { + setSaveState(State.ERROR); + }); + } + }, + [ + tables, + relationships, + notes, + areas, + types, + title, + id, + saveState, + tasks, + transform.zoom, + transform.pan, + setSaveState, + ] + ); + useEffect(() => { + const name = window.name.split(" "); + const op = name[0]; + const diagram = window.name === "" || op === "d" || op === "lt"; + + save(diagram); + }, [id, saveState, save]); + + useEffect(() => { + document.title = "Editor | drawDB"; + + const loadLatestDiagram = async () => { + db.diagrams + .orderBy("lastModified") + .last() + .then((d) => { + if (d) { + setId(d.id); + setTables(d.tables); + setRelationships(d.references); + setNotes(d.notes); + setAreas(d.areas); + setTypes(d.types); + setTasks(d.todos ?? []); + setTransform({ pan: d.pan, zoom: d.zoom }); + window.name = `d ${d.id}`; + } else { + window.name = ""; + } + }) + .catch((error) => { + console.log(error); + }); + }; + + const loadDiagram = async (id) => { + db.diagrams + .get(id) + .then((diagram) => { + if (diagram) { + setId(diagram.id); + setTitle(diagram.name); + setTables(diagram.tables); + setTypes(diagram.types); + setRelationships(diagram.references); + setAreas(diagram.areas); + setNotes(diagram.notes); + setTasks(diagram.todos ?? []); + setTransform({ + pan: diagram.pan, + zoom: diagram.zoom, + }); + setUndoStack([]); + setRedoStack([]); + window.name = `d ${diagram.id}`; + } else { + window.name = ""; + } + }) + .catch((error) => { + console.log(error); + }); + }; + + const loadTemplate = async (id) => { + db.templates + .get(id) + .then((diagram) => { + if (diagram) { + setId(diagram.id); + setTitle(diagram.title); + setTables(diagram.tables); + setTypes(diagram.types); + setRelationships(diagram.relationships); + setAreas(diagram.subjectAreas); + setTasks(diagram.todos ?? []); + setNotes(diagram.notes); + setTransform({ + zoom: 1, + pan: { x: 0, y: 0 }, + }); + setUndoStack([]); + setRedoStack([]); + } + }) + .catch((error) => { + console.log(error); + }); + }; + + if (window.name == "") { + console.log("Loading the latest diagram"); + loadLatestDiagram(); + } else { + const name = window.name.split(" "); + const op = name[0]; + const did = parseInt(name[1]); + switch (op) { + case "d": { + loadDiagram(did); + break; + } + case "lt": { + loadTemplate(did); + break; + } + case "t": { + loadTemplate(did); + break; + } + default: + break; + } + } + }, [ + setSettings, + setTransform, + setRedoStack, + setUndoStack, + setRelationships, + setTables, + setAreas, + setNotes, + setTypes, + setTasks, + ]); + + return ( +
+ +
setResize(false)} + onMouseLeave={() => setResize(false)} + onMouseMove={handleResize} + > + {layout.sidebar && ( + + )} +
+ + {!(layout.sidebar || layout.toolbar || layout.header) && ( +
+ +
+ )} +
+
+
+ ); +} diff --git a/src/context/SaveStateContext.jsx b/src/context/SaveStateContext.jsx new file mode 100644 index 0000000..1e00232 --- /dev/null +++ b/src/context/SaveStateContext.jsx @@ -0,0 +1,14 @@ +import { createContext, useState } from "react"; +import { State } from "../data/data"; + +export const SaveStateContext = createContext(null); + +export default function SaveStateContextProvider({ children }) { + const [saveState, setSaveState] = useState(State.NONE); + + return ( + + {children} + + ); +} diff --git a/src/hooks/useSaveState.js b/src/hooks/useSaveState.js new file mode 100644 index 0000000..7a28de1 --- /dev/null +++ b/src/hooks/useSaveState.js @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { SaveStateContext } from "../context/SaveStateContext"; + +export default function useSaveState() { + return useContext(SaveStateContext); +} diff --git a/src/pages/Editor.jsx b/src/pages/Editor.jsx index 69161f6..5268cf2 100644 --- a/src/pages/Editor.jsx +++ b/src/pages/Editor.jsx @@ -1,30 +1,14 @@ -import { useState, createContext, useEffect, useCallback } from "react"; -import ControlPanel from "../components/ControlPanel"; -import Canvas from "../components/Canvas"; -import SidePanel from "../components/SidePanel"; -import { State } from "../data/data"; -import { db } from "../data/db"; -import useLayout from "../hooks/useLayout"; import LayoutContextProvider from "../context/LayoutContext"; -import useSettings from "../hooks/useSettings"; import TransformContextProvider from "../context/TransformContext"; -import useTransform from "../hooks/useTransform"; -import useTables from "../hooks/useTables"; import TablesContextProvider from "../context/TablesContext"; -import useUndoRedo from "../hooks/useUndoRedo"; import UndoRedoContextProvider from "../context/UndoRedoContext"; import SelectContextProvider from "../context/SelectContext"; import AreasContextProvider from "../context/AreasContext"; -import Controls from "../components/Controls"; -import useAreas from "../hooks/useAreas"; -import useNotes from "../hooks/useNotes"; import NotesContextProvider from "../context/NotesContext"; -import useTypes from "../hooks/useTypes"; import TypesContextProvider from "../context/TypesContext"; -import useTasks from "../hooks/useTasks"; import TasksContextProvider from "../context/TasksContext"; - -export const StateContext = createContext(); +import SaveStateContextProvider from "../context/SaveStateContext"; +import WorkSpace from "../components/Workspace"; export default function Editor() { return ( @@ -37,7 +21,9 @@ export default function Editor() { - + + + @@ -49,298 +35,3 @@ export default function Editor() { ); } - -function WorkSpace() { - const [id, setId] = useState(0); - const [title, setTitle] = useState("Untitled Diagram"); - const [state, setState] = useState(State.NONE); - const [lastSaved, setLastSaved] = useState(""); - const { types, setTypes } = useTypes(); - const [resize, setResize] = useState(false); - const [width, setWidth] = useState(340); - const { tasks, setTasks } = useTasks(); - const { layout } = useLayout(); - const { areas, setAreas } = useAreas(); - const { settings, setSettings } = useSettings(); - const { notes, setNotes } = useNotes(); - const { transform, setTransform } = useTransform(); - const { tables, relationships, setTables, setRelationships } = useTables(); - const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo(); - - const handleResize = (e) => { - if (!resize) return; - const w = e.clientX; - if (w > 340) setWidth(w); - }; - - useEffect(() => { - if ( - tables?.length === 0 && - areas?.length === 0 && - notes?.length === 0 && - types?.length === 0 && - tasks?.length === 0 - ) - return; - - if (settings.autosave) { - setState(State.SAVING); - } - }, [ - undoStack, - redoStack, - settings.autosave, - tables?.length, - areas?.length, - notes?.length, - types?.length, - relationships?.length, - tasks?.length, - transform.zoom, - title, - ]); - - const save = useCallback( - async (diagram = true) => { - if (state !== State.SAVING) { - return; - } - if (diagram) { - if ( - (id === 0 && window.name === "") || - window.name.split(" ")[0] === "lt" - ) { - db.diagrams - .add({ - name: title, - lastModified: new Date(), - tables: tables, - references: relationships, - types: types, - notes: notes, - areas: areas, - todos: tasks, - pan: transform.pan, - zoom: transform.zoom, - }) - .then((id) => { - setId(id); - window.name = `d ${id}`; - setState(State.SAVED); - setLastSaved(new Date().toLocaleString()); - }); - } else { - db.diagrams - .update(id, { - name: title, - lastModified: new Date(), - tables: tables, - references: relationships, - types: types, - notes: notes, - areas: areas, - todos: tasks, - pan: transform.pan, - zoom: transform.zoom, - }) - .then(() => { - setState(State.SAVED); - setLastSaved(new Date().toLocaleString()); - }); - } - } else { - db.templates - .update(id, { - title: title, - tables: tables, - relationships: relationships, - types: types, - notes: notes, - subjectAreas: areas, - todos: tasks, - pan: transform.pan, - zoom: transform.zoom, - }) - .then(() => { - setState(State.SAVED); - setLastSaved(new Date().toLocaleString()); - }) - .catch(() => { - setState(State.ERROR); - }); - } - }, - [ - tables, - relationships, - notes, - areas, - types, - title, - id, - state, - tasks, - transform.zoom, - transform.pan, - ] - ); - useEffect(() => { - const name = window.name.split(" "); - const op = name[0]; - const diagram = window.name === "" || op === "d" || op === "lt"; - - save(diagram); - }, [id, state, save]); - - useEffect(() => { - document.title = "Editor | drawDB"; - - const loadLatestDiagram = async () => { - db.diagrams - .orderBy("lastModified") - .last() - .then((d) => { - if (d) { - setId(d.id); - setTables(d.tables); - setRelationships(d.references); - setNotes(d.notes); - setAreas(d.areas); - setTypes(d.types); - setTasks(d.todos ?? []); - setTransform({ pan: d.pan, zoom: d.zoom }); - window.name = `d ${d.id}`; - } else { - window.name = ""; - } - }) - .catch((error) => { - console.log(error); - }); - }; - - const loadDiagram = async (id) => { - db.diagrams - .get(id) - .then((diagram) => { - if (diagram) { - setId(diagram.id); - setTitle(diagram.name); - setTables(diagram.tables); - setTypes(diagram.types); - setRelationships(diagram.references); - setAreas(diagram.areas); - setNotes(diagram.notes); - setTasks(diagram.todos ?? []); - setTransform({ - pan: diagram.pan, - zoom: diagram.zoom, - }); - setUndoStack([]); - setRedoStack([]); - window.name = `d ${diagram.id}`; - } else { - window.name = ""; - } - }) - .catch((error) => { - console.log(error); - }); - }; - - const loadTemplate = async (id) => { - db.templates - .get(id) - .then((diagram) => { - if (diagram) { - setId(diagram.id); - setTitle(diagram.title); - setTables(diagram.tables); - setTypes(diagram.types); - setRelationships(diagram.relationships); - setAreas(diagram.subjectAreas); - setTasks(diagram.todos ?? []); - setNotes(diagram.notes); - setTransform({ - zoom: 1, - pan: { x: 0, y: 0 }, - }); - setUndoStack([]); - setRedoStack([]); - } - }) - .catch((error) => { - console.log(error); - }); - }; - - if (window.name == "") { - console.log("Loading the latest diagram"); - loadLatestDiagram(); - } else { - const name = window.name.split(" "); - const op = name[0]; - const did = parseInt(name[1]); - switch (op) { - case "d": { - loadDiagram(did); - break; - } - case "lt": { - loadTemplate(did); - break; - } - case "t": { - loadTemplate(did); - break; - } - default: - break; - } - } - }, [ - setSettings, - setTransform, - setRedoStack, - setUndoStack, - setRelationships, - setTables, - setAreas, - setNotes, - setTypes, - setTasks, - ]); - - return ( - -
- -
setResize(false)} - onMouseLeave={() => setResize(false)} - onMouseMove={handleResize} - > - {layout.sidebar && ( - - )} -
- - {!(layout.sidebar || layout.toolbar || layout.header) && ( -
- -
- )} -
-
-
-
- ); -}