Clean up
This commit is contained in:
parent
7a406eb6d3
commit
5895d4c96e
@ -19,240 +19,109 @@ import useUndoRedo from "../hooks/useUndoRedo";
|
|||||||
import useSelect from "../hooks/useSelect";
|
import useSelect from "../hooks/useSelect";
|
||||||
import useAreas from "../hooks/useAreas";
|
import useAreas from "../hooks/useAreas";
|
||||||
import useSaveState from "../hooks/useSaveState";
|
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 [hovered, setHovered] = useState(false);
|
||||||
const [editField, setEditField] = useState({});
|
|
||||||
const { layout } = useLayout();
|
const { layout } = useLayout();
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
const { transform } = useTransform();
|
||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
const { updateArea, deleteArea } = useAreas();
|
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
|
||||||
const handleMouseDown = (e, dir) => {
|
const handleResize = (e, dir) => {
|
||||||
props.setResize({ id: props.areaData.id, dir: dir });
|
setResize({ id: data.id, dir: dir });
|
||||||
props.setInitCoords({
|
setInitCoords({
|
||||||
x: props.areaData.x,
|
x: data.x,
|
||||||
y: props.areaData.y,
|
y: data.y,
|
||||||
width: props.areaData.width,
|
width: data.width,
|
||||||
height: props.areaData.height,
|
height: data.height,
|
||||||
mouseX: e.clientX / props.zoom,
|
mouseX: e.clientX / transform.zoom,
|
||||||
mouseY: e.clientY / props.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 (
|
return (
|
||||||
<g
|
<g
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => setHovered(false)}
|
||||||
setHovered(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<foreignObject
|
<foreignObject
|
||||||
key={props.areaData.id}
|
key={data.id}
|
||||||
x={props.areaData.x}
|
x={data.x}
|
||||||
y={props.areaData.y}
|
y={data.y}
|
||||||
width={props.areaData.width > 0 ? props.areaData.width : 0}
|
width={data.width > 0 ? data.width : 0}
|
||||||
height={props.areaData.height > 0 ? props.areaData.height : 0}
|
height={data.height > 0 ? data.height : 0}
|
||||||
onMouseDown={props.onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`border-2 ${
|
className={`border-2 ${
|
||||||
hovered
|
hovered
|
||||||
? "border-dashed border-blue-500"
|
? "border-dashed border-blue-500"
|
||||||
: selectedElement.element === ObjectType.AREA &&
|
: selectedElement.element === ObjectType.AREA &&
|
||||||
selectedElement.id === props.areaData.id
|
selectedElement.id === data.id
|
||||||
? "border-blue-500"
|
? "border-blue-500"
|
||||||
: "border-slate-400"
|
: "border-slate-400"
|
||||||
} w-full h-full cursor-move rounded relative`}
|
} w-full h-full cursor-move rounded relative`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="opacity-40 w-fill p-2 h-full"
|
className="opacity-40 w-fill p-2 h-full"
|
||||||
style={{ backgroundColor: props.areaData.color }}
|
style={{ backgroundColor: data.color }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-color absolute top-2 left-3 select-none">
|
<div className="text-color absolute top-2 left-3 select-none">
|
||||||
{props.areaData.name}
|
{data.name}
|
||||||
</div>
|
</div>
|
||||||
{(hovered ||
|
{(hovered || (areaIsSelected() && !layout.sidebar)) && (
|
||||||
(selectedElement.element === ObjectType.AREA &&
|
|
||||||
selectedElement.id === props.areaData.id &&
|
|
||||||
selectedElement.open &&
|
|
||||||
!layout.sidebar)) && (
|
|
||||||
<div className="absolute top-2 right-3">
|
<div className="absolute top-2 right-3">
|
||||||
<Popover
|
<Popover
|
||||||
visible={
|
visible={areaIsSelected() && !layout.sidebar}
|
||||||
selectedElement.element === ObjectType.AREA &&
|
onClickOutSide={onClickOutSide}
|
||||||
selectedElement.id === props.areaData.id &&
|
|
||||||
selectedElement.open &&
|
|
||||||
!layout.sidebar
|
|
||||||
}
|
|
||||||
onClickOutSide={() => {
|
|
||||||
if (selectedElement.editFromToolbar) {
|
|
||||||
setSelectedElement((prev) => ({
|
|
||||||
...prev,
|
|
||||||
editFromToolbar: false,
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSelectedElement((prev) => ({
|
|
||||||
...prev,
|
|
||||||
open: false,
|
|
||||||
}));
|
|
||||||
setSaveState(State.SAVING);
|
|
||||||
}}
|
|
||||||
stopPropagation
|
stopPropagation
|
||||||
content={
|
content={<EditPopoverContent data={data} />}
|
||||||
<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}
|
|
||||||
placeholder="Name"
|
|
||||||
className="me-2"
|
|
||||||
onChange={(value) =>
|
|
||||||
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([]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Popover
|
|
||||||
content={
|
|
||||||
<div className="popover-theme">
|
|
||||||
<div className="flex justify-between items-center p-2">
|
|
||||||
<div className="font-medium">Theme</div>
|
|
||||||
<Button
|
|
||||||
type="tertiary"
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
updateArea(props.areaData.id, {
|
|
||||||
color: defaultBlue,
|
|
||||||
});
|
|
||||||
setSaveState(State.SAVING);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div className="py-3">
|
|
||||||
<div>
|
|
||||||
{tableThemes
|
|
||||||
.slice(0, Math.ceil(tableThemes.length / 2))
|
|
||||||
.map((c) => (
|
|
||||||
<button
|
|
||||||
key={c}
|
|
||||||
style={{ backgroundColor: c }}
|
|
||||||
className="p-3 rounded-full mx-1"
|
|
||||||
onClick={() => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.AREA,
|
|
||||||
aid: props.areaData.id,
|
|
||||||
undo: { color: props.areaData.color },
|
|
||||||
redo: { color: c },
|
|
||||||
message: `Edit area color to ${c}`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateArea(props.areaData.id, {
|
|
||||||
color: c,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.areaData.color === c ? (
|
|
||||||
<IconCheckboxTick
|
|
||||||
style={{ color: "white" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconCheckboxTick style={{ color: c }} />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-3">
|
|
||||||
{tableThemes
|
|
||||||
.slice(Math.ceil(tableThemes.length / 2))
|
|
||||||
.map((c) => (
|
|
||||||
<button
|
|
||||||
key={c}
|
|
||||||
style={{ backgroundColor: c }}
|
|
||||||
className="p-3 rounded-full mx-1"
|
|
||||||
onClick={() => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.AREA,
|
|
||||||
aid: props.areaData.id,
|
|
||||||
undo: { color: props.areaData.color },
|
|
||||||
redo: { color: c },
|
|
||||||
message: `Edit area color to ${c}`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateArea(props.areaData.id, {
|
|
||||||
color: c,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconCheckboxTick
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
props.areaData.color === c
|
|
||||||
? "white"
|
|
||||||
: c,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
position="rightTop"
|
|
||||||
showArrow
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="h-[32px] w-[32px] rounded"
|
|
||||||
style={{ backgroundColor: props.areaData.color }}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Button
|
|
||||||
icon={<IconDeleteStroked />}
|
|
||||||
type="danger"
|
|
||||||
block
|
|
||||||
onClick={() => {
|
|
||||||
Toast.success(`Area deleted!`);
|
|
||||||
deleteArea(props.areaData.id, true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
trigger="custom"
|
trigger="custom"
|
||||||
position="rightTop"
|
position="rightTop"
|
||||||
showArrow
|
showArrow
|
||||||
@ -265,29 +134,8 @@ export default function Area(props) {
|
|||||||
backgroundColor: "#2f68ad",
|
backgroundColor: "#2f68ad",
|
||||||
opacity: "0.7",
|
opacity: "0.7",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={edit}
|
||||||
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>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -295,47 +143,196 @@ export default function Area(props) {
|
|||||||
{hovered && (
|
{hovered && (
|
||||||
<>
|
<>
|
||||||
<circle
|
<circle
|
||||||
cx={props.areaData.x}
|
cx={data.x}
|
||||||
cy={props.areaData.y}
|
cy={data.y}
|
||||||
r={6}
|
r={6}
|
||||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||||
stroke="#5891db"
|
stroke="#5891db"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
cursor="nwse-resize"
|
cursor="nwse-resize"
|
||||||
onMouseDown={(e) => handleMouseDown(e, "tl")}
|
onMouseDown={(e) => handleResize(e, "tl")}
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={props.areaData.x + props.areaData.width}
|
cx={data.x + data.width}
|
||||||
cy={props.areaData.y}
|
cy={data.y}
|
||||||
r={6}
|
r={6}
|
||||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||||
stroke="#5891db"
|
stroke="#5891db"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
cursor="nesw-resize"
|
cursor="nesw-resize"
|
||||||
onMouseDown={(e) => handleMouseDown(e, "tr")}
|
onMouseDown={(e) => handleResize(e, "tr")}
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={props.areaData.x}
|
cx={data.x}
|
||||||
cy={props.areaData.y + props.areaData.height}
|
cy={data.y + data.height}
|
||||||
r={6}
|
r={6}
|
||||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||||
stroke="#5891db"
|
stroke="#5891db"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
cursor="nesw-resize"
|
cursor="nesw-resize"
|
||||||
onMouseDown={(e) => handleMouseDown(e, "bl")}
|
onMouseDown={(e) => handleResize(e, "bl")}
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={props.areaData.x + props.areaData.width}
|
cx={data.x + data.width}
|
||||||
cy={props.areaData.y + props.areaData.height}
|
cy={data.y + data.height}
|
||||||
r={6}
|
r={6}
|
||||||
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
|
||||||
stroke="#5891db"
|
stroke="#5891db"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
cursor="nwse-resize"
|
cursor="nwse-resize"
|
||||||
onMouseDown={(e) => handleMouseDown(e, "br")}
|
onMouseDown={(e) => handleResize(e, "br")}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</g>
|
</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={data.name}
|
||||||
|
placeholder="Name"
|
||||||
|
className="me-2"
|
||||||
|
onChange={(value) => 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([]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<div className="popover-theme">
|
||||||
|
<div className="flex justify-between items-center p-2">
|
||||||
|
<div className="font-medium">Theme</div>
|
||||||
|
<Button
|
||||||
|
type="tertiary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
updateArea(data.id, {
|
||||||
|
color: defaultBlue,
|
||||||
|
});
|
||||||
|
setSaveState(State.SAVING);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div className="py-3">
|
||||||
|
<div>
|
||||||
|
{tableThemes
|
||||||
|
.slice(0, Math.ceil(tableThemes.length / 2))
|
||||||
|
.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c}
|
||||||
|
style={{ backgroundColor: c }}
|
||||||
|
className="p-3 rounded-full mx-1"
|
||||||
|
onClick={() => {
|
||||||
|
setUndoStack((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.AREA,
|
||||||
|
aid: data.id,
|
||||||
|
undo: { color: data.color },
|
||||||
|
redo: { color: c },
|
||||||
|
message: `Edit area color to ${c}`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setRedoStack([]);
|
||||||
|
updateArea(data.id, {
|
||||||
|
color: c,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.color === c ? (
|
||||||
|
<IconCheckboxTick style={{ color: "white" }} />
|
||||||
|
) : (
|
||||||
|
<IconCheckboxTick style={{ color: c }} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
{tableThemes
|
||||||
|
.slice(Math.ceil(tableThemes.length / 2))
|
||||||
|
.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c}
|
||||||
|
style={{ backgroundColor: c }}
|
||||||
|
className="p-3 rounded-full mx-1"
|
||||||
|
onClick={() => {
|
||||||
|
setUndoStack((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.AREA,
|
||||||
|
aid: data.id,
|
||||||
|
undo: { color: data.color },
|
||||||
|
redo: { color: c },
|
||||||
|
message: `Edit area color to ${c}`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setRedoStack([]);
|
||||||
|
updateArea(data.id, {
|
||||||
|
color: c,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconCheckboxTick
|
||||||
|
style={{
|
||||||
|
color: data.color === c ? "white" : c,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="rightTop"
|
||||||
|
showArrow
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="h-[32px] w-[32px] rounded"
|
||||||
|
style={{ backgroundColor: data.color }}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<Button
|
||||||
|
icon={<IconDeleteStroked />}
|
||||||
|
type="danger"
|
||||||
|
block
|
||||||
|
onClick={() => {
|
||||||
|
Toast.success(`Area deleted!`);
|
||||||
|
deleteArea(data.id, true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Empty,
|
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
@ -9,10 +8,6 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
Toast,
|
Toast,
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import {
|
|
||||||
IllustrationNoContent,
|
|
||||||
IllustrationNoContentDark,
|
|
||||||
} from "@douyinfe/semi-illustrations";
|
|
||||||
import {
|
import {
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
@ -29,26 +24,21 @@ import {
|
|||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
import useAreas from "../hooks/useAreas";
|
import useAreas from "../hooks/useAreas";
|
||||||
import useSaveState from "../hooks/useSaveState";
|
import useSaveState from "../hooks/useSaveState";
|
||||||
|
import NoElements from "./NoElements";
|
||||||
|
|
||||||
export default function AreaOverview() {
|
export default function AreasOverview() {
|
||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
const { areas, addArea, deleteArea, updateArea } = useAreas();
|
const { areas, addArea, deleteArea, updateArea } = useAreas();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
const [value, setValue] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
areas.map((t) => {
|
areas.map((t) => t.name)
|
||||||
return t.name;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStringSearch = (value) => {
|
const handleStringSearch = (value) => {
|
||||||
setFilteredResult(
|
setFilteredResult(
|
||||||
areas
|
areas.map((t) => t.name).filter((i) => i.includes(value))
|
||||||
.map((t) => {
|
|
||||||
return t.name;
|
|
||||||
})
|
|
||||||
.filter((i) => i.includes(value))
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,7 +48,7 @@ export default function AreaOverview() {
|
|||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
data={filteredResult}
|
data={filteredResult}
|
||||||
value={value}
|
value={searchText}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
@ -66,7 +56,7 @@ export default function AreaOverview() {
|
|||||||
<div className="p-3 popover-theme">No areas found</div>
|
<div className="p-3 popover-theme">No areas found</div>
|
||||||
}
|
}
|
||||||
onSearch={(v) => handleStringSearch(v)}
|
onSearch={(v) => handleStringSearch(v)}
|
||||||
onChange={(v) => setValue(v)}
|
onChange={(v) => setSearchText(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
const { id } = areas.find((t) => t.name === v);
|
const { id } = areas.find((t) => t.name === v);
|
||||||
document
|
document
|
||||||
@ -77,24 +67,16 @@ export default function AreaOverview() {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Button icon={<IconPlus />} block onClick={() => addArea()}>
|
<Button icon={<IconPlus />} block onClick={addArea}>
|
||||||
Add area
|
Add area
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{areas.length <= 0 ? (
|
{areas.length <= 0 ? (
|
||||||
<div className="select-none mt-2">
|
<NoElements
|
||||||
<Empty
|
title="No subject areas"
|
||||||
image={
|
text="Add subject areas to organize tables!"
|
||||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
/>
|
||||||
}
|
|
||||||
darkModeImage={
|
|
||||||
<IllustrationNoContentDark style={{ width: 154, height: 154 }} />
|
|
||||||
}
|
|
||||||
title="No subject areas"
|
|
||||||
description="Add subject areas to compartmentalize tables!"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
{areas.map((a, i) => (
|
{areas.map((a, i) => (
|
||||||
@ -236,7 +218,7 @@ export default function AreaOverview() {
|
|||||||
Toast.success(`Area deleted!`);
|
Toast.success(`Area deleted!`);
|
||||||
deleteArea(i, true);
|
deleteArea(i, true);
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
@ -1,10 +1,10 @@
|
|||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import Table from "./Table";
|
|
||||||
import { Action, Cardinality, Constraint, ObjectType } from "../data/constants";
|
import { Action, Cardinality, Constraint, ObjectType } from "../data/constants";
|
||||||
|
import { Toast } from "@douyinfe/semi-ui";
|
||||||
|
import Table from "./Table";
|
||||||
import Area from "./Area";
|
import Area from "./Area";
|
||||||
import Relationship from "./Relationship";
|
import Relationship from "./Relationship";
|
||||||
import Note from "./Note";
|
import Note from "./Note";
|
||||||
import { Toast } from "@douyinfe/semi-ui";
|
|
||||||
import useSettings from "../hooks/useSettings";
|
import useSettings from "../hooks/useSettings";
|
||||||
import useTransform from "../hooks/useTransform";
|
import useTransform from "../hooks/useTransform";
|
||||||
import useTables from "../hooks/useTables";
|
import useTables from "../hooks/useTables";
|
||||||
@ -28,7 +28,7 @@ export default function Canvas() {
|
|||||||
prevY: 0,
|
prevY: 0,
|
||||||
});
|
});
|
||||||
const [linking, setLinking] = useState(false);
|
const [linking, setLinking] = useState(false);
|
||||||
const [line, setLine] = useState({
|
const [linkingLink, setLinkingLine] = useState({
|
||||||
startTableId: -1,
|
startTableId: -1,
|
||||||
startFieldId: -1,
|
startFieldId: -1,
|
||||||
endTableId: -1,
|
endTableId: -1,
|
||||||
@ -44,12 +44,17 @@ export default function Canvas() {
|
|||||||
mandatory: false,
|
mandatory: false,
|
||||||
});
|
});
|
||||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
||||||
const [onRect, setOnRect] = useState({
|
const [hoveredTable, setHoveredTable] = useState({
|
||||||
tableId: -1,
|
tableId: -1,
|
||||||
field: -2,
|
field: -2,
|
||||||
});
|
});
|
||||||
const [panning, setPanning] = useState({ state: false, x: 0, y: 0 });
|
const [panning, setPanning] = useState({
|
||||||
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
|
isPanning: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
dx: 0,
|
||||||
|
dy: 0,
|
||||||
|
});
|
||||||
const [areaResize, setAreaResize] = useState({ id: -1, dir: "none" });
|
const [areaResize, setAreaResize] = useState({ id: -1, dir: "none" });
|
||||||
const [initCoords, setInitCoords] = useState({
|
const [initCoords, setInitCoords] = useState({
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -63,7 +68,7 @@ export default function Canvas() {
|
|||||||
|
|
||||||
const canvas = useRef(null);
|
const canvas = useRef(null);
|
||||||
|
|
||||||
const handleMouseDownRect = (e, id, type) => {
|
const handleMouseDownOnElement = (e, id, type) => {
|
||||||
const { clientX, clientY } = e;
|
const { clientX, clientY } = e;
|
||||||
if (type === ObjectType.TABLE) {
|
if (type === ObjectType.TABLE) {
|
||||||
const table = tables.find((t) => t.id === id);
|
const table = tables.find((t) => t.id === id);
|
||||||
@ -72,7 +77,7 @@ export default function Canvas() {
|
|||||||
y: clientY / transform.zoom - table.y,
|
y: clientY / transform.zoom - table.y,
|
||||||
});
|
});
|
||||||
setDragging({
|
setDragging({
|
||||||
element: ObjectType.TABLE,
|
element: type,
|
||||||
id: id,
|
id: id,
|
||||||
prevX: table.x,
|
prevX: table.x,
|
||||||
prevY: table.y,
|
prevY: table.y,
|
||||||
@ -84,7 +89,7 @@ export default function Canvas() {
|
|||||||
y: clientY / transform.zoom - area.y,
|
y: clientY / transform.zoom - area.y,
|
||||||
});
|
});
|
||||||
setDragging({
|
setDragging({
|
||||||
element: ObjectType.AREA,
|
element: type,
|
||||||
id: id,
|
id: id,
|
||||||
prevX: area.x,
|
prevX: area.x,
|
||||||
prevY: area.y,
|
prevY: area.y,
|
||||||
@ -96,7 +101,7 @@ export default function Canvas() {
|
|||||||
y: clientY / transform.zoom - note.y,
|
y: clientY / transform.zoom - note.y,
|
||||||
});
|
});
|
||||||
setDragging({
|
setDragging({
|
||||||
element: ObjectType.NOTE,
|
element: type,
|
||||||
id: id,
|
id: id,
|
||||||
prevX: note.x,
|
prevX: note.x,
|
||||||
prevY: note.y,
|
prevY: note.y,
|
||||||
@ -113,29 +118,26 @@ export default function Canvas() {
|
|||||||
const handleMouseMove = (e) => {
|
const handleMouseMove = (e) => {
|
||||||
if (linking) {
|
if (linking) {
|
||||||
const rect = canvas.current.getBoundingClientRect();
|
const rect = canvas.current.getBoundingClientRect();
|
||||||
const offsetX = rect.left;
|
setLinkingLine({
|
||||||
const offsetY = rect.top;
|
...linkingLink,
|
||||||
|
endX: (e.clientX - rect.left - transform.pan?.x) / transform.zoom,
|
||||||
setLine({
|
endY: (e.clientY - rect.top - transform.pan?.y) / transform.zoom,
|
||||||
...line,
|
|
||||||
endX: (e.clientX - offsetX - transform.pan?.x) / transform.zoom,
|
|
||||||
endY: (e.clientY - offsetY - transform.pan?.y) / transform.zoom,
|
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (
|
||||||
panning.state &&
|
panning.isPanning &&
|
||||||
dragging.element === ObjectType.NONE &&
|
dragging.element === ObjectType.NONE &&
|
||||||
areaResize.id === -1
|
areaResize.id === -1
|
||||||
) {
|
) {
|
||||||
if (!settings.panning) {
|
if (!settings.panning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dx = e.clientX - panOffset.x;
|
const dx = e.clientX - panning.dx;
|
||||||
const dy = e.clientY - panOffset.y;
|
const dy = e.clientY - panning.dy;
|
||||||
setTransform((prev) => ({
|
setTransform((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pan: { x: prev.pan?.x + dx, y: prev.pan?.y + dy },
|
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) {
|
} else if (dragging.element === ObjectType.TABLE && dragging.id >= 0) {
|
||||||
const dx = e.clientX / transform.zoom - offset.x;
|
const dx = e.clientX / transform.zoom - offset.x;
|
||||||
const dy = e.clientY / transform.zoom - offset.y;
|
const dy = e.clientY / transform.zoom - offset.y;
|
||||||
@ -154,44 +156,41 @@ export default function Canvas() {
|
|||||||
updateNote(dragging.id, { x: dx, y: dy });
|
updateNote(dragging.id, { x: dx, y: dy });
|
||||||
} else if (areaResize.id !== -1) {
|
} else if (areaResize.id !== -1) {
|
||||||
if (areaResize.dir === "none") return;
|
if (areaResize.dir === "none") return;
|
||||||
|
let newDims = { ...initCoords };
|
||||||
let newX = initCoords.x;
|
delete newDims.mouseX;
|
||||||
let newY = initCoords.y;
|
delete newDims.mouseY;
|
||||||
let newWidth = initCoords.width;
|
|
||||||
let newHeight = initCoords.height;
|
|
||||||
const mouseX = e.clientX / transform.zoom;
|
const mouseX = e.clientX / transform.zoom;
|
||||||
const mouseY = e.clientY / 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") {
|
if (areaResize.dir === "br") {
|
||||||
newWidth = initCoords.width + (mouseX - initCoords.mouseX);
|
newDims.width = initCoords.width + (mouseX - initCoords.mouseX);
|
||||||
newHeight = initCoords.height + (mouseY - initCoords.mouseY);
|
newDims.height = initCoords.height + (mouseY - initCoords.mouseY);
|
||||||
} else if (areaResize.dir === "tl") {
|
} else if (areaResize.dir === "tl") {
|
||||||
newX = initCoords.x + (mouseX - initCoords.mouseX);
|
newDims.x = initCoords.x + (mouseX - initCoords.mouseX);
|
||||||
newY = initCoords.y + (mouseY - initCoords.mouseY);
|
newDims.y = initCoords.y + (mouseY - initCoords.mouseY);
|
||||||
newWidth = initCoords.width - (mouseX - initCoords.mouseX);
|
newDims.width = initCoords.width - (mouseX - initCoords.mouseX);
|
||||||
newHeight = initCoords.height - (mouseY - initCoords.mouseY);
|
newDims.height = initCoords.height - (mouseY - initCoords.mouseY);
|
||||||
} else if (areaResize.dir === "tr") {
|
} else if (areaResize.dir === "tr") {
|
||||||
newY = initCoords.y + (mouseY - initCoords.mouseY);
|
newDims.y = initCoords.y + (mouseY - initCoords.mouseY);
|
||||||
newWidth = initCoords.width + (mouseX - initCoords.mouseX);
|
newDims.width = initCoords.width + (mouseX - initCoords.mouseX);
|
||||||
newHeight = initCoords.height - (mouseY - initCoords.mouseY);
|
newDims.height = initCoords.height - (mouseY - initCoords.mouseY);
|
||||||
} else if (areaResize.dir === "bl") {
|
} else if (areaResize.dir === "bl") {
|
||||||
newX = initCoords.x + (mouseX - initCoords.mouseX);
|
newDims.x = initCoords.x + (mouseX - initCoords.mouseX);
|
||||||
newWidth = initCoords.width - (mouseX - initCoords.mouseX);
|
newDims.width = initCoords.width - (mouseX - initCoords.mouseX);
|
||||||
newHeight = initCoords.height + (mouseY - initCoords.mouseY);
|
newDims.height = initCoords.height + (mouseY - initCoords.mouseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateArea(areaResize.id, {
|
updateArea(areaResize.id, { ...newDims });
|
||||||
x: newX,
|
|
||||||
y: newY,
|
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = (e) => {
|
const handleMouseDown = (e) => {
|
||||||
setPanning({ state: true, ...transform.pan });
|
setPanning({
|
||||||
setPanOffset({ x: e.clientX, y: e.clientY });
|
isPanning: true,
|
||||||
|
...transform.pan,
|
||||||
|
dx: e.clientX,
|
||||||
|
dy: e.clientY,
|
||||||
|
});
|
||||||
setCursor("grabbing");
|
setCursor("grabbing");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -229,7 +228,7 @@ export default function Canvas() {
|
|||||||
const didPan = () =>
|
const didPan = () =>
|
||||||
!(transform.pan?.x === panning.x && transform.pan?.y === panning.y);
|
!(transform.pan?.x === panning.x && transform.pan?.y === panning.y);
|
||||||
|
|
||||||
const getMoveInfo = () => {
|
const getMovedElementDetails = () => {
|
||||||
switch (dragging.element) {
|
switch (dragging.element) {
|
||||||
case ObjectType.TABLE:
|
case ObjectType.TABLE:
|
||||||
return {
|
return {
|
||||||
@ -256,7 +255,7 @@ export default function Canvas() {
|
|||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
if (coordsDidUpdate(dragging.element)) {
|
if (coordsDidUpdate(dragging.element)) {
|
||||||
const info = getMoveInfo();
|
const info = getMovedElementDetails();
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
@ -273,7 +272,7 @@ export default function Canvas() {
|
|||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
}
|
}
|
||||||
setDragging({ element: ObjectType.NONE, id: -1, prevX: 0, prevY: 0 });
|
setDragging({ element: ObjectType.NONE, id: -1, prevX: 0, prevY: 0 });
|
||||||
if (panning.state && didPan()) {
|
if (panning.isPanning && didPan()) {
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
@ -291,7 +290,7 @@ export default function Canvas() {
|
|||||||
open: false,
|
open: false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
setPanning({ state: false, x: 0, y: 0 });
|
setPanning({ isPanning: false, x: 0, y: 0 });
|
||||||
setCursor("default");
|
setCursor("default");
|
||||||
if (linking) handleLinking();
|
if (linking) handleLinking();
|
||||||
setLinking(false);
|
setLinking(false);
|
||||||
@ -333,29 +332,29 @@ export default function Canvas() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLinking = () => {
|
const handleLinking = () => {
|
||||||
if (onRect.tableId < 0) return;
|
if (hoveredTable.tableId < 0) return;
|
||||||
if (onRect.field < 0) return;
|
if (hoveredTable.field < 0) return;
|
||||||
if (
|
if (
|
||||||
tables[line.startTableId].fields[line.startFieldId].type !==
|
tables[linkingLink.startTableId].fields[linkingLink.startFieldId].type !==
|
||||||
tables[onRect.tableId].fields[onRect.field].type
|
tables[hoveredTable.tableId].fields[hoveredTable.field].type
|
||||||
) {
|
) {
|
||||||
Toast.info("Cannot connect");
|
Toast.info("Cannot connect");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
line.startTableId === onRect.tableId &&
|
linkingLink.startTableId === hoveredTable.tableId &&
|
||||||
line.startFieldId === onRect.field
|
linkingLink.startFieldId === hoveredTable.field
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
addRelationship(true, {
|
addRelationship(true, {
|
||||||
...line,
|
...linkingLink,
|
||||||
endTableId: onRect.tableId,
|
endTableId: hoveredTable.tableId,
|
||||||
endFieldId: onRect.field,
|
endFieldId: hoveredTable.field,
|
||||||
endX: tables[onRect.tableId].x + 15,
|
endX: tables[hoveredTable.tableId].x + 15,
|
||||||
endY: tables[onRect.tableId].y + onRect.field * 36 + 69,
|
endY: tables[hoveredTable.tableId].y + hoveredTable.field * 36 + 69,
|
||||||
name: `${tables[line.startTableId].name}_${
|
name: `${tables[linkingLink.startTableId].name}_${
|
||||||
tables[line.startTableId].fields[line.startFieldId].name
|
tables[linkingLink.startTableId].fields[linkingLink.startFieldId].name
|
||||||
}_fk`,
|
}_fk`,
|
||||||
id: relationships.length,
|
id: relationships.length,
|
||||||
});
|
});
|
||||||
@ -434,14 +433,12 @@ export default function Canvas() {
|
|||||||
{areas.map((a) => (
|
{areas.map((a) => (
|
||||||
<Area
|
<Area
|
||||||
key={a.id}
|
key={a.id}
|
||||||
areaData={a}
|
data={a}
|
||||||
onMouseDown={(e) =>
|
onMouseDown={(e) =>
|
||||||
handleMouseDownRect(e, a.id, ObjectType.AREA)
|
handleMouseDownOnElement(e, a.id, ObjectType.AREA)
|
||||||
}
|
}
|
||||||
setResize={setAreaResize}
|
setResize={setAreaResize}
|
||||||
initCoords={initCoords}
|
|
||||||
setInitCoords={setInitCoords}
|
setInitCoords={setInitCoords}
|
||||||
zoom={transform.zoom}
|
|
||||||
></Area>
|
></Area>
|
||||||
))}
|
))}
|
||||||
{relationships.map((e, i) => (
|
{relationships.map((e, i) => (
|
||||||
@ -451,11 +448,11 @@ export default function Canvas() {
|
|||||||
<Table
|
<Table
|
||||||
key={table.id}
|
key={table.id}
|
||||||
tableData={table}
|
tableData={table}
|
||||||
setOnRect={setOnRect}
|
setHoveredTable={setHoveredTable}
|
||||||
handleGripField={handleGripField}
|
handleGripField={handleGripField}
|
||||||
setLine={setLine}
|
setLinkingLine={setLinkingLine}
|
||||||
onMouseDown={(e) =>
|
onMouseDown={(e) =>
|
||||||
handleMouseDownRect(e, table.id, ObjectType.TABLE)
|
handleMouseDownOnElement(e, table.id, ObjectType.TABLE)
|
||||||
}
|
}
|
||||||
active={
|
active={
|
||||||
selectedElement.element === ObjectType.TABLE &&
|
selectedElement.element === ObjectType.TABLE &&
|
||||||
@ -469,7 +466,7 @@ export default function Canvas() {
|
|||||||
))}
|
))}
|
||||||
{linking && (
|
{linking && (
|
||||||
<path
|
<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"
|
stroke="red"
|
||||||
strokeDasharray="8,8"
|
strokeDasharray="8,8"
|
||||||
/>
|
/>
|
||||||
@ -479,7 +476,7 @@ export default function Canvas() {
|
|||||||
key={n.id}
|
key={n.id}
|
||||||
data={n}
|
data={n}
|
||||||
onMouseDown={(e) =>
|
onMouseDown={(e) =>
|
||||||
handleMouseDownRect(e, n.id, ObjectType.NOTE)
|
handleMouseDownOnElement(e, n.id, ObjectType.NOTE)
|
||||||
}
|
}
|
||||||
></Note>
|
></Note>
|
||||||
))}
|
))}
|
||||||
|
@ -1725,7 +1725,7 @@ export default function ControlPanel({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
limit={1}
|
limit={1}
|
||||||
></Upload>
|
/>
|
||||||
{error.type === STATUS.ERROR ? (
|
{error.type === STATUS.ERROR ? (
|
||||||
<Banner
|
<Banner
|
||||||
type="danger"
|
type="danger"
|
||||||
@ -1803,7 +1803,7 @@ export default function ControlPanel({
|
|||||||
]}
|
]}
|
||||||
onChange={(e) => setData((prev) => ({ ...prev, dbms: e }))}
|
onChange={(e) => setData((prev) => ({ ...prev, dbms: e }))}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
></Select>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
aria-label="overwrite checkbox"
|
aria-label="overwrite checkbox"
|
||||||
checked={data.overwrite}
|
checked={data.overwrite}
|
||||||
@ -1914,7 +1914,7 @@ export default function ControlPanel({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td className="py-1">
|
<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}
|
{d.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-1">
|
<td className="py-1">
|
||||||
@ -2085,7 +2085,7 @@ export default function ControlPanel({
|
|||||||
className="hover-1"
|
className="hover-1"
|
||||||
>
|
>
|
||||||
<div className="flex items-center py-1 w-full">
|
<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 className="ms-2">{e.message}</div>
|
||||||
</div>
|
</div>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
@ -2166,7 +2166,7 @@ export default function ControlPanel({
|
|||||||
setTransform((prev) => ({ ...prev, zoom: prev.zoom * 1.2 }))
|
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>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Zoom out" position="bottom">
|
<Tooltip content="Zoom out" position="bottom">
|
||||||
@ -2176,7 +2176,7 @@ export default function ControlPanel({
|
|||||||
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }))
|
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>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider layout="vertical" margin="8px" />
|
<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"
|
className="py-1 px-2 hover-2 rounded text-xl -mt-0.5"
|
||||||
onClick={() => setSidesheet(SIDESHEET.TODO)}
|
onClick={() => setSidesheet(SIDESHEET.TODO)}
|
||||||
>
|
>
|
||||||
<i className="fa-regular fa-calendar-check"></i>
|
<i className="fa-regular fa-calendar-check" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -2401,11 +2401,7 @@ export default function ControlPanel({
|
|||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
icon={
|
icon={
|
||||||
layout.header ? (
|
layout.header ? <IconCheckboxTick /> : <div className="px-2" />
|
||||||
<IconCheckboxTick />
|
|
||||||
) : (
|
|
||||||
<div className="px-2"></div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onClick={() => invertLayout("header")}
|
onClick={() => invertLayout("header")}
|
||||||
>
|
>
|
||||||
@ -2413,11 +2409,7 @@ export default function ControlPanel({
|
|||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
icon={
|
icon={
|
||||||
layout.sidebar ? (
|
layout.sidebar ? <IconCheckboxTick /> : <div className="px-2" />
|
||||||
<IconCheckboxTick />
|
|
||||||
) : (
|
|
||||||
<div className="px-2"></div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onClick={() => invertLayout("sidebar")}
|
onClick={() => invertLayout("sidebar")}
|
||||||
>
|
>
|
||||||
@ -2425,11 +2417,7 @@ export default function ControlPanel({
|
|||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
icon={
|
icon={
|
||||||
layout.issues ? (
|
layout.issues ? <IconCheckboxTick /> : <div className="px-2" />
|
||||||
<IconCheckboxTick />
|
|
||||||
) : (
|
|
||||||
<div className="px-2"></div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onClick={() => invertLayout("issues")}
|
onClick={() => invertLayout("issues")}
|
||||||
>
|
>
|
||||||
@ -2437,7 +2425,7 @@ export default function ControlPanel({
|
|||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
icon={<div className="px-2"></div>}
|
icon={<div className="px-2" />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (layout.fullscreen) {
|
if (layout.fullscreen) {
|
||||||
exitFullscreen();
|
exitFullscreen();
|
||||||
|
@ -19,7 +19,7 @@ export default function Controls() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bi bi-dash-lg"></i>
|
<i className="bi bi-dash-lg" />
|
||||||
</button>
|
</button>
|
||||||
<Divider align="center" layout="vertical" />
|
<Divider align="center" layout="vertical" />
|
||||||
<div className="px-3 py-2">{parseInt(transform.zoom * 100)}%</div>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip content="Exit">
|
<Tooltip content="Exit">
|
||||||
@ -49,7 +49,7 @@ export default function Controls() {
|
|||||||
exitFullscreen();
|
exitFullscreen();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i className="bi bi-fullscreen-exit"></i>
|
<i className="bi bi-fullscreen-exit" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@ export default function Issues() {
|
|||||||
className="mt-1"
|
className="mt-1"
|
||||||
>
|
>
|
||||||
<div className="pe-3 select-none">
|
<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
|
Issues
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
@ -48,7 +48,9 @@ export default function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<SideSheet
|
<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}
|
visible={openMenu}
|
||||||
onCancel={() => setOpenMenu(false)}
|
onCancel={() => setOpenMenu(false)}
|
||||||
width={window.innerWidth}
|
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}`);
|
const textarea = document.getElementById(`note_${data.id}`);
|
||||||
textarea.style.height = "0";
|
textarea.style.height = "0";
|
||||||
textarea.style.height = textarea.scrollHeight + "px";
|
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 });
|
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 (
|
return (
|
||||||
<g
|
<g
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
@ -100,28 +140,10 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
height: data.height,
|
height: data.height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onBlur={(e) => {
|
onBlur={handleBlur}
|
||||||
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([]);
|
|
||||||
}}
|
|
||||||
className="w-full resize-none outline-none overflow-y-hidden border-none select-none"
|
className="w-full resize-none outline-none overflow-y-hidden border-none select-none"
|
||||||
style={{ backgroundColor: data.color }}
|
style={{ backgroundColor: data.color }}
|
||||||
></textarea>
|
/>
|
||||||
{(hovered ||
|
{(hovered ||
|
||||||
(selectedElement.element === ObjectType.NOTE &&
|
(selectedElement.element === ObjectType.NOTE &&
|
||||||
selectedElement.id === data.id &&
|
selectedElement.id === data.id &&
|
||||||
@ -253,26 +275,8 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
backgroundColor: "#2f68ad",
|
backgroundColor: "#2f68ad",
|
||||||
opacity: "0.7",
|
opacity: "0.7",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={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,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
></Button>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Empty,
|
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Button,
|
Button,
|
||||||
@ -11,10 +10,6 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Toast,
|
Toast,
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import {
|
|
||||||
IllustrationNoContent,
|
|
||||||
IllustrationNoContentDark,
|
|
||||||
} from "@douyinfe/semi-illustrations";
|
|
||||||
import {
|
import {
|
||||||
IconDeleteStroked,
|
IconDeleteStroked,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
@ -24,26 +19,21 @@ import {
|
|||||||
import { noteThemes, Action, ObjectType } from "../data/constants";
|
import { noteThemes, Action, ObjectType } from "../data/constants";
|
||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
import useNotes from "../hooks/useNotes";
|
import useNotes from "../hooks/useNotes";
|
||||||
|
import NoElements from "./NoElements";
|
||||||
|
|
||||||
export default function NotesOverview() {
|
export default function NotesOverview() {
|
||||||
const { notes, updateNote, addNote, deleteNote } = useNotes();
|
const { notes, updateNote, addNote, deleteNote } = useNotes();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [value, setValue] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
const [activeKey, setActiveKey] = useState("");
|
const [activeKey, setActiveKey] = useState("");
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
notes.map((t) => {
|
notes.map((t) => t.title)
|
||||||
return t.title;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStringSearch = (value) => {
|
const handleStringSearch = (value) => {
|
||||||
setFilteredResult(
|
setFilteredResult(
|
||||||
notes
|
notes.map((t) => t.title).filter((i) => i.includes(value))
|
||||||
.map((t) => {
|
|
||||||
return t.title;
|
|
||||||
})
|
|
||||||
.filter((i) => i.includes(value))
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,7 +43,7 @@ export default function NotesOverview() {
|
|||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
data={filteredResult}
|
data={filteredResult}
|
||||||
value={value}
|
value={searchText}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
@ -61,7 +51,7 @@ export default function NotesOverview() {
|
|||||||
<div className="p-3 popover-theme">No notes found</div>
|
<div className="p-3 popover-theme">No notes found</div>
|
||||||
}
|
}
|
||||||
onSearch={(v) => handleStringSearch(v)}
|
onSearch={(v) => handleStringSearch(v)}
|
||||||
onChange={(v) => setValue(v)}
|
onChange={(v) => setSearchText(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
const { id } = notes.find((t) => t.title === v);
|
const { id } = notes.find((t) => t.title === v);
|
||||||
setActiveKey(`${id}`);
|
setActiveKey(`${id}`);
|
||||||
@ -79,18 +69,7 @@ export default function NotesOverview() {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{notes.length <= 0 ? (
|
{notes.length <= 0 ? (
|
||||||
<div className="select-none mt-2">
|
<NoElements title="No text notes" text="Add notes cuz why not!" />
|
||||||
<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>
|
|
||||||
) : (
|
) : (
|
||||||
<Collapse
|
<Collapse
|
||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
@ -218,7 +197,7 @@ export default function NotesOverview() {
|
|||||||
Toast.success(`Note deleted!`);
|
Toast.success(`Note deleted!`);
|
||||||
deleteNote(i, true);
|
deleteNote(i, true);
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Collapse.Panel>
|
</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 { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
||||||
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
||||||
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
|
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
|
||||||
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
|
|
||||||
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
||||||
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
||||||
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
||||||
import { ClearEditorPlugin } from "@lexical/react/LexicalClearEditorPlugin";
|
import { ClearEditorPlugin } from "@lexical/react/LexicalClearEditorPlugin";
|
||||||
import { TRANSFORMERS } from "@lexical/markdown";
|
import { TRANSFORMERS } from "@lexical/markdown";
|
||||||
|
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
|
||||||
import ToolbarPlugin from "../plugins/ToolbarPlugin";
|
import ToolbarPlugin from "../plugins/ToolbarPlugin";
|
||||||
import ListMaxIndentLevelPlugin from "../plugins/ListMaxIndentLevelPlugin";
|
import ListMaxIndentLevelPlugin from "../plugins/ListMaxIndentLevelPlugin";
|
||||||
import CodeHighlightPlugin from "../plugins/CodeHighlightPlugin";
|
import CodeHighlightPlugin from "../plugins/CodeHighlightPlugin";
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Tabs } from "@douyinfe/semi-ui";
|
import { Tabs } from "@douyinfe/semi-ui";
|
||||||
import { Tab } from "../data/constants";
|
import { Tab } from "../data/constants";
|
||||||
import TableOverview from "./TableOverview";
|
import TablesOverview from "./TablesOverview";
|
||||||
import ReferenceOverview from "./ReferenceOverview";
|
import RelationshipsOverview from "./RelationshipsOverview";
|
||||||
import AreaOverview from "./AreaOverview";
|
import AreasOverview from "./AreasOverview";
|
||||||
import NotesOverview from "./NotesOverview";
|
import NotesOverview from "./NotesOverview";
|
||||||
import TypesOverview from "./TypesOverview";
|
import TypesOverview from "./TypesOverview";
|
||||||
import Issues from "./Issues";
|
import Issues from "./Issues";
|
||||||
@ -20,12 +20,13 @@ export default function SidePanel({ width, resize, setResize }) {
|
|||||||
{ tab: "Notes", itemKey: Tab.NOTES },
|
{ tab: "Notes", itemKey: Tab.NOTES },
|
||||||
{ tab: "Types", itemKey: Tab.TYPES },
|
{ tab: "Types", itemKey: Tab.TYPES },
|
||||||
];
|
];
|
||||||
|
|
||||||
const contentList = [
|
const contentList = [
|
||||||
<TableOverview key={1} />,
|
<TablesOverview key="tables" />,
|
||||||
<ReferenceOverview key={2} />,
|
<RelationshipsOverview key="relationships" />,
|
||||||
<AreaOverview key={3} />,
|
<AreasOverview key="areas" />,
|
||||||
<NotesOverview key={4} />,
|
<NotesOverview key="notes" />,
|
||||||
<TypesOverview key={5} />,
|
<TypesOverview key="types" />,
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -57,7 +58,7 @@ export default function SidePanel({ width, resize, setResize }) {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`flex justify-center items-center p-1 h-auto hover-2 cursor-col-resize ${
|
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)}
|
onMouseDown={() => setResize(true)}
|
||||||
>
|
>
|
||||||
|
@ -131,7 +131,7 @@ export default function Table(props) {
|
|||||||
.scrollIntoView({ behavior: "smooth" });
|
.scrollIntoView({ behavior: "smooth" });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
@ -205,7 +205,7 @@ export default function Table(props) {
|
|||||||
backgroundColor: "grey",
|
backgroundColor: "grey",
|
||||||
color: "white",
|
color: "white",
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -469,7 +469,7 @@ export default function Table(props) {
|
|||||||
updateField(props.tableData.id, j, { primary: !f.primary });
|
updateField(props.tableData.id, j, { primary: !f.primary });
|
||||||
}}
|
}}
|
||||||
icon={<IconKeyStroked />}
|
icon={<IconKeyStroked />}
|
||||||
></Button>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<Popover
|
<Popover
|
||||||
@ -848,7 +848,7 @@ export default function Table(props) {
|
|||||||
position="right"
|
position="right"
|
||||||
showArrow
|
showArrow
|
||||||
>
|
>
|
||||||
<Button type="tertiary" icon={<IconMore />}></Button>
|
<Button type="tertiary" icon={<IconMore />} />
|
||||||
</Popover>
|
</Popover>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -1000,7 +1000,7 @@ export default function Table(props) {
|
|||||||
icon={<IconMore />}
|
icon={<IconMore />}
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
style={{ marginLeft: "12px" }}
|
style={{ marginLeft: "12px" }}
|
||||||
></Button>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -1229,7 +1229,7 @@ export default function Table(props) {
|
|||||||
Toast.success(`Table deleted!`);
|
Toast.success(`Table deleted!`);
|
||||||
deleteTable(props.tableData.id);
|
deleteTable(props.tableData.id);
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
@ -1241,13 +1241,12 @@ export default function Table(props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
index === props.tableData.fields.length - 1
|
index === props.tableData.fields.length - 1 ||
|
||||||
? ""
|
"border-b border-gray-400"
|
||||||
: "border-b border-gray-400"
|
|
||||||
} h-[36px] px-2 py-1 flex justify-between`}
|
} h-[36px] px-2 py-1 flex justify-between`}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
setHoveredField(index);
|
setHoveredField(index);
|
||||||
props.setOnRect({
|
props.setHoveredTable({
|
||||||
tableId: props.tableData.id,
|
tableId: props.tableData.id,
|
||||||
field: index,
|
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`}
|
className={`w-[10px] h-[10px] bg-[#2f68ad] opacity-80 z-50 rounded-full me-2`}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
props.handleGripField(index);
|
props.handleGripField(index);
|
||||||
props.setLine((prev) => ({
|
props.setLinkingLine((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
startFieldId: index,
|
startFieldId: index,
|
||||||
startTableId: props.tableData.id,
|
startTableId: props.tableData.id,
|
||||||
@ -1271,7 +1270,7 @@ export default function Table(props) {
|
|||||||
endY: props.tableData.y + index * 36 + 50 + 19,
|
endY: props.tableData.y + index * 36 + 50 + 19,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
></button>
|
/>
|
||||||
{fieldData.name.length <= 11
|
{fieldData.name.length <= 11
|
||||||
? fieldData.name
|
? fieldData.name
|
||||||
: fieldData.name.substring(0, 11)}
|
: fieldData.name.substring(0, 11)}
|
||||||
@ -1352,7 +1351,7 @@ export default function Table(props) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
) : (
|
) : (
|
||||||
fieldData.type
|
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}
|
width={a.width > 0 ? a.width : 0}
|
||||||
height={a.height > 0 ? a.height : 0}
|
height={a.height > 0 ? a.height : 0}
|
||||||
>
|
>
|
||||||
<div
|
<div className="border border-slate-400 w-full h-full rounded-sm relative">
|
||||||
className={`border border-slate-400 w-full h-full rounded-sm relative`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="opacity-40 w-fill h-full"
|
className="opacity-40 w-fill h-full"
|
||||||
style={{ backgroundColor: a.color }}
|
style={{ backgroundColor: a.color }}
|
||||||
@ -82,7 +80,7 @@ export function Thumbnail({ diagram, i, zoom }) {
|
|||||||
<div
|
<div
|
||||||
className="h-2 w-full rounded-t-sm"
|
className="h-2 w-full rounded-t-sm"
|
||||||
style={{ backgroundColor: table.color }}
|
style={{ backgroundColor: table.color }}
|
||||||
></div>
|
/>
|
||||||
<div className="rounded-b-[3px]">
|
<div className="rounded-b-[3px]">
|
||||||
<div
|
<div
|
||||||
className={`font-bold py-1 px-2 border-b ${
|
className={`font-bold py-1 px-2 border-b ${
|
||||||
|
@ -235,7 +235,7 @@ export default function Todo() {
|
|||||||
showArrow
|
showArrow
|
||||||
className="w-[180px]"
|
className="w-[180px]"
|
||||||
>
|
>
|
||||||
<Button icon={<IconMore />} type="tertiary"></Button>
|
<Button icon={<IconMore />} type="tertiary" />
|
||||||
</Popover>
|
</Popover>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
InputNumber,
|
InputNumber,
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Toast,
|
Toast,
|
||||||
Empty,
|
|
||||||
Popover,
|
Popover,
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import {
|
import {
|
||||||
@ -23,32 +22,21 @@ import {
|
|||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconMore,
|
IconMore,
|
||||||
} from "@douyinfe/semi-icons";
|
} from "@douyinfe/semi-icons";
|
||||||
import {
|
|
||||||
IllustrationNoContent,
|
|
||||||
IllustrationNoContentDark,
|
|
||||||
} from "@douyinfe/semi-illustrations";
|
|
||||||
import { isSized, hasPrecision, getSize } from "../utils/toSQL";
|
import { isSized, hasPrecision, getSize } from "../utils/toSQL";
|
||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
import useTypes from "../hooks/useTypes";
|
import useTypes from "../hooks/useTypes";
|
||||||
|
import NoElements from "./NoElements";
|
||||||
|
|
||||||
export default function TableOverview() {
|
export default function TypesOverview() {
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
const { types, addType, deleteType, updateType } = useTypes();
|
const { types, addType } = useTypes();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
|
||||||
const [editField, setEditField] = useState({});
|
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
types.map((t) => {
|
types.map((t) => t.name)
|
||||||
return t.name;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStringSearch = (value) => {
|
const handleStringSearch = (value) => {
|
||||||
setFilteredResult(
|
setFilteredResult(
|
||||||
types
|
types.map((t) => t.name).filter((i) => i.includes(value))
|
||||||
.map((t) => {
|
|
||||||
return t.name;
|
|
||||||
})
|
|
||||||
.filter((i) => i.includes(value))
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,414 +99,403 @@ export default function TableOverview() {
|
|||||||
</Popover>
|
</Popover>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Collapse accordion>
|
{types.length <= 0 ? (
|
||||||
{types.length <= 0 ? (
|
<NoElements title="No types" text="Make your own custom data types" />
|
||||||
<div className="select-none mt-2">
|
) : (
|
||||||
<Empty
|
<Collapse accordion>
|
||||||
image={
|
{types.map((t, i) => (
|
||||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
<TypePanel data={t} key={i} index={i} />
|
||||||
}
|
))}
|
||||||
darkModeImage={
|
</Collapse>
|
||||||
<IllustrationNoContentDark
|
)}
|
||||||
style={{ width: 154, height: 154 }}
|
</>
|
||||||
/>
|
);
|
||||||
}
|
}
|
||||||
title="No types"
|
|
||||||
description={<div>Make your own custom data types.</div>}
|
function TypePanel({ index, data }) {
|
||||||
/>
|
const { types, deleteType, updateType } = useTypes();
|
||||||
</div>
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
) : (
|
const [editField, setEditField] = useState({});
|
||||||
types.map((t, i) => (
|
|
||||||
<div id={`scroll_type_${i}`} key={i}>
|
return (
|
||||||
<Collapse.Panel header={<div>{t.name}</div>} itemKey={`${i}`}>
|
<div id={`scroll_type_${index}`}>
|
||||||
<div className="flex items-center mb-2.5">
|
<Collapse.Panel header={<div>{data.name}</div>} itemKey={`${index}`}>
|
||||||
<div className="text-md font-semibold">Name: </div>
|
<div className="flex items-center mb-2.5">
|
||||||
<Input
|
<div className="text-md font-semibold">Name: </div>
|
||||||
value={t.name}
|
<Input
|
||||||
validateStatus={t.name === "" ? "error" : "default"}
|
value={data.name}
|
||||||
placeholder="Name"
|
validateStatus={data.name === "" ? "error" : "default"}
|
||||||
className="ms-2"
|
placeholder="Name"
|
||||||
onChange={(value) => updateType(i, { name: value })}
|
className="ms-2"
|
||||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
onChange={(value) => updateType(index, { name: value })}
|
||||||
onBlur={(e) => {
|
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||||
if (e.target.value === editField.name) return;
|
onBlur={(e) => {
|
||||||
setUndoStack((prev) => [
|
if (e.target.value === editField.name) return;
|
||||||
...prev,
|
setUndoStack((prev) => [
|
||||||
{
|
...prev,
|
||||||
action: Action.EDIT,
|
{
|
||||||
element: ObjectType.TYPE,
|
action: Action.EDIT,
|
||||||
component: "self",
|
element: ObjectType.TYPE,
|
||||||
tid: i,
|
component: "self",
|
||||||
undo: editField,
|
tid: index,
|
||||||
redo: { name: e.target.value },
|
undo: editField,
|
||||||
message: `Edit type name to ${e.target.value}`,
|
redo: { name: e.target.value },
|
||||||
},
|
message: `Edit type name to ${e.target.value}`,
|
||||||
]);
|
},
|
||||||
setRedoStack([]);
|
]);
|
||||||
}}
|
setRedoStack([]);
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
{t.fields.map((f, j) => (
|
</div>
|
||||||
<Row gutter={6} key={j} className="hover-1 my-2">
|
{data.fields.map((f, j) => (
|
||||||
<Col span={10}>
|
<Row gutter={6} key={j} className="hover-1 my-2">
|
||||||
<Input
|
<Col span={10}>
|
||||||
value={f.name}
|
<Input
|
||||||
validateStatus={f.name === "" ? "error" : "default"}
|
value={f.name}
|
||||||
placeholder="Name"
|
validateStatus={f.name === "" ? "error" : "default"}
|
||||||
onChange={(value) =>
|
placeholder="Name"
|
||||||
updateType(i, {
|
onChange={(value) =>
|
||||||
fields: t.fields.map((e, id) =>
|
updateType(index, {
|
||||||
id === j ? { ...f, name: value } : e
|
fields: data.fields.map((e, id) =>
|
||||||
),
|
id === j ? { ...f, name: value } : e
|
||||||
})
|
),
|
||||||
}
|
})
|
||||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
}
|
||||||
onBlur={(e) => {
|
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||||
if (e.target.value === editField.name) return;
|
onBlur={(e) => {
|
||||||
setUndoStack((prev) => [
|
if (e.target.value === editField.name) return;
|
||||||
...prev,
|
setUndoStack((prev) => [
|
||||||
{
|
...prev,
|
||||||
action: Action.EDIT,
|
{
|
||||||
element: ObjectType.TYPE,
|
action: Action.EDIT,
|
||||||
component: "field",
|
element: ObjectType.TYPE,
|
||||||
tid: i,
|
component: "field",
|
||||||
fid: j,
|
tid: index,
|
||||||
undo: editField,
|
fid: j,
|
||||||
redo: { name: e.target.value },
|
undo: editField,
|
||||||
message: `Edit type field name to ${e.target.value}`,
|
redo: { name: e.target.value },
|
||||||
},
|
message: `Edit type field name to ${e.target.value}`,
|
||||||
]);
|
},
|
||||||
setRedoStack([]);
|
]);
|
||||||
}}
|
setRedoStack([]);
|
||||||
/>
|
}}
|
||||||
</Col>
|
/>
|
||||||
<Col span={11}>
|
</Col>
|
||||||
<Select
|
<Col span={11}>
|
||||||
className="w-full"
|
<Select
|
||||||
optionList={[
|
className="w-full"
|
||||||
...sqlDataTypes.map((value) => ({
|
optionList={[
|
||||||
label: value,
|
...sqlDataTypes.map((value) => ({
|
||||||
value: value,
|
label: value,
|
||||||
})),
|
value: value,
|
||||||
...types
|
})),
|
||||||
.filter((type) => type.name !== t.name)
|
...types
|
||||||
.map((type) => ({
|
.filter((type) => type.name !== data.name)
|
||||||
label: type.name.toUpperCase(),
|
.map((type) => ({
|
||||||
value: type.name.toUpperCase(),
|
label: type.name.toUpperCase(),
|
||||||
})),
|
value: type.name.toUpperCase(),
|
||||||
]}
|
})),
|
||||||
filter
|
]}
|
||||||
value={f.type}
|
filter
|
||||||
validateStatus={f.type === "" ? "error" : "default"}
|
value={f.type}
|
||||||
placeholder="Type"
|
validateStatus={f.type === "" ? "error" : "default"}
|
||||||
onChange={(value) => {
|
placeholder="Type"
|
||||||
if (value === f.type) return;
|
onChange={(value) => {
|
||||||
setUndoStack((prev) => [
|
if (value === f.type) return;
|
||||||
...prev,
|
setUndoStack((prev) => [
|
||||||
{
|
...prev,
|
||||||
action: Action.EDIT,
|
{
|
||||||
element: ObjectType.TYPE,
|
action: Action.EDIT,
|
||||||
component: "field",
|
element: ObjectType.TYPE,
|
||||||
tid: i,
|
component: "field",
|
||||||
fid: j,
|
tid: index,
|
||||||
undo: { type: f.type },
|
fid: j,
|
||||||
redo: { type: value },
|
undo: { type: f.type },
|
||||||
message: `Edit type field type to ${value}`,
|
redo: { type: value },
|
||||||
},
|
message: `Edit type field type to ${value}`,
|
||||||
]);
|
},
|
||||||
setRedoStack([]);
|
]);
|
||||||
if (value === "ENUM" || value === "SET") {
|
setRedoStack([]);
|
||||||
updateType(i, {
|
if (value === "ENUM" || value === "SET") {
|
||||||
fields: t.fields.map((e, id) =>
|
updateType(index, {
|
||||||
id === j
|
fields: data.fields.map((e, id) =>
|
||||||
? {
|
id === j
|
||||||
...f,
|
? {
|
||||||
type: value,
|
...f,
|
||||||
values: f.values ? [...f.values] : [],
|
type: value,
|
||||||
}
|
values: f.values ? [...f.values] : [],
|
||||||
: e
|
}
|
||||||
),
|
: e
|
||||||
});
|
),
|
||||||
} else if (isSized(value) || hasPrecision(value)) {
|
});
|
||||||
updateType(i, {
|
} else if (isSized(value) || hasPrecision(value)) {
|
||||||
fields: t.fields.map((e, id) =>
|
updateType(index, {
|
||||||
id === j
|
fields: data.fields.map((e, id) =>
|
||||||
? { ...f, type: value, size: getSize(value) }
|
id === j
|
||||||
: e
|
? { ...f, type: value, size: getSize(value) }
|
||||||
),
|
: e
|
||||||
});
|
),
|
||||||
} else {
|
});
|
||||||
updateType(i, {
|
} else {
|
||||||
fields: t.fields.map((e, id) =>
|
updateType(index, {
|
||||||
id === j ? { ...f, type: value } : e
|
fields: data.fields.map((e, id) =>
|
||||||
),
|
id === j ? { ...f, type: value } : e
|
||||||
});
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Select>
|
||||||
|
</Col>
|
||||||
|
<Col span={3}>
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<div className="popover-theme w-[240px]">
|
||||||
|
{(f.type === "ENUM" || f.type === "SET") && (
|
||||||
|
<>
|
||||||
|
<div className="font-semibold mb-1">
|
||||||
|
{f.type} values
|
||||||
|
</div>
|
||||||
|
<TagInput
|
||||||
|
separator={[",", ", ", " ,"]}
|
||||||
|
value={f.values}
|
||||||
|
validateStatus={
|
||||||
|
!f.values || f.values.length === 0
|
||||||
|
? "error"
|
||||||
|
: "default"
|
||||||
}
|
}
|
||||||
}}
|
className="my-2"
|
||||||
></Select>
|
placeholder="Use ',' for batch input"
|
||||||
</Col>
|
onChange={(v) =>
|
||||||
<Col span={3}>
|
updateType(index, {
|
||||||
<Popover
|
fields: data.fields.map((e, id) =>
|
||||||
content={
|
id === j ? { ...f, values: v } : e
|
||||||
<div className="popover-theme w-[240px]">
|
),
|
||||||
{(f.type === "ENUM" || f.type === "SET") && (
|
})
|
||||||
<>
|
}
|
||||||
<div className="font-semibold mb-1">
|
onFocus={() => setEditField({ values: f.values })}
|
||||||
{f.type} values
|
onBlur={() => {
|
||||||
</div>
|
if (
|
||||||
<TagInput
|
JSON.stringify(editField.values) ===
|
||||||
separator={[",", ", ", " ,"]}
|
JSON.stringify(f.values)
|
||||||
value={f.values}
|
)
|
||||||
validateStatus={
|
return;
|
||||||
!f.values || f.values.length === 0
|
setUndoStack((prev) => [
|
||||||
? "error"
|
...prev,
|
||||||
: "default"
|
{
|
||||||
}
|
action: Action.EDIT,
|
||||||
className="my-2"
|
element: ObjectType.TYPE,
|
||||||
placeholder="Use ',' for batch input"
|
component: "field",
|
||||||
onChange={(v) =>
|
tid: index,
|
||||||
updateType(i, {
|
fid: j,
|
||||||
fields: t.fields.map((e, id) =>
|
undo: editField,
|
||||||
id === j ? { ...f, values: v } : e
|
redo: { values: f.values },
|
||||||
),
|
message: `Edit type field values to "${JSON.stringify(
|
||||||
})
|
f.values
|
||||||
}
|
)}"`,
|
||||||
onFocus={() =>
|
},
|
||||||
setEditField({ values: f.values })
|
]);
|
||||||
}
|
setRedoStack([]);
|
||||||
onBlur={() => {
|
}}
|
||||||
if (
|
/>
|
||||||
JSON.stringify(editField.values) ===
|
</>
|
||||||
JSON.stringify(f.values)
|
)}
|
||||||
)
|
{isSized(f.type) && (
|
||||||
return;
|
<>
|
||||||
setUndoStack((prev) => [
|
<div className="font-semibold">Size</div>
|
||||||
...prev,
|
<InputNumber
|
||||||
{
|
className="my-2 w-full"
|
||||||
action: Action.EDIT,
|
placeholder="Set length"
|
||||||
element: ObjectType.TYPE,
|
value={f.size}
|
||||||
component: "field",
|
onChange={(value) =>
|
||||||
tid: i,
|
updateType(index, {
|
||||||
fid: j,
|
fields: data.fields.map((e, id) =>
|
||||||
undo: editField,
|
id === j ? { ...f, size: value } : e
|
||||||
redo: { values: f.values },
|
),
|
||||||
message: `Edit type field values to "${JSON.stringify(
|
})
|
||||||
f.values
|
}
|
||||||
)}"`,
|
onFocus={(e) =>
|
||||||
},
|
setEditField({ size: e.target.value })
|
||||||
]);
|
}
|
||||||
setRedoStack([]);
|
onBlur={(e) => {
|
||||||
}}
|
if (e.target.value === editField.size) return;
|
||||||
/>
|
setUndoStack((prev) => [
|
||||||
</>
|
...prev,
|
||||||
)}
|
{
|
||||||
{isSized(f.type) && (
|
action: Action.EDIT,
|
||||||
<>
|
element: ObjectType.TABLE,
|
||||||
<div className="font-semibold">Size</div>
|
component: "field",
|
||||||
<InputNumber
|
tid: index,
|
||||||
className="my-2 w-full"
|
fid: j,
|
||||||
placeholder="Set length"
|
undo: editField,
|
||||||
value={f.size}
|
redo: { size: e.target.value },
|
||||||
onChange={(value) =>
|
message: `Edit type field size to ${e.target.value}`,
|
||||||
updateType(i, {
|
},
|
||||||
fields: t.fields.map((e, id) =>
|
]);
|
||||||
id === j ? { ...f, size: value } : e
|
setRedoStack([]);
|
||||||
),
|
}}
|
||||||
})
|
/>
|
||||||
}
|
</>
|
||||||
onFocus={(e) =>
|
)}
|
||||||
setEditField({ size: e.target.value })
|
{hasPrecision(f.type) && (
|
||||||
}
|
<>
|
||||||
onBlur={(e) => {
|
<div className="font-semibold">Precision</div>
|
||||||
if (e.target.value === editField.size)
|
<Input
|
||||||
return;
|
className="my-2 w-full"
|
||||||
setUndoStack((prev) => [
|
placeholder="Set precision: (size, d)"
|
||||||
...prev,
|
validateStatus={
|
||||||
{
|
/^\(\d+,\s*\d+\)$|^$/.test(f.size)
|
||||||
action: Action.EDIT,
|
? "default"
|
||||||
element: ObjectType.TABLE,
|
: "error"
|
||||||
component: "field",
|
}
|
||||||
tid: i,
|
value={f.size}
|
||||||
fid: j,
|
onChange={(value) =>
|
||||||
undo: editField,
|
updateType(index, {
|
||||||
redo: { size: e.target.value },
|
fields: data.fields.map((e, id) =>
|
||||||
message: `Edit type field size to ${e.target.value}`,
|
id === j ? { ...f, size: value } : e
|
||||||
},
|
),
|
||||||
]);
|
})
|
||||||
setRedoStack([]);
|
}
|
||||||
}}
|
onFocus={(e) =>
|
||||||
/>
|
setEditField({ size: e.target.value })
|
||||||
</>
|
}
|
||||||
)}
|
onBlur={(e) => {
|
||||||
{hasPrecision(f.type) && (
|
if (e.target.value === editField.size) return;
|
||||||
<>
|
setUndoStack((prev) => [
|
||||||
<div className="font-semibold">Precision</div>
|
...prev,
|
||||||
<Input
|
{
|
||||||
className="my-2 w-full"
|
action: Action.EDIT,
|
||||||
placeholder="Set precision: (size, d)"
|
element: ObjectType.TABLE,
|
||||||
validateStatus={
|
component: "field",
|
||||||
/^\(\d+,\s*\d+\)$|^$/.test(f.size)
|
tid: index,
|
||||||
? "default"
|
fid: j,
|
||||||
: "error"
|
undo: editField,
|
||||||
}
|
redo: { size: e.target.value },
|
||||||
value={f.size}
|
message: `Edit type field precision to ${e.target.value}`,
|
||||||
onChange={(value) =>
|
},
|
||||||
updateType(i, {
|
]);
|
||||||
fields: t.fields.map((e, id) =>
|
setRedoStack([]);
|
||||||
id === j ? { ...f, size: value } : e
|
}}
|
||||||
),
|
/>
|
||||||
})
|
</>
|
||||||
}
|
)}
|
||||||
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 type field precision to ${e.target.value}`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
icon={<IconDeleteStroked />}
|
|
||||||
block
|
|
||||||
type="danger"
|
|
||||||
onClick={() => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.TYPE,
|
|
||||||
component: "field_delete",
|
|
||||||
tid: i,
|
|
||||||
fid: j,
|
|
||||||
data: f,
|
|
||||||
message: `Delete field`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
updateType(i, {
|
|
||||||
fields: t.fields.filter(
|
|
||||||
(field, k) => k !== j
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete field
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
showArrow
|
|
||||||
trigger="click"
|
|
||||||
position="right"
|
|
||||||
>
|
|
||||||
<Button icon={<IconMore />} type="tertiary"></Button>
|
|
||||||
</Popover>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
))}
|
|
||||||
<Card
|
|
||||||
bodyStyle={{ padding: "4px" }}
|
|
||||||
style={{ marginTop: "12px", marginBottom: "12px" }}
|
|
||||||
headerLine={false}
|
|
||||||
>
|
|
||||||
<Collapse>
|
|
||||||
<Collapse.Panel header="Comment" itemKey="1">
|
|
||||||
<TextArea
|
|
||||||
field="comment"
|
|
||||||
value={t.comment}
|
|
||||||
autosize
|
|
||||||
placeholder="Add comment"
|
|
||||||
rows={1}
|
|
||||||
onChange={(value) =>
|
|
||||||
updateType(i, { comment: value }, false)
|
|
||||||
}
|
|
||||||
onFocus={(e) =>
|
|
||||||
setEditField({ comment: e.target.value })
|
|
||||||
}
|
|
||||||
onBlur={(e) => {
|
|
||||||
if (e.target.value === editField.comment) return;
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.TYPE,
|
|
||||||
component: "self",
|
|
||||||
tid: i,
|
|
||||||
undo: editField,
|
|
||||||
redo: { comment: e.target.value },
|
|
||||||
message: `Edit type comment to ${e.target.value}`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Collapse.Panel>
|
|
||||||
</Collapse>
|
|
||||||
</Card>
|
|
||||||
<Row gutter={6} className="mt-2">
|
|
||||||
<Col span={12}>
|
|
||||||
<Button
|
<Button
|
||||||
icon={<IconPlus />}
|
icon={<IconDeleteStroked />}
|
||||||
|
block
|
||||||
|
type="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
action: Action.EDIT,
|
action: Action.EDIT,
|
||||||
element: ObjectType.TYPE,
|
element: ObjectType.TYPE,
|
||||||
component: "field_add",
|
component: "field_delete",
|
||||||
tid: i,
|
tid: index,
|
||||||
message: `Add field to type`,
|
fid: j,
|
||||||
|
data: f,
|
||||||
|
message: `Delete field`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
updateType(index, {
|
||||||
updateType(i, {
|
fields: data.fields.filter((field, k) => k !== j),
|
||||||
fields: [
|
|
||||||
...t.fields,
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
type: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
block
|
|
||||||
>
|
>
|
||||||
Add field
|
Delete field
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</div>
|
||||||
<Col span={12}>
|
}
|
||||||
<Button
|
showArrow
|
||||||
icon={<IconDeleteStroked />}
|
trigger="click"
|
||||||
type="danger"
|
position="right"
|
||||||
onClick={() => {
|
>
|
||||||
Toast.success(`Type deleted!`);
|
<Button icon={<IconMore />} type="tertiary" />
|
||||||
deleteType(i);
|
</Popover>
|
||||||
}}
|
</Col>
|
||||||
block
|
</Row>
|
||||||
>
|
))}
|
||||||
Delete
|
<Card
|
||||||
</Button>
|
bodyStyle={{ padding: "4px" }}
|
||||||
</Col>
|
style={{ marginTop: "12px", marginBottom: "12px" }}
|
||||||
</Row>
|
headerLine={false}
|
||||||
</Collapse.Panel>
|
>
|
||||||
</div>
|
<Collapse>
|
||||||
))
|
<Collapse.Panel header="Comment" itemKey="1">
|
||||||
)}
|
<TextArea
|
||||||
</Collapse>
|
field="comment"
|
||||||
</>
|
value={data.comment}
|
||||||
|
autosize
|
||||||
|
placeholder="Add comment"
|
||||||
|
rows={1}
|
||||||
|
onChange={(value) =>
|
||||||
|
updateType(index, { comment: value }, false)
|
||||||
|
}
|
||||||
|
onFocus={(e) => setEditField({ comment: e.target.value })}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (e.target.value === editField.comment) return;
|
||||||
|
setUndoStack((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.TYPE,
|
||||||
|
component: "self",
|
||||||
|
tid: index,
|
||||||
|
undo: editField,
|
||||||
|
redo: { comment: e.target.value },
|
||||||
|
message: `Edit type comment to ${e.target.value}`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setRedoStack([]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Collapse.Panel>
|
||||||
|
</Collapse>
|
||||||
|
</Card>
|
||||||
|
<Row gutter={6} className="mt-2">
|
||||||
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
icon={<IconPlus />}
|
||||||
|
onClick={() => {
|
||||||
|
setUndoStack((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.TYPE,
|
||||||
|
component: "field_add",
|
||||||
|
tid: index,
|
||||||
|
message: `Add field to type`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setRedoStack([]);
|
||||||
|
updateType(index, {
|
||||||
|
fields: [
|
||||||
|
...data.fields,
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
block
|
||||||
|
>
|
||||||
|
Add field
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
icon={<IconDeleteStroked />}
|
||||||
|
type="danger"
|
||||||
|
onClick={() => {
|
||||||
|
Toast.success(`Type deleted!`);
|
||||||
|
deleteType(index);
|
||||||
|
}}
|
||||||
|
block
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Collapse.Panel>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ export default function BugReport() {
|
|||||||
}
|
}
|
||||||
theme="borderless"
|
theme="borderless"
|
||||||
onClick={changeTheme}
|
onClick={changeTheme}
|
||||||
></Button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr
|
<hr
|
||||||
|
@ -123,7 +123,7 @@ export default function Shortcuts() {
|
|||||||
}
|
}
|
||||||
theme="borderless"
|
theme="borderless"
|
||||||
onClick={changeTheme}
|
onClick={changeTheme}
|
||||||
></Button>
|
/>
|
||||||
<div className="ms-2 lg:inline md:inline sm:hidden">
|
<div className="ms-2 lg:inline md:inline sm:hidden">
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
|
@ -286,7 +286,7 @@ export default function Survey() {
|
|||||||
}
|
}
|
||||||
theme="borderless"
|
theme="borderless"
|
||||||
onClick={changeTheme}
|
onClick={changeTheme}
|
||||||
></Button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr
|
<hr
|
||||||
|
Loading…
Reference in New Issue
Block a user