Clean up
This commit is contained in:
parent
7a406eb6d3
commit
5895d4c96e
@ -19,76 +19,52 @@ 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,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<g
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => {
|
||||
setHovered(false);
|
||||
}}
|
||||
>
|
||||
<foreignObject
|
||||
key={props.areaData.id}
|
||||
x={props.areaData.x}
|
||||
y={props.areaData.y}
|
||||
width={props.areaData.width > 0 ? props.areaData.width : 0}
|
||||
height={props.areaData.height > 0 ? props.areaData.height : 0}
|
||||
onMouseDown={props.onMouseDown}
|
||||
>
|
||||
<div
|
||||
className={`border-2 ${
|
||||
hovered
|
||||
? "border-dashed border-blue-500"
|
||||
: selectedElement.element === ObjectType.AREA &&
|
||||
selectedElement.id === props.areaData.id
|
||||
? "border-blue-500"
|
||||
: "border-slate-400"
|
||||
} w-full h-full cursor-move rounded relative`}
|
||||
>
|
||||
<div
|
||||
className="opacity-40 w-fill p-2 h-full"
|
||||
style={{ backgroundColor: props.areaData.color }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-color absolute top-2 left-3 select-none">
|
||||
{props.areaData.name}
|
||||
</div>
|
||||
{(hovered ||
|
||||
(selectedElement.element === ObjectType.AREA &&
|
||||
selectedElement.id === props.areaData.id &&
|
||||
selectedElement.open &&
|
||||
!layout.sidebar)) && (
|
||||
<div className="absolute top-2 right-3">
|
||||
<Popover
|
||||
visible={
|
||||
selectedElement.element === ObjectType.AREA &&
|
||||
selectedElement.id === props.areaData.id &&
|
||||
selectedElement.open &&
|
||||
!layout.sidebar
|
||||
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,
|
||||
}));
|
||||
}
|
||||
onClickOutSide={() => {
|
||||
};
|
||||
|
||||
const onClickOutSide = () => {
|
||||
if (selectedElement.editFromToolbar) {
|
||||
setSelectedElement((prev) => ({
|
||||
...prev,
|
||||
@ -101,21 +77,132 @@ export default function Area(props) {
|
||||
open: false,
|
||||
}));
|
||||
setSaveState(State.SAVING);
|
||||
}}
|
||||
stopPropagation
|
||||
content={
|
||||
<div className="popover-theme">
|
||||
<div className="font-semibold mb-2 ms-1">
|
||||
Edit subject area
|
||||
};
|
||||
|
||||
const areaIsSelected = () =>
|
||||
selectedElement.element === ObjectType.AREA &&
|
||||
selectedElement.id === data.id &&
|
||||
selectedElement.open;
|
||||
|
||||
return (
|
||||
<g
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<foreignObject
|
||||
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}
|
||||
>
|
||||
<div
|
||||
className={`border-2 ${
|
||||
hovered
|
||||
? "border-dashed border-blue-500"
|
||||
: selectedElement.element === ObjectType.AREA &&
|
||||
selectedElement.id === data.id
|
||||
? "border-blue-500"
|
||||
: "border-slate-400"
|
||||
} w-full h-full cursor-move rounded relative`}
|
||||
>
|
||||
<div
|
||||
className="opacity-40 w-fill p-2 h-full"
|
||||
style={{ backgroundColor: data.color }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-color absolute top-2 left-3 select-none">
|
||||
{data.name}
|
||||
</div>
|
||||
{(hovered || (areaIsSelected() && !layout.sidebar)) && (
|
||||
<div className="absolute top-2 right-3">
|
||||
<Popover
|
||||
visible={areaIsSelected() && !layout.sidebar}
|
||||
onClickOutSide={onClickOutSide}
|
||||
stopPropagation
|
||||
content={<EditPopoverContent data={data} />}
|
||||
trigger="custom"
|
||||
position="rightTop"
|
||||
showArrow
|
||||
>
|
||||
<Button
|
||||
icon={<IconEdit />}
|
||||
size="small"
|
||||
theme="solid"
|
||||
style={{
|
||||
backgroundColor: "#2f68ad",
|
||||
opacity: "0.7",
|
||||
}}
|
||||
onClick={edit}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
</foreignObject>
|
||||
{hovered && (
|
||||
<>
|
||||
<circle
|
||||
cx={data.x}
|
||||
cy={data.y}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nwse-resize"
|
||||
onMouseDown={(e) => handleResize(e, "tl")}
|
||||
/>
|
||||
<circle
|
||||
cx={data.x + data.width}
|
||||
cy={data.y}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nesw-resize"
|
||||
onMouseDown={(e) => handleResize(e, "tr")}
|
||||
/>
|
||||
<circle
|
||||
cx={data.x}
|
||||
cy={data.y + data.height}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nesw-resize"
|
||||
onMouseDown={(e) => handleResize(e, "bl")}
|
||||
/>
|
||||
<circle
|
||||
cx={data.x + data.width}
|
||||
cy={data.y + data.height}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nwse-resize"
|
||||
onMouseDown={(e) => handleResize(e, "br")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
function EditPopoverContent({ data }) {
|
||||
const [editField, setEditField] = useState({});
|
||||
const { setSaveState } = useSaveState();
|
||||
const { updateArea, deleteArea } = useAreas();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
|
||||
return (
|
||||
<div className="popover-theme">
|
||||
<div className="font-semibold mb-2 ms-1">Edit subject area</div>
|
||||
<div className="w-[280px] flex items-center mb-2">
|
||||
<Input
|
||||
value={props.areaData.name}
|
||||
value={data.name}
|
||||
placeholder="Name"
|
||||
className="me-2"
|
||||
onChange={(value) =>
|
||||
updateArea(props.areaData.id, { name: value })
|
||||
}
|
||||
onChange={(value) => updateArea(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.name) return;
|
||||
@ -124,7 +211,7 @@ export default function Area(props) {
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.AREA,
|
||||
aid: props.areaData.id,
|
||||
aid: data.id,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit area name to ${e.target.value}`,
|
||||
@ -142,7 +229,7 @@ export default function Area(props) {
|
||||
type="tertiary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
updateArea(props.areaData.id, {
|
||||
updateArea(data.id, {
|
||||
color: defaultBlue,
|
||||
});
|
||||
setSaveState(State.SAVING);
|
||||
@ -167,22 +254,20 @@ export default function Area(props) {
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.AREA,
|
||||
aid: props.areaData.id,
|
||||
undo: { color: props.areaData.color },
|
||||
aid: data.id,
|
||||
undo: { color: data.color },
|
||||
redo: { color: c },
|
||||
message: `Edit area color to ${c}`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
updateArea(props.areaData.id, {
|
||||
updateArea(data.id, {
|
||||
color: c,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{props.areaData.color === c ? (
|
||||
<IconCheckboxTick
|
||||
style={{ color: "white" }}
|
||||
/>
|
||||
{data.color === c ? (
|
||||
<IconCheckboxTick style={{ color: "white" }} />
|
||||
) : (
|
||||
<IconCheckboxTick style={{ color: c }} />
|
||||
)}
|
||||
@ -203,24 +288,21 @@ export default function Area(props) {
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.AREA,
|
||||
aid: props.areaData.id,
|
||||
undo: { color: props.areaData.color },
|
||||
aid: data.id,
|
||||
undo: { color: data.color },
|
||||
redo: { color: c },
|
||||
message: `Edit area color to ${c}`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
updateArea(props.areaData.id, {
|
||||
updateArea(data.id, {
|
||||
color: c,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconCheckboxTick
|
||||
style={{
|
||||
color:
|
||||
props.areaData.color === c
|
||||
? "white"
|
||||
: c,
|
||||
color: data.color === c ? "white" : c,
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
@ -234,7 +316,7 @@ export default function Area(props) {
|
||||
>
|
||||
<div
|
||||
className="h-[32px] w-[32px] rounded"
|
||||
style={{ backgroundColor: props.areaData.color }}
|
||||
style={{ backgroundColor: data.color }}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
@ -245,97 +327,12 @@ export default function Area(props) {
|
||||
block
|
||||
onClick={() => {
|
||||
Toast.success(`Area deleted!`);
|
||||
deleteArea(props.areaData.id, true);
|
||||
deleteArea(data.id, true);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
trigger="custom"
|
||||
position="rightTop"
|
||||
showArrow
|
||||
>
|
||||
<Button
|
||||
icon={<IconEdit />}
|
||||
size="small"
|
||||
theme="solid"
|
||||
style={{
|
||||
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,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
></Button>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
</foreignObject>
|
||||
{hovered && (
|
||||
<>
|
||||
<circle
|
||||
cx={props.areaData.x}
|
||||
cy={props.areaData.y}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nwse-resize"
|
||||
onMouseDown={(e) => handleMouseDown(e, "tl")}
|
||||
/>
|
||||
<circle
|
||||
cx={props.areaData.x + props.areaData.width}
|
||||
cy={props.areaData.y}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nesw-resize"
|
||||
onMouseDown={(e) => handleMouseDown(e, "tr")}
|
||||
/>
|
||||
<circle
|
||||
cx={props.areaData.x}
|
||||
cy={props.areaData.y + props.areaData.height}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nesw-resize"
|
||||
onMouseDown={(e) => handleMouseDown(e, "bl")}
|
||||
/>
|
||||
<circle
|
||||
cx={props.areaData.x + props.areaData.width}
|
||||
cy={props.areaData.y + props.areaData.height}
|
||||
r={6}
|
||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nwse-resize"
|
||||
onMouseDown={(e) => handleMouseDown(e, "br")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
@ -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() {
|
||||
<Col span={16}>
|
||||
<AutoComplete
|
||||
data={filteredResult}
|
||||
value={value}
|
||||
value={searchText}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
@ -66,7 +56,7 @@ export default function AreaOverview() {
|
||||
<div className="p-3 popover-theme">No areas found</div>
|
||||
}
|
||||
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() {
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button icon={<IconPlus />} block onClick={() => addArea()}>
|
||||
<Button icon={<IconPlus />} block onClick={addArea}>
|
||||
Add area
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
{areas.length <= 0 ? (
|
||||
<div className="select-none mt-2">
|
||||
<Empty
|
||||
image={
|
||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoContentDark style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
<NoElements
|
||||
title="No subject areas"
|
||||
description="Add subject areas to compartmentalize tables!"
|
||||
text="Add subject areas to organize tables!"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-2">
|
||||
{areas.map((a, i) => (
|
||||
@ -236,7 +218,7 @@ export default function AreaOverview() {
|
||||
Toast.success(`Area deleted!`);
|
||||
deleteArea(i, true);
|
||||
}}
|
||||
></Button>
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
@ -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) => (
|
||||
<Area
|
||||
key={a.id}
|
||||
areaData={a}
|
||||
data={a}
|
||||
onMouseDown={(e) =>
|
||||
handleMouseDownRect(e, a.id, ObjectType.AREA)
|
||||
handleMouseDownOnElement(e, a.id, ObjectType.AREA)
|
||||
}
|
||||
setResize={setAreaResize}
|
||||
initCoords={initCoords}
|
||||
setInitCoords={setInitCoords}
|
||||
zoom={transform.zoom}
|
||||
></Area>
|
||||
))}
|
||||
{relationships.map((e, i) => (
|
||||
@ -451,11 +448,11 @@ export default function Canvas() {
|
||||
<Table
|
||||
key={table.id}
|
||||
tableData={table}
|
||||
setOnRect={setOnRect}
|
||||
setHoveredTable={setHoveredTable}
|
||||
handleGripField={handleGripField}
|
||||
setLine={setLine}
|
||||
setLinkingLine={setLinkingLine}
|
||||
onMouseDown={(e) =>
|
||||
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 && (
|
||||
<path
|
||||
d={`M ${line.startX} ${line.startY} L ${line.endX} ${line.endY}`}
|
||||
d={`M ${linkingLink.startX} ${linkingLink.startY} L ${linkingLink.endX} ${linkingLink.endY}`}
|
||||
stroke="red"
|
||||
strokeDasharray="8,8"
|
||||
/>
|
||||
@ -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)
|
||||
}
|
||||
></Note>
|
||||
))}
|
||||
|
@ -1725,7 +1725,7 @@ export default function ControlPanel({
|
||||
})
|
||||
}
|
||||
limit={1}
|
||||
></Upload>
|
||||
/>
|
||||
{error.type === STATUS.ERROR ? (
|
||||
<Banner
|
||||
type="danger"
|
||||
@ -1803,7 +1803,7 @@ export default function ControlPanel({
|
||||
]}
|
||||
onChange={(e) => setData((prev) => ({ ...prev, dbms: e }))}
|
||||
className="w-full"
|
||||
></Select>
|
||||
/>
|
||||
<Checkbox
|
||||
aria-label="overwrite checkbox"
|
||||
checked={data.overwrite}
|
||||
@ -1914,7 +1914,7 @@ export default function ControlPanel({
|
||||
}}
|
||||
>
|
||||
<td className="py-1">
|
||||
<i className="bi bi-file-earmark-text text-[16px] me-1 opacity-60"></i>
|
||||
<i className="bi bi-file-earmark-text text-[16px] me-1 opacity-60" />
|
||||
{d.name}
|
||||
</td>
|
||||
<td className="py-1">
|
||||
@ -2085,7 +2085,7 @@ export default function ControlPanel({
|
||||
className="hover-1"
|
||||
>
|
||||
<div className="flex items-center py-1 w-full">
|
||||
<i className="block fa-regular fa-circle fa-xs"></i>
|
||||
<i className="block fa-regular fa-circle fa-xs" />
|
||||
<div className="ms-2">{e.message}</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
@ -2166,7 +2166,7 @@ export default function ControlPanel({
|
||||
setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 }))
|
||||
}
|
||||
>
|
||||
<i className="fa-solid fa-magnifying-glass-plus"></i>
|
||||
<i className="fa-solid fa-magnifying-glass-plus" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Zoom out" position="bottom">
|
||||
@ -2176,7 +2176,7 @@ export default function ControlPanel({
|
||||
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }))
|
||||
}
|
||||
>
|
||||
<i className="fa-solid fa-magnifying-glass-minus"></i>
|
||||
<i className="fa-solid fa-magnifying-glass-minus" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Divider layout="vertical" margin="8px" />
|
||||
@ -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)}
|
||||
>
|
||||
<i className="fa-regular fa-calendar-check"></i>
|
||||
<i className="fa-regular fa-calendar-check" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@ -2401,11 +2401,7 @@ export default function ControlPanel({
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item
|
||||
icon={
|
||||
layout.header ? (
|
||||
<IconCheckboxTick />
|
||||
) : (
|
||||
<div className="px-2"></div>
|
||||
)
|
||||
layout.header ? <IconCheckboxTick /> : <div className="px-2" />
|
||||
}
|
||||
onClick={() => invertLayout("header")}
|
||||
>
|
||||
@ -2413,11 +2409,7 @@ export default function ControlPanel({
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
icon={
|
||||
layout.sidebar ? (
|
||||
<IconCheckboxTick />
|
||||
) : (
|
||||
<div className="px-2"></div>
|
||||
)
|
||||
layout.sidebar ? <IconCheckboxTick /> : <div className="px-2" />
|
||||
}
|
||||
onClick={() => invertLayout("sidebar")}
|
||||
>
|
||||
@ -2425,11 +2417,7 @@ export default function ControlPanel({
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
icon={
|
||||
layout.issues ? (
|
||||
<IconCheckboxTick />
|
||||
) : (
|
||||
<div className="px-2"></div>
|
||||
)
|
||||
layout.issues ? <IconCheckboxTick /> : <div className="px-2" />
|
||||
}
|
||||
onClick={() => invertLayout("issues")}
|
||||
>
|
||||
@ -2437,7 +2425,7 @@ export default function ControlPanel({
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item
|
||||
icon={<div className="px-2"></div>}
|
||||
icon={<div className="px-2" />}
|
||||
onClick={() => {
|
||||
if (layout.fullscreen) {
|
||||
exitFullscreen();
|
||||
|
@ -19,7 +19,7 @@ export default function Controls() {
|
||||
}))
|
||||
}
|
||||
>
|
||||
<i className="bi bi-dash-lg"></i>
|
||||
<i className="bi bi-dash-lg" />
|
||||
</button>
|
||||
<Divider align="center" layout="vertical" />
|
||||
<div className="px-3 py-2">{parseInt(transform.zoom * 100)}%</div>
|
||||
@ -33,7 +33,7 @@ export default function Controls() {
|
||||
}))
|
||||
}
|
||||
>
|
||||
<i className="bi bi-plus-lg"></i>
|
||||
<i className="bi bi-plus-lg" />
|
||||
</button>
|
||||
</div>
|
||||
<Tooltip content="Exit">
|
||||
@ -49,7 +49,7 @@ export default function Controls() {
|
||||
exitFullscreen();
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-fullscreen-exit"></i>
|
||||
<i className="bi bi-fullscreen-exit" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -39,7 +39,7 @@ export default function Issues() {
|
||||
className="mt-1"
|
||||
>
|
||||
<div className="pe-3 select-none">
|
||||
<i className="fa-solid fa-triangle-exclamation me-2 text-yellow-500"></i>
|
||||
<i className="fa-solid fa-triangle-exclamation me-2 text-yellow-500" />
|
||||
Issues
|
||||
</div>
|
||||
</Badge>
|
||||
|
@ -48,7 +48,9 @@ export default function Navbar() {
|
||||
</div>
|
||||
<hr />
|
||||
<SideSheet
|
||||
title={<img src={logo} alt="logo" className="sm:h-[32px]" />}
|
||||
title={
|
||||
<img src={logo} alt="logo" className="sm:h-[32px] md:h-[42px]" />
|
||||
}
|
||||
visible={openMenu}
|
||||
onCancel={() => setOpenMenu(false)}
|
||||
width={window.innerWidth}
|
||||
|
20
src/components/NoElements.jsx
Normal file
20
src/components/NoElements.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import {
|
||||
IllustrationNoContent,
|
||||
IllustrationNoContentDark,
|
||||
} from "@douyinfe/semi-illustrations";
|
||||
import { Empty } from "@douyinfe/semi-ui";
|
||||
|
||||
export default function NoElements({ title, text }) {
|
||||
return (
|
||||
<div className="select-none mt-2">
|
||||
<Empty
|
||||
image={<IllustrationNoContent style={{ width: 154, height: 154 }} />}
|
||||
darkModeImage={
|
||||
<IllustrationNoContentDark style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
title={title}
|
||||
description={text}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<g
|
||||
onMouseEnter={() => 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 }}
|
||||
></textarea>
|
||||
/>
|
||||
{(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,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
></Button>
|
||||
onClick={edit}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
|
@ -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() {
|
||||
<Col span={16}>
|
||||
<AutoComplete
|
||||
data={filteredResult}
|
||||
value={value}
|
||||
value={searchText}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
@ -61,7 +51,7 @@ export default function NotesOverview() {
|
||||
<div className="p-3 popover-theme">No notes found</div>
|
||||
}
|
||||
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() {
|
||||
</Col>
|
||||
</Row>
|
||||
{notes.length <= 0 ? (
|
||||
<div className="select-none mt-2">
|
||||
<Empty
|
||||
image={
|
||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoContentDark style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
title="No text notes"
|
||||
description="Add notes cuz why not!"
|
||||
/>
|
||||
</div>
|
||||
<NoElements title="No text notes" text="Add notes cuz why not!" />
|
||||
) : (
|
||||
<Collapse
|
||||
activeKey={activeKey}
|
||||
@ -218,7 +197,7 @@ export default function NotesOverview() {
|
||||
Toast.success(`Note deleted!`);
|
||||
deleteNote(i, true);
|
||||
}}
|
||||
></Button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
|
@ -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 (
|
||||
<>
|
||||
<AutoComplete
|
||||
data={filteredResult}
|
||||
value={value}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
emptyContent={
|
||||
<div className="p-3 popover-theme">No relationships found</div>
|
||||
}
|
||||
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"
|
||||
/>
|
||||
<Collapse
|
||||
activeKey={refActiveIndex}
|
||||
onChange={(k) => setRefActiveIndex(k)}
|
||||
accordion
|
||||
>
|
||||
{relationships.length <= 0 ? (
|
||||
<div className="select-none mt-2">
|
||||
<Empty
|
||||
image={
|
||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoContentDark
|
||||
style={{ width: 154, height: 154 }}
|
||||
/>
|
||||
}
|
||||
title="No relationships"
|
||||
description="Drag to connect fields and form relationships!"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
relationships.map((r, i) => (
|
||||
<div id={`scroll_ref_${r.id}`} key={i}>
|
||||
<Collapse.Panel header={<div>{r.name}</div>} itemKey={`${i}`}>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="me-3">
|
||||
<span className="font-semibold">Primary: </span>
|
||||
{tables[r.endTableId].name}
|
||||
</div>
|
||||
<div className="mx-1">
|
||||
<span className="font-semibold">Foreign: </span>
|
||||
{tables[r.startTableId].name}
|
||||
</div>
|
||||
<div className="ms-1">
|
||||
<Popover
|
||||
content={
|
||||
<div className="p-2 popover-theme">
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={[
|
||||
{
|
||||
key: "1",
|
||||
foreign: `${tables[r.startTableId].name}(${
|
||||
tables[r.startTableId].fields[r.startFieldId]
|
||||
.name
|
||||
})`,
|
||||
primary: `${tables[r.endTableId].name}(${
|
||||
tables[r.endTableId].fields[r.endFieldId].name
|
||||
})`,
|
||||
},
|
||||
]}
|
||||
pagination={false}
|
||||
size="small"
|
||||
bordered
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
icon={<IconLoopTextStroked />}
|
||||
block
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: i,
|
||||
undo: {
|
||||
startTableId: r.startTableId,
|
||||
startFieldId: r.startFieldId,
|
||||
endTableId: r.endTableId,
|
||||
endFieldId: r.endFieldId,
|
||||
},
|
||||
redo: {
|
||||
startTableId: r.endTableId,
|
||||
startFieldId: r.endFieldId,
|
||||
endTableId: r.startTableId,
|
||||
endFieldId: r.startFieldId,
|
||||
},
|
||||
message: `Swap primary and foreign tables`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === i
|
||||
? {
|
||||
...e,
|
||||
startTableId: e.endTableId,
|
||||
startFieldId: e.endFieldId,
|
||||
endTableId: e.startTableId,
|
||||
endFieldId: e.startFieldId,
|
||||
}
|
||||
: e
|
||||
)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Swap
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
position="rightTop"
|
||||
showArrow
|
||||
>
|
||||
<Button icon={<IconMore />} type="tertiary"></Button>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-semibold my-1">Cardinality</div>
|
||||
<Select
|
||||
optionList={Object.values(Cardinality).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={r.cardinality}
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: i,
|
||||
undo: { cardinality: r.cardinality },
|
||||
redo: { cardinality: value },
|
||||
message: `Edit relationship cardinality`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === i ? { ...e, cardinality: value } : e
|
||||
)
|
||||
);
|
||||
}}
|
||||
></Select>
|
||||
<Row gutter={6} className="my-3">
|
||||
<Col span={12}>
|
||||
<div className="font-semibold">On update: </div>
|
||||
<Select
|
||||
optionList={Object.values(Constraint).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={r.updateConstraint}
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: i,
|
||||
undo: { updateConstraint: r.updateConstraint },
|
||||
redo: { updateConstraint: value },
|
||||
message: `Edit relationship update constraint`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === i ? { ...e, updateConstraint: value } : e
|
||||
)
|
||||
);
|
||||
}}
|
||||
></Select>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="font-semibold">On delete: </div>
|
||||
<Select
|
||||
optionList={Object.values(Constraint).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={r.deleteConstraint}
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: i,
|
||||
undo: { deleteConstraint: r.deleteConstraint },
|
||||
redo: { deleteConstraint: value },
|
||||
message: `Edit relationship delete constraint`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === i ? { ...e, deleteConstraint: value } : e
|
||||
)
|
||||
);
|
||||
}}
|
||||
></Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
block
|
||||
type="danger"
|
||||
onClick={() => deleteRelationship(r.id, true)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Collapse.Panel>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
}
|
281
src/components/RelationshipsOverview.jsx
Normal file
281
src/components/RelationshipsOverview.jsx
Normal file
@ -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 (
|
||||
<>
|
||||
<AutoComplete
|
||||
data={filteredResult}
|
||||
value={searchText}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
emptyContent={
|
||||
<div className="p-3 popover-theme">No relationships found</div>
|
||||
}
|
||||
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"
|
||||
/>
|
||||
<Collapse
|
||||
activeKey={refActiveIndex}
|
||||
onChange={(k) => setRefActiveIndex(k)}
|
||||
accordion
|
||||
>
|
||||
{relationships.length <= 0 ? (
|
||||
<NoElements
|
||||
title="No relationships"
|
||||
text="Drag to connect fields and form relationships!"
|
||||
/>
|
||||
) : (
|
||||
relationships.map((r) => <RelationshipPanel key={r.id} data={r} />)
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RelationshipPanel({ data }) {
|
||||
const columns = [
|
||||
{
|
||||
title: "Primary",
|
||||
dataIndex: "primary",
|
||||
},
|
||||
{
|
||||
title: "Foreign",
|
||||
dataIndex: "foreign",
|
||||
},
|
||||
];
|
||||
const { tables, setRelationships, deleteRelationship } = useTables();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
|
||||
return (
|
||||
<div id={`scroll_ref_${data.id}`}>
|
||||
<Collapse.Panel header={data.name} itemKey={`${data.id}`}>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="me-3">
|
||||
<span className="font-semibold">Primary: </span>
|
||||
{tables[data.endTableId].name}
|
||||
</div>
|
||||
<div className="mx-1">
|
||||
<span className="font-semibold">Foreign: </span>
|
||||
{tables[data.startTableId].name}
|
||||
</div>
|
||||
<div className="ms-1">
|
||||
<Popover
|
||||
content={
|
||||
<div className="p-2 popover-theme">
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={[
|
||||
{
|
||||
key: "1",
|
||||
foreign: `${tables[data.startTableId].name}(${
|
||||
tables[data.startTableId].fields[data.startFieldId]
|
||||
.name
|
||||
})`,
|
||||
primary: `${tables[data.endTableId].name}(${
|
||||
tables[data.endTableId].fields[data.endFieldId].name
|
||||
})`,
|
||||
},
|
||||
]}
|
||||
pagination={false}
|
||||
size="small"
|
||||
bordered
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
icon={<IconLoopTextStroked />}
|
||||
block
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: data.id,
|
||||
undo: {
|
||||
startTableId: data.startTableId,
|
||||
startFieldId: data.startFieldId,
|
||||
endTableId: data.endTableId,
|
||||
endFieldId: data.endFieldId,
|
||||
},
|
||||
redo: {
|
||||
startTableId: data.endTableId,
|
||||
startFieldId: data.endFieldId,
|
||||
endTableId: data.startTableId,
|
||||
endFieldId: data.startFieldId,
|
||||
},
|
||||
message: `Swap primary and foreign tables`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === data.id
|
||||
? {
|
||||
...e,
|
||||
startTableId: e.endTableId,
|
||||
startFieldId: e.endFieldId,
|
||||
endTableId: e.startTableId,
|
||||
endFieldId: e.startFieldId,
|
||||
}
|
||||
: e
|
||||
)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Swap
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
position="rightTop"
|
||||
showArrow
|
||||
>
|
||||
<Button icon={<IconMore />} type="tertiary" />
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-semibold my-1">Cardinality</div>
|
||||
<Select
|
||||
optionList={Object.values(Cardinality).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={data.cardinality}
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: data.id,
|
||||
undo: { cardinality: data.cardinality },
|
||||
redo: { cardinality: value },
|
||||
message: `Edit relationship cardinality`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === data.id ? { ...e, cardinality: value } : e
|
||||
)
|
||||
);
|
||||
}}
|
||||
></Select>
|
||||
<Row gutter={6} className="my-3">
|
||||
<Col span={12}>
|
||||
<div className="font-semibold">On update: </div>
|
||||
<Select
|
||||
optionList={Object.values(Constraint).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={data.updateConstraint}
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: data.id,
|
||||
undo: { updateConstraint: data.updateConstraint },
|
||||
redo: { updateConstraint: value },
|
||||
message: `Edit relationship update constraint`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === data.id ? { ...e, updateConstraint: value } : e
|
||||
)
|
||||
);
|
||||
}}
|
||||
></Select>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="font-semibold">On delete: </div>
|
||||
<Select
|
||||
optionList={Object.values(Constraint).map((v) => ({
|
||||
label: v,
|
||||
value: v,
|
||||
}))}
|
||||
value={data.deleteConstraint}
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.RELATIONSHIP,
|
||||
rid: data.id,
|
||||
undo: { deleteConstraint: data.deleteConstraint },
|
||||
redo: { deleteConstraint: value },
|
||||
message: `Edit relationship delete constraint`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) =>
|
||||
idx === data.id ? { ...e, deleteConstraint: value } : e
|
||||
)
|
||||
);
|
||||
}}
|
||||
></Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
block
|
||||
type="danger"
|
||||
onClick={() => deleteRelationship(data.id, true)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Collapse.Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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";
|
||||
|
@ -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 = [
|
||||
<TableOverview key={1} />,
|
||||
<ReferenceOverview key={2} />,
|
||||
<AreaOverview key={3} />,
|
||||
<NotesOverview key={4} />,
|
||||
<TypesOverview key={5} />,
|
||||
<TablesOverview key="tables" />,
|
||||
<RelationshipsOverview key="relationships" />,
|
||||
<AreasOverview key="areas" />,
|
||||
<NotesOverview key="notes" />,
|
||||
<TypesOverview key="types" />,
|
||||
];
|
||||
|
||||
return (
|
||||
@ -57,7 +58,7 @@ export default function SidePanel({ width, resize, setResize }) {
|
||||
</div>
|
||||
<div
|
||||
className={`flex justify-center items-center p-1 h-auto hover-2 cursor-col-resize ${
|
||||
resize ? "bg-semi-grey-2" : ""
|
||||
resize && "bg-semi-grey-2"
|
||||
}`}
|
||||
onMouseDown={() => setResize(true)}
|
||||
>
|
||||
|
@ -131,7 +131,7 @@ export default function Table(props) {
|
||||
.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}}
|
||||
></Button>
|
||||
/>
|
||||
<Popover
|
||||
content={
|
||||
<div className="popover-theme">
|
||||
@ -205,7 +205,7 @@ export default function Table(props) {
|
||||
backgroundColor: "grey",
|
||||
color: "white",
|
||||
}}
|
||||
></Button>
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
@ -469,7 +469,7 @@ export default function Table(props) {
|
||||
updateField(props.tableData.id, j, { primary: !f.primary });
|
||||
}}
|
||||
icon={<IconKeyStroked />}
|
||||
></Button>
|
||||
/>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Popover
|
||||
@ -848,7 +848,7 @@ export default function Table(props) {
|
||||
position="right"
|
||||
showArrow
|
||||
>
|
||||
<Button type="tertiary" icon={<IconMore />}></Button>
|
||||
<Button type="tertiary" icon={<IconMore />} />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -1000,7 +1000,7 @@ export default function Table(props) {
|
||||
icon={<IconMore />}
|
||||
type="tertiary"
|
||||
style={{ marginLeft: "12px" }}
|
||||
></Button>
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
))}
|
||||
@ -1229,7 +1229,7 @@ export default function Table(props) {
|
||||
Toast.success(`Table deleted!`);
|
||||
deleteTable(props.tableData.id);
|
||||
}}
|
||||
></Button>
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
@ -1241,13 +1241,12 @@ export default function Table(props) {
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
index === props.tableData.fields.length - 1
|
||||
? ""
|
||||
: "border-b border-gray-400"
|
||||
index === props.tableData.fields.length - 1 ||
|
||||
"border-b border-gray-400"
|
||||
} h-[36px] px-2 py-1 flex justify-between`}
|
||||
onMouseEnter={() => {
|
||||
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,
|
||||
}));
|
||||
}}
|
||||
></button>
|
||||
/>
|
||||
{fieldData.name.length <= 11
|
||||
? fieldData.name
|
||||
: fieldData.name.substring(0, 11)}
|
||||
@ -1352,7 +1351,7 @@ export default function Table(props) {
|
||||
}),
|
||||
});
|
||||
}}
|
||||
></Button>
|
||||
/>
|
||||
) : (
|
||||
fieldData.type
|
||||
)}
|
||||
|
File diff suppressed because it is too large
Load Diff
1078
src/components/TablesOverview.jsx
Normal file
1078
src/components/TablesOverview.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -49,9 +49,7 @@ export function Thumbnail({ diagram, i, zoom }) {
|
||||
width={a.width > 0 ? a.width : 0}
|
||||
height={a.height > 0 ? a.height : 0}
|
||||
>
|
||||
<div
|
||||
className={`border border-slate-400 w-full h-full rounded-sm relative`}
|
||||
>
|
||||
<div className="border border-slate-400 w-full h-full rounded-sm relative">
|
||||
<div
|
||||
className="opacity-40 w-fill h-full"
|
||||
style={{ backgroundColor: a.color }}
|
||||
@ -82,7 +80,7 @@ export function Thumbnail({ diagram, i, zoom }) {
|
||||
<div
|
||||
className="h-2 w-full rounded-t-sm"
|
||||
style={{ backgroundColor: table.color }}
|
||||
></div>
|
||||
/>
|
||||
<div className="rounded-b-[3px]">
|
||||
<div
|
||||
className={`font-bold py-1 px-2 border-b ${
|
||||
|
@ -235,7 +235,7 @@ export default function Todo() {
|
||||
showArrow
|
||||
className="w-[180px]"
|
||||
>
|
||||
<Button icon={<IconMore />} type="tertiary"></Button>
|
||||
<Button icon={<IconMore />} type="tertiary" />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
InputNumber,
|
||||
AutoComplete,
|
||||
Toast,
|
||||
Empty,
|
||||
Popover,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import {
|
||||
@ -23,32 +22,21 @@ import {
|
||||
IconInfoCircle,
|
||||
IconMore,
|
||||
} from "@douyinfe/semi-icons";
|
||||
import {
|
||||
IllustrationNoContent,
|
||||
IllustrationNoContentDark,
|
||||
} from "@douyinfe/semi-illustrations";
|
||||
import { isSized, hasPrecision, getSize } from "../utils/toSQL";
|
||||
import useUndoRedo from "../hooks/useUndoRedo";
|
||||
import useTypes from "../hooks/useTypes";
|
||||
import NoElements from "./NoElements";
|
||||
|
||||
export default function TableOverview() {
|
||||
export default function TypesOverview() {
|
||||
const [value, setValue] = useState("");
|
||||
const { types, addType, deleteType, updateType } = useTypes();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
const { types, addType } = useTypes();
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
types.map((t) => {
|
||||
return t.name;
|
||||
})
|
||||
types.map((t) => t.name)
|
||||
);
|
||||
|
||||
const handleStringSearch = (value) => {
|
||||
setFilteredResult(
|
||||
types
|
||||
.map((t) => {
|
||||
return t.name;
|
||||
})
|
||||
.filter((i) => i.includes(value))
|
||||
types.map((t) => t.name).filter((i) => i.includes(value))
|
||||
);
|
||||
};
|
||||
|
||||
@ -111,34 +99,35 @@ export default function TableOverview() {
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Collapse accordion>
|
||||
{types.length <= 0 ? (
|
||||
<div className="select-none mt-2">
|
||||
<Empty
|
||||
image={
|
||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoContentDark
|
||||
style={{ width: 154, height: 154 }}
|
||||
/>
|
||||
}
|
||||
title="No types"
|
||||
description={<div>Make your own custom data types.</div>}
|
||||
/>
|
||||
</div>
|
||||
<NoElements title="No types" text="Make your own custom data types" />
|
||||
) : (
|
||||
types.map((t, i) => (
|
||||
<div id={`scroll_type_${i}`} key={i}>
|
||||
<Collapse.Panel header={<div>{t.name}</div>} itemKey={`${i}`}>
|
||||
<Collapse accordion>
|
||||
{types.map((t, i) => (
|
||||
<TypePanel data={t} key={i} index={i} />
|
||||
))}
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TypePanel({ index, data }) {
|
||||
const { types, deleteType, updateType } = useTypes();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
|
||||
return (
|
||||
<div id={`scroll_type_${index}`}>
|
||||
<Collapse.Panel header={<div>{data.name}</div>} itemKey={`${index}`}>
|
||||
<div className="flex items-center mb-2.5">
|
||||
<div className="text-md font-semibold">Name: </div>
|
||||
<Input
|
||||
value={t.name}
|
||||
validateStatus={t.name === "" ? "error" : "default"}
|
||||
value={data.name}
|
||||
validateStatus={data.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
className="ms-2"
|
||||
onChange={(value) => updateType(i, { name: value })}
|
||||
onChange={(value) => updateType(index, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.name) return;
|
||||
@ -148,7 +137,7 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "self",
|
||||
tid: i,
|
||||
tid: index,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit type name to ${e.target.value}`,
|
||||
@ -158,7 +147,7 @@ export default function TableOverview() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{t.fields.map((f, j) => (
|
||||
{data.fields.map((f, j) => (
|
||||
<Row gutter={6} key={j} className="hover-1 my-2">
|
||||
<Col span={10}>
|
||||
<Input
|
||||
@ -166,8 +155,8 @@ export default function TableOverview() {
|
||||
validateStatus={f.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
onChange={(value) =>
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j ? { ...f, name: value } : e
|
||||
),
|
||||
})
|
||||
@ -181,7 +170,7 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
tid: index,
|
||||
fid: j,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
@ -201,7 +190,7 @@ export default function TableOverview() {
|
||||
value: value,
|
||||
})),
|
||||
...types
|
||||
.filter((type) => type.name !== t.name)
|
||||
.filter((type) => type.name !== data.name)
|
||||
.map((type) => ({
|
||||
label: type.name.toUpperCase(),
|
||||
value: type.name.toUpperCase(),
|
||||
@ -219,7 +208,7 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
tid: index,
|
||||
fid: j,
|
||||
undo: { type: f.type },
|
||||
redo: { type: value },
|
||||
@ -228,8 +217,8 @@ export default function TableOverview() {
|
||||
]);
|
||||
setRedoStack([]);
|
||||
if (value === "ENUM" || value === "SET") {
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j
|
||||
? {
|
||||
...f,
|
||||
@ -240,16 +229,16 @@ export default function TableOverview() {
|
||||
),
|
||||
});
|
||||
} else if (isSized(value) || hasPrecision(value)) {
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j
|
||||
? { ...f, type: value, size: getSize(value) }
|
||||
: e
|
||||
),
|
||||
});
|
||||
} else {
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j ? { ...f, type: value } : e
|
||||
),
|
||||
});
|
||||
@ -277,15 +266,13 @@ export default function TableOverview() {
|
||||
className="my-2"
|
||||
placeholder="Use ',' for batch input"
|
||||
onChange={(v) =>
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j ? { ...f, values: v } : e
|
||||
),
|
||||
})
|
||||
}
|
||||
onFocus={() =>
|
||||
setEditField({ values: f.values })
|
||||
}
|
||||
onFocus={() => setEditField({ values: f.values })}
|
||||
onBlur={() => {
|
||||
if (
|
||||
JSON.stringify(editField.values) ===
|
||||
@ -298,7 +285,7 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
tid: index,
|
||||
fid: j,
|
||||
undo: editField,
|
||||
redo: { values: f.values },
|
||||
@ -320,8 +307,8 @@ export default function TableOverview() {
|
||||
placeholder="Set length"
|
||||
value={f.size}
|
||||
onChange={(value) =>
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j ? { ...f, size: value } : e
|
||||
),
|
||||
})
|
||||
@ -330,15 +317,14 @@ export default function TableOverview() {
|
||||
setEditField({ size: e.target.value })
|
||||
}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.size)
|
||||
return;
|
||||
if (e.target.value === editField.size) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TABLE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
tid: index,
|
||||
fid: j,
|
||||
undo: editField,
|
||||
redo: { size: e.target.value },
|
||||
@ -363,8 +349,8 @@ export default function TableOverview() {
|
||||
}
|
||||
value={f.size}
|
||||
onChange={(value) =>
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
updateType(index, {
|
||||
fields: data.fields.map((e, id) =>
|
||||
id === j ? { ...f, size: value } : e
|
||||
),
|
||||
})
|
||||
@ -373,15 +359,14 @@ export default function TableOverview() {
|
||||
setEditField({ size: e.target.value })
|
||||
}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.size)
|
||||
return;
|
||||
if (e.target.value === editField.size) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TABLE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
tid: index,
|
||||
fid: j,
|
||||
undo: editField,
|
||||
redo: { size: e.target.value },
|
||||
@ -404,16 +389,14 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field_delete",
|
||||
tid: i,
|
||||
tid: index,
|
||||
fid: j,
|
||||
data: f,
|
||||
message: `Delete field`,
|
||||
},
|
||||
]);
|
||||
updateType(i, {
|
||||
fields: t.fields.filter(
|
||||
(field, k) => k !== j
|
||||
),
|
||||
updateType(index, {
|
||||
fields: data.fields.filter((field, k) => k !== j),
|
||||
});
|
||||
}}
|
||||
>
|
||||
@ -425,7 +408,7 @@ export default function TableOverview() {
|
||||
trigger="click"
|
||||
position="right"
|
||||
>
|
||||
<Button icon={<IconMore />} type="tertiary"></Button>
|
||||
<Button icon={<IconMore />} type="tertiary" />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -439,16 +422,14 @@ export default function TableOverview() {
|
||||
<Collapse.Panel header="Comment" itemKey="1">
|
||||
<TextArea
|
||||
field="comment"
|
||||
value={t.comment}
|
||||
value={data.comment}
|
||||
autosize
|
||||
placeholder="Add comment"
|
||||
rows={1}
|
||||
onChange={(value) =>
|
||||
updateType(i, { comment: value }, false)
|
||||
}
|
||||
onFocus={(e) =>
|
||||
setEditField({ comment: e.target.value })
|
||||
updateType(index, { comment: value }, false)
|
||||
}
|
||||
onFocus={(e) => setEditField({ comment: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.comment) return;
|
||||
setUndoStack((prev) => [
|
||||
@ -457,7 +438,7 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "self",
|
||||
tid: i,
|
||||
tid: index,
|
||||
undo: editField,
|
||||
redo: { comment: e.target.value },
|
||||
message: `Edit type comment to ${e.target.value}`,
|
||||
@ -480,14 +461,14 @@ export default function TableOverview() {
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field_add",
|
||||
tid: i,
|
||||
tid: index,
|
||||
message: `Add field to type`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
updateType(i, {
|
||||
updateType(index, {
|
||||
fields: [
|
||||
...t.fields,
|
||||
...data.fields,
|
||||
{
|
||||
name: "",
|
||||
type: "",
|
||||
@ -506,7 +487,7 @@ export default function TableOverview() {
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Type deleted!`);
|
||||
deleteType(i);
|
||||
deleteType(index);
|
||||
}}
|
||||
block
|
||||
>
|
||||
@ -516,9 +497,5 @@ export default function TableOverview() {
|
||||
</Row>
|
||||
</Collapse.Panel>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ export default function BugReport() {
|
||||
}
|
||||
theme="borderless"
|
||||
onClick={changeTheme}
|
||||
></Button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
|
@ -123,7 +123,7 @@ export default function Shortcuts() {
|
||||
}
|
||||
theme="borderless"
|
||||
onClick={changeTheme}
|
||||
></Button>
|
||||
/>
|
||||
<div className="ms-2 lg:inline md:inline sm:hidden">
|
||||
<AutoComplete
|
||||
prefix={<IconSearch />}
|
||||
|
@ -286,7 +286,7 @@ export default function Survey() {
|
||||
}
|
||||
theme="borderless"
|
||||
onClick={changeTheme}
|
||||
></Button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
|
Loading…
Reference in New Issue
Block a user