Abstract save state from editor

This commit is contained in:
1ilit 2024-03-15 16:00:23 +02:00
parent d347e856a8
commit 1132edbbb3
9 changed files with 379 additions and 354 deletions

View File

@ -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

View File

@ -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

View File

@ -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 ? (
<Spin size="small" />
) : null
}

View File

@ -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={

View File

@ -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);
}}
></Checkbox>
</Col>
@ -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)}
></Input>
</Col>
<Col span={3}>
@ -187,7 +189,7 @@ export default function Todo() {
<RadioGroup
onChange={(e) => {
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)}
></TextArea>
</Col>
</Row>

View File

@ -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 (
<div className="h-[100vh] flex flex-col overflow-hidden theme">
<ControlPanel
diagramId={id}
setDiagramId={setId}
title={title}
setTitle={setTitle}
lastSaved={lastSaved}
setLastSaved={setLastSaved}
/>
<div
className="flex h-full overflow-y-auto"
onMouseUp={() => setResize(false)}
onMouseLeave={() => setResize(false)}
onMouseMove={handleResize}
>
{layout.sidebar && (
<SidePanel resize={resize} setResize={setResize} width={width} />
)}
<div className="relative w-full h-full overflow-hidden">
<Canvas saveState={saveState} setSaveState={setSaveState} />
{!(layout.sidebar || layout.toolbar || layout.header) && (
<div className="fixed right-5 bottom-4">
<Controls />
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -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 (
<SaveStateContext.Provider value={{ saveState, setSaveState }}>
{children}
</SaveStateContext.Provider>
);
}

View File

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

View File

@ -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() {
<NotesContextProvider>
<TypesContextProvider>
<TablesContextProvider>
<WorkSpace />
<SaveStateContextProvider>
<WorkSpace />
</SaveStateContextProvider>
</TablesContextProvider>
</TypesContextProvider>
</NotesContextProvider>
@ -49,298 +35,3 @@ export default function Editor() {
</LayoutContextProvider>
);
}
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 (
<StateContext.Provider value={{ state, setState }}>
<div className="h-[100vh] flex flex-col overflow-hidden theme">
<ControlPanel
diagramId={id}
setDiagramId={setId}
title={title}
setTitle={setTitle}
lastSaved={lastSaved}
setLastSaved={setLastSaved}
/>
<div
className="flex h-full overflow-y-auto"
onMouseUp={() => setResize(false)}
onMouseLeave={() => setResize(false)}
onMouseMove={handleResize}
>
{layout.sidebar && (
<SidePanel resize={resize} setResize={setResize} width={width} />
)}
<div className="relative w-full h-full overflow-hidden">
<Canvas state={state} setState={setState} />
{!(layout.sidebar || layout.toolbar || layout.header) && (
<div className="fixed right-5 bottom-4">
<Controls />
</div>
)}
</div>
</div>
</div>
</StateContext.Provider>
);
}