mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-10-19 09:16:09 +00:00
Reorganize files
This commit is contained in:
2453
src/components/EditorHeader/ControlPanel.jsx
Normal file
2453
src/components/EditorHeader/ControlPanel.jsx
Normal file
File diff suppressed because it is too large
Load Diff
174
src/components/EditorHeader/Thumbnail.jsx
Normal file
174
src/components/EditorHeader/Thumbnail.jsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import { calcPath } from "../../utils/calcPath";
|
||||
|
||||
export function Thumbnail({ diagram, i, zoom }) {
|
||||
const translateX = 32 * zoom;
|
||||
const translateY = 32 * zoom;
|
||||
const theme = localStorage.getItem("theme");
|
||||
return (
|
||||
<svg
|
||||
className={`${
|
||||
theme === "dark" ? "bg-[#222229]" : "bg-white"
|
||||
} w-full h-full rounded-md text-color`}
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
id={"pattern-circles-" + i}
|
||||
x="0"
|
||||
y="0"
|
||||
width="10"
|
||||
height="10"
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternContentUnits="userSpaceOnUse"
|
||||
>
|
||||
<circle
|
||||
id={"pattern-circle-" + i}
|
||||
cx="2"
|
||||
cy="2"
|
||||
r="0.4"
|
||||
fill="rgb(99, 152, 191)"
|
||||
></circle>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill={"url(#pattern-circles-" + i + ")"}
|
||||
></rect>
|
||||
<g
|
||||
style={{
|
||||
transform: `translate(${translateX}px, ${translateY}px) scale(${zoom})`,
|
||||
}}
|
||||
>
|
||||
{diagram.subjectAreas?.map((a) => (
|
||||
<foreignObject
|
||||
key={a.id}
|
||||
x={a.x}
|
||||
y={a.y}
|
||||
width={a.width > 0 ? a.width : 0}
|
||||
height={a.height > 0 ? a.height : 0}
|
||||
>
|
||||
<div className="border border-slate-400 w-full h-full rounded-sm relative">
|
||||
<div
|
||||
className="opacity-40 w-fill h-full"
|
||||
style={{ backgroundColor: a.color }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-color absolute top-1 left-2 select-none">
|
||||
{a.name}
|
||||
</div>
|
||||
</foreignObject>
|
||||
))}
|
||||
{diagram.tables?.map((table, i) => {
|
||||
const height = table.fields.length * 36 + 50 + 7;
|
||||
return (
|
||||
<foreignObject
|
||||
x={table.x}
|
||||
y={table.y}
|
||||
width={200}
|
||||
height={height}
|
||||
key={i}
|
||||
>
|
||||
<div
|
||||
className={`border rounded-md ${
|
||||
theme === "dark"
|
||||
? "bg-zinc-800"
|
||||
: "border-zinc-300 bg-zinc-100"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="h-2 w-full rounded-t-sm"
|
||||
style={{ backgroundColor: table.color }}
|
||||
/>
|
||||
<div className="rounded-b-[3px]">
|
||||
<div
|
||||
className={`font-bold py-1 px-2 border-b ${
|
||||
theme === "dark" ? "bg-zinc-900" : "bg-zinc-200"
|
||||
} border-gray-300`}
|
||||
>
|
||||
{table.name}
|
||||
</div>
|
||||
{table.fields.map((f, j) => (
|
||||
<div
|
||||
className={`flex justify-between items-center py-1 px-2 ${
|
||||
j < table.fields.length - 1 ? "border-b" : ""
|
||||
}`}
|
||||
key={j}
|
||||
>
|
||||
<div className="flex items-center justify-start">
|
||||
<div
|
||||
className={`w-[6px] h-[6px] bg-[#2f68ad] opacity-80 z-50 rounded-full me-2`}
|
||||
></div>
|
||||
<div>{f.name}</div>
|
||||
</div>
|
||||
<div className="text-zinc-500">{f.type}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
);
|
||||
})}
|
||||
{diagram.relationships?.map((e, i) => (
|
||||
<path
|
||||
key={i}
|
||||
d={calcPath(
|
||||
e.startX,
|
||||
e.endX,
|
||||
e.startY - translateY / zoom,
|
||||
e.endY - (translateY / zoom) * 0.5
|
||||
)}
|
||||
fill="none"
|
||||
strokeWidth={1}
|
||||
stroke="#ddd"
|
||||
/>
|
||||
))}
|
||||
{diagram.notes?.map((n) => {
|
||||
const x = n.x;
|
||||
const y = n.y;
|
||||
const w = 180;
|
||||
const r = 3;
|
||||
const fold = 24;
|
||||
const h = n.height;
|
||||
return (
|
||||
<g key={n.id}>
|
||||
<path
|
||||
d={`M${x + fold} ${y} L${x + w - r} ${y} A${r} ${r} 0 0 1 ${
|
||||
x + w
|
||||
} ${y + r} L${x + w} ${y + h - r} A${r} ${r} 0 0 1 ${
|
||||
x + w - r
|
||||
} ${y + h} L${x + r} ${y + h} A${r} ${r} 0 0 1 ${x} ${
|
||||
y + h - r
|
||||
} L${x} ${y + fold}`}
|
||||
fill={n.color}
|
||||
stroke="rgb(168 162 158)"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
<path
|
||||
d={`M${x} ${y + fold} L${x + fold - r} ${
|
||||
y + fold
|
||||
} A${r} ${r} 0 0 0 ${x + fold} ${y + fold - r} L${
|
||||
x + fold
|
||||
} ${y} L${x} ${y + fold} Z`}
|
||||
fill={n.color}
|
||||
stroke={"rgb(168 162 158)"}
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
<foreignObject x={x} y={y} width={w} height={h}>
|
||||
<div className="text-gray-900 w-full h-full px-4 py-2">
|
||||
<label htmlFor={`note_${n.id}`} className="ms-4">
|
||||
{n.title}
|
||||
</label>
|
||||
<div className="mt-[2px]">{n.content}</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
276
src/components/EditorHeader/Todo.jsx
Normal file
276
src/components/EditorHeader/Todo.jsx
Normal file
@@ -0,0 +1,276 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Checkbox,
|
||||
Input,
|
||||
TextArea,
|
||||
Row,
|
||||
Col,
|
||||
Dropdown,
|
||||
Button,
|
||||
Popover,
|
||||
Tag,
|
||||
List,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import {
|
||||
IconPlus,
|
||||
IconMore,
|
||||
IconDeleteStroked,
|
||||
IconCaretdown,
|
||||
} from "@douyinfe/semi-icons";
|
||||
import { State } from "../../data/constants";
|
||||
import useTasks from "../../hooks/useTasks";
|
||||
import useSaveState from "../../hooks/useSaveState";
|
||||
|
||||
const Priority = {
|
||||
NONE: 0,
|
||||
LOW: 1,
|
||||
MEDIUM: 2,
|
||||
HIGH: 3,
|
||||
};
|
||||
|
||||
const SortOrder = {
|
||||
ORIGINAL: "My order",
|
||||
PRIORITY: "Priority",
|
||||
COMPLETED: "Completed",
|
||||
ALPHABETICALLY: "Alphabetically",
|
||||
};
|
||||
|
||||
export default function Todo() {
|
||||
const [activeTask, setActiveTask] = useState(-1);
|
||||
const [, setSortOrder] = useState(SortOrder.ORIGINAL);
|
||||
const { tasks, setTasks, updateTask } = useTasks();
|
||||
const { setSaveState } = useSaveState();
|
||||
|
||||
const priorityLabel = (p) => {
|
||||
switch (p) {
|
||||
case Priority.NONE:
|
||||
return "None";
|
||||
case Priority.LOW:
|
||||
return "Low";
|
||||
case Priority.MEDIUM:
|
||||
return "Medium";
|
||||
case Priority.HIGH:
|
||||
return "High";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const priorityColor = (p) => {
|
||||
switch (p) {
|
||||
case Priority.NONE:
|
||||
return "blue";
|
||||
case Priority.LOW:
|
||||
return "green";
|
||||
case Priority.MEDIUM:
|
||||
return "yellow";
|
||||
case Priority.HIGH:
|
||||
return "red";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const sort = (s) => {
|
||||
setActiveTask(-1);
|
||||
switch (s) {
|
||||
case SortOrder.ORIGINAL:
|
||||
setTasks((prev) => prev.sort((a, b) => a.order - b.order));
|
||||
return;
|
||||
case SortOrder.PRIORITY:
|
||||
setTasks((prev) => prev.sort((a, b) => b.priority - a.priority));
|
||||
return;
|
||||
case SortOrder.COMPLETED:
|
||||
setTasks((prev) =>
|
||||
prev.sort((a, b) => {
|
||||
if (a.complete && !b.complete) {
|
||||
return 1;
|
||||
} else if (!a.complete && b.complete) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
);
|
||||
break;
|
||||
case SortOrder.ALPHABETICALLY:
|
||||
setTasks((prev) => prev.sort((a, b) => a.title.localeCompare(b.title)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center mx-5 mb-2 sidesheet-theme">
|
||||
<Dropdown
|
||||
render={
|
||||
<Dropdown.Menu>
|
||||
{Object.values(SortOrder).map((order) => (
|
||||
<Dropdown.Item
|
||||
key={order}
|
||||
onClick={() => {
|
||||
setSortOrder(order);
|
||||
sort(order);
|
||||
}}
|
||||
>
|
||||
{order}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button
|
||||
style={{ marginRight: "10px" }}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
>
|
||||
Sort by <IconCaretdown />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
icon={<IconPlus />}
|
||||
block
|
||||
onClick={() => {
|
||||
setTasks((prev) => [
|
||||
{
|
||||
complete: false,
|
||||
details: "",
|
||||
title: "",
|
||||
priority: Priority.NONE,
|
||||
order: prev.length,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Add task
|
||||
</Button>
|
||||
</div>
|
||||
{tasks.length > 0 ? (
|
||||
<List className="sidesheet-theme">
|
||||
{tasks.map((t, i) => (
|
||||
<List.Item
|
||||
key={i}
|
||||
style={{ paddingLeft: "18px", paddingRight: "18px" }}
|
||||
className="hover-1"
|
||||
onClick={() => setActiveTask(i)}
|
||||
>
|
||||
<div className="w-full">
|
||||
<Row gutter={6} align="middle" type="flex" className="mb-2">
|
||||
<Col span={2}>
|
||||
<Checkbox
|
||||
checked={t.complete}
|
||||
onChange={(e) => {
|
||||
updateTask(i, { complete: e.target.checked });
|
||||
setSaveState(State.SAVING);
|
||||
}}
|
||||
></Checkbox>
|
||||
</Col>
|
||||
<Col span={19}>
|
||||
<Input
|
||||
placeholder="Title"
|
||||
onChange={(v) => updateTask(i, { title: v })}
|
||||
value={t.title}
|
||||
onBlur={() => setSaveState(State.SAVING)}
|
||||
></Input>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Popover
|
||||
content={
|
||||
<div className="p-2 popover-theme">
|
||||
<div className="mb-2 font-semibold">
|
||||
Set priority:
|
||||
</div>
|
||||
<RadioGroup
|
||||
onChange={(e) => {
|
||||
updateTask(i, { priority: e.target.value });
|
||||
setSaveState(State.SAVING);
|
||||
}}
|
||||
value={t.priority}
|
||||
direction="vertical"
|
||||
>
|
||||
<Radio value={Priority.NONE}>
|
||||
<Tag color={priorityColor(Priority.NONE)}>
|
||||
{priorityLabel(Priority.NONE)}
|
||||
</Tag>
|
||||
</Radio>
|
||||
<Radio value={Priority.LOW}>
|
||||
<Tag color={priorityColor(Priority.LOW)}>
|
||||
{priorityLabel(Priority.LOW)}
|
||||
</Tag>
|
||||
</Radio>
|
||||
<Radio value={Priority.MEDIUM}>
|
||||
<Tag color={priorityColor(Priority.MEDIUM)}>
|
||||
{priorityLabel(Priority.MEDIUM)}
|
||||
</Tag>
|
||||
</Radio>
|
||||
<Radio value={Priority.HIGH}>
|
||||
<Tag color={priorityColor(Priority.HIGH)}>
|
||||
{priorityLabel(Priority.HIGH)}
|
||||
</Tag>
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
block
|
||||
style={{ marginTop: "12px" }}
|
||||
onClick={() => {
|
||||
setTasks((prev) =>
|
||||
prev.filter((task, j) => i !== j)
|
||||
);
|
||||
setSaveState(State.SAVING);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
showArrow
|
||||
className="w-[180px]"
|
||||
>
|
||||
<Button icon={<IconMore />} type="tertiary" />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
{activeTask === i && (
|
||||
<Row className="mb-2">
|
||||
<Col span={2}></Col>
|
||||
<Col span={22}>
|
||||
<TextArea
|
||||
placeholder="Details"
|
||||
onChange={(v) => updateTask(i, { details: v })}
|
||||
value={t.details}
|
||||
onBlur={() => setSaveState(State.SAVING)}
|
||||
></TextArea>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row>
|
||||
<Col span={2}></Col>
|
||||
<Col span={22}>
|
||||
Priority:{" "}
|
||||
<Tag color={priorityColor(t.priority)}>
|
||||
{priorityLabel(t.priority)}
|
||||
</Tag>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<div className="m-5 sidesheet-theme">
|
||||
You have no tasks yet. Add your to-dos and keep track of your
|
||||
progress.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user