import { useState, useEffect, useCallback, createContext, useMemo, } from "react"; import ControlPanel from "./EditorHeader/ControlPanel"; import Canvas from "./EditorCanvas/Canvas"; import { CanvasContextProvider } from "../context/CanvasContext"; import SidePanel from "./EditorSidePanel/SidePanel"; import { DB, State } from "../data/constants"; import { db } from "../data/db"; import { useLayout, useSettings, useTransform, useDiagram, useUndoRedo, useAreas, useNotes, useTypes, useTasks, useSaveState, useEnums, } from "../hooks"; import FloatingControls from "./FloatingControls"; import { Modal } from "@douyinfe/semi-ui"; import { useTranslation } from "react-i18next"; import { databases } from "../data/databases"; import { isRtl } from "../i18n/utils/rtl"; import { useSearchParams } from "react-router-dom"; import { Octokit } from "octokit"; export const IdContext = createContext({ gistId: "" }); export default function WorkSpace() { const [id, setId] = useState(0); const [gistId, setGistId] = useState(""); const [loadedFromGistId, setLoadedFromGistId] = useState(""); const [title, setTitle] = useState("Untitled Diagram"); const [resize, setResize] = useState(false); const [width, setWidth] = useState(340); const [lastSaved, setLastSaved] = useState(""); const [showSelectDbModal, setShowSelectDbModal] = useState(false); const [selectedDb, setSelectedDb] = useState(""); const { layout } = useLayout(); const { settings } = useSettings(); const { types, setTypes } = useTypes(); const { areas, setAreas } = useAreas(); const { tasks, setTasks } = useTasks(); const { notes, setNotes } = useNotes(); const { saveState, setSaveState } = useSaveState(); const { transform, setTransform } = useTransform(); const { enums, setEnums } = useEnums(); const { tables, relationships, setTables, setRelationships, database, setDatabase, } = useDiagram(); const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo(); const { t, i18n } = useTranslation(); let [searchParams] = useSearchParams(); const userToken = localStorage.getItem("github_token"); const octokit = useMemo(() => { return new Octokit({ auth: userToken ?? import.meta.env.VITE_GITHUB_ACCESS_TOKEN, }); }, [userToken]); const handleResize = (e) => { if (!resize) return; const w = isRtl(i18n.language) ? window.innerWidth - e.clientX : e.clientX; if (w > 340) setWidth(w); }; const save = useCallback(async () => { const name = window.name.split(" "); const op = name[0]; const saveAsDiagram = window.name === "" || op === "d" || op === "lt"; if (saveAsDiagram) { if ( (id === 0 && window.name === "") || window.name.split(" ")[0] === "lt" ) { await db.diagrams .add({ database: database, name: title, gistId: gistId ?? "", lastModified: new Date(), tables: tables, references: relationships, notes: notes, areas: areas, todos: tasks, pan: transform.pan, zoom: transform.zoom, loadedFromGistId: loadedFromGistId, ...(databases[database].hasEnums && { enums: enums }), ...(databases[database].hasTypes && { types: types }), }) .then((id) => { setId(id); window.name = `d ${id}`; setSaveState(State.SAVED); setLastSaved(new Date().toLocaleString()); }); } else { await db.diagrams .update(id, { database: database, name: title, lastModified: new Date(), tables: tables, references: relationships, notes: notes, areas: areas, todos: tasks, gistId: gistId ?? "", pan: transform.pan, zoom: transform.zoom, loadedFromGistId: loadedFromGistId, ...(databases[database].hasEnums && { enums: enums }), ...(databases[database].hasTypes && { types: types }), }) .then(() => { setSaveState(State.SAVED); setLastSaved(new Date().toLocaleString()); }); } } else { await db.templates .update(id, { database: database, title: title, tables: tables, relationships: relationships, 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); setLastSaved(new Date().toLocaleString()); }) .catch(() => { setSaveState(State.ERROR); }); } }, [ tables, relationships, notes, areas, types, title, id, tasks, transform, setSaveState, database, enums, gistId, loadedFromGistId, ]); const load = useCallback(async () => { const loadLatestDiagram = async () => { await db.diagrams .orderBy("lastModified") .last() .then((d) => { if (d) { if (d.database) { setDatabase(d.database); } else { setDatabase(DB.GENERIC); } setId(d.id); setGistId(d.gistId); setLoadedFromGistId(d.loadedFromGistId); setTitle(d.name); setTables(d.tables); setRelationships(d.references); setNotes(d.notes); setAreas(d.areas); 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 = ""; if (selectedDb === "") setShowSelectDbModal(true); } }) .catch((error) => { console.log(error); }); }; const loadDiagram = async (id) => { await db.diagrams .get(id) .then((diagram) => { if (diagram) { if (diagram.database) { setDatabase(diagram.database); } else { setDatabase(DB.GENERIC); } setId(diagram.id); setGistId(diagram.gistId); setLoadedFromGistId(diagram.loadedFromGistId); setTitle(diagram.name); setTables(diagram.tables); setRelationships(diagram.references); setAreas(diagram.areas); setNotes(diagram.notes); setTasks(diagram.todos ?? []); setTransform({ pan: diagram.pan, zoom: diagram.zoom, }); setUndoStack([]); setRedoStack([]); if (databases[database].hasTypes) { setTypes(diagram.types ?? []); } if (databases[database].hasEnums) { setEnums(diagram.enums ?? []); } window.name = `d ${diagram.id}`; } else { window.name = ""; } }) .catch((error) => { console.log(error); }); }; const loadTemplate = async (id) => { await db.templates .get(id) .then((diagram) => { if (diagram) { if (diagram.database) { setDatabase(diagram.database); } else { setDatabase(DB.GENERIC); } setId(diagram.id); setTitle(diagram.title); setTables(diagram.tables); setRelationships(diagram.relationships); setAreas(diagram.subjectAreas); setTasks(diagram.todos ?? []); setNotes(diagram.notes); setTransform({ zoom: 1, pan: { x: 0, y: 0 }, }); setUndoStack([]); setRedoStack([]); if (databases[database].hasTypes) { setTypes(diagram.types ?? []); } if (databases[database].hasEnums) { setEnums(diagram.enums ?? []); } } else { if (selectedDb === "") setShowSelectDbModal(true); } }) .catch((error) => { console.log(error); if (selectedDb === "") setShowSelectDbModal(true); }); }; if (window.name === "") { loadLatestDiagram(); } else { const name = window.name.split(" "); const op = name[0]; const id = parseInt(name[1]); switch (op) { case "d": { loadDiagram(id); break; } case "t": case "lt": { loadTemplate(id); break; } default: break; } } }, [ setTransform, setRedoStack, setUndoStack, setRelationships, setTables, setAreas, setNotes, setTypes, setTasks, setDatabase, database, setEnums, selectedDb, ]); const loadFromGist = useCallback( async (shareId) => { const d = await db.diagrams.get({ loadedFromGistId: shareId }); if (d) { window.name = "d " + d.id; } else { window.name = ""; } try { const res = await octokit.request(`GET /gists/${shareId}`, { gist_id: shareId, headers: { "X-GitHub-Api-Version": "2022-11-28", }, }); const diagramSrc = res.data.files["share.json"].content; const d = JSON.parse(diagramSrc); setUndoStack([]); setRedoStack([]); setLoadedFromGistId(shareId); setDatabase(d.database); setTitle(d.title); setTables(d.tables); setRelationships(d.relationships); setNotes(d.notes); setAreas(d.subjectAreas); setTransform(d.transform); if (databases[d.database].hasTypes) { setTypes(d.types ?? []); } if (databases[d.database].hasEnums) { setEnums(d.enums ?? []); } } catch (e) { console.log(e); } }, [ octokit, setAreas, setDatabase, setEnums, setNotes, setRelationships, setTables, setTypes, setTransform, setRedoStack, setUndoStack, ], ); 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, ]); useEffect(() => { if (gistId && gistId !== "") { setSaveState(State.SAVING); } }, [gistId, setSaveState]); useEffect(() => { if (saveState !== State.SAVING) return; save(); }, [id, gistId, saveState, save]); useEffect(() => { document.title = "Editor | drawDB"; const shareId = searchParams.get("shareId"); if (shareId) { loadFromGist(shareId); } else { load(); } }, [load, searchParams, loadFromGist]); return (
e.isPrimary && setResize(false)} onPointerLeave={(e) => e.isPrimary && setResize(false)} onPointerMove={(e) => e.isPrimary && handleResize(e)} onPointerDown={(e) => { // Required for onPointerLeave to trigger when a touch pointer leaves // https://stackoverflow.com/a/70976017/1137077 e.target.releasePointerCapture(e.pointerId); }} style={isRtl(i18n.language) ? { direction: "rtl" } : {}} > {layout.sidebar && ( )}
{!(layout.sidebar || layout.toolbar || layout.header) && (
)}
{ if (selectedDb === "") return; setDatabase(selectedDb); setShowSelectDbModal(false); }} okButtonProps={{ disabled: selectedDb === "" }} >
{Object.values(databases).map((x) => (
setSelectedDb(x.label)} className={`space-y-3 py-3 px-4 rounded-md border-2 select-none ${ settings.mode === "dark" ? "bg-zinc-700 hover:bg-zinc-600" : "bg-zinc-100 hover:bg-zinc-200" } ${selectedDb === x.label ? "border-zinc-400" : "border-transparent"}`} >
{x.name}
{x.image && ( )}
{x.description}
))}
); }