diff --git a/src/components/Area.jsx b/src/components/Area.jsx index 53b1521..04bbe52 100644 --- a/src/components/Area.jsx +++ b/src/components/Area.jsx @@ -19,240 +19,109 @@ import useUndoRedo from "../hooks/useUndoRedo"; import useSelect from "../hooks/useSelect"; import useAreas from "../hooks/useAreas"; import useSaveState from "../hooks/useSaveState"; +import useTransform from "../hooks/useTransform"; -export default function Area(props) { +export default function Area({ data, onMouseDown, setResize, setInitCoords }) { const [hovered, setHovered] = useState(false); - const [editField, setEditField] = useState({}); const { layout } = useLayout(); const { settings } = useSettings(); + const { transform } = useTransform(); const { setSaveState } = useSaveState(); - const { updateArea, deleteArea } = useAreas(); - const { setUndoStack, setRedoStack } = useUndoRedo(); const { selectedElement, setSelectedElement } = useSelect(); - const handleMouseDown = (e, dir) => { - props.setResize({ id: props.areaData.id, dir: dir }); - props.setInitCoords({ - x: props.areaData.x, - y: props.areaData.y, - width: props.areaData.width, - height: props.areaData.height, - mouseX: e.clientX / props.zoom, - mouseY: e.clientY / props.zoom, + const handleResize = (e, dir) => { + setResize({ id: data.id, dir: dir }); + setInitCoords({ + x: data.x, + y: data.y, + width: data.width, + height: data.height, + mouseX: e.clientX / transform.zoom, + mouseY: e.clientY / transform.zoom, }); }; + const edit = () => { + if (layout.sidebar) { + setSelectedElement((prev) => ({ + ...prev, + element: ObjectType.AREA, + id: data.id, + currentTab: Tab.AREAS, + open: true, + })); + if (selectedElement.currentTab !== Tab.AREAS) return; + document + .getElementById(`scroll_area_${data.id}`) + .scrollIntoView({ behavior: "smooth" }); + } else { + setSelectedElement((prev) => ({ + ...prev, + element: ObjectType.AREA, + id: data.id, + open: true, + })); + } + }; + + const onClickOutSide = () => { + if (selectedElement.editFromToolbar) { + setSelectedElement((prev) => ({ + ...prev, + editFromToolbar: false, + })); + return; + } + setSelectedElement((prev) => ({ + ...prev, + open: false, + })); + setSaveState(State.SAVING); + }; + + const areaIsSelected = () => + selectedElement.element === ObjectType.AREA && + selectedElement.id === data.id && + selectedElement.open; + return ( setHovered(true)} - onMouseLeave={() => { - setHovered(false); - }} + onMouseLeave={() => setHovered(false)} > 0 ? props.areaData.width : 0} - height={props.areaData.height > 0 ? props.areaData.height : 0} - onMouseDown={props.onMouseDown} + key={data.id} + x={data.x} + y={data.y} + width={data.width > 0 ? data.width : 0} + height={data.height > 0 ? data.height : 0} + onMouseDown={onMouseDown} >
- {props.areaData.name} + {data.name}
- {(hovered || - (selectedElement.element === ObjectType.AREA && - selectedElement.id === props.areaData.id && - selectedElement.open && - !layout.sidebar)) && ( + {(hovered || (areaIsSelected() && !layout.sidebar)) && (
{ - if (selectedElement.editFromToolbar) { - setSelectedElement((prev) => ({ - ...prev, - editFromToolbar: false, - })); - return; - } - setSelectedElement((prev) => ({ - ...prev, - open: false, - })); - setSaveState(State.SAVING); - }} + visible={areaIsSelected() && !layout.sidebar} + onClickOutSide={onClickOutSide} stopPropagation - content={ -
-
- Edit subject area -
-
- - updateArea(props.areaData.id, { name: value }) - } - onFocus={(e) => setEditField({ name: e.target.value })} - onBlur={(e) => { - if (e.target.value === editField.name) return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.AREA, - aid: props.areaData.id, - undo: editField, - redo: { name: e.target.value }, - message: `Edit area name to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> - -
-
Theme
- -
-
-
-
- {tableThemes - .slice(0, Math.ceil(tableThemes.length / 2)) - .map((c) => ( - - ))} -
-
- {tableThemes - .slice(Math.ceil(tableThemes.length / 2)) - .map((c) => ( - - ))} -
-
-
- } - position="rightTop" - showArrow - > -
- -
-
- -
-
- } + content={} trigger="custom" position="rightTop" showArrow @@ -265,29 +134,8 @@ export default function Area(props) { backgroundColor: "#2f68ad", opacity: "0.7", }} - onClick={() => { - if (layout.sidebar) { - setSelectedElement((prev) => ({ - ...prev, - element: ObjectType.AREA, - id: props.areaData.id, - currentTab: Tab.AREAS, - open: true, - })); - if (selectedElement.currentTab !== Tab.AREAS) return; - document - .getElementById(`scroll_area_${props.areaData.id}`) - .scrollIntoView({ behavior: "smooth" }); - } else { - setSelectedElement((prev) => ({ - ...prev, - element: ObjectType.AREA, - id: props.areaData.id, - open: true, - })); - } - }} - > + onClick={edit} + />
)} @@ -295,47 +143,196 @@ export default function Area(props) { {hovered && ( <> handleMouseDown(e, "tl")} + onMouseDown={(e) => handleResize(e, "tl")} /> handleMouseDown(e, "tr")} + onMouseDown={(e) => handleResize(e, "tr")} /> handleMouseDown(e, "bl")} + onMouseDown={(e) => handleResize(e, "bl")} /> handleMouseDown(e, "br")} + onMouseDown={(e) => handleResize(e, "br")} /> )} ); } + +function EditPopoverContent({ data }) { + const [editField, setEditField] = useState({}); + const { setSaveState } = useSaveState(); + const { updateArea, deleteArea } = useAreas(); + const { setUndoStack, setRedoStack } = useUndoRedo(); + + return ( +
+
Edit subject area
+
+ updateArea(data.id, { name: value })} + onFocus={(e) => setEditField({ name: e.target.value })} + onBlur={(e) => { + if (e.target.value === editField.name) return; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.AREA, + aid: data.id, + undo: editField, + redo: { name: e.target.value }, + message: `Edit area name to ${e.target.value}`, + }, + ]); + setRedoStack([]); + }} + /> + +
+
Theme
+ +
+
+
+
+ {tableThemes + .slice(0, Math.ceil(tableThemes.length / 2)) + .map((c) => ( + + ))} +
+
+ {tableThemes + .slice(Math.ceil(tableThemes.length / 2)) + .map((c) => ( + + ))} +
+
+
+ } + position="rightTop" + showArrow + > +
+ +
+
+ +
+
+ ); +} diff --git a/src/components/AreaOverview.jsx b/src/components/AreasOverview.jsx similarity index 88% rename from src/components/AreaOverview.jsx rename to src/components/AreasOverview.jsx index b6ecfe9..34e46b5 100644 --- a/src/components/AreaOverview.jsx +++ b/src/components/AreasOverview.jsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { - Empty, Row, Col, AutoComplete, @@ -9,10 +8,6 @@ import { Popover, Toast, } from "@douyinfe/semi-ui"; -import { - IllustrationNoContent, - IllustrationNoContentDark, -} from "@douyinfe/semi-illustrations"; import { IconPlus, IconSearch, @@ -29,26 +24,21 @@ import { import useUndoRedo from "../hooks/useUndoRedo"; import useAreas from "../hooks/useAreas"; import useSaveState from "../hooks/useSaveState"; +import NoElements from "./NoElements"; -export default function AreaOverview() { +export default function AreasOverview() { const { setSaveState } = useSaveState(); const { areas, addArea, deleteArea, updateArea } = useAreas(); const { setUndoStack, setRedoStack } = useUndoRedo(); const [editField, setEditField] = useState({}); - const [value, setValue] = useState(""); + const [searchText, setSearchText] = useState(""); const [filteredResult, setFilteredResult] = useState( - areas.map((t) => { - return t.name; - }) + areas.map((t) => t.name) ); const handleStringSearch = (value) => { setFilteredResult( - areas - .map((t) => { - return t.name; - }) - .filter((i) => i.includes(value)) + areas.map((t) => t.name).filter((i) => i.includes(value)) ); }; @@ -58,7 +48,7 @@ export default function AreaOverview() { } placeholder="Search..." @@ -66,7 +56,7 @@ export default function AreaOverview() {
No areas found
} onSearch={(v) => handleStringSearch(v)} - onChange={(v) => setValue(v)} + onChange={(v) => setSearchText(v)} onSelect={(v) => { const { id } = areas.find((t) => t.name === v); document @@ -77,24 +67,16 @@ export default function AreaOverview() { /> - {areas.length <= 0 ? ( -
- - } - darkModeImage={ - - } - title="No subject areas" - description="Add subject areas to compartmentalize tables!" - /> -
+ ) : (
{areas.map((a, i) => ( @@ -236,7 +218,7 @@ export default function AreaOverview() { Toast.success(`Area deleted!`); deleteArea(i, true); }} - > + /> ))} diff --git a/src/components/Canvas.jsx b/src/components/Canvas.jsx index 438ba62..390b459 100644 --- a/src/components/Canvas.jsx +++ b/src/components/Canvas.jsx @@ -1,10 +1,10 @@ import { useRef, useState, useEffect } from "react"; -import Table from "./Table"; import { Action, Cardinality, Constraint, ObjectType } from "../data/constants"; +import { Toast } from "@douyinfe/semi-ui"; +import Table from "./Table"; import Area from "./Area"; import Relationship from "./Relationship"; import Note from "./Note"; -import { Toast } from "@douyinfe/semi-ui"; import useSettings from "../hooks/useSettings"; import useTransform from "../hooks/useTransform"; import useTables from "../hooks/useTables"; @@ -28,7 +28,7 @@ export default function Canvas() { prevY: 0, }); const [linking, setLinking] = useState(false); - const [line, setLine] = useState({ + const [linkingLink, setLinkingLine] = useState({ startTableId: -1, startFieldId: -1, endTableId: -1, @@ -44,12 +44,17 @@ export default function Canvas() { mandatory: false, }); const [offset, setOffset] = useState({ x: 0, y: 0 }); - const [onRect, setOnRect] = useState({ + const [hoveredTable, setHoveredTable] = useState({ tableId: -1, field: -2, }); - const [panning, setPanning] = useState({ state: false, x: 0, y: 0 }); - const [panOffset, setPanOffset] = useState({ x: 0, y: 0 }); + const [panning, setPanning] = useState({ + isPanning: false, + x: 0, + y: 0, + dx: 0, + dy: 0, + }); const [areaResize, setAreaResize] = useState({ id: -1, dir: "none" }); const [initCoords, setInitCoords] = useState({ x: 0, @@ -63,7 +68,7 @@ export default function Canvas() { const canvas = useRef(null); - const handleMouseDownRect = (e, id, type) => { + const handleMouseDownOnElement = (e, id, type) => { const { clientX, clientY } = e; if (type === ObjectType.TABLE) { const table = tables.find((t) => t.id === id); @@ -72,7 +77,7 @@ export default function Canvas() { y: clientY / transform.zoom - table.y, }); setDragging({ - element: ObjectType.TABLE, + element: type, id: id, prevX: table.x, prevY: table.y, @@ -84,7 +89,7 @@ export default function Canvas() { y: clientY / transform.zoom - area.y, }); setDragging({ - element: ObjectType.AREA, + element: type, id: id, prevX: area.x, prevY: area.y, @@ -96,7 +101,7 @@ export default function Canvas() { y: clientY / transform.zoom - note.y, }); setDragging({ - element: ObjectType.NOTE, + element: type, id: id, prevX: note.x, prevY: note.y, @@ -113,29 +118,26 @@ export default function Canvas() { const handleMouseMove = (e) => { if (linking) { const rect = canvas.current.getBoundingClientRect(); - const offsetX = rect.left; - const offsetY = rect.top; - - setLine({ - ...line, - endX: (e.clientX - offsetX - transform.pan?.x) / transform.zoom, - endY: (e.clientY - offsetY - transform.pan?.y) / transform.zoom, + setLinkingLine({ + ...linkingLink, + endX: (e.clientX - rect.left - transform.pan?.x) / transform.zoom, + endY: (e.clientY - rect.top - transform.pan?.y) / transform.zoom, }); } else if ( - panning.state && + panning.isPanning && dragging.element === ObjectType.NONE && areaResize.id === -1 ) { if (!settings.panning) { return; } - const dx = e.clientX - panOffset.x; - const dy = e.clientY - panOffset.y; + const dx = e.clientX - panning.dx; + const dy = e.clientY - panning.dy; setTransform((prev) => ({ ...prev, pan: { x: prev.pan?.x + dx, y: prev.pan?.y + dy }, })); - setPanOffset({ x: e.clientX, y: e.clientY }); + setPanning((prev) => ({ ...prev, dx: e.clientX, dy: e.clientY })); } else if (dragging.element === ObjectType.TABLE && dragging.id >= 0) { const dx = e.clientX / transform.zoom - offset.x; const dy = e.clientY / transform.zoom - offset.y; @@ -154,44 +156,41 @@ export default function Canvas() { updateNote(dragging.id, { x: dx, y: dy }); } else if (areaResize.id !== -1) { if (areaResize.dir === "none") return; - - let newX = initCoords.x; - let newY = initCoords.y; - let newWidth = initCoords.width; - let newHeight = initCoords.height; + let newDims = { ...initCoords }; + delete newDims.mouseX; + delete newDims.mouseY; const mouseX = e.clientX / transform.zoom; const mouseY = e.clientY / transform.zoom; - setPanning({ state: false, x: 0, y: 0 }); + setPanning({ isPanning: false, x: 0, y: 0 }); if (areaResize.dir === "br") { - newWidth = initCoords.width + (mouseX - initCoords.mouseX); - newHeight = initCoords.height + (mouseY - initCoords.mouseY); + newDims.width = initCoords.width + (mouseX - initCoords.mouseX); + newDims.height = initCoords.height + (mouseY - initCoords.mouseY); } else if (areaResize.dir === "tl") { - newX = initCoords.x + (mouseX - initCoords.mouseX); - newY = initCoords.y + (mouseY - initCoords.mouseY); - newWidth = initCoords.width - (mouseX - initCoords.mouseX); - newHeight = initCoords.height - (mouseY - initCoords.mouseY); + newDims.x = initCoords.x + (mouseX - initCoords.mouseX); + newDims.y = initCoords.y + (mouseY - initCoords.mouseY); + newDims.width = initCoords.width - (mouseX - initCoords.mouseX); + newDims.height = initCoords.height - (mouseY - initCoords.mouseY); } else if (areaResize.dir === "tr") { - newY = initCoords.y + (mouseY - initCoords.mouseY); - newWidth = initCoords.width + (mouseX - initCoords.mouseX); - newHeight = initCoords.height - (mouseY - initCoords.mouseY); + newDims.y = initCoords.y + (mouseY - initCoords.mouseY); + newDims.width = initCoords.width + (mouseX - initCoords.mouseX); + newDims.height = initCoords.height - (mouseY - initCoords.mouseY); } else if (areaResize.dir === "bl") { - newX = initCoords.x + (mouseX - initCoords.mouseX); - newWidth = initCoords.width - (mouseX - initCoords.mouseX); - newHeight = initCoords.height + (mouseY - initCoords.mouseY); + newDims.x = initCoords.x + (mouseX - initCoords.mouseX); + newDims.width = initCoords.width - (mouseX - initCoords.mouseX); + newDims.height = initCoords.height + (mouseY - initCoords.mouseY); } - updateArea(areaResize.id, { - x: newX, - y: newY, - width: newWidth, - height: newHeight, - }); + updateArea(areaResize.id, { ...newDims }); } }; const handleMouseDown = (e) => { - setPanning({ state: true, ...transform.pan }); - setPanOffset({ x: e.clientX, y: e.clientY }); + setPanning({ + isPanning: true, + ...transform.pan, + dx: e.clientX, + dy: e.clientY, + }); setCursor("grabbing"); }; @@ -229,7 +228,7 @@ export default function Canvas() { const didPan = () => !(transform.pan?.x === panning.x && transform.pan?.y === panning.y); - const getMoveInfo = () => { + const getMovedElementDetails = () => { switch (dragging.element) { case ObjectType.TABLE: return { @@ -256,7 +255,7 @@ export default function Canvas() { const handleMouseUp = () => { if (coordsDidUpdate(dragging.element)) { - const info = getMoveInfo(); + const info = getMovedElementDetails(); setUndoStack((prev) => [ ...prev, { @@ -273,7 +272,7 @@ export default function Canvas() { setRedoStack([]); } setDragging({ element: ObjectType.NONE, id: -1, prevX: 0, prevY: 0 }); - if (panning.state && didPan()) { + if (panning.isPanning && didPan()) { setUndoStack((prev) => [ ...prev, { @@ -291,7 +290,7 @@ export default function Canvas() { open: false, })); } - setPanning({ state: false, x: 0, y: 0 }); + setPanning({ isPanning: false, x: 0, y: 0 }); setCursor("default"); if (linking) handleLinking(); setLinking(false); @@ -333,29 +332,29 @@ export default function Canvas() { }; const handleLinking = () => { - if (onRect.tableId < 0) return; - if (onRect.field < 0) return; + if (hoveredTable.tableId < 0) return; + if (hoveredTable.field < 0) return; if ( - tables[line.startTableId].fields[line.startFieldId].type !== - tables[onRect.tableId].fields[onRect.field].type + tables[linkingLink.startTableId].fields[linkingLink.startFieldId].type !== + tables[hoveredTable.tableId].fields[hoveredTable.field].type ) { Toast.info("Cannot connect"); return; } if ( - line.startTableId === onRect.tableId && - line.startFieldId === onRect.field + linkingLink.startTableId === hoveredTable.tableId && + linkingLink.startFieldId === hoveredTable.field ) return; addRelationship(true, { - ...line, - endTableId: onRect.tableId, - endFieldId: onRect.field, - endX: tables[onRect.tableId].x + 15, - endY: tables[onRect.tableId].y + onRect.field * 36 + 69, - name: `${tables[line.startTableId].name}_${ - tables[line.startTableId].fields[line.startFieldId].name + ...linkingLink, + endTableId: hoveredTable.tableId, + endFieldId: hoveredTable.field, + endX: tables[hoveredTable.tableId].x + 15, + endY: tables[hoveredTable.tableId].y + hoveredTable.field * 36 + 69, + name: `${tables[linkingLink.startTableId].name}_${ + tables[linkingLink.startTableId].fields[linkingLink.startFieldId].name }_fk`, id: relationships.length, }); @@ -434,14 +433,12 @@ export default function Canvas() { {areas.map((a) => ( - handleMouseDownRect(e, a.id, ObjectType.AREA) + handleMouseDownOnElement(e, a.id, ObjectType.AREA) } setResize={setAreaResize} - initCoords={initCoords} setInitCoords={setInitCoords} - zoom={transform.zoom} > ))} {relationships.map((e, i) => ( @@ -451,11 +448,11 @@ export default function Canvas() { - handleMouseDownRect(e, table.id, ObjectType.TABLE) + handleMouseDownOnElement(e, table.id, ObjectType.TABLE) } active={ selectedElement.element === ObjectType.TABLE && @@ -469,7 +466,7 @@ export default function Canvas() { ))} {linking && ( @@ -479,7 +476,7 @@ export default function Canvas() { key={n.id} data={n} onMouseDown={(e) => - handleMouseDownRect(e, n.id, ObjectType.NOTE) + handleMouseDownOnElement(e, n.id, ObjectType.NOTE) } > ))} diff --git a/src/components/ControlPanel.jsx b/src/components/ControlPanel.jsx index 25a8019..17117d8 100644 --- a/src/components/ControlPanel.jsx +++ b/src/components/ControlPanel.jsx @@ -1725,7 +1725,7 @@ export default function ControlPanel({ }) } limit={1} - > + /> {error.type === STATUS.ERROR ? ( setData((prev) => ({ ...prev, dbms: e }))} className="w-full" - > + /> } placeholder="Search..." @@ -61,7 +51,7 @@ export default function NotesOverview() {
No notes found
} onSearch={(v) => handleStringSearch(v)} - onChange={(v) => setValue(v)} + onChange={(v) => setSearchText(v)} onSelect={(v) => { const { id } = notes.find((t) => t.title === v); setActiveKey(`${id}`); @@ -79,18 +69,7 @@ export default function NotesOverview() { {notes.length <= 0 ? ( -
- - } - darkModeImage={ - - } - title="No text notes" - description="Add notes cuz why not!" - /> -
+ ) : ( + /> diff --git a/src/components/ReferenceOverview.jsx b/src/components/ReferenceOverview.jsx deleted file mode 100644 index 80c47cd..0000000 --- a/src/components/ReferenceOverview.jsx +++ /dev/null @@ -1,295 +0,0 @@ -import { useState } from "react"; -import { - AutoComplete, - Collapse, - Empty, - Row, - Col, - Select, - Button, - Popover, - Table, -} from "@douyinfe/semi-ui"; -import { - IconDeleteStroked, - IconLoopTextStroked, - IconMore, - IconSearch, -} from "@douyinfe/semi-icons"; -import { - IllustrationNoContent, - IllustrationNoContentDark, -} from "@douyinfe/semi-illustrations"; -import { Cardinality, Constraint, Action, ObjectType } from "../data/constants"; -import useTables from "../hooks/useTables"; -import useUndoRedo from "../hooks/useUndoRedo"; - -export default function ReferenceOverview() { - const columns = [ - { - title: "Primary", - dataIndex: "primary", - }, - { - title: "Foreign", - dataIndex: "foreign", - }, - ]; - const { tables, relationships, setRelationships, deleteRelationship } = - useTables(); - const { setUndoStack, setRedoStack } = useUndoRedo(); - const [refActiveIndex, setRefActiveIndex] = useState(""); - const [value, setValue] = useState(""); - const [filteredResult, setFilteredResult] = useState( - relationships.map((t) => { - return t.name; - }) - ); - - const handleStringSearch = (value) => { - setFilteredResult( - relationships - .map((t) => { - return t.name; - }) - .filter((i) => i.includes(value)) - ); - }; - return ( - <> - } - placeholder="Search..." - emptyContent={ -
No relationships found
- } - onSearch={(v) => handleStringSearch(v)} - onChange={(v) => setValue(v)} - onSelect={(v) => { - const { id } = relationships.find((t) => t.name === v); - setRefActiveIndex(`${id}`); - document - .getElementById(`scroll_ref_${id}`) - .scrollIntoView({ behavior: "smooth" }); - }} - className="w-full" - /> - setRefActiveIndex(k)} - accordion - > - {relationships.length <= 0 ? ( -
- - } - darkModeImage={ - - } - title="No relationships" - description="Drag to connect fields and form relationships!" - /> -
- ) : ( - relationships.map((r, i) => ( -
- {r.name}
} itemKey={`${i}`}> -
-
- Primary: - {tables[r.endTableId].name} -
-
- Foreign: - {tables[r.startTableId].name} -
-
- -
- + {d.name} @@ -2085,7 +2085,7 @@ export default function ControlPanel({ className="hover-1" >
- +
{e.message}
@@ -2166,7 +2166,7 @@ export default function ControlPanel({ setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 })) } > - + @@ -2176,7 +2176,7 @@ export default function ControlPanel({ setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 })) } > - + @@ -2241,7 +2241,7 @@ export default function ControlPanel({ className="py-1 px-2 hover-2 rounded text-xl -mt-0.5" onClick={() => setSidesheet(SIDESHEET.TODO)} > - + @@ -2401,11 +2401,7 @@ export default function ControlPanel({ - ) : ( -
- ) + layout.header ? :
} onClick={() => invertLayout("header")} > @@ -2413,11 +2409,7 @@ export default function ControlPanel({ - ) : ( -
- ) + layout.sidebar ? :
} onClick={() => invertLayout("sidebar")} > @@ -2425,11 +2417,7 @@ export default function ControlPanel({ - ) : ( -
- ) + layout.issues ? :
} onClick={() => invertLayout("issues")} > @@ -2437,7 +2425,7 @@ export default function ControlPanel({
} + icon={
} onClick={() => { if (layout.fullscreen) { exitFullscreen(); diff --git a/src/components/Controls.jsx b/src/components/Controls.jsx index 06b78f0..7730864 100644 --- a/src/components/Controls.jsx +++ b/src/components/Controls.jsx @@ -19,7 +19,7 @@ export default function Controls() { })) } > - +
{parseInt(transform.zoom * 100)}%
@@ -33,7 +33,7 @@ export default function Controls() { })) } > - +
@@ -49,7 +49,7 @@ export default function Controls() { exitFullscreen(); }} > - +
diff --git a/src/components/Issues.jsx b/src/components/Issues.jsx index eb767f6..8d011aa 100644 --- a/src/components/Issues.jsx +++ b/src/components/Issues.jsx @@ -39,7 +39,7 @@ export default function Issues() { className="mt-1" >
- + Issues
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index afbd898..fe22aac 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -48,7 +48,9 @@ export default function Navbar() {

} + title={ + logo + } visible={openMenu} onCancel={() => setOpenMenu(false)} width={window.innerWidth} diff --git a/src/components/NoElements.jsx b/src/components/NoElements.jsx new file mode 100644 index 0000000..b38aa03 --- /dev/null +++ b/src/components/NoElements.jsx @@ -0,0 +1,20 @@ +import { + IllustrationNoContent, + IllustrationNoContentDark, +} from "@douyinfe/semi-illustrations"; +import { Empty } from "@douyinfe/semi-ui"; + +export default function NoElements({ title, text }) { + return ( +
+ } + darkModeImage={ + + } + title={title} + description={text} + /> +
+ ); +} diff --git a/src/components/Note.jsx b/src/components/Note.jsx index 8a803b9..19da9df 100644 --- a/src/components/Note.jsx +++ b/src/components/Note.jsx @@ -28,10 +28,50 @@ export default function Note({ data, onMouseDown }) { const textarea = document.getElementById(`note_${data.id}`); textarea.style.height = "0"; textarea.style.height = textarea.scrollHeight + "px"; - const newHeight = textarea.scrollHeight + 41; + const newHeight = textarea.scrollHeight + 42; updateNote(data.id, { content: e.target.value, height: newHeight }); }; + const handleBlur = (e) => { + if (e.target.value === editField.content) return; + const textarea = document.getElementById(`note_${data.id}`); + textarea.style.height = "0"; + textarea.style.height = textarea.scrollHeight + "px"; + const newHeight = textarea.scrollHeight + 16 + 20 + 4; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.NOTE, + nid: data.id, + undo: editField, + redo: { content: e.target.value, height: newHeight }, + message: `Edit note content to "${e.target.value}"`, + }, + ]); + setRedoStack([]); + }; + + const edit = () => { + if (layout.sidebar) { + setSelectedElement((prev) => ({ + ...prev, + currentTab: Tab.NOTES, + })); + if (selectedElement.currentTab !== Tab.NOTES) return; + document + .getElementById(`scroll_note_${data.id}`) + .scrollIntoView({ behavior: "smooth" }); + } else { + setSelectedElement((prev) => ({ + ...prev, + element: ObjectType.NOTE, + id: data.id, + open: true, + })); + } + }; + return ( setHovered(true)} @@ -100,28 +140,10 @@ export default function Note({ data, onMouseDown }) { height: data.height, }) } - onBlur={(e) => { - if (e.target.value === editField.content) return; - const textarea = document.getElementById(`note_${data.id}`); - textarea.style.height = "0"; - textarea.style.height = textarea.scrollHeight + "px"; - const newHeight = textarea.scrollHeight + 16 + 20 + 4; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.NOTE, - nid: data.id, - undo: editField, - redo: { content: e.target.value, height: newHeight }, - message: `Edit note content to "${e.target.value}"`, - }, - ]); - setRedoStack([]); - }} + onBlur={handleBlur} className="w-full resize-none outline-none overflow-y-hidden border-none select-none" style={{ backgroundColor: data.color }} - > + /> {(hovered || (selectedElement.element === ObjectType.NOTE && selectedElement.id === data.id && @@ -253,26 +275,8 @@ export default function Note({ data, onMouseDown }) { backgroundColor: "#2f68ad", opacity: "0.7", }} - onClick={() => { - if (layout.sidebar) { - setSelectedElement((prev) => ({ - ...prev, - currentTab: Tab.NOTES, - })); - if (selectedElement.currentTab !== Tab.NOTES) return; - document - .getElementById(`scroll_note_${data.id}`) - .scrollIntoView({ behavior: "smooth" }); - } else { - setSelectedElement((prev) => ({ - ...prev, - element: ObjectType.NOTE, - id: data.id, - open: true, - })); - } - }} - > + onClick={edit} + /> )} diff --git a/src/components/NotesOverview.jsx b/src/components/NotesOverview.jsx index 5e5c90f..fe8963d 100644 --- a/src/components/NotesOverview.jsx +++ b/src/components/NotesOverview.jsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { - Empty, Row, Col, Button, @@ -11,10 +10,6 @@ import { Input, Toast, } from "@douyinfe/semi-ui"; -import { - IllustrationNoContent, - IllustrationNoContentDark, -} from "@douyinfe/semi-illustrations"; import { IconDeleteStroked, IconPlus, @@ -24,26 +19,21 @@ import { import { noteThemes, Action, ObjectType } from "../data/constants"; import useUndoRedo from "../hooks/useUndoRedo"; import useNotes from "../hooks/useNotes"; +import NoElements from "./NoElements"; export default function NotesOverview() { const { notes, updateNote, addNote, deleteNote } = useNotes(); const { setUndoStack, setRedoStack } = useUndoRedo(); - const [value, setValue] = useState(""); + const [searchText, setSearchText] = useState(""); const [editField, setEditField] = useState({}); const [activeKey, setActiveKey] = useState(""); const [filteredResult, setFilteredResult] = useState( - notes.map((t) => { - return t.title; - }) + notes.map((t) => t.title) ); const handleStringSearch = (value) => { setFilteredResult( - notes - .map((t) => { - return t.title; - }) - .filter((i) => i.includes(value)) + notes.map((t) => t.title).filter((i) => i.includes(value)) ); }; @@ -53,7 +43,7 @@ export default function NotesOverview() {
-
- -
- - } - trigger="click" - position="rightTop" - showArrow - > - - - - -
Cardinality
- - - -
On update:
- - - -
On delete:
- - - - - - - )) - )} - - - ); -} diff --git a/src/components/RelationshipsOverview.jsx b/src/components/RelationshipsOverview.jsx new file mode 100644 index 0000000..0ca2053 --- /dev/null +++ b/src/components/RelationshipsOverview.jsx @@ -0,0 +1,281 @@ +import { useState } from "react"; +import { + AutoComplete, + Collapse, + Row, + Col, + Select, + Button, + Popover, + Table, +} from "@douyinfe/semi-ui"; +import { + IconDeleteStroked, + IconLoopTextStroked, + IconMore, + IconSearch, +} from "@douyinfe/semi-icons"; +import { Cardinality, Constraint, Action, ObjectType } from "../data/constants"; +import useTables from "../hooks/useTables"; +import useUndoRedo from "../hooks/useUndoRedo"; +import NoElements from "./NoElements"; + +export default function RelationshipsOverview() { + const { relationships } = useTables(); + const [refActiveIndex, setRefActiveIndex] = useState(""); + const [searchText, setSearchText] = useState(""); + const [filteredResult, setFilteredResult] = useState( + relationships.map((t) => t.name) + ); + + const handleStringSearch = (value) => { + setFilteredResult( + relationships.map((t) => t.name).filter((i) => i.includes(value)) + ); + }; + + return ( + <> + } + placeholder="Search..." + emptyContent={ +
No relationships found
+ } + onSearch={(v) => handleStringSearch(v)} + onChange={(v) => setSearchText(v)} + onSelect={(v) => { + const { id } = relationships.find((t) => t.name === v); + setRefActiveIndex(`${id}`); + document + .getElementById(`scroll_ref_${id}`) + .scrollIntoView({ behavior: "smooth" }); + }} + className="w-full" + /> + setRefActiveIndex(k)} + accordion + > + {relationships.length <= 0 ? ( + + ) : ( + relationships.map((r) => ) + )} + + + ); +} + +function RelationshipPanel({ data }) { + const columns = [ + { + title: "Primary", + dataIndex: "primary", + }, + { + title: "Foreign", + dataIndex: "foreign", + }, + ]; + const { tables, setRelationships, deleteRelationship } = useTables(); + const { setUndoStack, setRedoStack } = useUndoRedo(); + + return ( +
+ +
+
+ Primary: + {tables[data.endTableId].name} +
+
+ Foreign: + {tables[data.startTableId].name} +
+
+ +
+
+ +
+ + } + trigger="click" + position="rightTop" + showArrow + > + +
On update:
+ + + +
On delete:
+ + + + + + + ); +} diff --git a/src/components/RichEditor.jsx b/src/components/RichEditor.jsx index aba271e..9abd32e 100644 --- a/src/components/RichEditor.jsx +++ b/src/components/RichEditor.jsx @@ -2,13 +2,12 @@ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; import { ContentEditable } from "@lexical/react/LexicalContentEditable"; import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin"; -import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin"; import { ListPlugin } from "@lexical/react/LexicalListPlugin"; import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin"; import { ClearEditorPlugin } from "@lexical/react/LexicalClearEditorPlugin"; import { TRANSFORMERS } from "@lexical/markdown"; - +import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; import ToolbarPlugin from "../plugins/ToolbarPlugin"; import ListMaxIndentLevelPlugin from "../plugins/ListMaxIndentLevelPlugin"; import CodeHighlightPlugin from "../plugins/CodeHighlightPlugin"; diff --git a/src/components/SidePanel.jsx b/src/components/SidePanel.jsx index b3215e7..9d072d9 100644 --- a/src/components/SidePanel.jsx +++ b/src/components/SidePanel.jsx @@ -1,8 +1,8 @@ import { Tabs } from "@douyinfe/semi-ui"; import { Tab } from "../data/constants"; -import TableOverview from "./TableOverview"; -import ReferenceOverview from "./ReferenceOverview"; -import AreaOverview from "./AreaOverview"; +import TablesOverview from "./TablesOverview"; +import RelationshipsOverview from "./RelationshipsOverview"; +import AreasOverview from "./AreasOverview"; import NotesOverview from "./NotesOverview"; import TypesOverview from "./TypesOverview"; import Issues from "./Issues"; @@ -20,12 +20,13 @@ export default function SidePanel({ width, resize, setResize }) { { tab: "Notes", itemKey: Tab.NOTES }, { tab: "Types", itemKey: Tab.TYPES }, ]; + const contentList = [ - , - , - , - , - , + , + , + , + , + , ]; return ( @@ -57,7 +58,7 @@ export default function SidePanel({ width, resize, setResize }) {
setResize(true)} > diff --git a/src/components/Table.jsx b/src/components/Table.jsx index 874cd68..1ef895a 100644 --- a/src/components/Table.jsx +++ b/src/components/Table.jsx @@ -131,7 +131,7 @@ export default function Table(props) { .scrollIntoView({ behavior: "smooth" }); } }} - > + /> @@ -205,7 +205,7 @@ export default function Table(props) { backgroundColor: "grey", color: "white", }} - > + />
)} @@ -469,7 +469,7 @@ export default function Table(props) { updateField(props.tableData.id, j, { primary: !f.primary }); }} icon={} - > + />
- + + /> ))} @@ -1229,7 +1229,7 @@ export default function Table(props) { Toast.success(`Table deleted!`); deleteTable(props.tableData.id); }} - > + /> @@ -1241,13 +1241,12 @@ export default function Table(props) { return (
{ setHoveredField(index); - props.setOnRect({ + props.setHoveredTable({ tableId: props.tableData.id, field: index, }); @@ -1261,7 +1260,7 @@ export default function Table(props) { className={`w-[10px] h-[10px] bg-[#2f68ad] opacity-80 z-50 rounded-full me-2`} onMouseDown={() => { props.handleGripField(index); - props.setLine((prev) => ({ + props.setLinkingLine((prev) => ({ ...prev, startFieldId: index, startTableId: props.tableData.id, @@ -1271,7 +1270,7 @@ export default function Table(props) { endY: props.tableData.y + index * 36 + 50 + 19, })); }} - > + /> {fieldData.name.length <= 11 ? fieldData.name : fieldData.name.substring(0, 11)} @@ -1352,7 +1351,7 @@ export default function Table(props) { }), }); }} - > + /> ) : ( fieldData.type )} diff --git a/src/components/TableOverview.jsx b/src/components/TableOverview.jsx deleted file mode 100644 index 6e15d72..0000000 --- a/src/components/TableOverview.jsx +++ /dev/null @@ -1,1130 +0,0 @@ -import { useState } from "react"; -import { - Action, - ObjectType, - defaultBlue, - sqlDataTypes, - tableThemes, -} from "../data/constants"; -import { - Collapse, - Row, - Col, - Input, - TextArea, - Button, - Card, - TagInput, - InputNumber, - Popover, - Checkbox, - Select, - AutoComplete, - Toast, - Empty, -} from "@douyinfe/semi-ui"; -import { - IconMore, - IconKeyStroked, - IconDeleteStroked, - IconCheckboxTick, - IconPlus, - IconSearch, -} from "@douyinfe/semi-icons"; -import { - IllustrationNoContent, - IllustrationNoContentDark, -} from "@douyinfe/semi-illustrations"; -import { getSize, hasCheck, hasPrecision, isSized } from "../utils/toSQL"; -import useTables from "../hooks/useTables"; -import useUndoRedo from "../hooks/useUndoRedo"; -import useSelect from "../hooks/useSelect"; -import useTypes from "../hooks/useTypes"; - -export default function TableOverview() { - const [indexActiveKey, setIndexActiveKey] = useState(""); - const [value, setValue] = useState(""); - const { - tables, - addTable, - deleteTable, - updateField, - updateTable, - setRelationships, - } = useTables(); - const { types } = useTypes(); - const { setUndoStack, setRedoStack } = useUndoRedo(); - const { selectedElement, setSelectedElement } = useSelect(); - const [editField, setEditField] = useState({}); - const [filteredResult, setFilteredResult] = useState( - tables.map((t) => { - return t.name; - }) - ); - - const handleStringSearch = (value) => { - setFilteredResult( - tables - .map((t) => { - return t.name; - }) - .filter((i) => i.includes(value)) - ); - }; - - return ( - <> - -
- } - placeholder="Search..." - onSearch={(v) => handleStringSearch(v)} - emptyContent={ -
No tables found
- } - onChange={(v) => setValue(v)} - onSelect={(v) => { - const { id } = tables.find((t) => t.name === v); - setSelectedElement((prev) => ({ - ...prev, - id: id, - open: true, - })); - document - .getElementById(`scroll_table_${id}`) - .scrollIntoView({ behavior: "smooth" }); - }} - className="w-full" - /> - -
- - - - - setSelectedElement((prev) => ({ - ...prev, - id: parseInt(k), - open: true, - })) - } - accordion - > - {tables.length <= 0 ? ( -
- - } - darkModeImage={ - - } - title="No tables" - description="Start building your diagram!" - /> -
- ) : ( - tables.map((t, i) => ( -
- {t.name}
} itemKey={`${t.id}`}> -
-
Name:
- updateTable(t.id, { name: value })} - onFocus={(e) => setEditField({ name: e.target.value })} - onBlur={(e) => { - if (e.target.value === editField.name) return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "self", - tid: t.id, - undo: editField, - redo: { name: e.target.value }, - message: `Edit table name to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> -
- {t.fields.map((f, j) => ( - -
- updateField(i, j, { name: value })} - onFocus={(e) => setEditField({ name: e.target.value })} - onBlur={(e) => { - if (e.target.value === editField.name) return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: editField, - redo: { name: e.target.value }, - message: `Edit table field name to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> - - - - - - - - - - - - -
Default value
- - updateField(i, j, { default: value }) - } - onFocus={(e) => - setEditField({ default: e.target.value }) - } - onBlur={(e) => { - if (e.target.value === editField.default) - return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: editField, - redo: { default: e.target.value }, - message: `Edit table field default to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> - {(f.type === "ENUM" || f.type === "SET") && ( - <> -
- {f.type} values -
- - updateField(i, j, { values: v }) - } - onFocus={() => - setEditField({ values: f.values }) - } - onBlur={() => { - if ( - JSON.stringify(editField.values) === - JSON.stringify(f.values) - ) - return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: editField, - redo: { values: f.values }, - message: `Edit table field values to "${JSON.stringify( - f.values - )}"`, - }, - ]); - setRedoStack([]); - }} - /> - - )} - {isSized(f.type) && ( - <> -
Size
- - updateField(i, j, { size: value }) - } - onFocus={(e) => - setEditField({ size: e.target.value }) - } - onBlur={(e) => { - if (e.target.value === editField.size) - return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: editField, - redo: { size: e.target.value }, - message: `Edit table field size to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> - - )} - {hasPrecision(f.type) && ( - <> -
Precision
- - updateField(i, j, { size: value }) - } - onFocus={(e) => - setEditField({ size: e.target.value }) - } - onBlur={(e) => { - if (e.target.value === editField.size) - return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: editField, - redo: { size: e.target.value }, - message: `Edit table field precision to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> - - )} - {hasCheck(f.type) && ( - <> -
- Check Expression -
- - updateField(i, j, { check: value }) - } - onFocus={(e) => - setEditField({ check: e.target.value }) - } - onBlur={(e) => { - if (e.target.value === editField.check) - return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: editField, - redo: { check: e.target.value }, - message: `Edit table field check expression to ${e.target.value}`, - }, - ]); - setRedoStack([]); - }} - /> -
- *This will be in the script as is. -
- - )} -
-
Unique
- { - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: { - [checkedValues.target.value]: - !checkedValues.target.checked, - }, - redo: { - [checkedValues.target.value]: - checkedValues.target.checked, - }, - }, - ]); - setRedoStack([]); - updateField(i, j, { - [checkedValues.target.value]: - checkedValues.target.checked, - }); - }} - > -
-
-
Autoincrement
- { - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: i, - fid: j, - undo: { - [checkedValues.target.value]: - !checkedValues.target.checked, - }, - redo: { - [checkedValues.target.value]: - checkedValues.target.checked, - }, - message: `Edit table field to${ - f.increment ? " not" : "" - } auto increment`, - }, - ]); - setRedoStack([]); - updateField(i, j, { - increment: !f.increment, - check: f.increment ? f.check : "", - }); - }} - > -
-
Comment
-