From 173b02daa2571f7b2514a98cfe5a5819b4fc2f2b Mon Sep 17 00:00:00 2001 From: 1ilit Date: Sat, 6 Apr 2024 08:19:12 +0300 Subject: [PATCH] Move controlpanel modal and sidesheet into folders --- src/components/EditorHeader/ControlPanel.jsx | 1050 +---------------- .../EditorHeader/LayoutDropdown.jsx | 71 ++ .../EditorHeader/Modal/ImportDiagram.jsx | 127 ++ .../EditorHeader/Modal/ImportSource.jsx | 66 ++ src/components/EditorHeader/Modal/Modal.jsx | 312 +++++ src/components/EditorHeader/Modal/New.jsx | 43 + src/components/EditorHeader/Modal/Open.jsx | 73 ++ src/components/EditorHeader/Modal/Rename.jsx | 11 + .../EditorHeader/SideSheet/Sidesheet.jsx | 61 + .../EditorHeader/SideSheet/Timeline.jsx | 32 + .../EditorHeader/{ => SideSheet}/Todo.jsx | 4 +- src/data/constants.js | 25 + src/utils/astToDiagram.js | 252 ++++ src/utils/modalTitles.js | 44 + 14 files changed, 1159 insertions(+), 1012 deletions(-) create mode 100644 src/components/EditorHeader/LayoutDropdown.jsx create mode 100644 src/components/EditorHeader/Modal/ImportDiagram.jsx create mode 100644 src/components/EditorHeader/Modal/ImportSource.jsx create mode 100644 src/components/EditorHeader/Modal/Modal.jsx create mode 100644 src/components/EditorHeader/Modal/New.jsx create mode 100644 src/components/EditorHeader/Modal/Open.jsx create mode 100644 src/components/EditorHeader/Modal/Rename.jsx create mode 100644 src/components/EditorHeader/SideSheet/Sidesheet.jsx create mode 100644 src/components/EditorHeader/SideSheet/Timeline.jsx rename src/components/EditorHeader/{ => SideSheet}/Todo.jsx (98%) create mode 100644 src/utils/astToDiagram.js create mode 100644 src/utils/modalTitles.js diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index e2f4b14..0a160dd 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -4,11 +4,9 @@ import { IconChevronRight, IconChevronUp, IconChevronDown, - IconCheckboxTick, IconSaveStroked, IconUndo, IconRedo, - IconRowsStroked, IconEdit, } from "@douyinfe/semi-icons"; import { Link, useNavigate } from "react-router-dom"; @@ -19,20 +17,9 @@ import { Dropdown, InputNumber, Tooltip, - Image, - Modal, Spin, - Input, - Upload, - Banner, Toast, - SideSheet, - List, - Checkbox, } from "@douyinfe/semi-ui"; -import timeLine from "../../assets/process.png"; -import timeLineDark from "../../assets/process_dark.png"; -import todo from "../../assets/calendar.png"; import { toPng, toJpeg, toSvg } from "html-to-image"; import { saveAs } from "file-saver"; import { @@ -47,17 +34,14 @@ import { Action, Tab, State, - Cardinality, + MODAL, + SIDESHEET, } from "../../data/constants"; import jsPDF from "jspdf"; import { useHotkeys } from "react-hotkeys-hook"; import { Validator } from "jsonschema"; import { areaSchema, noteSchema, tableSchema } from "../../data/schemas"; -import Editor from "@monaco-editor/react"; import { db } from "../../data/db"; -import { useLiveQuery } from "dexie-react-hooks"; -import { Parser } from "node-sql-parser"; -import Todo from "./Todo"; import { useLayout, useSettings, @@ -66,18 +50,16 @@ import { useUndoRedo, useSelect, } from "../../hooks"; -import { enterFullscreen, exitFullscreen } from "../../utils/fullscreen"; -import { - ddbDiagramIsValid, - jsonDiagramIsValid, -} from "../../utils/validateSchema"; +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 Thumbnail from "../Thumbnail"; import { IconAddArea, IconAddNote, IconAddTable } from "../../icons"; +import LayoutDropdown from "./LayoutDropdown"; +import Sidesheet from "./SideSheet/Sidesheet"; +import Modal from "./Modal/Modal"; export default function ControlPanel({ diagramId, @@ -86,47 +68,15 @@ export default function ControlPanel({ setTitle, lastSaved, }) { - const defaultTemplates = useLiveQuery(() => db.templates.toArray()); - const MODAL = { - NONE: 0, - IMG: 1, - CODE: 2, - IMPORT: 3, - RENAME: 4, - OPEN: 5, - SAVEAS: 6, - NEW: 7, - IMPORT_SRC: 8, - }; - const STATUS = { - NONE: 0, - WARNING: 1, - ERROR: 2, - OK: 3, - }; - const SIDESHEET = { - NONE: 0, - TODO: 1, - TIMELINE: 2, - }; - const diagrams = useLiveQuery(() => db.diagrams.toArray()); - const [visible, setVisible] = useState(MODAL.NONE); + const [modal, setModal] = useState(MODAL.NONE); const [sidesheet, setSidesheet] = useState(SIDESHEET.NONE); const [prevTitle, setPrevTitle] = useState(title); - const [saveAsTitle, setSaveAsTitle] = useState(title); - const [selectedDiagramId, setSelectedDiagramId] = useState(0); - const [selectedTemplateId, setSelectedTemplateId] = useState(-1); const [showEditName, setShowEditName] = useState(false); const [exportData, setExportData] = useState({ data: null, filename: `${title}_${new Date().toISOString()}`, extension: "", }); - const [error, setError] = useState({ - type: STATUS.NONE, - message: "", - }); - const [data, setData] = useState(null); const { saveState, setSaveState } = useSaveState(); const { layout, setLayout } = useLayout(); const { settings, setSettings } = useSettings(); @@ -149,28 +99,10 @@ export default function ControlPanel({ const { selectedElement, setSelectedElement } = useSelect(); const { transform, setTransform } = useTransform(); const navigate = useNavigate(); + const invertLayout = (component) => setLayout((prev) => ({ ...prev, [component]: !prev[component] })); - const diagramIsEmpty = () => { - return ( - tables.length === 0 && - relationships.length === 0 && - notes.length === 0 && - areas.length === 0 - ); - }; - - const overwriteDiagram = () => { - setTables(data.tables); - setRelationships(data.relationships); - setAreas(data.subjectAreas); - setNotes(data.notes); - if (data.title) { - setTitle(data.title); - } - }; - const undo = () => { if (undoStack.length === 0) return; const a = undoStack[undoStack.length - 1]; @@ -517,7 +449,7 @@ export default function ControlPanel({ } }; - const fileImport = () => setVisible(MODAL.IMPORT); + const fileImport = () => setModal(MODAL.IMPORT); const viewGrid = () => setSettings((prev) => ({ ...prev, showGrid: !prev.showGrid })); const zoomIn = () => @@ -555,14 +487,10 @@ export default function ControlPanel({ const fitWindow = () => { const diagram = document.getElementById("diagram").getBoundingClientRect(); const canvas = document.getElementById("canvas").getBoundingClientRect(); - console.log(diagram); - console.log(canvas); const scaleX = canvas.width / diagram.width; const scaleY = canvas.height / diagram.height; - const scale = Math.min(scaleX, scaleY); - const translateX = canvas.left; const translateY = canvas.top; @@ -737,44 +665,13 @@ export default function ControlPanel({ del(); }; const save = () => setSaveState(State.SAVING); - const open = () => setVisible(MODAL.OPEN); - const saveDiagramAs = () => setVisible(MODAL.SAVEAS); - const loadDiagram = async (id) => { - await db.diagrams - .get(id) - .then((diagram) => { - if (diagram) { - setDiagramId(diagram.id); - setTitle(diagram.name); - setTables(diagram.tables); - setTypes(diagram.types); - setRelationships(diagram.references); - setAreas(diagram.areas); - setNotes(diagram.notes); - setTransform({ - pan: diagram.pan, - zoom: diagram.zoom, - }); - setUndoStack([]); - setRedoStack([]); - window.name = `d ${diagram.id}`; - } else { - Toast.error("Oops! Something went wrong."); - } - }) - .catch(() => { - Toast.error("Oops! Couldn't load diagram."); - }); - }; - const createNewDiagram = (id) => { - const newWindow = window.open("/editor"); - newWindow.name = "lt " + id; - }; + const open = () => setModal(MODAL.OPEN); + const saveDiagramAs = () => setModal(MODAL.SAVEAS); const menu = { File: { New: { - function: () => setVisible(MODAL.NEW), + function: () => setModal(MODAL.NEW), }, "New window": { function: () => { @@ -813,7 +710,7 @@ export default function ControlPanel({ }, Rename: { function: () => { - setVisible(MODAL.RENAME); + setModal(MODAL.RENAME); setPrevTitle(title); }, }, @@ -840,10 +737,7 @@ export default function ControlPanel({ shortcut: "Ctrl+I", }, "Import from source": { - function: () => { - setData({ src: "", overwrite: true, dbms: "MySQL" }); - setVisible(MODAL.IMPORT_SRC); - }, + function: () => setModal(MODAL.IMPORT_SRC), }, "Export as": { children: [ @@ -856,7 +750,7 @@ export default function ControlPanel({ extension: "png", })); }); - setVisible(MODAL.IMG); + setModal(MODAL.IMG); }, }, { @@ -870,12 +764,12 @@ export default function ControlPanel({ })); } ); - setVisible(MODAL.IMG); + setModal(MODAL.IMG); }, }, { JSON: () => { - setVisible(MODAL.CODE); + setModal(MODAL.CODE); const result = JSON.stringify( { tables: tables, @@ -907,7 +801,7 @@ export default function ControlPanel({ })); } ); - setVisible(MODAL.IMG); + setModal(MODAL.IMG); }, }, { @@ -959,7 +853,7 @@ export default function ControlPanel({ children: [ { MySQL: () => { - setVisible(MODAL.CODE); + setModal(MODAL.CODE); const src = jsonToMySQL({ tables: tables, references: relationships, @@ -974,7 +868,7 @@ export default function ControlPanel({ }, { PostgreSQL: () => { - setVisible(MODAL.CODE); + setModal(MODAL.CODE); const src = jsonToPostgreSQL({ tables: tables, references: relationships, @@ -989,7 +883,7 @@ export default function ControlPanel({ }, { SQLite: () => { - setVisible(MODAL.CODE); + setModal(MODAL.CODE); const src = jsonToSQLite({ tables: tables, references: relationships, @@ -1004,7 +898,7 @@ export default function ControlPanel({ }, { MariaDB: () => { - setVisible(MODAL.CODE); + setModal(MODAL.CODE); const src = jsonToMariaDB({ tables: tables, references: relationships, @@ -1019,7 +913,7 @@ export default function ControlPanel({ }, { MSSQL: () => { - setVisible(MODAL.CODE); + setModal(MODAL.CODE); const src = jsonToSQLServer({ tables: tables, references: relationships, @@ -1252,838 +1146,33 @@ export default function ControlPanel({ }); useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true }); - const getModalTitle = () => { - switch (visible) { - case MODAL.IMPORT: - case MODAL.IMPORT_SRC: - return "Import diagram"; - case MODAL.CODE: - return "Export source"; - case MODAL.IMG: - return "Export image"; - case MODAL.RENAME: - return "Rename diagram"; - case MODAL.OPEN: - return "Open diagram"; - case MODAL.SAVEAS: - return "Save as"; - case MODAL.NEW: - return "Create new diagram"; - default: - return ""; - } - }; - - const getOkText = () => { - switch (visible) { - case MODAL.IMPORT: - case MODAL.IMPORT_SRC: - return "Import"; - case MODAL.CODE: - case MODAL.IMG: - return "Export"; - case MODAL.RENAME: - return "Rename"; - case MODAL.OPEN: - return "Open"; - case MODAL.SAVEAS: - return "Save as"; - case MODAL.NEW: - return "Create"; - default: - return "Confirm"; - } - }; - - const parseSQLAndLoadDiagram = () => { - const parser = new Parser(); - let ast = null; - try { - ast = parser.astify(data.src, { database: "MySQL" }); - } catch (err) { - Toast.error( - "Could not parse the sql file. Make sure there are no syntax errors." - ); - return; - } - - const tables = []; - const relationships = []; - const inlineForeignKeys = []; - - ast.forEach((e) => { - if (e.type === "create") { - if (e.keyword === "table") { - const table = {}; - table.name = e.table[0].table; - table.comment = ""; - table.color = "#175e7a"; - table.fields = []; - table.indices = []; - table.x = 0; - table.y = 0; - e.create_definitions.forEach((d) => { - if (d.resource === "column") { - const field = {}; - field.name = d.column.column; - field.type = d.definition.dataType; - field.comment = ""; - field.unique = false; - if (d.unique) field.unique = true; - field.increment = false; - if (d.auto_increment) field.increment = true; - field.notNull = false; - if (d.nullable) field.notNull = true; - field.primary = false; - if (d.primary_key) field.primary = true; - field.default = ""; - if (d.default_val) field.default = d.default_val.value.value; - if (d.definition["length"]) field.size = d.definition["length"]; - field.check = ""; - if (d.check) { - let check = ""; - if (d.check.definition[0].left.column) { - let value = d.check.definition[0].right.value; - if ( - d.check.definition[0].right.type === - "double_quote_string" || - d.check.definition[0].right.type === "single_quote_string" - ) - value = "'" + value + "'"; - check = - d.check.definition[0].left.column + - " " + - d.check.definition[0].operator + - " " + - value; - } else { - let value = d.check.definition[0].right.value; - if ( - d.check.definition[0].left.type === "double_quote_string" || - d.check.definition[0].left.type === "single_quote_string" - ) - value = "'" + value + "'"; - check = - value + - " " + - d.check.definition[0].operator + - " " + - d.check.definition[0].right.column; - } - field.check = check; - } - - table.fields.push(field); - } else if (d.resource === "constraint") { - if (d.constraint_type === "primary key") { - d.definition.forEach((c) => { - table.fields.forEach((f) => { - if (f.name === c.column && !f.primary) { - f.primary = true; - } - }); - }); - } else if (d.constraint_type === "FOREIGN KEY") { - inlineForeignKeys.push({ ...d, startTable: e.table[0].table }); - } - } - }); - tables.push(table); - tables.forEach((e, i) => { - e.id = i; - e.fields.forEach((f, j) => { - f.id = j; - }); - }); - } else if (e.keyword === "index") { - const index = {}; - index.name = e.index; - index.unique = false; - if (e.index_type === "unique") index.unique = true; - index.fields = []; - e.index_columns.forEach((f) => index.fields.push(f.column)); - - let found = -1; - tables.forEach((t, i) => { - if (found !== -1) return; - if (t.name === e.table.table) { - t.indices.push(index); - found = i; - } - }); - - if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j)); - } - } else if (e.type === "alter") { - if ( - e.expr[0].action === "add" && - e.expr[0].create_definitions.constraint_type === "FOREIGN KEY" - ) { - const relationship = {}; - const startTable = e.table[0].table; - const startField = e.expr[0].create_definitions.definition[0].column; - const endTable = - e.expr[0].create_definitions.reference_definition.table[0].table; - const endField = - e.expr[0].create_definitions.reference_definition.definition[0] - .column; - let updateConstraint = "No action"; - let deleteConstraint = "No action"; - e.expr[0].create_definitions.reference_definition.on_action.forEach( - (c) => { - if (c.type === "on update") { - updateConstraint = c.value.value; - updateConstraint = - updateConstraint[0].toUpperCase() + - updateConstraint.substring(1); - } else if (c.type === "on delete") { - deleteConstraint = c.value.value; - deleteConstraint = - deleteConstraint[0].toUpperCase() + - deleteConstraint.substring(1); - } - } - ); - - let startTableId = -1; - let startFieldId = -1; - let endTableId = -1; - let endFieldId = -1; - - tables.forEach((t) => { - if (t.name === startTable) { - startTableId = t.id; - return; - } - - if (t.name === endTable) { - endTableId = t.id; - } - }); - - if (startTableId === -1 || endTableId === -1) return; - - tables[startTableId].fields.forEach((f) => { - if (f.name === startField) { - startFieldId = f.id; - return; - } - - if (f.name === endField) { - endFieldId = f.id; - } - }); - - if (startFieldId === -1 || endFieldId === -1) return; - - relationship.name = startTable + "_" + startField + "_fk"; - relationship.startTableId = startTableId; - relationship.startFieldId = startFieldId; - relationship.endTableId = endTableId; - relationship.endFieldId = endFieldId; - relationship.updateConstraint = updateConstraint; - relationship.deleteConstraint = deleteConstraint; - relationship.cardinality = Cardinality.ONE_TO_ONE; - relationships.push(relationship); - - relationships.forEach((r, i) => (r.id = i)); - } - } - }); - - inlineForeignKeys.forEach((fk) => { - const relationship = {}; - const startTable = fk.startTable; - const startField = fk.definition[0].column; - const endTable = fk.reference_definition.table[0].table; - const endField = fk.reference_definition.definition[0].column; - let updateConstraint = "No action"; - let deleteConstraint = "No action"; - fk.reference_definition.on_action.forEach((c) => { - if (c.type === "on update") { - updateConstraint = c.value.value; - updateConstraint = - updateConstraint[0].toUpperCase() + updateConstraint.substring(1); - } else if (c.type === "on delete") { - deleteConstraint = c.value.value; - deleteConstraint = - deleteConstraint[0].toUpperCase() + deleteConstraint.substring(1); - } - }); - - let startTableId = -1; - let startFieldId = -1; - let endTableId = -1; - let endFieldId = -1; - - tables.forEach((t) => { - if (t.name === startTable) { - startTableId = t.id; - return; - } - - if (t.name === endTable) { - endTableId = t.id; - } - }); - - if (startTableId === -1 || endTableId === -1) return; - - tables[startTableId].fields.forEach((f) => { - if (f.name === startField) { - startFieldId = f.id; - return; - } - - if (f.name === endField) { - endFieldId = f.id; - } - }); - - if (startFieldId === -1 || endFieldId === -1) return; - - relationship.name = startTable + "_" + startField + "_fk"; - relationship.startTableId = startTableId; - relationship.startFieldId = startFieldId; - relationship.endTableId = endTableId; - relationship.endFieldId = endFieldId; - relationship.updateConstraint = updateConstraint; - relationship.deleteConstraint = deleteConstraint; - relationship.cardinality = Cardinality.ONE_TO_ONE; - relationships.push(relationship); - }); - - relationships.forEach((r, i) => (r.id = i)); - - if (data.overwrite) { - setTables(tables); - setRelationships(relationships); - setNotes([]); - setAreas([]); - setTypes([]); - setUndoStack([]); - setRedoStack([]); - } else { - setTables((prev) => [...prev, ...tables]); - setRelationships((prev) => [...prev, ...relationships]); - } - - console.log(tables); - console.log(relationships); - }; - - const getModalOnOk = async () => { - switch (visible) { - case MODAL.IMG: - saveAs( - exportData.data, - `${exportData.filename}.${exportData.extension}` - ); - return; - case MODAL.CODE: { - const blob = new Blob([exportData.data], { - type: "application/json", - }); - saveAs(blob, `${exportData.filename}.${exportData.extension}`); - return; - } - case MODAL.IMPORT: - if (error.type !== STATUS.ERROR) { - setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } })); - overwriteDiagram(); - setData(null); - setVisible(MODAL.NONE); - setUndoStack([]); - setRedoStack([]); - } - return; - case MODAL.IMPORT_SRC: - parseSQLAndLoadDiagram(); - setVisible(MODAL.NONE); - return; - case MODAL.OPEN: - if (selectedDiagramId === 0) return; - loadDiagram(selectedDiagramId); - setVisible(MODAL.NONE); - return; - case MODAL.RENAME: - setPrevTitle(title); - setVisible(MODAL.NONE); - return; - case MODAL.SAVEAS: - setTitle(saveAsTitle); - setVisible(MODAL.NONE); - return; - case MODAL.NEW: - setVisible(MODAL.NONE); - createNewDiagram(selectedTemplateId); - return; - default: - setVisible(MODAL.NONE); - return; - } - }; - - const importModalBody = () => { - return ( - <> - { - const f = fileList[0].fileInstance; - if (!f) { - return; - } - const reader = new FileReader(); - reader.onload = async (e) => { - let jsonObject = null; - try { - jsonObject = JSON.parse(e.target.result); - } catch (error) { - setError({ - type: STATUS.ERROR, - message: "The file contains an error.", - }); - return; - } - if (f.type === "application/json") { - if (!jsonDiagramIsValid(jsonObject)) { - setError({ - type: STATUS.ERROR, - message: - "The file is missing necessary properties for a diagram.", - }); - return; - } - } else if (f.name.split(".").pop() === "ddb") { - if (!ddbDiagramIsValid(jsonObject)) { - setError({ - type: STATUS.ERROR, - message: - "The file is missing necessary properties for a diagram.", - }); - return; - } - } - setData(jsonObject); - if (diagramIsEmpty()) { - setError({ - type: STATUS.OK, - message: "Everything looks good. You can now import.", - }); - } else { - setError({ - type: STATUS.WARNING, - message: - "The current diagram is not empty. Importing a new diagram will overwrite the current changes.", - }); - } - }; - reader.readAsText(f); - - return { - autoRemove: false, - fileInstance: file.fileInstance, - status: "success", - shouldUpload: false, - }; - }} - draggable={true} - dragMainText="Drag and drop the file here or click to upload." - dragSubText="Support json and ddb" - accept="application/json,.ddb" - onRemove={() => - setError({ - type: STATUS.NONE, - message: "", - }) - } - onFileChange={() => - setError({ - type: STATUS.NONE, - message: "", - }) - } - limit={1} - /> - {error.type === STATUS.ERROR ? ( - {error.message}} - /> - ) : error.type === STATUS.OK ? ( - {error.message}} - /> - ) : ( - error.type === STATUS.WARNING && ( - {error.message}} - /> - ) - )} - - ); - }; - - const importSrcModalBody = () => { - return ( - <> - { - const f = fileList[0].fileInstance; - if (!f) { - return; - } - const reader = new FileReader(); - reader.onload = async (e) => { - setData((prev) => ({ ...prev, src: e.target.result })); - }; - reader.readAsText(f); - - return { - autoRemove: false, - fileInstance: file.fileInstance, - status: "success", - shouldUpload: false, - }; - }} - draggable={true} - dragMainText="Drag and drop the file here or click to upload." - dragSubText="Upload an sql file to autogenerate your tables and columns." - accept=".sql" - onRemove={() => { - setError({ - type: STATUS.NONE, - message: "", - }); - setData((prev) => ({ ...prev, src: "" })); - }} - onFileChange={() => - setError({ - type: STATUS.NONE, - message: "", - }) - } - limit={1} - > -
-
- * For the time being loading only MySQL scripts is supported. -
- - setData((prev) => ({ ...prev, overwrite: e.target.checked })) - } - > - Overwrite existing diagram - -
- - ); - }; - - const newModalBody = () => ( -
-
setSelectedTemplateId(0)}> -
- -
-
Blank
-
- {defaultTemplates.map((temp, i) => ( -
setSelectedTemplateId(temp.id)}> -
- -
-
{temp.title}
-
- ))} -
- ); - - const getModalBody = () => { - switch (visible) { - case MODAL.IMPORT: - return importModalBody(); - case MODAL.IMPORT_SRC: - return importSrcModalBody(); - case MODAL.NEW: - return newModalBody(); - case MODAL.RENAME: - return ( - setTitle(v)} - /> - ); - case MODAL.OPEN: - return ( -
- {diagrams?.length === 0 ? ( - You have no saved diagrams.
} - /> - ) : ( -
- - - - - - - - - - {diagrams?.map((d) => { - const size = JSON.stringify(d).length; - let sizeStr; - if (size >= 1024 && size < 1024 * 1024) - sizeStr = (size / 1024).toFixed(1) + "KB"; - else if (size >= 1024 * 1024) - sizeStr = (size / (1024 * 1024)).toFixed(1) + "MB"; - else sizeStr = size + "B"; - return ( - { - setSelectedDiagramId(d.id); - }} - onDoubleClick={() => { - loadDiagram(d.id); - window.name = "d " + d.id; - setVisible(MODAL.NONE); - }} - > - - - - - ); - })} - -
NameLast ModifiedSize
- - {d.name} - - {d.lastModified.toLocaleDateString() + - " " + - d.lastModified.toLocaleTimeString()} - {sizeStr}
-
- )} - - ); - case MODAL.SAVEAS: - return ( - setSaveAsTitle(v)} - /> - ); - case MODAL.CODE: - case MODAL.IMG: - if (exportData.data !== "" || exportData.data) { - return ( - <> - {visible === MODAL.IMG ? ( - Diagram - ) : ( - - )} -
Filename:
- {`.${exportData.extension}`}} - onChange={(value) => - setExportData((prev) => ({ ...prev, filename: value })) - } - field="filename" - /> - - ); - } else { - return ( -
- -
- ); - } - default: - return <>; - } - }; - return ( <> {layout.header && header()} {layout.toolbar && toolbar()} { - setExportData(() => ({ - data: "", - extension: "", - filename: `${title}_${new Date().toISOString()}`, - })); - setError({ - type: STATUS.NONE, - message: "", - }); - setData(null); - }} - onCancel={() => { - if (visible === MODAL.RENAME) setTitle(prevTitle); - setVisible(MODAL.NONE); - }} - centered - closeOnEsc={true} - okText={getOkText()} - okButtonProps={{ - disabled: - (error && error.type && error.type === STATUS.ERROR) || - (visible === MODAL.IMPORT && - (error.type === STATUS.ERROR || !data)) || - ((visible === MODAL.IMG || visible === MODAL.CODE) && - !exportData.data) || - (visible === MODAL.RENAME && title === "") || - (visible === MODAL.SAVEAS && saveAsTitle === "") || - (visible === MODAL.IMPORT_SRC && data.src === ""), - }} - cancelText="Cancel" - width={visible === MODAL.NEW ? 740 : 600} - > - {getModalBody()} - - { - setSidesheet(SIDESHEET.NONE); - }} - width={340} - title={getTitle(sidesheet)} - style={{ paddingBottom: "16px" }} - bodyStyle={{ padding: "0px" }} - > - {getContent(sidesheet)} - + modal={modal} + exportData={exportData} + setExportData={setExportData} + title={title} + setTitle={setTitle} + setPrevTitle={setPrevTitle} + setDiagramId={setDiagramId} + setModal={setModal} + prevTitle={prevTitle} + /> + setSidesheet(SIDESHEET.NONE)} + /> ); - function getTitle(type) { - switch (type) { - case SIDESHEET.TIMELINE: - return ( -
- chat icon -
Timeline
-
- ); - case SIDESHEET.TODO: - return ( -
- todo icon -
To-do list
-
- ); - default: - break; - } - } - - function getContent(type) { - switch (type) { - case SIDESHEET.TIMELINE: - return renderTimeline(); - case SIDESHEET.TODO: - return ; - default: - break; - } - } - - function renderTimeline() { - if (undoStack.length > 0) { - return ( - - {[...undoStack].reverse().map((e, i) => ( - -
- -
{e.message}
-
-
- ))} -
- ); - } else { - return ( -
- No activity was recorded. You have not added anything to your diagram - yet. -
- ); - } - } - function toolbar() { return (
- {layoutDropdown()} + setShowEditName(true)} onMouseLeave={() => setShowEditName(false)} - onClick={() => setVisible(MODAL.RENAME)} + onClick={() => setModal(MODAL.RENAME)} > {window.name.split(" ")[0] === "t" ? "Templates/" : "Diagrams/"} {title}
- {(showEditName || visible === MODAL.RENAME) && } + {(showEditName || modal === MODAL.RENAME) && }
@@ -2368,63 +1457,4 @@ export default function ControlPanel({ ); } - - function layoutDropdown() { - return ( - - :
- } - onClick={() => invertLayout("header")} - > - Header - - :
- } - onClick={() => invertLayout("sidebar")} - > - Sidebar - - :
- } - onClick={() => invertLayout("issues")} - > - Issues - - - } - onClick={() => { - if (layout.fullscreen) { - exitFullscreen(); - } else { - enterFullscreen(); - } - invertLayout("fullscreen"); - }} - > - Fullscreen - - - } - trigger="click" - > -
- -
- -
-
- - ); - } } diff --git a/src/components/EditorHeader/LayoutDropdown.jsx b/src/components/EditorHeader/LayoutDropdown.jsx new file mode 100644 index 0000000..11d4425 --- /dev/null +++ b/src/components/EditorHeader/LayoutDropdown.jsx @@ -0,0 +1,71 @@ +import { + IconCaretdown, + IconCheckboxTick, + IconRowsStroked, +} from "@douyinfe/semi-icons"; +import { Dropdown } from "@douyinfe/semi-ui"; +import { useLayout } from "../../hooks"; +import { enterFullscreen, exitFullscreen } from "../../utils/fullscreen"; + +export default function LayoutDropdown() { + const { layout, setLayout } = useLayout(); + const invertLayout = (component) => + setLayout((prev) => ({ ...prev, [component]: !prev[component] })); + + return ( + + :
+ } + onClick={() => invertLayout("header")} + > + Header + + :
+ } + onClick={() => invertLayout("sidebar")} + > + Sidebar + + :
+ } + onClick={() => invertLayout("issues")} + > + Issues + + + } + onClick={() => { + if (layout.fullscreen) { + exitFullscreen(); + } else { + enterFullscreen(); + } + invertLayout("fullscreen"); + }} + > + Fullscreen + + + } + trigger="click" + > +
+ +
+ +
+
+ + ); +} diff --git a/src/components/EditorHeader/Modal/ImportDiagram.jsx b/src/components/EditorHeader/Modal/ImportDiagram.jsx new file mode 100644 index 0000000..39ad1dd --- /dev/null +++ b/src/components/EditorHeader/Modal/ImportDiagram.jsx @@ -0,0 +1,127 @@ +import { + ddbDiagramIsValid, + jsonDiagramIsValid, +} from "../../../utils/validateSchema"; +import { Upload, Banner } from "@douyinfe/semi-ui"; +import { STATUS } from "../../../data/constants"; +import { useAreas, useNotes, useTables } from "../../../hooks"; + +export default function ImportDiagram({ setImportData, error, setError }) { + const { areas } = useAreas(); + const { notes } = useNotes(); + const { tables, relationships } = useTables(); + + const diagramIsEmpty = () => { + return ( + tables.length === 0 && + relationships.length === 0 && + notes.length === 0 && + areas.length === 0 + ); + }; + + return ( +
+ { + const f = fileList[0].fileInstance; + if (!f) { + return; + } + const reader = new FileReader(); + reader.onload = async (e) => { + let jsonObject = null; + try { + jsonObject = JSON.parse(e.target.result); + } catch (error) { + setError({ + type: STATUS.ERROR, + message: "The file contains an error.", + }); + return; + } + if (f.type === "application/json") { + if (!jsonDiagramIsValid(jsonObject)) { + setError({ + type: STATUS.ERROR, + message: + "The file is missing necessary properties for a diagram.", + }); + return; + } + } else if (f.name.split(".").pop() === "ddb") { + if (!ddbDiagramIsValid(jsonObject)) { + setError({ + type: STATUS.ERROR, + message: + "The file is missing necessary properties for a diagram.", + }); + return; + } + } + setImportData(jsonObject); + if (diagramIsEmpty()) { + setError({ + type: STATUS.OK, + message: "Everything looks good. You can now import.", + }); + } else { + setError({ + type: STATUS.WARNING, + message: + "The current diagram is not empty. Importing a new diagram will overwrite the current changes.", + }); + } + }; + reader.readAsText(f); + + return { + autoRemove: false, + fileInstance: file.fileInstance, + status: "success", + shouldUpload: false, + }; + }} + draggable={true} + dragMainText="Drag and drop the file here or click to upload." + dragSubText="Support json and ddb" + accept="application/json,.ddb" + onRemove={() => + setError({ + type: STATUS.NONE, + message: "", + }) + } + onFileChange={() => + setError({ + type: STATUS.NONE, + message: "", + }) + } + limit={1} + /> + {error.type === STATUS.ERROR ? ( + {error.message}
} + /> + ) : error.type === STATUS.OK ? ( + {error.message}
} + /> + ) : ( + error.type === STATUS.WARNING && ( + {error.message}
} + /> + ) + )} +
+ ); +} diff --git a/src/components/EditorHeader/Modal/ImportSource.jsx b/src/components/EditorHeader/Modal/ImportSource.jsx new file mode 100644 index 0000000..1b86454 --- /dev/null +++ b/src/components/EditorHeader/Modal/ImportSource.jsx @@ -0,0 +1,66 @@ +import { Upload, Checkbox } from "@douyinfe/semi-ui"; +import { STATUS } from "../../../data/constants"; + +export default function ImportSource({ importData, setImportData, setError }) { + return ( +
+ { + const f = fileList[0].fileInstance; + if (!f) { + return; + } + const reader = new FileReader(); + reader.onload = async (e) => { + setImportData((prev) => ({ ...prev, src: e.target.result })); + }; + reader.readAsText(f); + + return { + autoRemove: false, + fileInstance: file.fileInstance, + status: "success", + shouldUpload: false, + }; + }} + draggable={true} + dragMainText="Drag and drop the file here or click to upload." + dragSubText="Upload an sql file to autogenerate your tables and columns." + accept=".sql" + onRemove={() => { + setError({ + type: STATUS.NONE, + message: "", + }); + setImportData((prev) => ({ ...prev, src: "" })); + }} + onFileChange={() => + setError({ + type: STATUS.NONE, + message: "", + }) + } + limit={1} + /> +
+
+ * For the time being loading only MySQL scripts is supported. +
+ + setImportData((prev) => ({ + ...prev, + overwrite: e.target.checked, + })) + } + > + Overwrite existing diagram + +
+
+ ); +} diff --git a/src/components/EditorHeader/Modal/Modal.jsx b/src/components/EditorHeader/Modal/Modal.jsx new file mode 100644 index 0000000..5e10296 --- /dev/null +++ b/src/components/EditorHeader/Modal/Modal.jsx @@ -0,0 +1,312 @@ +import { + Spin, + Input, + Image, + Toast, + Modal as SemiUIModal, +} from "@douyinfe/semi-ui"; +import { MODAL, STATUS } from "../../../data/constants"; +import { useState } from "react"; +import { db } from "../../../data/db"; +import { + useAreas, + useNotes, + useSettings, + useTables, + useTransform, + useTypes, + useUndoRedo, +} from "../../../hooks"; +import { saveAs } from "file-saver"; +import { Parser } from "node-sql-parser"; +import { astToDiagram } from "../../../utils/astToDiagram"; +import { getModalTitle, getOkText } from "../../../utils/modalTitles"; +import Rename from "./Rename"; +import Open from "./Open"; +import New from "./New"; +import ImportDiagram from "./ImportDiagram"; +import ImportSource from "./ImportSource"; +import Editor from "@monaco-editor/react"; + +export default function Modal({ + modal, + setModal, + title, + setTitle, + prevTitle, + setPrevTitle, + setDiagramId, + exportData, + setExportData, +}) { + const { setTables, setRelationships } = useTables(); + const { setNotes } = useNotes(); + const { setAreas } = useAreas(); + const { setTypes } = useTypes(); + const { settings } = useSettings(); + const { setTransform } = useTransform(); + const { setUndoStack, setRedoStack } = useUndoRedo(); + const [importSource, setImportSource] = useState({ + src: "", + overwrite: true, + dbms: "MySQL", + }); + const [importData, setImportData] = useState(null); + const [error, setError] = useState({ + type: STATUS.NONE, + message: "", + }); + const [selectedTemplateId, setSelectedTemplateId] = useState(-1); + const [selectedDiagramId, setSelectedDiagramId] = useState(0); + const [saveAsTitle, setSaveAsTitle] = useState(title); + + const overwriteDiagram = () => { + setTables(importData.tables); + setRelationships(importData.relationships); + setAreas(importData.subjectAreas); + setNotes(importData.notes); + if (importData.title) { + setTitle(importData.title); + } + }; + + const loadDiagram = async (id) => { + await db.diagrams + .get(id) + .then((diagram) => { + if (diagram) { + setDiagramId(diagram.id); + setTitle(diagram.name); + setTables(diagram.tables); + setTypes(diagram.types); + setRelationships(diagram.references); + setAreas(diagram.areas); + setNotes(diagram.notes); + setTransform({ + pan: diagram.pan, + zoom: diagram.zoom, + }); + setUndoStack([]); + setRedoStack([]); + window.name = `d ${diagram.id}`; + } else { + Toast.error("Oops! Something went wrong."); + } + }) + .catch(() => { + Toast.error("Oops! Couldn't load diagram."); + }); + }; + + const parseSQLAndLoadDiagram = () => { + const parser = new Parser(); + let ast = null; + try { + ast = parser.astify(importData.src, { database: "MySQL" }); + } catch (err) { + Toast.error( + "Could not parse the sql file. Make sure there are no syntax errors." + ); + return; + } + + const d = astToDiagram(ast); + if (importData.overwrite) { + setTables(d.tables); + setRelationships(d.relationships); + setNotes([]); + setAreas([]); + setTypes([]); + setUndoStack([]); + setRedoStack([]); + } else { + setTables((prev) => [...prev, ...d.tables]); + setRelationships((prev) => [...prev, ...d.relationships]); + } + }; + + const createNewDiagram = (id) => { + const newWindow = window.open("/editor"); + newWindow.name = "lt " + id; + }; + + const getModalOnOk = async () => { + switch (modal) { + case MODAL.IMG: + saveAs( + exportData.data, + `${exportData.filename}.${exportData.extension}` + ); + return; + case MODAL.CODE: { + const blob = new Blob([exportData.data], { + type: "application/json", + }); + saveAs(blob, `${exportData.filename}.${exportData.extension}`); + return; + } + case MODAL.IMPORT: + if (error.type !== STATUS.ERROR) { + setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } })); + overwriteDiagram(); + setImportData(null); + setModal(MODAL.NONE); + setUndoStack([]); + setRedoStack([]); + } + return; + case MODAL.IMPORT_SRC: + parseSQLAndLoadDiagram(); + setModal(MODAL.NONE); + return; + case MODAL.OPEN: + if (selectedDiagramId === 0) return; + loadDiagram(selectedDiagramId); + setModal(MODAL.NONE); + return; + case MODAL.RENAME: + setPrevTitle(title); + setModal(MODAL.NONE); + return; + case MODAL.SAVEAS: + setTitle(saveAsTitle); + setModal(MODAL.NONE); + return; + case MODAL.NEW: + setModal(MODAL.NONE); + createNewDiagram(selectedTemplateId); + return; + default: + setModal(MODAL.NONE); + return; + } + }; + + const getModalBody = () => { + switch (modal) { + case MODAL.IMPORT: + return ( + + ); + case MODAL.IMPORT_SRC: + return ( + + ); + case MODAL.NEW: + return ( + + ); + case MODAL.RENAME: + return ; + case MODAL.OPEN: + return ( + + ); + case MODAL.SAVEAS: + return ( + setSaveAsTitle(v)} + /> + ); + case MODAL.CODE: + case MODAL.IMG: + if (exportData.data !== "" || exportData.data) { + return ( + <> + {modal === MODAL.IMG ? ( + Diagram + ) : ( + + )} +
Filename:
+ {`.${exportData.extension}`}
} + onChange={(value) => + setExportData((prev) => ({ ...prev, filename: value })) + } + field="filename" + /> + + ); + } else { + return ( +
+ +
+ ); + } + default: + return <>; + } + }; + + return ( + { + setExportData(() => ({ + data: "", + extension: "", + filename: `${title}_${new Date().toISOString()}`, + })); + setError({ + type: STATUS.NONE, + message: "", + }); + setImportData(null); + setImportSource({ + src: "", + overwrite: true, + dbms: "MySQL", + }); + }} + onCancel={() => { + if (modal === MODAL.RENAME) setTitle(prevTitle); + setModal(MODAL.NONE); + }} + centered + closeOnEsc={true} + okText={getOkText(modal)} + okButtonProps={{ + disabled: + (error && error?.type === STATUS.ERROR) || + (modal === MODAL.IMPORT && + (error.type === STATUS.ERROR || !importData)) || + (modal === MODAL.RENAME && title === "") || + ((modal === MODAL.IMG || modal === MODAL.CODE) && !exportData.data) || + (modal === MODAL.SAVEAS && saveAsTitle === "") || + (modal === MODAL.IMPORT_SRC && importSource.src === ""), + }} + cancelText="Cancel" + width={modal === MODAL.NEW ? 740 : 600} + > + {getModalBody()} + + ); +} diff --git a/src/components/EditorHeader/Modal/New.jsx b/src/components/EditorHeader/Modal/New.jsx new file mode 100644 index 0000000..0733ef7 --- /dev/null +++ b/src/components/EditorHeader/Modal/New.jsx @@ -0,0 +1,43 @@ +import { db } from "../../../data/db"; +import { useSettings } from "../../../hooks"; +import { useLiveQuery } from "dexie-react-hooks"; +import Thumbnail from "../../Thumbnail"; + +export default function New({ selectedTemplateId, setSelectedTemplateId }) { + const { settings } = useSettings(); + const templates = useLiveQuery(() => db.templates.toArray()); + + return ( +
+
setSelectedTemplateId(0)}> +
+ +
+
Blank
+
+ {templates?.map((temp, i) => ( +
setSelectedTemplateId(temp.id)}> +
+ +
+
{temp.title}
+
+ ))} +
+ ); +} diff --git a/src/components/EditorHeader/Modal/Open.jsx b/src/components/EditorHeader/Modal/Open.jsx new file mode 100644 index 0000000..e138b92 --- /dev/null +++ b/src/components/EditorHeader/Modal/Open.jsx @@ -0,0 +1,73 @@ +import { db } from "../../../data/db"; +import { Banner } from "@douyinfe/semi-ui"; +import { useLiveQuery } from "dexie-react-hooks"; + +export default function Open({ selectedDiagramId, setSelectedDiagramId }) { + const diagrams = useLiveQuery(() => db.diagrams.toArray()); + + const getDiagramSize = (d) => { + const size = JSON.stringify(d).length; + let sizeStr; + if (size >= 1024 && size < 1024 * 1024) + sizeStr = (size / 1024).toFixed(1) + "KB"; + else if (size >= 1024 * 1024) + sizeStr = (size / (1024 * 1024)).toFixed(1) + "MB"; + else sizeStr = size + "B"; + + return sizeStr; + }; + return ( +
+ {diagrams?.length === 0 ? ( + You have no saved diagrams.
} + /> + ) : ( +
+ + + + + + + + + + {diagrams?.map((d) => { + return ( + { + setSelectedDiagramId(d.id); + }} + > + + + + + ); + })} + +
NameLast ModifiedSize
+ + {d.name} + + {d.lastModified.toLocaleDateString() + + " " + + d.lastModified.toLocaleTimeString()} + {getDiagramSize(d)}
+
+ )} +
+ ); +} diff --git a/src/components/EditorHeader/Modal/Rename.jsx b/src/components/EditorHeader/Modal/Rename.jsx new file mode 100644 index 0000000..9042859 --- /dev/null +++ b/src/components/EditorHeader/Modal/Rename.jsx @@ -0,0 +1,11 @@ +import { Input } from "@douyinfe/semi-ui"; + +export default function Rename({ title, setTitle }) { + return ( + setTitle(v)} + /> + ); +} diff --git a/src/components/EditorHeader/SideSheet/Sidesheet.jsx b/src/components/EditorHeader/SideSheet/Sidesheet.jsx new file mode 100644 index 0000000..0dce650 --- /dev/null +++ b/src/components/EditorHeader/SideSheet/Sidesheet.jsx @@ -0,0 +1,61 @@ +import { SideSheet as SemiUISideSheet } from "@douyinfe/semi-ui"; +import { SIDESHEET } from "../../../data/constants"; +import { useSettings } from "../../../hooks"; +import timeLine from "../../../assets/process.png"; +import timeLineDark from "../../../assets/process_dark.png"; +import todo from "../../../assets/calendar.png"; +import Timeline from "./Timeline"; +import Todo from "./Todo"; + +export default function Sidesheet({ type, onClose }) { + const { settings } = useSettings(); + + function getTitle(type) { + switch (type) { + case SIDESHEET.TIMELINE: + return ( +
+ chat icon +
Timeline
+
+ ); + case SIDESHEET.TODO: + return ( +
+ todo icon +
To-do list
+
+ ); + default: + break; + } + } + + function getContent(type) { + switch (type) { + case SIDESHEET.TIMELINE: + return ; + case SIDESHEET.TODO: + return ; + default: + break; + } + } + + return ( + + {getContent(type)} + + ); +} diff --git a/src/components/EditorHeader/SideSheet/Timeline.jsx b/src/components/EditorHeader/SideSheet/Timeline.jsx new file mode 100644 index 0000000..4f46757 --- /dev/null +++ b/src/components/EditorHeader/SideSheet/Timeline.jsx @@ -0,0 +1,32 @@ +import { useUndoRedo } from "../../../hooks"; +import { List } from "@douyinfe/semi-ui"; + +export default function Timeline() { + const { undoStack } = useUndoRedo(); + + if (undoStack.length > 0) { + return ( + + {[...undoStack].reverse().map((e, i) => ( + +
+ +
{e.message}
+
+
+ ))} +
+ ); + } else { + return ( +
+ No activity was recorded. You have not added anything to your diagram + yet. +
+ ); + } +} diff --git a/src/components/EditorHeader/Todo.jsx b/src/components/EditorHeader/SideSheet/Todo.jsx similarity index 98% rename from src/components/EditorHeader/Todo.jsx rename to src/components/EditorHeader/SideSheet/Todo.jsx index ba98594..0aea674 100644 --- a/src/components/EditorHeader/Todo.jsx +++ b/src/components/EditorHeader/SideSheet/Todo.jsx @@ -19,8 +19,8 @@ import { IconDeleteStroked, IconCaretdown, } from "@douyinfe/semi-icons"; -import { State } from "../../data/constants"; -import { useTasks, useSaveState } from "../../hooks"; +import { State } from "../../../data/constants"; +import { useTasks, useSaveState } from "../../../hooks"; const Priority = { NONE: 0, diff --git a/src/data/constants.js b/src/data/constants.js index b42947b..46433e9 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -99,3 +99,28 @@ export const State = { LOADING: 3, ERROR: 4, }; + +export const MODAL = { + NONE: 0, + IMG: 1, + CODE: 2, + IMPORT: 3, + RENAME: 4, + OPEN: 5, + SAVEAS: 6, + NEW: 7, + IMPORT_SRC: 8, +}; + +export const STATUS = { + NONE: 0, + WARNING: 1, + ERROR: 2, + OK: 3, +}; + +export const SIDESHEET = { + NONE: 0, + TODO: 1, + TIMELINE: 2, +}; \ No newline at end of file diff --git a/src/utils/astToDiagram.js b/src/utils/astToDiagram.js new file mode 100644 index 0000000..e10ac1a --- /dev/null +++ b/src/utils/astToDiagram.js @@ -0,0 +1,252 @@ +import { Cardinality } from "../data/constants"; + +export function astToDiagram(ast) { + const tables = []; + const relationships = []; + const inlineForeignKeys = []; + + ast.forEach((e) => { + if (e.type === "create") { + if (e.keyword === "table") { + const table = {}; + table.name = e.table[0].table; + table.comment = ""; + table.color = "#175e7a"; + table.fields = []; + table.indices = []; + table.x = 0; + table.y = 0; + e.create_definitions.forEach((d) => { + if (d.resource === "column") { + const field = {}; + field.name = d.column.column; + field.type = d.definition.dataType; + field.comment = ""; + field.unique = false; + if (d.unique) field.unique = true; + field.increment = false; + if (d.auto_increment) field.increment = true; + field.notNull = false; + if (d.nullable) field.notNull = true; + field.primary = false; + if (d.primary_key) field.primary = true; + field.default = ""; + if (d.default_val) field.default = d.default_val.value.value; + if (d.definition["length"]) field.size = d.definition["length"]; + field.check = ""; + if (d.check) { + let check = ""; + if (d.check.definition[0].left.column) { + let value = d.check.definition[0].right.value; + if ( + d.check.definition[0].right.type === "double_quote_string" || + d.check.definition[0].right.type === "single_quote_string" + ) + value = "'" + value + "'"; + check = + d.check.definition[0].left.column + + " " + + d.check.definition[0].operator + + " " + + value; + } else { + let value = d.check.definition[0].right.value; + if ( + d.check.definition[0].left.type === "double_quote_string" || + d.check.definition[0].left.type === "single_quote_string" + ) + value = "'" + value + "'"; + check = + value + + " " + + d.check.definition[0].operator + + " " + + d.check.definition[0].right.column; + } + field.check = check; + } + + table.fields.push(field); + } else if (d.resource === "constraint") { + if (d.constraint_type === "primary key") { + d.definition.forEach((c) => { + table.fields.forEach((f) => { + if (f.name === c.column && !f.primary) { + f.primary = true; + } + }); + }); + } else if (d.constraint_type === "FOREIGN KEY") { + inlineForeignKeys.push({ ...d, startTable: e.table[0].table }); + } + } + }); + tables.push(table); + tables.forEach((e, i) => { + e.id = i; + e.fields.forEach((f, j) => { + f.id = j; + }); + }); + } else if (e.keyword === "index") { + const index = {}; + index.name = e.index; + index.unique = false; + if (e.index_type === "unique") index.unique = true; + index.fields = []; + e.index_columns.forEach((f) => index.fields.push(f.column)); + + let found = -1; + tables.forEach((t, i) => { + if (found !== -1) return; + if (t.name === e.table.table) { + t.indices.push(index); + found = i; + } + }); + + if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j)); + } + } else if (e.type === "alter") { + if ( + e.expr[0].action === "add" && + e.expr[0].create_definitions.constraint_type === "FOREIGN KEY" + ) { + const relationship = {}; + const startTable = e.table[0].table; + const startField = e.expr[0].create_definitions.definition[0].column; + const endTable = + e.expr[0].create_definitions.reference_definition.table[0].table; + const endField = + e.expr[0].create_definitions.reference_definition.definition[0] + .column; + let updateConstraint = "No action"; + let deleteConstraint = "No action"; + e.expr[0].create_definitions.reference_definition.on_action.forEach( + (c) => { + if (c.type === "on update") { + updateConstraint = c.value.value; + updateConstraint = + updateConstraint[0].toUpperCase() + + updateConstraint.substring(1); + } else if (c.type === "on delete") { + deleteConstraint = c.value.value; + deleteConstraint = + deleteConstraint[0].toUpperCase() + + deleteConstraint.substring(1); + } + } + ); + + let startTableId = -1; + let startFieldId = -1; + let endTableId = -1; + let endFieldId = -1; + + tables.forEach((t) => { + if (t.name === startTable) { + startTableId = t.id; + return; + } + + if (t.name === endTable) { + endTableId = t.id; + } + }); + + if (startTableId === -1 || endTableId === -1) return; + + tables[startTableId].fields.forEach((f) => { + if (f.name === startField) { + startFieldId = f.id; + return; + } + + if (f.name === endField) { + endFieldId = f.id; + } + }); + + if (startFieldId === -1 || endFieldId === -1) return; + + relationship.name = startTable + "_" + startField + "_fk"; + relationship.startTableId = startTableId; + relationship.startFieldId = startFieldId; + relationship.endTableId = endTableId; + relationship.endFieldId = endFieldId; + relationship.updateConstraint = updateConstraint; + relationship.deleteConstraint = deleteConstraint; + relationship.cardinality = Cardinality.ONE_TO_ONE; + relationships.push(relationship); + + relationships.forEach((r, i) => (r.id = i)); + } + } + }); + + inlineForeignKeys.forEach((fk) => { + const relationship = {}; + const startTable = fk.startTable; + const startField = fk.definition[0].column; + const endTable = fk.reference_definition.table[0].table; + const endField = fk.reference_definition.definition[0].column; + let updateConstraint = "No action"; + let deleteConstraint = "No action"; + fk.reference_definition.on_action.forEach((c) => { + if (c.type === "on update") { + updateConstraint = c.value.value; + updateConstraint = + updateConstraint[0].toUpperCase() + updateConstraint.substring(1); + } else if (c.type === "on delete") { + deleteConstraint = c.value.value; + deleteConstraint = + deleteConstraint[0].toUpperCase() + deleteConstraint.substring(1); + } + }); + + let startTableId = -1; + let startFieldId = -1; + let endTableId = -1; + let endFieldId = -1; + + tables.forEach((t) => { + if (t.name === startTable) { + startTableId = t.id; + return; + } + + if (t.name === endTable) { + endTableId = t.id; + } + }); + + if (startTableId === -1 || endTableId === -1) return; + + tables[startTableId].fields.forEach((f) => { + if (f.name === startField) { + startFieldId = f.id; + return; + } + + if (f.name === endField) { + endFieldId = f.id; + } + }); + + if (startFieldId === -1 || endFieldId === -1) return; + + relationship.name = startTable + "_" + startField + "_fk"; + relationship.startTableId = startTableId; + relationship.startFieldId = startFieldId; + relationship.endTableId = endTableId; + relationship.endFieldId = endFieldId; + relationship.updateConstraint = updateConstraint; + relationship.deleteConstraint = deleteConstraint; + relationship.cardinality = Cardinality.ONE_TO_ONE; + relationships.push(relationship); + }); + + relationships.forEach((r, i) => (r.id = i)); + + return { tables, relationships }; +} diff --git a/src/utils/modalTitles.js b/src/utils/modalTitles.js new file mode 100644 index 0000000..e7be422 --- /dev/null +++ b/src/utils/modalTitles.js @@ -0,0 +1,44 @@ +import { MODAL } from "../data/constants"; + +export const getModalTitle = (modal) => { + switch (modal) { + case MODAL.IMPORT: + case MODAL.IMPORT_SRC: + return "Import diagram"; + case MODAL.CODE: + return "Export source"; + case MODAL.IMG: + return "Export image"; + case MODAL.RENAME: + return "Rename diagram"; + case MODAL.OPEN: + return "Open diagram"; + case MODAL.SAVEAS: + return "Save as"; + case MODAL.NEW: + return "Create new diagram"; + default: + return ""; + } +}; + +export const getOkText = (modal) => { + switch (modal) { + case MODAL.IMPORT: + case MODAL.IMPORT_SRC: + return "Import"; + case MODAL.CODE: + case MODAL.IMG: + return "Export"; + case MODAL.RENAME: + return "Rename"; + case MODAL.OPEN: + return "Open"; + case MODAL.SAVEAS: + return "Save as"; + case MODAL.NEW: + return "Create"; + default: + return "Confirm"; + } +};