Configure i18n and add simplified chinese (#99)
This commit is contained in:
parent
9de091c990
commit
2b4b01c358
76
package-lock.json
generated
76
package-lock.json
generated
@ -22,6 +22,8 @@
|
|||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^10.18.0",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"i18next": "^23.11.4",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
"lexical": "^0.12.5",
|
"lexical": "^0.12.5",
|
||||||
@ -29,6 +31,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys-hook": "^4.4.1",
|
"react-hotkeys-hook": "^4.4.1",
|
||||||
|
"react-i18next": "^14.1.1",
|
||||||
"react-router-dom": "^6.21.0",
|
"react-router-dom": "^6.21.0",
|
||||||
"url": "^0.11.1"
|
"url": "^0.11.1"
|
||||||
},
|
},
|
||||||
@ -361,9 +364,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.23.6",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
|
||||||
"integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
|
"integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@ -3611,6 +3614,14 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-parse-stringify": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||||
|
"dependencies": {
|
||||||
|
"void-elements": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-to-image": {
|
"node_modules/html-to-image": {
|
||||||
"version": "1.11.11",
|
"version": "1.11.11",
|
||||||
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz",
|
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz",
|
||||||
@ -3629,6 +3640,36 @@
|
|||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "23.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.4.tgz",
|
||||||
|
"integrity": "sha512-CCUjtd5TfaCl+mLUzAA0uPSN+AVn4fP/kWCYt/hocPUwusTpMVczdrRyOBUwk6N05iH40qiKx6q1DoNJtBIwdg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
||||||
@ -4978,6 +5019,27 @@
|
|||||||
"react-dom": ">=16.8.1"
|
"react-dom": ">=16.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "14.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.1.tgz",
|
||||||
|
"integrity": "sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.9",
|
||||||
|
"html-parse-stringify": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 23.2.3",
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
@ -5898,6 +5960,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/void-elements": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^10.18.0",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"i18next": "^23.11.4",
|
||||||
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
"lexical": "^0.12.5",
|
"lexical": "^0.12.5",
|
||||||
@ -31,6 +33,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys-hook": "^4.4.1",
|
"react-hotkeys-hook": "^4.4.1",
|
||||||
|
"react-i18next": "^14.1.1",
|
||||||
"react-router-dom": "^6.21.0",
|
"react-router-dom": "^6.21.0",
|
||||||
"url": "^0.11.1"
|
"url": "^0.11.1"
|
||||||
},
|
},
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { Button } from "@douyinfe/semi-ui";
|
import { Button } from "@douyinfe/semi-ui";
|
||||||
import { IconCheckboxTick } from "@douyinfe/semi-icons";
|
import { IconCheckboxTick } from "@douyinfe/semi-icons";
|
||||||
import { tableThemes } from "../data/constants";
|
import { tableThemes } from "../data/constants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function ColorPalette({
|
export default function ColorPalette({
|
||||||
currentColor,
|
currentColor,
|
||||||
onClearColor,
|
onClearColor,
|
||||||
onPickColor,
|
onPickColor,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center p-2">
|
<div className="flex justify-between items-center p-2">
|
||||||
<div className="font-medium">Theme</div>
|
<div className="font-medium">{t("theme")}</div>
|
||||||
<Button type="tertiary" size="small" onClick={onClearColor}>
|
<Button type="tertiary" size="small" onClick={onClearColor}>
|
||||||
Clear
|
{t("clear")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button, Popover, Input, Toast } from "@douyinfe/semi-ui";
|
import { Button, Popover, Input } from "@douyinfe/semi-ui";
|
||||||
import { IconEdit, IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconEdit, IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import {
|
import {
|
||||||
Tab,
|
Tab,
|
||||||
@ -17,7 +17,8 @@ import {
|
|||||||
useSaveState,
|
useSaveState,
|
||||||
useTransform,
|
useTransform,
|
||||||
} from "../../hooks";
|
} from "../../hooks";
|
||||||
import ColorPalette from "../ColorPalette";
|
import ColorPalette from "../ColorPicker";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
@ -191,14 +192,15 @@ function EditPopoverContent({ data }) {
|
|||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
const { updateArea, deleteArea } = useAreas();
|
const { updateArea, deleteArea } = useAreas();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
<div className="font-semibold mb-2 ms-1">Edit subject area</div>
|
<div className="font-semibold mb-2 ms-1">{t("edit")}</div>
|
||||||
<div className="w-[280px] flex items-center mb-2">
|
<div className="w-[280px] flex items-center mb-2">
|
||||||
<Input
|
<Input
|
||||||
value={data.name}
|
value={data.name}
|
||||||
placeholder="Name"
|
placeholder={t("name")}
|
||||||
className="me-2"
|
className="me-2"
|
||||||
onChange={(value) => updateArea(data.id, { name: value })}
|
onChange={(value) => updateArea(data.id, { name: value })}
|
||||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||||
@ -212,7 +214,10 @@ function EditPopoverContent({ data }) {
|
|||||||
aid: data.id,
|
aid: data.id,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { name: e.target.value },
|
redo: { name: e.target.value },
|
||||||
message: `Edit area name to ${e.target.value}`,
|
message: t("edit_area", {
|
||||||
|
areaName: e.target.value,
|
||||||
|
extra: "[name]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -232,7 +237,10 @@ function EditPopoverContent({ data }) {
|
|||||||
aid: data.id,
|
aid: data.id,
|
||||||
undo: { color: data.color },
|
undo: { color: data.color },
|
||||||
redo: { color: c },
|
redo: { color: c },
|
||||||
message: `Edit area color to ${c}`,
|
message: t("edit_area", {
|
||||||
|
areaName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -263,12 +271,9 @@ function EditPopoverContent({ data }) {
|
|||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
block
|
block
|
||||||
onClick={() => {
|
onClick={() => deleteArea(data.id, true)}
|
||||||
Toast.success(`Area deleted!`);
|
|
||||||
deleteArea(data.id, true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,8 +20,11 @@ import {
|
|||||||
useNotes,
|
useNotes,
|
||||||
useLayout,
|
useLayout,
|
||||||
} from "../../hooks";
|
} from "../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { diagram } from "../../data/heroDiagram";
|
||||||
|
|
||||||
export default function Canvas() {
|
export default function Canvas() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { tables, updateTable, relationships, addRelationship } = useTables();
|
const { tables, updateTable, relationships, addRelationship } = useTables();
|
||||||
const { areas, updateArea } = useAreas();
|
const { areas, updateArea } = useAreas();
|
||||||
const { notes, updateNote } = useNotes();
|
const { notes, updateNote } = useNotes();
|
||||||
@ -278,7 +281,10 @@ export default function Canvas() {
|
|||||||
toX: info.x,
|
toX: info.x,
|
||||||
toY: info.y,
|
toY: info.y,
|
||||||
id: dragging.id,
|
id: dragging.id,
|
||||||
message: `Move ${info.name} to (${info.x}, ${info.y})`,
|
message: t("move_element", {
|
||||||
|
coords: `(${info.x}, ${info.y})`,
|
||||||
|
name: info.name,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -291,7 +297,10 @@ export default function Canvas() {
|
|||||||
action: Action.PAN,
|
action: Action.PAN,
|
||||||
undo: { x: panning.x, y: panning.y },
|
undo: { x: panning.x, y: panning.y },
|
||||||
redo: transform.pan,
|
redo: transform.pan,
|
||||||
message: `Move diagram to (${transform.pan?.x}, ${transform.pan?.y})`,
|
message: t("move_element", {
|
||||||
|
coords: `(${transform?.pan.x}, ${transform?.pan.y})`,
|
||||||
|
name: diagram,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -321,7 +330,10 @@ export default function Canvas() {
|
|||||||
height: initCoords.height,
|
height: initCoords.height,
|
||||||
},
|
},
|
||||||
redo: areas[areaResize.id],
|
redo: areas[areaResize.id],
|
||||||
message: `Resize area`,
|
message: t("edit_area", {
|
||||||
|
areaName: areas[areaResize.id].name,
|
||||||
|
extra: "[resize]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -350,7 +362,7 @@ export default function Canvas() {
|
|||||||
tables[linkingLine.startTableId].fields[linkingLine.startFieldId].type !==
|
tables[linkingLine.startTableId].fields[linkingLine.startFieldId].type !==
|
||||||
tables[hoveredTable.tableId].fields[hoveredTable.field].type
|
tables[hoveredTable.tableId].fields[hoveredTable.field].type
|
||||||
) {
|
) {
|
||||||
Toast.info("Cannot connect");
|
Toast.info(t("connot_connect"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
State,
|
State,
|
||||||
noteThemes,
|
noteThemes,
|
||||||
} from "../../data/constants";
|
} from "../../data/constants";
|
||||||
import { Input, Button, Popover, Toast } from "@douyinfe/semi-ui";
|
import { Input, Button, Popover } from "@douyinfe/semi-ui";
|
||||||
import {
|
import {
|
||||||
IconEdit,
|
IconEdit,
|
||||||
IconDeleteStroked,
|
IconDeleteStroked,
|
||||||
@ -19,6 +19,7 @@ import {
|
|||||||
useNotes,
|
useNotes,
|
||||||
useSaveState,
|
useSaveState,
|
||||||
} from "../../hooks";
|
} from "../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Note({ data, onMouseDown }) {
|
export default function Note({ data, onMouseDown }) {
|
||||||
const w = 180;
|
const w = 180;
|
||||||
@ -27,6 +28,7 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
const { layout } = useLayout();
|
const { layout } = useLayout();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
const { updateNote, deleteNote } = useNotes();
|
const { updateNote, deleteNote } = useNotes();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
@ -54,7 +56,10 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
nid: data.id,
|
nid: data.id,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { content: e.target.value, height: newHeight },
|
redo: { content: e.target.value, height: newHeight },
|
||||||
message: `Edit note content to "${e.target.value}"`,
|
message: t("edit_note", {
|
||||||
|
noteTitle: e.target.value,
|
||||||
|
extra: "[content]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -168,11 +173,11 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
stopPropagation
|
stopPropagation
|
||||||
content={
|
content={
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
<div className="font-semibold mb-2 ms-1">Edit note</div>
|
<div className="font-semibold mb-2 ms-1">{t("edit")}</div>
|
||||||
<div className="w-[280px] flex items-center mb-2">
|
<div className="w-[280px] flex items-center mb-2">
|
||||||
<Input
|
<Input
|
||||||
value={data.title}
|
value={data.title}
|
||||||
placeholder="Title"
|
placeholder={t("title")}
|
||||||
className="me-2"
|
className="me-2"
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateNote(data.id, { title: value })
|
updateNote(data.id, { title: value })
|
||||||
@ -190,7 +195,10 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
nid: data.id,
|
nid: data.id,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { title: e.target.value },
|
redo: { title: e.target.value },
|
||||||
message: `Edit note title to "${e.target.value}"`,
|
message: t("edit_note", {
|
||||||
|
noteTitle: e.target.value,
|
||||||
|
extra: "[title]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -199,7 +207,9 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
<div className="font-medium mb-1">Theme</div>
|
<div className="font-medium mb-1">
|
||||||
|
{t("theme")}
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
{noteThemes.map((c) => (
|
{noteThemes.map((c) => (
|
||||||
@ -216,7 +226,10 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
nid: data.id,
|
nid: data.id,
|
||||||
undo: { color: data.color },
|
undo: { color: data.color },
|
||||||
redo: { color: c },
|
redo: { color: c },
|
||||||
message: `Edit note color to ${c}`,
|
message: t("edit_note", {
|
||||||
|
noteTitle: data.title,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -249,12 +262,9 @@ export default function Note({ data, onMouseDown }) {
|
|||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
block
|
block
|
||||||
onClick={() => {
|
onClick={() => deleteNote(data.id, true)}
|
||||||
Toast.success(`Note deleted!`);
|
|
||||||
deleteNote(data.id, true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,9 +13,10 @@ import {
|
|||||||
IconDeleteStroked,
|
IconDeleteStroked,
|
||||||
IconKeyStroked,
|
IconKeyStroked,
|
||||||
} from "@douyinfe/semi-icons";
|
} from "@douyinfe/semi-icons";
|
||||||
import { Popover, Tag, Button, Toast, SideSheet } from "@douyinfe/semi-ui";
|
import { Popover, Tag, Button, SideSheet } from "@douyinfe/semi-ui";
|
||||||
import { useLayout, useSettings, useTables, useSelect } from "../../hooks";
|
import { useLayout, useSettings, useTables, useSelect } from "../../hooks";
|
||||||
import TableInfo from "../EditorSidePanel/TablesTab/TableInfo";
|
import TableInfo from "../EditorSidePanel/TablesTab/TableInfo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Table(props) {
|
export default function Table(props) {
|
||||||
const [hoveredField, setHoveredField] = useState(-1);
|
const [hoveredField, setHoveredField] = useState(-1);
|
||||||
@ -29,6 +30,7 @@ export default function Table(props) {
|
|||||||
const { layout } = useLayout();
|
const { layout } = useLayout();
|
||||||
const { deleteTable, deleteField } = useTables();
|
const { deleteTable, deleteField } = useTables();
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
|
||||||
const height =
|
const height =
|
||||||
@ -110,9 +112,9 @@ export default function Table(props) {
|
|||||||
content={
|
content={
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<strong>Comment :</strong>{" "}
|
<strong>{t("comment")}:</strong>{" "}
|
||||||
{tableData.comment === "" ? (
|
{tableData.comment === "" ? (
|
||||||
"No comment"
|
t("not_set")
|
||||||
) : (
|
) : (
|
||||||
<div>{tableData.comment}</div>
|
<div>{tableData.comment}</div>
|
||||||
)}
|
)}
|
||||||
@ -123,10 +125,10 @@ export default function Table(props) {
|
|||||||
tableData.indices.length === 0 ? "" : "block"
|
tableData.indices.length === 0 ? "" : "block"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Indices :
|
{t("indices")}:
|
||||||
</strong>{" "}
|
</strong>{" "}
|
||||||
{tableData.indices.length === 0 ? (
|
{tableData.indices.length === 0 ? (
|
||||||
"No indices"
|
t("not_set")
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
{tableData.indices.map((index, k) => (
|
{tableData.indices.map((index, k) => (
|
||||||
@ -156,12 +158,9 @@ export default function Table(props) {
|
|||||||
type="danger"
|
type="danger"
|
||||||
block
|
block
|
||||||
style={{ marginTop: "8px" }}
|
style={{ marginTop: "8px" }}
|
||||||
onClick={() => {
|
onClick={() => deleteTable(tableData.id)}
|
||||||
Toast.success(`Table deleted!`);
|
|
||||||
deleteTable(tableData.id);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Delete table
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -196,37 +195,31 @@ export default function Table(props) {
|
|||||||
<hr />
|
<hr />
|
||||||
{e.primary && (
|
{e.primary && (
|
||||||
<Tag color="blue" className="me-2 my-2">
|
<Tag color="blue" className="me-2 my-2">
|
||||||
Primary
|
{t("primary")}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{e.unique && (
|
{e.unique && (
|
||||||
<Tag color="amber" className="me-2 my-2">
|
<Tag color="amber" className="me-2 my-2">
|
||||||
Unique
|
{t("unique")}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{e.notNull && (
|
{e.notNull && (
|
||||||
<Tag color="purple" className="me-2 my-2">
|
<Tag color="purple" className="me-2 my-2">
|
||||||
Not null
|
{t("not_null")}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{e.increment && (
|
{e.increment && (
|
||||||
<Tag color="green" className="me-2 my-2">
|
<Tag color="green" className="me-2 my-2">
|
||||||
Increment
|
{t("autoincrement")}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
<p>
|
<p>
|
||||||
<strong>Default: </strong>
|
<strong>{t("default_value")}: </strong>
|
||||||
{e.default === "" ? "Not set" : e.default}
|
{e.default === "" ? t("not_set") : e.default}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Comment: </strong>
|
<strong>{t("comment")}: </strong>
|
||||||
{e.comment === "" ? (
|
{e.comment === "" ? t("not_set") : e.comment}
|
||||||
"No comment"
|
|
||||||
) : (
|
|
||||||
<div className="max-w-[260px] break-words">
|
|
||||||
{e.comment}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -242,7 +235,7 @@ export default function Table(props) {
|
|||||||
</div>
|
</div>
|
||||||
</foreignObject>
|
</foreignObject>
|
||||||
<SideSheet
|
<SideSheet
|
||||||
title="Edit table"
|
title={t("edit")}
|
||||||
size="small"
|
size="small"
|
||||||
visible={
|
visible={
|
||||||
selectedElement.element === ObjectType.TABLE &&
|
selectedElement.element === ObjectType.TABLE &&
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
Toast,
|
Toast,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Tag,
|
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import { toPng, toJpeg, toSvg } from "html-to-image";
|
import { toPng, toJpeg, toSvg } from "html-to-image";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
@ -62,6 +61,7 @@ import { IconAddArea, IconAddNote, IconAddTable } from "../../icons";
|
|||||||
import LayoutDropdown from "./LayoutDropdown";
|
import LayoutDropdown from "./LayoutDropdown";
|
||||||
import Sidesheet from "./SideSheet/Sidesheet";
|
import Sidesheet from "./SideSheet/Sidesheet";
|
||||||
import Modal from "./Modal/Modal";
|
import Modal from "./Modal/Modal";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function ControlPanel({
|
export default function ControlPanel({
|
||||||
diagramId,
|
diagramId,
|
||||||
@ -100,6 +100,7 @@ export default function ControlPanel({
|
|||||||
const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo();
|
const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
const { transform, setTransform } = useTransform();
|
const { transform, setTransform } = useTransform();
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const invertLayout = (component) =>
|
const invertLayout = (component) =>
|
||||||
@ -460,16 +461,12 @@ export default function ControlPanel({
|
|||||||
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }));
|
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }));
|
||||||
const viewStrictMode = () => {
|
const viewStrictMode = () => {
|
||||||
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
|
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
|
||||||
Toast.success(`Stict mode is ${settings.strictMode ? "on" : "off"}.`);
|
|
||||||
};
|
};
|
||||||
const viewFieldSummary = () => {
|
const viewFieldSummary = () => {
|
||||||
setSettings((prev) => ({
|
setSettings((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
showFieldSummary: !prev.showFieldSummary,
|
showFieldSummary: !prev.showFieldSummary,
|
||||||
}));
|
}));
|
||||||
Toast.success(
|
|
||||||
`Field summary is ${settings.showFieldSummary ? "off" : "on"}.`,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
const copyAsImage = () => {
|
const copyAsImage = () => {
|
||||||
toPng(document.getElementById("canvas")).then(function (dataUrl) {
|
toPng(document.getElementById("canvas")).then(function (dataUrl) {
|
||||||
@ -477,10 +474,10 @@ export default function ControlPanel({
|
|||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.write([new ClipboardItem({ "image/png": blob })])
|
.write([new ClipboardItem({ "image/png": blob })])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Toast.success("Copied to clipboard.");
|
Toast.success(t("copied_to_clipboard"));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
Toast.error("Could not copy to clipboard.");
|
Toast.error(t("oops_smth_went_wrong"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -607,23 +604,17 @@ export default function ControlPanel({
|
|||||||
case ObjectType.TABLE:
|
case ObjectType.TABLE:
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(JSON.stringify({ ...tables[selectedElement.id] }))
|
.writeText(JSON.stringify({ ...tables[selectedElement.id] }))
|
||||||
.catch(() => {
|
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||||
Toast.error("Could not copy");
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case ObjectType.NOTE:
|
case ObjectType.NOTE:
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(JSON.stringify({ ...notes[selectedElement.id] }))
|
.writeText(JSON.stringify({ ...notes[selectedElement.id] }))
|
||||||
.catch(() => {
|
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||||
Toast.error("Could not copy");
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case ObjectType.AREA:
|
case ObjectType.AREA:
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(JSON.stringify({ ...areas[selectedElement.id] }))
|
.writeText(JSON.stringify({ ...areas[selectedElement.id] }))
|
||||||
.catch(() => {
|
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||||
Toast.error("Could not copy");
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -671,29 +662,29 @@ export default function ControlPanel({
|
|||||||
const saveDiagramAs = () => setModal(MODAL.SAVEAS);
|
const saveDiagramAs = () => setModal(MODAL.SAVEAS);
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
File: {
|
file: {
|
||||||
New: {
|
new: {
|
||||||
function: () => setModal(MODAL.NEW),
|
function: () => setModal(MODAL.NEW),
|
||||||
},
|
},
|
||||||
"New window": {
|
new_window: {
|
||||||
function: () => {
|
function: () => {
|
||||||
const newWindow = window.open("/editor", "_blank");
|
const newWindow = window.open("/editor", "_blank");
|
||||||
newWindow.name = window.name;
|
newWindow.name = window.name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Open: {
|
open: {
|
||||||
function: open,
|
function: open,
|
||||||
shortcut: "Ctrl+O",
|
shortcut: "Ctrl+O",
|
||||||
},
|
},
|
||||||
Save: {
|
save: {
|
||||||
function: save,
|
function: save,
|
||||||
shortcut: "Ctrl+S",
|
shortcut: "Ctrl+S",
|
||||||
},
|
},
|
||||||
"Save as": {
|
save_as: {
|
||||||
function: saveDiagramAs,
|
function: saveDiagramAs,
|
||||||
shortcut: "Ctrl+Shift+S",
|
shortcut: "Ctrl+Shift+S",
|
||||||
},
|
},
|
||||||
"Save as template": {
|
save_as_template: {
|
||||||
function: () => {
|
function: () => {
|
||||||
db.templates
|
db.templates
|
||||||
.add({
|
.add({
|
||||||
@ -706,21 +697,20 @@ export default function ControlPanel({
|
|||||||
custom: 1,
|
custom: 1,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Toast.success("Template saved!");
|
Toast.success(t("template_saved"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Rename: {
|
rename: {
|
||||||
function: () => {
|
function: () => {
|
||||||
setModal(MODAL.RENAME);
|
setModal(MODAL.RENAME);
|
||||||
setPrevTitle(title);
|
setPrevTitle(title);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Delete diagram": {
|
delete_diagram: {
|
||||||
warning: {
|
warning: {
|
||||||
title: "Delete diagram",
|
title: t("delete_diagram"),
|
||||||
message:
|
message: t("are_you_sure_delete_diagram"),
|
||||||
"Are you sure you want to delete this diagram? This operation is irreversible.",
|
|
||||||
},
|
},
|
||||||
function: async () => {
|
function: async () => {
|
||||||
await db.diagrams
|
await db.diagrams
|
||||||
@ -736,17 +726,17 @@ export default function ControlPanel({
|
|||||||
setUndoStack([]);
|
setUndoStack([]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
})
|
})
|
||||||
.catch(() => Toast.error("Oops! Something went wrong."));
|
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Import diagram": {
|
import_diagram: {
|
||||||
function: fileImport,
|
function: fileImport,
|
||||||
shortcut: "Ctrl+I",
|
shortcut: "Ctrl+I",
|
||||||
},
|
},
|
||||||
"Import from source": {
|
import_from_source: {
|
||||||
function: () => setModal(MODAL.IMPORT_SRC),
|
function: () => setModal(MODAL.IMPORT_SRC),
|
||||||
},
|
},
|
||||||
"Export as": {
|
export_as: {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
PNG: () => {
|
PNG: () => {
|
||||||
@ -856,7 +846,7 @@ export default function ControlPanel({
|
|||||||
],
|
],
|
||||||
function: () => {},
|
function: () => {},
|
||||||
},
|
},
|
||||||
"Export source": {
|
export_source: {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
MySQL: () => {
|
MySQL: () => {
|
||||||
@ -936,23 +926,27 @@ export default function ControlPanel({
|
|||||||
],
|
],
|
||||||
function: () => {},
|
function: () => {},
|
||||||
},
|
},
|
||||||
Exit: {
|
exit: {
|
||||||
function: () => {
|
function: () => {
|
||||||
save();
|
save();
|
||||||
if (saveState === State.SAVED) navigate("/");
|
if (saveState === State.SAVED) navigate("/");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Edit: {
|
edit: {
|
||||||
Undo: {
|
undo: {
|
||||||
function: undo,
|
function: undo,
|
||||||
shortcut: "Ctrl+Z",
|
shortcut: "Ctrl+Z",
|
||||||
},
|
},
|
||||||
Redo: {
|
redo: {
|
||||||
function: redo,
|
function: redo,
|
||||||
shortcut: "Ctrl+Y",
|
shortcut: "Ctrl+Y",
|
||||||
},
|
},
|
||||||
Clear: {
|
clear: {
|
||||||
|
warning: {
|
||||||
|
title: t("clear"),
|
||||||
|
message: t("are_you_sure_clear"),
|
||||||
|
},
|
||||||
function: () => {
|
function: () => {
|
||||||
setTables([]);
|
setTables([]);
|
||||||
setRelationships([]);
|
setRelationships([]);
|
||||||
@ -962,57 +956,73 @@ export default function ControlPanel({
|
|||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Edit: {
|
edit: {
|
||||||
function: edit,
|
function: edit,
|
||||||
shortcut: "Ctrl+E",
|
shortcut: "Ctrl+E",
|
||||||
},
|
},
|
||||||
Cut: {
|
cut: {
|
||||||
function: cut,
|
function: cut,
|
||||||
shortcut: "Ctrl+X",
|
shortcut: "Ctrl+X",
|
||||||
},
|
},
|
||||||
Copy: {
|
copy: {
|
||||||
function: copy,
|
function: copy,
|
||||||
shortcut: "Ctrl+C",
|
shortcut: "Ctrl+C",
|
||||||
},
|
},
|
||||||
Paste: {
|
paste: {
|
||||||
function: paste,
|
function: paste,
|
||||||
shortcut: "Ctrl+V",
|
shortcut: "Ctrl+V",
|
||||||
},
|
},
|
||||||
Duplicate: {
|
duplicate: {
|
||||||
function: duplicate,
|
function: duplicate,
|
||||||
shortcut: "Ctrl+D",
|
shortcut: "Ctrl+D",
|
||||||
},
|
},
|
||||||
Delete: {
|
delete: {
|
||||||
function: del,
|
function: del,
|
||||||
shortcut: "Del",
|
shortcut: "Del",
|
||||||
},
|
},
|
||||||
"Copy as image": {
|
copy_as_image: {
|
||||||
function: copyAsImage,
|
function: copyAsImage,
|
||||||
shortcut: "Ctrl+Alt+C",
|
shortcut: "Ctrl+Alt+C",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
View: {
|
view: {
|
||||||
Header: {
|
header: {
|
||||||
state: layout.header ? "on" : "off",
|
state: layout.header ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: () =>
|
function: () =>
|
||||||
setLayout((prev) => ({ ...prev, header: !prev.header })),
|
setLayout((prev) => ({ ...prev, header: !prev.header })),
|
||||||
},
|
},
|
||||||
Sidebar: {
|
sidebar: {
|
||||||
state: layout.sidebar ? "on" : "off",
|
state: layout.sidebar ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: () =>
|
function: () =>
|
||||||
setLayout((prev) => ({ ...prev, sidebar: !prev.sidebar })),
|
setLayout((prev) => ({ ...prev, sidebar: !prev.sidebar })),
|
||||||
},
|
},
|
||||||
Issues: {
|
issues: {
|
||||||
state: layout.issues ? "on" : "off",
|
state: layout.issues ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: () =>
|
function: () =>
|
||||||
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
|
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
|
||||||
},
|
},
|
||||||
"Strict mode": {
|
strict_mode: {
|
||||||
state: settings.strictMode ? "off" : "on",
|
state: settings.strictMode ? (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
),
|
||||||
function: viewStrictMode,
|
function: viewStrictMode,
|
||||||
shortcut: "Ctrl+Shift+M",
|
shortcut: "Ctrl+Shift+M",
|
||||||
},
|
},
|
||||||
"Presentation mode": {
|
presentation_mode: {
|
||||||
function: () => {
|
function: () => {
|
||||||
setLayout((prev) => ({
|
setLayout((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -1023,32 +1033,44 @@ export default function ControlPanel({
|
|||||||
enterFullscreen();
|
enterFullscreen();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Field details": {
|
field_details: {
|
||||||
state: settings.showFieldSummary ? "on" : "off",
|
state: settings.showFieldSummary ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: viewFieldSummary,
|
function: viewFieldSummary,
|
||||||
shortcut: "Ctrl+Shift+F",
|
shortcut: "Ctrl+Shift+F",
|
||||||
},
|
},
|
||||||
"Reset view": {
|
reset_view: {
|
||||||
function: resetView,
|
function: resetView,
|
||||||
shortcut: "Ctrl+R",
|
shortcut: "Ctrl+R",
|
||||||
},
|
},
|
||||||
"Show grid": {
|
show_grid: {
|
||||||
state: settings.showGrid ? "on" : "off",
|
state: settings.showGrid ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: viewGrid,
|
function: viewGrid,
|
||||||
shortcut: "Ctrl+Shift+G",
|
shortcut: "Ctrl+Shift+G",
|
||||||
},
|
},
|
||||||
"Show cardinality": {
|
show_cardinality: {
|
||||||
state: settings.showCardinality ? "on" : "off",
|
state: settings.showCardinality ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: () =>
|
function: () =>
|
||||||
setSettings((prev) => ({
|
setSettings((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
showCardinality: !prev.showCardinality,
|
showCardinality: !prev.showCardinality,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
Theme: {
|
theme: {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
Light: () => {
|
light: () => {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
if (body.hasAttribute("theme-mode")) {
|
if (body.hasAttribute("theme-mode")) {
|
||||||
body.setAttribute("theme-mode", "light");
|
body.setAttribute("theme-mode", "light");
|
||||||
@ -1058,7 +1080,7 @@ export default function ControlPanel({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Dark: () => {
|
dark: () => {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
if (body.hasAttribute("theme-mode")) {
|
if (body.hasAttribute("theme-mode")) {
|
||||||
body.setAttribute("theme-mode", "dark");
|
body.setAttribute("theme-mode", "dark");
|
||||||
@ -1070,71 +1092,75 @@ export default function ControlPanel({
|
|||||||
],
|
],
|
||||||
function: () => {},
|
function: () => {},
|
||||||
},
|
},
|
||||||
"Zoom in": {
|
zoom_in: {
|
||||||
function: zoomIn,
|
function: zoomIn,
|
||||||
shortcut: "Ctrl+Up/Wheel",
|
shortcut: "Ctrl+Up/Wheel",
|
||||||
},
|
},
|
||||||
"Zoom out": {
|
zoom_out: {
|
||||||
function: zoomOut,
|
function: zoomOut,
|
||||||
shortcut: "Ctrl+Down/Wheel",
|
shortcut: "Ctrl+Down/Wheel",
|
||||||
},
|
},
|
||||||
Fullscreen: {
|
fullscreen: {
|
||||||
function: enterFullscreen,
|
function: enterFullscreen,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Settings: {
|
settings: {
|
||||||
"Show timeline": {
|
show_timeline: {
|
||||||
function: () => setSidesheet(SIDESHEET.TIMELINE),
|
function: () => setSidesheet(SIDESHEET.TIMELINE),
|
||||||
},
|
},
|
||||||
Autosave: {
|
autosave: {
|
||||||
state: settings.autosave ? "on" : "off",
|
state: settings.autosave ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: () =>
|
function: () =>
|
||||||
setSettings((prev) => {
|
setSettings((prev) => ({ ...prev, autosave: !prev.autosave })),
|
||||||
Toast.success(`Autosave is ${settings.autosave ? "off" : "on"}`);
|
|
||||||
return { ...prev, autosave: !prev.autosave };
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Panning: {
|
panning: {
|
||||||
state: settings.panning ? "on" : "off",
|
state: settings.panning ? (
|
||||||
|
<i className="bi bi-toggle-on" />
|
||||||
|
) : (
|
||||||
|
<i className="bi bi-toggle-off" />
|
||||||
|
),
|
||||||
function: () =>
|
function: () =>
|
||||||
setSettings((prev) => {
|
setSettings((prev) => ({ ...prev, panning: !prev.panning })),
|
||||||
Toast.success(`Panning is ${settings.panning ? "off" : "on"}`);
|
|
||||||
return { ...prev, panning: !prev.panning };
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
"Table width": {
|
table_width: {
|
||||||
function: () => setModal(MODAL.TABLE_WIDTH),
|
function: () => setModal(MODAL.TABLE_WIDTH),
|
||||||
},
|
},
|
||||||
"Flush storage": {
|
language: {
|
||||||
|
function: () => setModal(MODAL.LANGUAGE),
|
||||||
|
},
|
||||||
|
flush_storage: {
|
||||||
warning: {
|
warning: {
|
||||||
title: "Flush storage",
|
title: t("flush_storage"),
|
||||||
message:
|
message: t("are_you_sure_flush_storage"),
|
||||||
"Are you sure you want to flush the storage? This will irreversibly delete all your diagrams and custom templates.",
|
|
||||||
},
|
},
|
||||||
function: async () => {
|
function: async () => {
|
||||||
db.delete()
|
db.delete()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Toast.success("Storage flushed");
|
Toast.success(t("storage_flushed"));
|
||||||
window.location.reload(false);
|
window.location.reload(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
Toast.error("Oops! Something went wrong.");
|
Toast.error(t("oops_smth_went_wrong"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Help: {
|
help: {
|
||||||
Shortcuts: {
|
shortcuts: {
|
||||||
function: () => window.open("/shortcuts", "_blank"),
|
function: () => window.open("/shortcuts", "_blank"),
|
||||||
shortcut: "Ctrl+H",
|
shortcut: "Ctrl+H",
|
||||||
},
|
},
|
||||||
"Ask us on discord": {
|
ask_on_discord: {
|
||||||
function: () => window.open("https://discord.gg/BrjZgNrmR6", "_blank"),
|
function: () => window.open("https://discord.gg/BrjZgNrmR6", "_blank"),
|
||||||
},
|
},
|
||||||
"Report a bug": {
|
report_bug: {
|
||||||
function: () => window.open("/bug-report", "_blank"),
|
function: () => window.open("/bug-report", "_blank"),
|
||||||
},
|
},
|
||||||
"Give feedback": {
|
feedback: {
|
||||||
function: () => window.open("/survey", "_blank"),
|
function: () => window.open("/survey", "_blank"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1207,7 +1233,7 @@ export default function ControlPanel({
|
|||||||
onClick={fitWindow}
|
onClick={fitWindow}
|
||||||
style={{ display: "flex", justifyContent: "space-between" }}
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
>
|
>
|
||||||
<div>Fit window / Reset</div>
|
<div>{t("fit_window_reset")}</div>
|
||||||
<div className="text-gray-400">Ctrl+Alt+W</div>
|
<div className="text-gray-400">Ctrl+Alt+W</div>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
@ -1225,8 +1251,8 @@ export default function ControlPanel({
|
|||||||
<Dropdown.Item>
|
<Dropdown.Item>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
field="zoom"
|
field="zoom"
|
||||||
label="Custom zoom"
|
label={t("zoom")}
|
||||||
placeholder="Zoom"
|
placeholder={t("zoom")}
|
||||||
suffix={<div className="p-1">%</div>}
|
suffix={<div className="p-1">%</div>}
|
||||||
onChange={(v) =>
|
onChange={(v) =>
|
||||||
setTransform((prev) => ({
|
setTransform((prev) => ({
|
||||||
@ -1249,7 +1275,7 @@ export default function ControlPanel({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Tooltip content="Zoom in" position="bottom">
|
<Tooltip content={t("zoom_in")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded text-lg"
|
className="py-1 px-2 hover-2 rounded text-lg"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -1259,7 +1285,7 @@ export default function ControlPanel({
|
|||||||
<i className="fa-solid fa-magnifying-glass-plus" />
|
<i className="fa-solid fa-magnifying-glass-plus" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Zoom out" position="bottom">
|
<Tooltip content={t("zoom_out")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded text-lg"
|
className="py-1 px-2 hover-2 rounded text-lg"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -1270,7 +1296,7 @@ export default function ControlPanel({
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider layout="vertical" margin="8px" />
|
<Divider layout="vertical" margin="8px" />
|
||||||
<Tooltip content="Undo" position="bottom">
|
<Tooltip content={t("undo")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||||
onClick={undo}
|
onClick={undo}
|
||||||
@ -1281,7 +1307,7 @@ export default function ControlPanel({
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Redo" position="bottom">
|
<Tooltip content={t("redo")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||||
onClick={redo}
|
onClick={redo}
|
||||||
@ -1293,7 +1319,7 @@ export default function ControlPanel({
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider layout="vertical" margin="8px" />
|
<Divider layout="vertical" margin="8px" />
|
||||||
<Tooltip content="Add table" position="bottom">
|
<Tooltip content={t("add_table")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="flex items-center py-1 px-2 hover-2 rounded"
|
className="flex items-center py-1 px-2 hover-2 rounded"
|
||||||
onClick={() => addTable()}
|
onClick={() => addTable()}
|
||||||
@ -1301,7 +1327,7 @@ export default function ControlPanel({
|
|||||||
<IconAddTable />
|
<IconAddTable />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Add subject area" position="bottom">
|
<Tooltip content={t("add_area")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||||
onClick={() => addArea()}
|
onClick={() => addArea()}
|
||||||
@ -1309,7 +1335,7 @@ export default function ControlPanel({
|
|||||||
<IconAddArea />
|
<IconAddArea />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Add note" position="bottom">
|
<Tooltip content={t("add_note")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||||
onClick={() => addNote()}
|
onClick={() => addNote()}
|
||||||
@ -1318,7 +1344,7 @@ export default function ControlPanel({
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider layout="vertical" margin="8px" />
|
<Divider layout="vertical" margin="8px" />
|
||||||
<Tooltip content="Save" position="bottom">
|
<Tooltip content={t("save")} position="bottom">
|
||||||
<button
|
<button
|
||||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||||
onClick={save}
|
onClick={save}
|
||||||
@ -1326,7 +1352,7 @@ export default function ControlPanel({
|
|||||||
<IconSaveStroked size="extra-large" />
|
<IconSaveStroked size="extra-large" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="To-do" position="bottom">
|
<Tooltip content={t("to_do")} position="bottom">
|
||||||
<button
|
<button
|
||||||
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)}
|
||||||
@ -1335,16 +1361,16 @@ export default function ControlPanel({
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider layout="vertical" margin="8px" />
|
<Divider layout="vertical" margin="8px" />
|
||||||
<Tooltip content="Change theme" position="bottom">
|
<Tooltip content={t("theme")} position="bottom">
|
||||||
<button
|
<button
|
||||||
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={() => {
|
onClick={() => {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
if (body.hasAttribute("theme-mode")) {
|
if (body.hasAttribute("theme-mode")) {
|
||||||
if (body.getAttribute("theme-mode") === "light") {
|
if (body.getAttribute("theme-mode") === "light") {
|
||||||
menu["View"]["Theme"].children[1]["Dark"]();
|
menu["view"]["theme"].children[1]["dark"]();
|
||||||
} else {
|
} else {
|
||||||
menu["View"]["Theme"].children[0]["Light"]();
|
menu["view"]["theme"].children[0]["light"]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -1366,15 +1392,15 @@ export default function ControlPanel({
|
|||||||
function getState() {
|
function getState() {
|
||||||
switch (saveState) {
|
switch (saveState) {
|
||||||
case State.NONE:
|
case State.NONE:
|
||||||
return "No changes";
|
return t("no_changes");
|
||||||
case State.LOADING:
|
case State.LOADING:
|
||||||
return "Loading . . .";
|
return t("loading");
|
||||||
case State.SAVED:
|
case State.SAVED:
|
||||||
return `Last saved ${lastSaved}`;
|
return `${t("last_saved")} ${lastSaved}`;
|
||||||
case State.SAVING:
|
case State.SAVING:
|
||||||
return "Saving . . .";
|
return t("saving");
|
||||||
case State.ERROR:
|
case State.ERROR:
|
||||||
return "Failed to save";
|
return t("failed_to_save");
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -1429,7 +1455,7 @@ export default function ControlPanel({
|
|||||||
key={i}
|
key={i}
|
||||||
onClick={Object.values(e)[0]}
|
onClick={Object.values(e)[0]}
|
||||||
>
|
>
|
||||||
{Object.keys(e)[0]}
|
{t(Object.keys(e)[0])}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@ -1444,7 +1470,7 @@ export default function ControlPanel({
|
|||||||
}}
|
}}
|
||||||
onClick={menu[category][item].function}
|
onClick={menu[category][item].function}
|
||||||
>
|
>
|
||||||
{item}
|
{t(item)}
|
||||||
<IconChevronRight />
|
<IconChevronRight />
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -1458,8 +1484,10 @@ export default function ControlPanel({
|
|||||||
content={menu[category][item].warning.message}
|
content={menu[category][item].warning.message}
|
||||||
onConfirm={menu[category][item].function}
|
onConfirm={menu[category][item].function}
|
||||||
position="right"
|
position="right"
|
||||||
|
okText={t("confirm")}
|
||||||
|
cancelText={t("cancel")}
|
||||||
>
|
>
|
||||||
<Dropdown.Item>{item}</Dropdown.Item>
|
<Dropdown.Item>{t(item)}</Dropdown.Item>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1476,18 +1504,15 @@ export default function ControlPanel({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center justify-between">
|
<div className="w-full flex items-center justify-between">
|
||||||
<div>{item}</div>
|
<div>{t(item)}</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{menu[category][item].shortcut && (
|
{menu[category][item].shortcut && (
|
||||||
<div className="text-gray-400">
|
<div className="text-gray-400">
|
||||||
{menu[category][item].shortcut}
|
{menu[category][item].shortcut}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{menu[category][item].state && (
|
{menu[category][item].state &&
|
||||||
<Tag color="blue">
|
menu[category][item].state}
|
||||||
{menu[category][item].state}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
@ -1496,7 +1521,9 @@ export default function ControlPanel({
|
|||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="px-3 py-1 hover-2 rounded">{category}</div>
|
<div className="px-3 py-1 hover-2 rounded">
|
||||||
|
{t(category)}
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,9 +6,12 @@ import {
|
|||||||
import { Dropdown } from "@douyinfe/semi-ui";
|
import { Dropdown } from "@douyinfe/semi-ui";
|
||||||
import { useLayout } from "../../hooks";
|
import { useLayout } from "../../hooks";
|
||||||
import { enterFullscreen, exitFullscreen } from "../../utils/fullscreen";
|
import { enterFullscreen, exitFullscreen } from "../../utils/fullscreen";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function LayoutDropdown() {
|
export default function LayoutDropdown() {
|
||||||
const { layout, setLayout } = useLayout();
|
const { layout, setLayout } = useLayout();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const invertLayout = (component) =>
|
const invertLayout = (component) =>
|
||||||
setLayout((prev) => ({ ...prev, [component]: !prev[component] }));
|
setLayout((prev) => ({ ...prev, [component]: !prev[component] }));
|
||||||
|
|
||||||
@ -24,7 +27,7 @@ export default function LayoutDropdown() {
|
|||||||
}
|
}
|
||||||
onClick={() => invertLayout("header")}
|
onClick={() => invertLayout("header")}
|
||||||
>
|
>
|
||||||
Header
|
{t("header")}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
icon={
|
icon={
|
||||||
@ -32,7 +35,7 @@ export default function LayoutDropdown() {
|
|||||||
}
|
}
|
||||||
onClick={() => invertLayout("sidebar")}
|
onClick={() => invertLayout("sidebar")}
|
||||||
>
|
>
|
||||||
Sidebar
|
{t("sidebar")}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
icon={
|
icon={
|
||||||
@ -40,7 +43,7 @@ export default function LayoutDropdown() {
|
|||||||
}
|
}
|
||||||
onClick={() => invertLayout("issues")}
|
onClick={() => invertLayout("issues")}
|
||||||
>
|
>
|
||||||
Issues
|
{t("issues")}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
@ -54,7 +57,7 @@ export default function LayoutDropdown() {
|
|||||||
invertLayout("fullscreen");
|
invertLayout("fullscreen");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Fullscreen
|
{t("fullscreen")}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,13 @@ import {
|
|||||||
import { Upload, Banner } from "@douyinfe/semi-ui";
|
import { Upload, Banner } from "@douyinfe/semi-ui";
|
||||||
import { STATUS } from "../../../data/constants";
|
import { STATUS } from "../../../data/constants";
|
||||||
import { useAreas, useNotes, useTables } from "../../../hooks";
|
import { useAreas, useNotes, useTables } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function ImportDiagram({ setImportData, error, setError }) {
|
export default function ImportDiagram({ setImportData, error, setError }) {
|
||||||
const { areas } = useAreas();
|
const { areas } = useAreas();
|
||||||
const { notes } = useNotes();
|
const { notes } = useNotes();
|
||||||
const { tables, relationships } = useTables();
|
const { tables, relationships } = useTables();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const diagramIsEmpty = () => {
|
const diagramIsEmpty = () => {
|
||||||
return (
|
return (
|
||||||
@ -84,8 +86,8 @@ export default function ImportDiagram({ setImportData, error, setError }) {
|
|||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
dragMainText="Drag and drop the file here or click to upload."
|
dragMainText={t("drag_and_drop_files")}
|
||||||
dragSubText="Support json and ddb"
|
dragSubText={t("support_json_and_ddb")}
|
||||||
accept="application/json,.ddb"
|
accept="application/json,.ddb"
|
||||||
onRemove={() =>
|
onRemove={() =>
|
||||||
setError({
|
setError({
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Upload, Checkbox, Banner } from "@douyinfe/semi-ui";
|
import { Upload, Checkbox, Banner } from "@douyinfe/semi-ui";
|
||||||
import { STATUS } from "../../../data/constants";
|
import { STATUS } from "../../../data/constants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function ImportSource({
|
export default function ImportSource({
|
||||||
importData,
|
importData,
|
||||||
@ -7,6 +8,8 @@ export default function ImportSource({
|
|||||||
error,
|
error,
|
||||||
setError,
|
setError,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Upload
|
<Upload
|
||||||
@ -30,8 +33,8 @@ export default function ImportSource({
|
|||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
dragMainText="Drag and drop the file here or click to upload."
|
dragMainText={t("drag_and_drop_files")}
|
||||||
dragSubText="Upload an sql file to autogenerate your tables and columns."
|
dragSubText={t("upload_sql_to_generate_diagrams")}
|
||||||
accept=".sql"
|
accept=".sql"
|
||||||
onRemove={() => {
|
onRemove={() => {
|
||||||
setError({
|
setError({
|
||||||
@ -50,7 +53,7 @@ export default function ImportSource({
|
|||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs mb-3 mt-1 opacity-80">
|
<div className="text-xs mb-3 mt-1 opacity-80">
|
||||||
* For the time being loading only MySQL scripts is supported.
|
{t("only_mysql_supported")}
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
aria-label="overwrite checkbox"
|
aria-label="overwrite checkbox"
|
||||||
@ -63,7 +66,7 @@ export default function ImportSource({
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Overwrite existing diagram
|
{t("overwrite_existing_diagram")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
{error.type === STATUS.ERROR ? (
|
{error.type === STATUS.ERROR ? (
|
||||||
|
30
src/components/EditorHeader/Modal/Language.jsx
Normal file
30
src/components/EditorHeader/Modal/Language.jsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useSettings } from "../../../hooks";
|
||||||
|
import { languages } from "../../../i18n/i18n";
|
||||||
|
|
||||||
|
export default function Language() {
|
||||||
|
const { settings } = useSettings();
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
{languages.map((l) => (
|
||||||
|
<button
|
||||||
|
key={l.code}
|
||||||
|
onClick={() => i18n.changeLanguage(l.code)}
|
||||||
|
className={`space-y-1 py-3 px-4 rounded-md border-2 ${
|
||||||
|
settings.mode === "dark"
|
||||||
|
? "bg-zinc-700 hover:bg-zinc-600"
|
||||||
|
: "bg-zinc-100 hover:bg-zinc-200"
|
||||||
|
} ${i18n.resolvedLanguage === l.code ? "border-zinc-400" : "border-transparent"}`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="font-semibold">{l.native_name}</div>
|
||||||
|
<div className="opacity-60">{l.code}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-start">{l.name}</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -27,11 +27,13 @@ import New from "./New";
|
|||||||
import ImportDiagram from "./ImportDiagram";
|
import ImportDiagram from "./ImportDiagram";
|
||||||
import ImportSource from "./ImportSource";
|
import ImportSource from "./ImportSource";
|
||||||
import SetTableWidth from "./SetTableWidth";
|
import SetTableWidth from "./SetTableWidth";
|
||||||
|
import Language from "./Language";
|
||||||
import CodeMirror from "@uiw/react-codemirror";
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
import { sql } from "@codemirror/lang-sql";
|
import { sql } from "@codemirror/lang-sql";
|
||||||
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
|
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
|
||||||
import { json } from "@codemirror/lang-json";
|
import { json } from "@codemirror/lang-json";
|
||||||
import { githubLight } from "@uiw/codemirror-theme-github";
|
import { githubLight } from "@uiw/codemirror-theme-github";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const languageExtension = {
|
const languageExtension = {
|
||||||
sql: [sql()],
|
sql: [sql()],
|
||||||
@ -49,6 +51,7 @@ export default function Modal({
|
|||||||
exportData,
|
exportData,
|
||||||
setExportData,
|
setExportData,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { setTables, setRelationships } = useTables();
|
const { setTables, setRelationships } = useTables();
|
||||||
const { setNotes } = useNotes();
|
const { setNotes } = useNotes();
|
||||||
const { setAreas } = useAreas();
|
const { setAreas } = useAreas();
|
||||||
@ -239,7 +242,7 @@ export default function Modal({
|
|||||||
case MODAL.SAVEAS:
|
case MODAL.SAVEAS:
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
placeholder="Diagram name"
|
placeholder={t("name")}
|
||||||
value={saveAsTitle}
|
value={saveAsTitle}
|
||||||
onChange={(v) => setSaveAsTitle(v)}
|
onChange={(v) => setSaveAsTitle(v)}
|
||||||
/>
|
/>
|
||||||
@ -261,10 +264,10 @@ export default function Modal({
|
|||||||
theme={settings.mode === "dark" ? vscodeDark : githubLight}
|
theme={settings.mode === "dark" ? vscodeDark : githubLight}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="text-sm font-semibold mt-2">Filename:</div>
|
<div className="text-sm font-semibold mt-2">{t("filename")}:</div>
|
||||||
<Input
|
<Input
|
||||||
value={exportData.filename}
|
value={exportData.filename}
|
||||||
placeholder="Filename"
|
placeholder={t("filename")}
|
||||||
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
|
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setExportData((prev) => ({ ...prev, filename: value }))
|
setExportData((prev) => ({ ...prev, filename: value }))
|
||||||
@ -276,12 +279,14 @@ export default function Modal({
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="text-center my-3">
|
<div className="text-center my-3">
|
||||||
<Spin tip="Loading..." size="large" />
|
<Spin tip={t("loading")} size="large" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case MODAL.TABLE_WIDTH:
|
case MODAL.TABLE_WIDTH:
|
||||||
return <SetTableWidth />;
|
return <SetTableWidth />;
|
||||||
|
case MODAL.LANGUAGE:
|
||||||
|
return <Language />;
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -326,7 +331,7 @@ export default function Modal({
|
|||||||
(modal === MODAL.SAVEAS && saveAsTitle === "") ||
|
(modal === MODAL.SAVEAS && saveAsTitle === "") ||
|
||||||
(modal === MODAL.IMPORT_SRC && importSource.src === ""),
|
(modal === MODAL.IMPORT_SRC && importSource.src === ""),
|
||||||
}}
|
}}
|
||||||
cancelText="Cancel"
|
cancelText={t("cancel")}
|
||||||
width={modal === MODAL.NEW ? 740 : 600}
|
width={modal === MODAL.NEW ? 740 : 600}
|
||||||
>
|
>
|
||||||
{getModalBody()}
|
{getModalBody()}
|
||||||
|
@ -2,9 +2,11 @@ import { db } from "../../../data/db";
|
|||||||
import { useSettings } from "../../../hooks";
|
import { useSettings } from "../../../hooks";
|
||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import Thumbnail from "../../Thumbnail";
|
import Thumbnail from "../../Thumbnail";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function New({ selectedTemplateId, setSelectedTemplateId }) {
|
export default function New({ selectedTemplateId, setSelectedTemplateId }) {
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
const { t } = useTranslation();
|
||||||
const templates = useLiveQuery(() => db.templates.toArray());
|
const templates = useLiveQuery(() => db.templates.toArray());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -17,7 +19,7 @@ export default function New({ selectedTemplateId, setSelectedTemplateId }) {
|
|||||||
>
|
>
|
||||||
<Thumbnail i={0} diagram={{}} zoom={0.24} theme={settings.mode} />
|
<Thumbnail i={0} diagram={{}} zoom={0.24} theme={settings.mode} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center mt-1">Blank</div>
|
<div className="text-center mt-1">{t("blank")}</div>
|
||||||
</div>
|
</div>
|
||||||
{templates?.map((temp, i) => (
|
{templates?.map((temp, i) => (
|
||||||
<div key={i} onClick={() => setSelectedTemplateId(temp.id)}>
|
<div key={i} onClick={() => setSelectedTemplateId(temp.id)}>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { db } from "../../../data/db";
|
import { db } from "../../../data/db";
|
||||||
import { Banner } from "@douyinfe/semi-ui";
|
import { Banner } from "@douyinfe/semi-ui";
|
||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
||||||
const diagrams = useLiveQuery(() => db.diagrams.toArray());
|
const diagrams = useLiveQuery(() => db.diagrams.toArray());
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getDiagramSize = (d) => {
|
const getDiagramSize = (d) => {
|
||||||
const size = JSON.stringify(d).length;
|
const size = JSON.stringify(d).length;
|
||||||
@ -32,9 +34,9 @@ export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
|||||||
<table className="w-full text-left border-separate border-spacing-x-0">
|
<table className="w-full text-left border-separate border-spacing-x-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>{t("name")}</th>
|
||||||
<th>Last Modified</th>
|
<th>{t("last_modified")}</th>
|
||||||
<th>Size</th>
|
<th>{t("size")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { Input } from "@douyinfe/semi-ui";
|
import { Input } from "@douyinfe/semi-ui";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Rename({ title, setTitle }) {
|
export default function Rename({ title, setTitle }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
placeholder="Diagram name"
|
placeholder={t("name")}
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(v) => setTitle(v)}
|
onChange={(v) => setTitle(v)}
|
||||||
/>
|
/>
|
||||||
|
@ -6,8 +6,10 @@ import timeLineDark from "../../../assets/process_dark.png";
|
|||||||
import todo from "../../../assets/calendar.png";
|
import todo from "../../../assets/calendar.png";
|
||||||
import Timeline from "./Timeline";
|
import Timeline from "./Timeline";
|
||||||
import Todo from "./Todo";
|
import Todo from "./Todo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Sidesheet({ type, onClose }) {
|
export default function Sidesheet({ type, onClose }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
|
||||||
function getTitle(type) {
|
function getTitle(type) {
|
||||||
@ -20,14 +22,14 @@ export default function Sidesheet({ type, onClose }) {
|
|||||||
className="w-7"
|
className="w-7"
|
||||||
alt="chat icon"
|
alt="chat icon"
|
||||||
/>
|
/>
|
||||||
<div className="ms-3 text-lg">Timeline</div>
|
<div className="ms-3 text-lg">{t("timeline")}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case SIDESHEET.TODO:
|
case SIDESHEET.TODO:
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img src={todo} className="w-7" alt="todo icon" />
|
<img src={todo} className="w-7" alt="todo icon" />
|
||||||
<div className="ms-3 text-lg">To-do list</div>
|
<div className="ms-3 text-lg">{t("to_do")}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useUndoRedo } from "../../../hooks";
|
import { useUndoRedo } from "../../../hooks";
|
||||||
import { List } from "@douyinfe/semi-ui";
|
import { List } from "@douyinfe/semi-ui";
|
||||||
|
|
||||||
export default function Timeline() {
|
export default function Timeline() {
|
||||||
const { undoStack } = useUndoRedo();
|
const { undoStack } = useUndoRedo();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (undoStack.length > 0) {
|
if (undoStack.length > 0) {
|
||||||
return (
|
return (
|
||||||
@ -22,11 +24,6 @@ export default function Timeline() {
|
|||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return <div className="m-5 sidesheet-theme">{t("no_activity")}</div>;
|
||||||
<div className="m-5 sidesheet-theme">
|
|
||||||
No activity was recorded. You have not added anything to your diagram
|
|
||||||
yet.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
} from "@douyinfe/semi-icons";
|
} from "@douyinfe/semi-icons";
|
||||||
import { State } from "../../../data/constants";
|
import { State } from "../../../data/constants";
|
||||||
import { useTasks, useSaveState } from "../../../hooks";
|
import { useTasks, useSaveState } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Priority = {
|
const Priority = {
|
||||||
NONE: 0,
|
NONE: 0,
|
||||||
@ -30,10 +31,10 @@ const Priority = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SortOrder = {
|
const SortOrder = {
|
||||||
ORIGINAL: "My order",
|
ORIGINAL: "my_order",
|
||||||
PRIORITY: "Priority",
|
PRIORITY: "priority",
|
||||||
COMPLETED: "Completed",
|
COMPLETED: "completed",
|
||||||
ALPHABETICALLY: "Alphabetically",
|
ALPHABETICALLY: "alphabetically",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Todo() {
|
export default function Todo() {
|
||||||
@ -41,17 +42,18 @@ export default function Todo() {
|
|||||||
const [, setSortOrder] = useState(SortOrder.ORIGINAL);
|
const [, setSortOrder] = useState(SortOrder.ORIGINAL);
|
||||||
const { tasks, setTasks, updateTask } = useTasks();
|
const { tasks, setTasks, updateTask } = useTasks();
|
||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const priorityLabel = (p) => {
|
const priorityLabel = (p) => {
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case Priority.NONE:
|
case Priority.NONE:
|
||||||
return "None";
|
return t("none");
|
||||||
case Priority.LOW:
|
case Priority.LOW:
|
||||||
return "Low";
|
return t("low");
|
||||||
case Priority.MEDIUM:
|
case Priority.MEDIUM:
|
||||||
return "Medium";
|
return t("medium");
|
||||||
case Priority.HIGH:
|
case Priority.HIGH:
|
||||||
return "High";
|
return t("high");
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -91,7 +93,7 @@ export default function Todo() {
|
|||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case SortOrder.ALPHABETICALLY:
|
case SortOrder.ALPHABETICALLY:
|
||||||
@ -116,7 +118,7 @@ export default function Todo() {
|
|||||||
sort(order);
|
sort(order);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{order}
|
{t(order)}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
))}
|
))}
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
@ -128,7 +130,7 @@ export default function Todo() {
|
|||||||
theme="borderless"
|
theme="borderless"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
>
|
>
|
||||||
Sort by <IconCaretdown />
|
{t("sort_by")} <IconCaretdown />
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Button
|
<Button
|
||||||
@ -147,12 +149,12 @@ export default function Todo() {
|
|||||||
]);
|
]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add task
|
{t("add_task")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{tasks.length > 0 ? (
|
{tasks.length > 0 ? (
|
||||||
<List className="sidesheet-theme">
|
<List className="sidesheet-theme">
|
||||||
{tasks.map((t, i) => (
|
{tasks.map((task, i) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={i}
|
key={i}
|
||||||
style={{ paddingLeft: "18px", paddingRight: "18px" }}
|
style={{ paddingLeft: "18px", paddingRight: "18px" }}
|
||||||
@ -163,7 +165,7 @@ export default function Todo() {
|
|||||||
<Row gutter={6} align="middle" type="flex" className="mb-2">
|
<Row gutter={6} align="middle" type="flex" className="mb-2">
|
||||||
<Col span={2}>
|
<Col span={2}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={t.complete}
|
checked={task.complete}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateTask(i, { complete: e.target.checked });
|
updateTask(i, { complete: e.target.checked });
|
||||||
setSaveState(State.SAVING);
|
setSaveState(State.SAVING);
|
||||||
@ -172,25 +174,25 @@ export default function Todo() {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={19}>
|
<Col span={19}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Title"
|
placeholder={t("title")}
|
||||||
onChange={(v) => updateTask(i, { title: v })}
|
onChange={(v) => updateTask(i, { title: v })}
|
||||||
value={t.title}
|
value={task.title}
|
||||||
onBlur={() => setSaveState(State.SAVING)}
|
onBlur={() => setSaveState(State.SAVING)}
|
||||||
></Input>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<div className="p-2 popover-theme">
|
<div className="p-2 popover-theme">
|
||||||
<div className="mb-2 font-semibold">
|
<div className="mb-2 font-semibold">
|
||||||
Set priority:
|
{t("priority")}:
|
||||||
</div>
|
</div>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateTask(i, { priority: e.target.value });
|
updateTask(i, { priority: e.target.value });
|
||||||
setSaveState(State.SAVING);
|
setSaveState(State.SAVING);
|
||||||
}}
|
}}
|
||||||
value={t.priority}
|
value={task.priority}
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
>
|
>
|
||||||
<Radio value={Priority.NONE}>
|
<Radio value={Priority.NONE}>
|
||||||
@ -221,12 +223,12 @@ export default function Todo() {
|
|||||||
style={{ marginTop: "12px" }}
|
style={{ marginTop: "12px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTasks((prev) =>
|
setTasks((prev) =>
|
||||||
prev.filter((task, j) => i !== j)
|
prev.filter((_, j) => i !== j),
|
||||||
);
|
);
|
||||||
setSaveState(State.SAVING);
|
setSaveState(State.SAVING);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -243,7 +245,7 @@ export default function Todo() {
|
|||||||
<Col span={2}></Col>
|
<Col span={2}></Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
<TextArea
|
<TextArea
|
||||||
placeholder="Details"
|
placeholder={t("details")}
|
||||||
onChange={(v) => updateTask(i, { details: v })}
|
onChange={(v) => updateTask(i, { details: v })}
|
||||||
value={t.details}
|
value={t.details}
|
||||||
onBlur={() => setSaveState(State.SAVING)}
|
onBlur={() => setSaveState(State.SAVING)}
|
||||||
@ -254,9 +256,9 @@ export default function Todo() {
|
|||||||
<Row>
|
<Row>
|
||||||
<Col span={2}></Col>
|
<Col span={2}></Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
Priority:{" "}
|
{t("priority")}:{" "}
|
||||||
<Tag color={priorityColor(t.priority)}>
|
<Tag color={priorityColor(task.priority)}>
|
||||||
{priorityLabel(t.priority)}
|
{priorityLabel(task.priority)}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -265,10 +267,7 @@ export default function Todo() {
|
|||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
) : (
|
) : (
|
||||||
<div className="m-5 sidesheet-theme">
|
<div className="m-5 sidesheet-theme">{t("no_tasks")}</div>
|
||||||
You have no tasks yet. Add your to-dos and keep track of your
|
|
||||||
progress.
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Row, Col, Button, Input, Popover, Toast } from "@douyinfe/semi-ui";
|
import { Row, Col, Button, Input, Popover } from "@douyinfe/semi-ui";
|
||||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { useAreas, useSaveState, useUndoRedo } from "../../../hooks";
|
import { useAreas, useSaveState, useUndoRedo } from "../../../hooks";
|
||||||
import {
|
import {
|
||||||
@ -8,9 +8,11 @@ import {
|
|||||||
State,
|
State,
|
||||||
defaultBlue,
|
defaultBlue,
|
||||||
} from "../../../data/constants";
|
} from "../../../data/constants";
|
||||||
import ColorPalette from "../../ColorPalette";
|
import ColorPalette from "../../ColorPicker";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function AreaInfo({ data, i }) {
|
export default function AreaInfo({ data, i }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
const { deleteArea, updateArea } = useAreas();
|
const { deleteArea, updateArea } = useAreas();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
@ -28,7 +30,7 @@ export default function AreaInfo({ data, i }) {
|
|||||||
<Col span={18}>
|
<Col span={18}>
|
||||||
<Input
|
<Input
|
||||||
value={data.name}
|
value={data.name}
|
||||||
placeholder="Name"
|
placeholder={t("name")}
|
||||||
onChange={(value) => updateArea(data.id, { name: value })}
|
onChange={(value) => updateArea(data.id, { name: value })}
|
||||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
@ -41,7 +43,10 @@ export default function AreaInfo({ data, i }) {
|
|||||||
aid: i,
|
aid: i,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { name: e.target.value },
|
redo: { name: e.target.value },
|
||||||
message: `Edit area name to ${e.target.value}`,
|
message: t("edit_area", {
|
||||||
|
areaName: e.target.value,
|
||||||
|
extra: "[name]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -67,7 +72,10 @@ export default function AreaInfo({ data, i }) {
|
|||||||
aid: i,
|
aid: i,
|
||||||
undo: { color: data.color },
|
undo: { color: data.color },
|
||||||
redo: { color: c },
|
redo: { color: c },
|
||||||
message: `Edit area color to ${c}`,
|
message: t("edit_area", {
|
||||||
|
areaName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -90,10 +98,7 @@ export default function AreaInfo({ data, i }) {
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => {
|
onClick={() => deleteArea(i, true)}
|
||||||
Toast.success(`Area deleted!`);
|
|
||||||
deleteArea(i, true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -4,9 +4,11 @@ import Empty from "../Empty";
|
|||||||
import { useAreas } from "../../../hooks";
|
import { useAreas } from "../../../hooks";
|
||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import AreaInfo from "./AreaDetails";
|
import AreaInfo from "./AreaDetails";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function AreasTab() {
|
export default function AreasTab() {
|
||||||
const { areas, addArea } = useAreas();
|
const { areas, addArea } = useAreas();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -16,14 +18,14 @@ export default function AreasTab() {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Button icon={<IconPlus />} block onClick={addArea}>
|
<Button icon={<IconPlus />} block onClick={addArea}>
|
||||||
Add area
|
{t("add_area")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{areas.length <= 0 ? (
|
{areas.length <= 0 ? (
|
||||||
<Empty
|
<Empty
|
||||||
title="No subject areas"
|
title={t("no_subject_areas")}
|
||||||
text="Add subject areas to organize tables!"
|
text={t("no_subject_areas_text")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
|
@ -2,18 +2,20 @@ import { useState } from "react";
|
|||||||
import { useAreas } from "../../../hooks";
|
import { useAreas } from "../../../hooks";
|
||||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||||
import { IconSearch } from "@douyinfe/semi-icons";
|
import { IconSearch } from "@douyinfe/semi-icons";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function SearchBar() {
|
export default function SearchBar() {
|
||||||
const { areas } = useAreas();
|
const { areas } = useAreas();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
areas.map((t) => t.name)
|
areas.map((t) => t.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStringSearch = (value) => {
|
const handleStringSearch = (value) => {
|
||||||
setFilteredResult(
|
setFilteredResult(
|
||||||
areas.map((t) => t.name).filter((i) => i.includes(value))
|
areas.map((t) => t.name).filter((i) => i.includes(value)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,8 +25,8 @@ export default function SearchBar() {
|
|||||||
value={searchText}
|
value={searchText}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder={t("search")}
|
||||||
emptyContent={<div className="p-3 popover-theme">No areas found</div>}
|
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||||
onSearch={(v) => handleStringSearch(v)}
|
onSearch={(v) => handleStringSearch(v)}
|
||||||
onChange={(v) => setSearchText(v)}
|
onChange={(v) => setSearchText(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
|
@ -3,10 +3,12 @@ import { Collapse, Badge } from "@douyinfe/semi-ui";
|
|||||||
import { arrayIsEqual } from "../../utils/utils";
|
import { arrayIsEqual } from "../../utils/utils";
|
||||||
import { getIssues } from "../../utils/issues";
|
import { getIssues } from "../../utils/issues";
|
||||||
import { useSettings, useTables, useTypes } from "../../hooks";
|
import { useSettings, useTables, useTypes } from "../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Issues() {
|
export default function Issues() {
|
||||||
const { settings } = useSettings();
|
|
||||||
const { types } = useTypes();
|
const { types } = useTypes();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { settings } = useSettings();
|
||||||
const { tables, relationships } = useTables();
|
const { tables, relationships } = useTables();
|
||||||
const [issues, setIssues] = useState([]);
|
const [issues, setIssues] = useState([]);
|
||||||
|
|
||||||
@ -38,7 +40,7 @@ export default function Issues() {
|
|||||||
>
|
>
|
||||||
<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 className="fa-solid fa-triangle-exclamation me-2 text-yellow-500" />
|
||||||
Issues
|
{t("issues")}
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
}
|
||||||
@ -46,9 +48,7 @@ export default function Issues() {
|
|||||||
>
|
>
|
||||||
<div className="max-h-[160px] overflow-y-auto">
|
<div className="max-h-[160px] overflow-y-auto">
|
||||||
{settings.strictMode ? (
|
{settings.strictMode ? (
|
||||||
<div className="mb-1">
|
<div className="mb-1">{t("strict_mode_is_on_no_issues")}</div>
|
||||||
Strict mode is off so no issues will be displayed.
|
|
||||||
</div>
|
|
||||||
) : issues.length > 0 ? (
|
) : issues.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{issues.map((e, i) => (
|
{issues.map((e, i) => (
|
||||||
@ -58,7 +58,7 @@ export default function Issues() {
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div>No issues were detected.</div>
|
<div>{t("no_issues")}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import { Button, Collapse, TextArea, Popover, Input } from "@douyinfe/semi-ui";
|
||||||
Button,
|
|
||||||
Collapse,
|
|
||||||
TextArea,
|
|
||||||
Popover,
|
|
||||||
Input,
|
|
||||||
Toast,
|
|
||||||
} from "@douyinfe/semi-ui";
|
|
||||||
import { IconDeleteStroked, IconCheckboxTick } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked, IconCheckboxTick } from "@douyinfe/semi-icons";
|
||||||
import { noteThemes, Action, ObjectType } from "../../../data/constants";
|
import { noteThemes, Action, ObjectType } from "../../../data/constants";
|
||||||
import { useNotes, useUndoRedo } from "../../../hooks";
|
import { useNotes, useUndoRedo } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function NoteInfo({ data, nid }) {
|
export default function NoteInfo({ data, nid }) {
|
||||||
const { updateNote, deleteNote } = useNotes();
|
const { updateNote, deleteNote } = useNotes();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse.Panel
|
<Collapse.Panel
|
||||||
@ -27,10 +22,10 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
id={`scroll_note_${data.id}`}
|
id={`scroll_note_${data.id}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center mb-2">
|
||||||
<div className="font-semibold me-2">Title:</div>
|
<div className="font-semibold me-2 break-keep">{t("title")}:</div>
|
||||||
<Input
|
<Input
|
||||||
value={data.title}
|
value={data.title}
|
||||||
placeholder="Title"
|
placeholder={t("title")}
|
||||||
onChange={(value) => updateNote(data.id, { title: value })}
|
onChange={(value) => updateNote(data.id, { title: value })}
|
||||||
onFocus={(e) => setEditField({ title: e.target.value })}
|
onFocus={(e) => setEditField({ title: e.target.value })}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
@ -43,7 +38,10 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
nid: data.id,
|
nid: data.id,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { title: e.target.value },
|
redo: { title: e.target.value },
|
||||||
message: `Edit note title to "${e.target.name}"`,
|
message: t("edit_note", {
|
||||||
|
noteTitle: e.target.value,
|
||||||
|
extra: "[title]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -52,7 +50,7 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between align-top">
|
<div className="flex justify-between align-top">
|
||||||
<TextArea
|
<TextArea
|
||||||
placeholder="Add content"
|
placeholder={t("content")}
|
||||||
value={data.content}
|
value={data.content}
|
||||||
autosize
|
autosize
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
@ -79,7 +77,10 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
nid: nid,
|
nid: nid,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { content: e.target.value, height: newHeight },
|
redo: { content: e.target.value, height: newHeight },
|
||||||
message: `Edit note content to "${e.target.value}"`,
|
message: t("edit_note", {
|
||||||
|
noteTitle: e.target.value,
|
||||||
|
extra: "[content]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -90,7 +91,7 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
<div className="font-medium mb-1">Theme</div>
|
<div className="font-medium mb-1">{t("theme")}</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
{noteThemes.map((c) => (
|
{noteThemes.map((c) => (
|
||||||
@ -107,7 +108,10 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
nid: nid,
|
nid: nid,
|
||||||
undo: { color: data.color },
|
undo: { color: data.color },
|
||||||
redo: { color: c },
|
redo: { color: c },
|
||||||
message: `Edit note color to ${c}`,
|
message: t("edit_note", {
|
||||||
|
noteTitle: data.title,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -136,10 +140,7 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => {
|
onClick={() => deleteNote(nid, true)}
|
||||||
Toast.success(`Note deleted!`);
|
|
||||||
deleteNote(nid, true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,10 +4,12 @@ import { useNotes, useSelect } from "../../../hooks";
|
|||||||
import Empty from "../Empty";
|
import Empty from "../Empty";
|
||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import NoteInfo from "./NoteInfo";
|
import NoteInfo from "./NoteInfo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function NotesTab() {
|
export default function NotesTab() {
|
||||||
const { notes, addNote } = useNotes();
|
const { notes, addNote } = useNotes();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -24,12 +26,12 @@ export default function NotesTab() {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Button icon={<IconPlus />} block onClick={() => addNote()}>
|
<Button icon={<IconPlus />} block onClick={() => addNote()}>
|
||||||
Add note
|
{t("add_note")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{notes.length <= 0 ? (
|
{notes.length <= 0 ? (
|
||||||
<Empty title="No text notes" text="Add notes cuz why not!" />
|
<Empty title={t("no_notes")} text={t("no_notes_text")} />
|
||||||
) : (
|
) : (
|
||||||
<Collapse
|
<Collapse
|
||||||
activeKey={selectedElement.open ? `${selectedElement.id}` : ""}
|
activeKey={selectedElement.open ? `${selectedElement.id}` : ""}
|
||||||
|
@ -2,17 +2,20 @@ import { useState } from "react";
|
|||||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||||
import { IconSearch } from "@douyinfe/semi-icons";
|
import { IconSearch } from "@douyinfe/semi-icons";
|
||||||
import { useNotes } from "../../../hooks";
|
import { useNotes } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function SearchBar({ setActiveKey }) {
|
export default function SearchBar({ setActiveKey }) {
|
||||||
const { notes } = useNotes();
|
const { notes } = useNotes();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
notes.map((t) => t.title)
|
notes.map((t) => t.title),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStringSearch = (value) => {
|
const handleStringSearch = (value) => {
|
||||||
setFilteredResult(
|
setFilteredResult(
|
||||||
notes.map((t) => t.title).filter((i) => i.includes(value))
|
notes.map((t) => t.title).filter((i) => i.includes(value)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,8 +25,8 @@ export default function SearchBar({ setActiveKey }) {
|
|||||||
value={searchText}
|
value={searchText}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder={t("search")}
|
||||||
emptyContent={<div className="p-3 popover-theme">No notes found</div>}
|
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||||
onSearch={(v) => handleStringSearch(v)}
|
onSearch={(v) => handleStringSearch(v)}
|
||||||
onChange={(v) => setSearchText(v)}
|
onChange={(v) => setSearchText(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
|
@ -19,14 +19,16 @@ import {
|
|||||||
ObjectType,
|
ObjectType,
|
||||||
} from "../../../data/constants";
|
} from "../../../data/constants";
|
||||||
import { useTables, useUndoRedo } from "../../../hooks";
|
import { useTables, useUndoRedo } from "../../../hooks";
|
||||||
|
import i18n from "../../../i18n/i18n";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Primary",
|
title: i18n.t("primary"),
|
||||||
dataIndex: "primary",
|
dataIndex: "primary",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Foreign",
|
title: i18n.t("foreign"),
|
||||||
dataIndex: "foreign",
|
dataIndex: "foreign",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -34,6 +36,7 @@ const columns = [
|
|||||||
export default function RelationshipInfo({ data }) {
|
export default function RelationshipInfo({ data }) {
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const { tables, setRelationships, deleteRelationship } = useTables();
|
const { tables, setRelationships, deleteRelationship } = useTables();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const swapKeys = () => {
|
const swapKeys = () => {
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
@ -54,7 +57,10 @@ export default function RelationshipInfo({ data }) {
|
|||||||
endTableId: data.startTableId,
|
endTableId: data.startTableId,
|
||||||
endFieldId: data.startFieldId,
|
endFieldId: data.startFieldId,
|
||||||
},
|
},
|
||||||
message: `Swap primary and foreign tables`,
|
message: t("edit_relationship", {
|
||||||
|
refName: data.name,
|
||||||
|
extra: "[swap keys]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -71,8 +77,8 @@ export default function RelationshipInfo({ data }) {
|
|||||||
endTableId: e.startTableId,
|
endTableId: e.startTableId,
|
||||||
endFieldId: e.startFieldId,
|
endFieldId: e.startFieldId,
|
||||||
}
|
}
|
||||||
: e
|
: e,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,12 +91,17 @@ export default function RelationshipInfo({ data }) {
|
|||||||
rid: data.id,
|
rid: data.id,
|
||||||
undo: { cardinality: data.cardinality },
|
undo: { cardinality: data.cardinality },
|
||||||
redo: { cardinality: value },
|
redo: { cardinality: value },
|
||||||
message: `Edit relationship cardinality`,
|
message: t("edit_relationship", {
|
||||||
|
refName: data.name,
|
||||||
|
extra: "[cardinality]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
setRelationships((prev) =>
|
setRelationships((prev) =>
|
||||||
prev.map((e, idx) => (idx === data.id ? { ...e, cardinality: value } : e))
|
prev.map((e, idx) =>
|
||||||
|
idx === data.id ? { ...e, cardinality: value } : e,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +113,10 @@ export default function RelationshipInfo({ data }) {
|
|||||||
rid: data.id,
|
rid: data.id,
|
||||||
undo: { [undoKey]: data[undoKey] },
|
undo: { [undoKey]: data[undoKey] },
|
||||||
redo: { [undoKey]: value },
|
redo: { [undoKey]: value },
|
||||||
message: `Edit relationship ${key} constraint`,
|
message: t("edit_relationship", {
|
||||||
|
refName: data.name,
|
||||||
|
extra: "[constraint]",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
@ -112,12 +126,15 @@ export default function RelationshipInfo({ data }) {
|
|||||||
rid: data.id,
|
rid: data.id,
|
||||||
undo: { [undoKey]: data[undoKey] },
|
undo: { [undoKey]: data[undoKey] },
|
||||||
redo: { [undoKey]: value },
|
redo: { [undoKey]: value },
|
||||||
message: `Edit relationship ${key} constraint`,
|
message: t("edit_relationship", {
|
||||||
|
refName: data.name,
|
||||||
|
extra: "[constraint]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
setRelationships((prev) =>
|
setRelationships((prev) =>
|
||||||
prev.map((e, idx) => (idx === data.id ? { ...e, [undoKey]: value } : e))
|
prev.map((e, idx) => (idx === data.id ? { ...e, [undoKey]: value } : e)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,11 +150,11 @@ export default function RelationshipInfo({ data }) {
|
|||||||
>
|
>
|
||||||
<div className="flex justify-between items-center mb-3">
|
<div className="flex justify-between items-center mb-3">
|
||||||
<div className="me-3">
|
<div className="me-3">
|
||||||
<span className="font-semibold">Primary: </span>
|
<span className="font-semibold">{t("primary")}: </span>
|
||||||
{tables[data.endTableId].name}
|
{tables[data.endTableId].name}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-1">
|
<div className="mx-1">
|
||||||
<span className="font-semibold">Foreign: </span>
|
<span className="font-semibold">{t("foreign")}: </span>
|
||||||
{tables[data.startTableId].name}
|
{tables[data.startTableId].name}
|
||||||
</div>
|
</div>
|
||||||
<div className="ms-1">
|
<div className="ms-1">
|
||||||
@ -168,7 +185,7 @@ export default function RelationshipInfo({ data }) {
|
|||||||
block
|
block
|
||||||
onClick={swapKeys}
|
onClick={swapKeys}
|
||||||
>
|
>
|
||||||
Swap
|
{t("swap")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -181,7 +198,7 @@ export default function RelationshipInfo({ data }) {
|
|||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold my-1">Cardinality</div>
|
<div className="font-semibold my-1">{t("cardinality")}:</div>
|
||||||
<Select
|
<Select
|
||||||
optionList={Object.values(Cardinality).map((v) => ({
|
optionList={Object.values(Cardinality).map((v) => ({
|
||||||
label: v,
|
label: v,
|
||||||
@ -193,7 +210,7 @@ export default function RelationshipInfo({ data }) {
|
|||||||
/>
|
/>
|
||||||
<Row gutter={6} className="my-3">
|
<Row gutter={6} className="my-3">
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<div className="font-semibold">On update: </div>
|
<div className="font-semibold">{t("on_update")}: </div>
|
||||||
<Select
|
<Select
|
||||||
optionList={Object.values(Constraint).map((v) => ({
|
optionList={Object.values(Constraint).map((v) => ({
|
||||||
label: v,
|
label: v,
|
||||||
@ -205,7 +222,7 @@ export default function RelationshipInfo({ data }) {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<div className="font-semibold">On delete: </div>
|
<div className="font-semibold">{t("on_delete")}: </div>
|
||||||
<Select
|
<Select
|
||||||
optionList={Object.values(Constraint).map((v) => ({
|
optionList={Object.values(Constraint).map((v) => ({
|
||||||
label: v,
|
label: v,
|
||||||
@ -223,7 +240,7 @@ export default function RelationshipInfo({ data }) {
|
|||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => deleteRelationship(data.id, true)}
|
onClick={() => deleteRelationship(data.id, true)}
|
||||||
>
|
>
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,10 +4,12 @@ import Empty from "../Empty";
|
|||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import RelationshipInfo from "./RelationshipInfo";
|
import RelationshipInfo from "./RelationshipInfo";
|
||||||
import { ObjectType } from "../../../data/constants";
|
import { ObjectType } from "../../../data/constants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function RelationshipsTab() {
|
export default function RelationshipsTab() {
|
||||||
const { relationships } = useTables();
|
const { relationships } = useTables();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -33,8 +35,8 @@ export default function RelationshipsTab() {
|
|||||||
>
|
>
|
||||||
{relationships.length <= 0 ? (
|
{relationships.length <= 0 ? (
|
||||||
<Empty
|
<Empty
|
||||||
title="No relationships"
|
title={t("no_relationships")}
|
||||||
text="Drag to connect fields and form relationships!"
|
text={t("no_relationships_text")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
relationships.map((r) => <RelationshipInfo key={r.id} data={r} />)
|
relationships.map((r) => <RelationshipInfo key={r.id} data={r} />)
|
||||||
|
@ -3,11 +3,14 @@ import { useSelect, useTables } from "../../../hooks";
|
|||||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||||
import { IconSearch } from "@douyinfe/semi-icons";
|
import { IconSearch } from "@douyinfe/semi-icons";
|
||||||
import { ObjectType } from "../../../data/constants";
|
import { ObjectType } from "../../../data/constants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function SearchBar() {
|
export default function SearchBar() {
|
||||||
const { relationships } = useTables();
|
const { relationships } = useTables();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const { setSelectedElement } = useSelect();
|
const { setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
relationships.map((t) => t.name),
|
relationships.map((t) => t.name),
|
||||||
);
|
);
|
||||||
@ -24,10 +27,8 @@ export default function SearchBar() {
|
|||||||
value={searchText}
|
value={searchText}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder={t("search")}
|
||||||
emptyContent={
|
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||||
<div className="p-3 popover-theme">No relationships found</div>
|
|
||||||
}
|
|
||||||
onSearch={(v) => handleStringSearch(v)}
|
onSearch={(v) => handleStringSearch(v)}
|
||||||
onChange={(v) => setSearchText(v)}
|
onChange={(v) => setSearchText(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
|
@ -7,17 +7,23 @@ import Issues from "./Issues";
|
|||||||
import AreasTab from "./AreasTab/AreasTab";
|
import AreasTab from "./AreasTab/AreasTab";
|
||||||
import NotesTab from "./NotesTab/NotesTab";
|
import NotesTab from "./NotesTab/NotesTab";
|
||||||
import TablesTab from "./TablesTab/TablesTab";
|
import TablesTab from "./TablesTab/TablesTab";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function SidePanel({ width, resize, setResize }) {
|
export default function SidePanel({ width, resize, setResize }) {
|
||||||
const { layout } = useLayout();
|
const { layout } = useLayout();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const tabList = [
|
const tabList = [
|
||||||
{ tab: "Tables", itemKey: Tab.TABLES, component: <TablesTab /> },
|
{ tab: t("tables"), itemKey: Tab.TABLES, component: <TablesTab /> },
|
||||||
{ tab: "Relationships", itemKey: Tab.RELATIONSHIPS, component: <RelationshipsTab /> },
|
{
|
||||||
{ tab: "Subject Areas", itemKey: Tab.AREAS, component: <AreasTab /> },
|
tab: t("relationships"),
|
||||||
{ tab: "Notes", itemKey: Tab.NOTES, component: <NotesTab /> },
|
itemKey: Tab.RELATIONSHIPS,
|
||||||
{ tab: "Types", itemKey: Tab.TYPES, component: <TypesTab /> },
|
component: <RelationshipsTab />,
|
||||||
|
},
|
||||||
|
{ tab: t("subject_areas"), itemKey: Tab.AREAS, component: <AreasTab /> },
|
||||||
|
{ tab: t("notes"), itemKey: Tab.NOTES, component: <NotesTab /> },
|
||||||
|
{ tab: t("types"), itemKey: Tab.TYPES, component: <TypesTab /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -36,13 +42,12 @@ export default function SidePanel({ width, resize, setResize }) {
|
|||||||
}
|
}
|
||||||
collapsible
|
collapsible
|
||||||
>
|
>
|
||||||
{tabList.length && tabList.map(tab =>
|
{tabList.length &&
|
||||||
<TabPane tab={tab.tab} itemKey={tab.itemKey} key={tab.itemKey}>
|
tabList.map((tab) => (
|
||||||
<div className="p-2">
|
<TabPane tab={tab.tab} itemKey={tab.itemKey} key={tab.itemKey}>
|
||||||
{tab.component}
|
<div className="p-2">{tab.component}</div>
|
||||||
</div>
|
</TabPane>
|
||||||
</TabPane>
|
))}
|
||||||
)}
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
{layout.issues && (
|
{layout.issues && (
|
||||||
|
@ -11,18 +11,21 @@ import { Action, ObjectType } from "../../../data/constants";
|
|||||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { hasCheck, hasPrecision, isSized } from "../../../utils/toSQL";
|
import { hasCheck, hasPrecision, isSized } from "../../../utils/toSQL";
|
||||||
import { useTables, useUndoRedo } from "../../../hooks";
|
import { useTables, useUndoRedo } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function FieldDetails({ data, tid, index }) {
|
export default function FieldDetails({ data, tid, index }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { tables } = useTables();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const { updateField, deleteField } = useTables();
|
const { updateField, deleteField } = useTables();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold">Default value</div>
|
<div className="font-semibold">{t("default_value")}</div>
|
||||||
<Input
|
<Input
|
||||||
className="my-2"
|
className="my-2"
|
||||||
placeholder="Set default"
|
placeholder={t("default_value")}
|
||||||
value={data.default}
|
value={data.default}
|
||||||
disabled={
|
disabled={
|
||||||
data.type === "BLOB" ||
|
data.type === "BLOB" ||
|
||||||
@ -45,7 +48,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { default: e.target.value },
|
redo: { default: e.target.value },
|
||||||
message: `Edit table field default to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -53,7 +59,9 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
/>
|
/>
|
||||||
{(data.type === "ENUM" || data.type === "SET") && (
|
{(data.type === "ENUM" || data.type === "SET") && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold mb-1">{data.type} values</div>
|
<div className="font-semibold mb-1">
|
||||||
|
{data.type} {t("values")}
|
||||||
|
</div>
|
||||||
<TagInput
|
<TagInput
|
||||||
separator={[",", ", ", " ,"]}
|
separator={[",", ", ", " ,"]}
|
||||||
value={data.values}
|
value={data.values}
|
||||||
@ -62,7 +70,7 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
}
|
}
|
||||||
addOnBlur
|
addOnBlur
|
||||||
className="my-2"
|
className="my-2"
|
||||||
placeholder="Use ',' for batch input"
|
placeholder={t("use_for_batch_input")}
|
||||||
onChange={(v) => updateField(tid, index, { values: v })}
|
onChange={(v) => updateField(tid, index, { values: v })}
|
||||||
onFocus={() => setEditField({ values: data.values })}
|
onFocus={() => setEditField({ values: data.values })}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
@ -80,9 +88,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { values: data.values },
|
redo: { values: data.values },
|
||||||
message: `Edit table field values to "${JSON.stringify(
|
message: t("edit_table", {
|
||||||
data.values,
|
tableName: tables[tid].name,
|
||||||
)}"`,
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -92,7 +101,7 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
)}
|
)}
|
||||||
{isSized(data.type) && (
|
{isSized(data.type) && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold">Size</div>
|
<div className="font-semibold">{t("size")}</div>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
placeholder="Set length"
|
placeholder="Set length"
|
||||||
@ -111,7 +120,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { size: e.target.value },
|
redo: { size: e.target.value },
|
||||||
message: `Edit table field size to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -121,10 +133,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
)}
|
)}
|
||||||
{hasPrecision(data.type) && (
|
{hasPrecision(data.type) && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold">Precision</div>
|
<div className="font-semibold">{t("precision")}</div>
|
||||||
<Input
|
<Input
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
placeholder="Set precision: size, d"
|
placeholder={t("set_precision")}
|
||||||
validateStatus={
|
validateStatus={
|
||||||
!data.size || /^\d+,\s*\d+$|^$/.test(data.size)
|
!data.size || /^\d+,\s*\d+$|^$/.test(data.size)
|
||||||
? "default"
|
? "default"
|
||||||
@ -145,7 +157,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { size: e.target.value },
|
redo: { size: e.target.value },
|
||||||
message: `Edit table field precision to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -155,10 +170,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
)}
|
)}
|
||||||
{hasCheck(data.type) && (
|
{hasCheck(data.type) && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold">Check Expression</div>
|
<div className="font-semibold">{t("check")}</div>
|
||||||
<Input
|
<Input
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
placeholder="Set constraint"
|
placeholder={t("check")}
|
||||||
value={data.check}
|
value={data.check}
|
||||||
disabled={data.increment}
|
disabled={data.increment}
|
||||||
onChange={(value) => updateField(tid, index, { check: value })}
|
onChange={(value) => updateField(tid, index, { check: value })}
|
||||||
@ -175,19 +190,20 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { check: e.target.value },
|
redo: { check: e.target.value },
|
||||||
message: `Edit table field check expression to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-xs mt-1">
|
<div className="text-xs mt-1">{t("this_will_appear_as_is")}</div>
|
||||||
*This will appear in the script as is.
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-between items-center my-3">
|
<div className="flex justify-between items-center my-3">
|
||||||
<div className="font-medium">Unique</div>
|
<div className="font-medium">{t("unique")}</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="unique"
|
value="unique"
|
||||||
checked={data.unique}
|
checked={data.unique}
|
||||||
@ -216,7 +232,7 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center my-3">
|
<div className="flex justify-between items-center my-3">
|
||||||
<div className="font-medium">Autoincrement</div>
|
<div className="font-medium">{t("autoincrement")}</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="increment"
|
value="increment"
|
||||||
checked={data.increment}
|
checked={data.increment}
|
||||||
@ -242,9 +258,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
redo: {
|
redo: {
|
||||||
[checkedValues.target.value]: checkedValues.target.checked,
|
[checkedValues.target.value]: checkedValues.target.checked,
|
||||||
},
|
},
|
||||||
message: `Edit table field to${
|
message: t("edit_table", {
|
||||||
data.increment ? " not" : ""
|
tableName: tables[tid].name,
|
||||||
} auto increment`,
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -255,10 +272,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold">Comment</div>
|
<div className="font-semibold">{t("comment")}</div>
|
||||||
<TextArea
|
<TextArea
|
||||||
className="my-2"
|
className="my-2"
|
||||||
placeholder="Add comment"
|
placeholder={t("comment")}
|
||||||
value={data.comment}
|
value={data.comment}
|
||||||
autosize
|
autosize
|
||||||
rows={2}
|
rows={2}
|
||||||
@ -276,7 +293,10 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { comment: e.target.value },
|
redo: { comment: e.target.value },
|
||||||
message: `Edit field comment to "${e.target.value}"`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -288,7 +308,7 @@ export default function FieldDetails({ data, tid, index }) {
|
|||||||
block
|
block
|
||||||
onClick={() => deleteField(data, tid)}
|
onClick={() => deleteField(data, tid)}
|
||||||
>
|
>
|
||||||
Delete field
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,15 +2,17 @@ import { Action, ObjectType } from "../../../data/constants";
|
|||||||
import { Input, Button, Popover, Checkbox, Select } from "@douyinfe/semi-ui";
|
import { Input, Button, Popover, Checkbox, Select } from "@douyinfe/semi-ui";
|
||||||
import { IconMore, IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconMore, IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { useTables, useUndoRedo } from "../../../hooks";
|
import { useTables, useUndoRedo } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function IndexDetails({ data, fields, iid, tid }) {
|
export default function IndexDetails({ data, fields, iid, tid }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { tables, updateTable } = useTables();
|
const { tables, updateTable } = useTables();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<Select
|
<Select
|
||||||
placeholder="Select fields"
|
placeholder={t("select_fields")}
|
||||||
multiple
|
multiple
|
||||||
validateStatus={data.fields.length === 0 ? "error" : "default"}
|
validateStatus={data.fields.length === 0 ? "error" : "default"}
|
||||||
optionList={fields}
|
optionList={fields}
|
||||||
@ -33,7 +35,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
|||||||
fields: [...value],
|
fields: [...value],
|
||||||
name: `${value.join("_")}_index`,
|
name: `${value.join("_")}_index`,
|
||||||
},
|
},
|
||||||
message: `Edit index fields to "${JSON.stringify(value)}"`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[index field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -45,7 +50,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
|||||||
fields: [...value],
|
fields: [...value],
|
||||||
name: `${value.join("_")}_index`,
|
name: `${value.join("_")}_index`,
|
||||||
}
|
}
|
||||||
: index
|
: index,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -53,10 +58,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
|||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<div className="px-1 popover-theme">
|
<div className="px-1 popover-theme">
|
||||||
<div className="font-semibold mb-1">Index name: </div>
|
<div className="font-semibold mb-1">{t("name")}: </div>
|
||||||
<Input value={data.name} placeholder="Index name" disabled />
|
<Input value={data.name} placeholder={t("name")} disabled />
|
||||||
<div className="flex justify-between items-center my-3">
|
<div className="flex justify-between items-center my-3">
|
||||||
<div className="font-medium">Unique</div>
|
<div className="font-medium">{t("unique")}</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value="unique"
|
value="unique"
|
||||||
checked={data.unique}
|
checked={data.unique}
|
||||||
@ -77,9 +82,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
|||||||
[checkedValues.target.value]:
|
[checkedValues.target.value]:
|
||||||
checkedValues.target.checked,
|
checkedValues.target.checked,
|
||||||
},
|
},
|
||||||
message: `Edit table field to${
|
message: t("edit_table", {
|
||||||
data.unique ? " not" : ""
|
tableName: tables[tid].name,
|
||||||
} unique`,
|
extra: "[index field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -91,7 +97,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
|||||||
[checkedValues.target.value]:
|
[checkedValues.target.value]:
|
||||||
checkedValues.target.checked,
|
checkedValues.target.checked,
|
||||||
}
|
}
|
||||||
: index
|
: index,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -110,7 +116,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
|||||||
component: "index_delete",
|
component: "index_delete",
|
||||||
tid: tid,
|
tid: tid,
|
||||||
data: data,
|
data: data,
|
||||||
message: `Delete index`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[delete index]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
|
@ -3,10 +3,12 @@ import { useSelect } from "../../../hooks";
|
|||||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||||
import { IconSearch } from "@douyinfe/semi-icons";
|
import { IconSearch } from "@douyinfe/semi-icons";
|
||||||
import { ObjectType } from "../../../data/constants";
|
import { ObjectType } from "../../../data/constants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function SearchBar({ tables }) {
|
export default function SearchBar({ tables }) {
|
||||||
const { setSelectedElement } = useSelect();
|
const { setSelectedElement } = useSelect();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const { t } = useTranslation();
|
||||||
const filteredTable = useMemo(
|
const filteredTable = useMemo(
|
||||||
() => tables.map((t) => t.name).filter((i) => i.includes(searchText)),
|
() => tables.map((t) => t.name).filter((i) => i.includes(searchText)),
|
||||||
[tables, searchText],
|
[tables, searchText],
|
||||||
@ -18,8 +20,8 @@ export default function SearchBar({ tables }) {
|
|||||||
value={searchText}
|
value={searchText}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder={t("search")}
|
||||||
emptyContent={<div className="p-3 popover-theme">No tables found</div>}
|
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||||
onChange={(v) => setSearchText(v)}
|
onChange={(v) => setSearchText(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
const { id } = tables.find((t) => t.name === v);
|
const { id } = tables.find((t) => t.name === v);
|
||||||
|
@ -5,10 +5,13 @@ import { getSize, hasCheck, hasPrecision, isSized } from "../../../utils/toSQL";
|
|||||||
import { useTables, useTypes, useUndoRedo } from "../../../hooks";
|
import { useTables, useTypes, useUndoRedo } from "../../../hooks";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import FieldDetails from "./FieldDetails";
|
import FieldDetails from "./FieldDetails";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TableField({ data, tid, index }) {
|
export default function TableField({ data, tid, index }) {
|
||||||
const { updateField } = useTables();
|
const { updateField } = useTables();
|
||||||
const { types } = useTypes();
|
const { types } = useTypes();
|
||||||
|
const { tables } = useTables();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
|
||||||
@ -33,7 +36,10 @@ export default function TableField({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { name: e.target.value },
|
redo: { name: e.target.value },
|
||||||
message: `Edit table field name to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -69,7 +75,10 @@ export default function TableField({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: { type: data.type },
|
undo: { type: data.type },
|
||||||
redo: { type: value },
|
redo: { type: value },
|
||||||
message: `Edit table field type to ${value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -123,7 +132,7 @@ export default function TableField({ data, tid, index }) {
|
|||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<Button
|
<Button
|
||||||
type={data.notNull ? "primary" : "tertiary"}
|
type={data.notNull ? "primary" : "tertiary"}
|
||||||
title="Not Null"
|
title={t("not_null")}
|
||||||
theme={data.notNull ? "solid" : "light"}
|
theme={data.notNull ? "solid" : "light"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
@ -136,9 +145,10 @@ export default function TableField({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: { notNull: data.notNull },
|
undo: { notNull: data.notNull },
|
||||||
redo: { notNull: !data.notNull },
|
redo: { notNull: !data.notNull },
|
||||||
message: `Edit table field to${
|
message: t("edit_table", {
|
||||||
data.notNull ? "" : " not"
|
tableName: tables[tid].name,
|
||||||
} null`,
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -151,7 +161,7 @@ export default function TableField({ data, tid, index }) {
|
|||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<Button
|
<Button
|
||||||
type={data.primary ? "primary" : "tertiary"}
|
type={data.primary ? "primary" : "tertiary"}
|
||||||
title="Primary"
|
title={t("primary")}
|
||||||
theme={data.primary ? "solid" : "light"}
|
theme={data.primary ? "solid" : "light"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
@ -164,9 +174,10 @@ export default function TableField({ data, tid, index }) {
|
|||||||
fid: index,
|
fid: index,
|
||||||
undo: { primary: data.primary },
|
undo: { primary: data.primary },
|
||||||
redo: { primary: !data.primary },
|
redo: { primary: !data.primary },
|
||||||
message: `Edit table field to${
|
message: t("edit_table", {
|
||||||
data.primary ? " not" : ""
|
tableName: tables[tid].name,
|
||||||
} primary`,
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
|
@ -8,16 +8,17 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Popover,
|
Popover,
|
||||||
Toast,
|
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { useTables, useUndoRedo } from "../../../hooks";
|
import { useTables, useUndoRedo } from "../../../hooks";
|
||||||
import { Action, ObjectType, defaultBlue } from "../../../data/constants";
|
import { Action, ObjectType, defaultBlue } from "../../../data/constants";
|
||||||
import ColorPalette from "../../ColorPalette";
|
import ColorPalette from "../../ColorPicker";
|
||||||
import TableField from "./TableField";
|
import TableField from "./TableField";
|
||||||
import IndexDetails from "./IndexDetails";
|
import IndexDetails from "./IndexDetails";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TableInfo({ data }) {
|
export default function TableInfo({ data }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [indexActiveKey, setIndexActiveKey] = useState("");
|
const [indexActiveKey, setIndexActiveKey] = useState("");
|
||||||
const { deleteTable, updateTable, updateField, setRelationships } =
|
const { deleteTable, updateTable, updateField, setRelationships } =
|
||||||
useTables();
|
useTables();
|
||||||
@ -31,11 +32,11 @@ export default function TableInfo({ data }) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center mb-2.5">
|
<div className="flex items-center mb-2.5">
|
||||||
<div className="text-md font-semibold">Name: </div>
|
<div className="text-md font-semibold break-keep">{t("name")}: </div>
|
||||||
<Input
|
<Input
|
||||||
value={data.name}
|
value={data.name}
|
||||||
validateStatus={data.name === "" ? "error" : "default"}
|
validateStatus={data.name === "" ? "error" : "default"}
|
||||||
placeholder="Name"
|
placeholder={t("name")}
|
||||||
className="ms-2"
|
className="ms-2"
|
||||||
onChange={(value) => updateTable(data.id, { name: value })}
|
onChange={(value) => updateTable(data.id, { name: value })}
|
||||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||||
@ -50,7 +51,10 @@ export default function TableInfo({ data }) {
|
|||||||
tid: data.id,
|
tid: data.id,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { name: e.target.value },
|
redo: { name: e.target.value },
|
||||||
message: `Edit table name to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: e.target.value,
|
||||||
|
extra: "[name]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -149,7 +153,7 @@ export default function TableInfo({ data }) {
|
|||||||
onChange={(itemKey) => setIndexActiveKey(itemKey)}
|
onChange={(itemKey) => setIndexActiveKey(itemKey)}
|
||||||
accordion
|
accordion
|
||||||
>
|
>
|
||||||
<Collapse.Panel header="Indices" itemKey="1">
|
<Collapse.Panel header={t("indices")} itemKey="1">
|
||||||
{data.indices.map((idx, k) => (
|
{data.indices.map((idx, k) => (
|
||||||
<IndexDetails
|
<IndexDetails
|
||||||
key={"index_" + k}
|
key={"index_" + k}
|
||||||
@ -172,12 +176,12 @@ export default function TableInfo({ data }) {
|
|||||||
headerLine={false}
|
headerLine={false}
|
||||||
>
|
>
|
||||||
<Collapse keepDOM lazyRender>
|
<Collapse keepDOM lazyRender>
|
||||||
<Collapse.Panel header="Comment" itemKey="1">
|
<Collapse.Panel header={t("comment")} itemKey="1">
|
||||||
<TextArea
|
<TextArea
|
||||||
field="comment"
|
field="comment"
|
||||||
value={data.comment}
|
value={data.comment}
|
||||||
autosize
|
autosize
|
||||||
placeholder="Add comment"
|
placeholder={t("comment")}
|
||||||
rows={1}
|
rows={1}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateTable(data.id, { comment: value }, false)
|
updateTable(data.id, { comment: value }, false)
|
||||||
@ -194,7 +198,10 @@ export default function TableInfo({ data }) {
|
|||||||
tid: data.id,
|
tid: data.id,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { comment: e.target.value },
|
redo: { comment: e.target.value },
|
||||||
message: `Edit table comment to ${e.target.value}`,
|
message: t("edit_table", {
|
||||||
|
tableName: e.target.value,
|
||||||
|
extra: "[comment]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -220,7 +227,10 @@ export default function TableInfo({ data }) {
|
|||||||
tid: data.id,
|
tid: data.id,
|
||||||
undo: { color: data.color },
|
undo: { color: data.color },
|
||||||
redo: { color: defaultBlue },
|
redo: { color: defaultBlue },
|
||||||
message: `Edit table color to default`,
|
message: t("edit_table", {
|
||||||
|
tableName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -236,7 +246,10 @@ export default function TableInfo({ data }) {
|
|||||||
tid: data.id,
|
tid: data.id,
|
||||||
undo: { color: data.color },
|
undo: { color: data.color },
|
||||||
redo: { color: c },
|
redo: { color: c },
|
||||||
message: `Edit table color to ${c}`,
|
message: t("edit_table", {
|
||||||
|
tableName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -267,7 +280,10 @@ export default function TableInfo({ data }) {
|
|||||||
element: ObjectType.TABLE,
|
element: ObjectType.TABLE,
|
||||||
component: "index_add",
|
component: "index_add",
|
||||||
tid: data.id,
|
tid: data.id,
|
||||||
message: `Add index`,
|
message: t("edit_table", {
|
||||||
|
tableName: data.name,
|
||||||
|
extra: "[add index]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -284,7 +300,7 @@ export default function TableInfo({ data }) {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add index
|
{t("add_index")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
@ -297,7 +313,10 @@ export default function TableInfo({ data }) {
|
|||||||
element: ObjectType.TABLE,
|
element: ObjectType.TABLE,
|
||||||
component: "field_add",
|
component: "field_add",
|
||||||
tid: data.id,
|
tid: data.id,
|
||||||
message: `Add field`,
|
message: t("edit_table", {
|
||||||
|
tableName: data.name,
|
||||||
|
extra: "[add field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -321,17 +340,14 @@ export default function TableInfo({ data }) {
|
|||||||
}}
|
}}
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Add field
|
{t("add_field")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<Button
|
<Button
|
||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => {
|
onClick={() => deleteTable(data.id)}
|
||||||
Toast.success(`Table deleted!`);
|
|
||||||
deleteTable(data.id);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -5,10 +5,12 @@ import { ObjectType } from "../../../data/constants";
|
|||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import Empty from "../Empty";
|
import Empty from "../Empty";
|
||||||
import TableInfo from "./TableInfo";
|
import TableInfo from "./TableInfo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TablesTab() {
|
export default function TablesTab() {
|
||||||
const { tables, addTable } = useTables();
|
const { tables, addTable } = useTables();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -18,12 +20,12 @@ export default function TablesTab() {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Button icon={<IconPlus />} block onClick={() => addTable(true)}>
|
<Button icon={<IconPlus />} block onClick={() => addTable(true)}>
|
||||||
Add table
|
{t("add_table")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{tables.length === 0 ? (
|
{tables.length === 0 ? (
|
||||||
<Empty title="No tables" text="Start building your diagram!" />
|
<Empty title={t("no_tables")} text={t("no_tables_text")} />
|
||||||
) : (
|
) : (
|
||||||
<Collapse
|
<Collapse
|
||||||
activeKey={
|
activeKey={
|
||||||
|
@ -3,11 +3,13 @@ import { AutoComplete } from "@douyinfe/semi-ui";
|
|||||||
import { IconSearch } from "@douyinfe/semi-icons";
|
import { IconSearch } from "@douyinfe/semi-icons";
|
||||||
import { useSelect, useTypes } from "../../../hooks";
|
import { useSelect, useTypes } from "../../../hooks";
|
||||||
import { ObjectType } from "../../../data/constants";
|
import { ObjectType } from "../../../data/constants";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Searchbar() {
|
export default function Searchbar() {
|
||||||
const { types } = useTypes();
|
const { types } = useTypes();
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
const { setSelectedElement } = useSelect();
|
const { setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [filteredResult, setFilteredResult] = useState(
|
const [filteredResult, setFilteredResult] = useState(
|
||||||
types.map((t) => t.name),
|
types.map((t) => t.name),
|
||||||
@ -25,9 +27,9 @@ export default function Searchbar() {
|
|||||||
value={value}
|
value={value}
|
||||||
showClear
|
showClear
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="Search..."
|
placeholder={t("search")}
|
||||||
onSearch={(v) => handleStringSearch(v)}
|
onSearch={(v) => handleStringSearch(v)}
|
||||||
emptyContent={<div className="p-3 popover-theme">No types found</div>}
|
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||||
onChange={(v) => setValue(v)}
|
onChange={(v) => setValue(v)}
|
||||||
onSelect={(v) => {
|
onSelect={(v) => {
|
||||||
const i = types.findIndex((t) => t.name === v);
|
const i = types.findIndex((t) => t.name === v);
|
||||||
|
@ -13,22 +13,25 @@ import {
|
|||||||
import { IconDeleteStroked, IconMore } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked, IconMore } from "@douyinfe/semi-icons";
|
||||||
import { isSized, hasPrecision, getSize } from "../../../utils/toSQL";
|
import { isSized, hasPrecision, getSize } from "../../../utils/toSQL";
|
||||||
import { useUndoRedo, useTypes } from "../../../hooks";
|
import { useUndoRedo, useTypes } from "../../../hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TypeField({ data, tid, fid }) {
|
export default function TypeField({ data, tid, fid }) {
|
||||||
const { types, updateType } = useTypes();
|
const { types, updateType } = useTypes();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={6} className="hover-1 my-2">
|
<Row gutter={6} className="hover-1 my-2">
|
||||||
<Col span={10}>
|
<Col span={10}>
|
||||||
<Input
|
<Input
|
||||||
value={data.name}
|
value={data.name}
|
||||||
validateStatus={data.name === "" ? "error" : "default"}
|
validateStatus={data.name === "" ? "error" : "default"}
|
||||||
placeholder="Name"
|
placeholder={t("name")}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateType(tid, {
|
updateType(tid, {
|
||||||
fields: types[tid].fields.map((e, id) =>
|
fields: types[tid].fields.map((e, id) =>
|
||||||
id === fid ? { ...data, name: value } : e
|
id === fid ? { ...data, name: value } : e,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -45,7 +48,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
fid: fid,
|
fid: fid,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { name: e.target.value },
|
redo: { name: e.target.value },
|
||||||
message: `Edit type field name to ${e.target.value}`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -62,7 +68,7 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
})),
|
})),
|
||||||
...types
|
...types
|
||||||
.filter(
|
.filter(
|
||||||
(type) => type.name.toLowerCase() !== data.name.toLowerCase()
|
(type) => type.name.toLowerCase() !== data.name.toLowerCase(),
|
||||||
)
|
)
|
||||||
.map((type) => ({
|
.map((type) => ({
|
||||||
label: type.name.toUpperCase(),
|
label: type.name.toUpperCase(),
|
||||||
@ -72,7 +78,7 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
filter
|
filter
|
||||||
value={data.type}
|
value={data.type}
|
||||||
validateStatus={data.type === "" ? "error" : "default"}
|
validateStatus={data.type === "" ? "error" : "default"}
|
||||||
placeholder="Type"
|
placeholder={t("type")}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value === data.type) return;
|
if (value === data.type) return;
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
@ -85,7 +91,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
fid: fid,
|
fid: fid,
|
||||||
undo: { type: data?.type },
|
undo: { type: data?.type },
|
||||||
redo: { type: value },
|
redo: { type: value },
|
||||||
message: `Edit type field type to ${value}`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -98,7 +107,7 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
type: value,
|
type: value,
|
||||||
values: data.values ? [...data.values] : [],
|
values: data.values ? [...data.values] : [],
|
||||||
}
|
}
|
||||||
: e
|
: e,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else if (isSized(value) || hasPrecision(value)) {
|
} else if (isSized(value) || hasPrecision(value)) {
|
||||||
@ -106,13 +115,13 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
fields: types[tid].fields.map((e, id) =>
|
fields: types[tid].fields.map((e, id) =>
|
||||||
id === fid
|
id === fid
|
||||||
? { ...data, type: value, size: getSize(value) }
|
? { ...data, type: value, size: getSize(value) }
|
||||||
: e
|
: e,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
updateType(tid, {
|
updateType(tid, {
|
||||||
fields: types[tid].fields.map((e, id) =>
|
fields: types[tid].fields.map((e, id) =>
|
||||||
id === fid ? { ...data, type: value } : e
|
id === fid ? { ...data, type: value } : e,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -125,7 +134,9 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
<div className="popover-theme w-[240px]">
|
<div className="popover-theme w-[240px]">
|
||||||
{(data.type === "ENUM" || data.type === "SET") && (
|
{(data.type === "ENUM" || data.type === "SET") && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold mb-1">{data.type} values</div>
|
<div className="font-semibold mb-1">
|
||||||
|
{data.type} {t("values")}
|
||||||
|
</div>
|
||||||
<TagInput
|
<TagInput
|
||||||
separator={[",", ", ", " ,"]}
|
separator={[",", ", ", " ,"]}
|
||||||
value={data.values}
|
value={data.values}
|
||||||
@ -135,11 +146,11 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
: "default"
|
: "default"
|
||||||
}
|
}
|
||||||
className="my-2"
|
className="my-2"
|
||||||
placeholder="Use ',' for batch input"
|
placeholder={t("use_for_batch_input")}
|
||||||
onChange={(v) =>
|
onChange={(v) =>
|
||||||
updateType(tid, {
|
updateType(tid, {
|
||||||
fields: types[tid].fields.map((e, id) =>
|
fields: types[tid].fields.map((e, id) =>
|
||||||
id === fid ? { ...data, values: v } : e
|
id === fid ? { ...data, values: v } : e,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -160,9 +171,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
fid: fid,
|
fid: fid,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { values: data.values },
|
redo: { values: data.values },
|
||||||
message: `Edit type field values to "${JSON.stringify(
|
message: t("edit_type", {
|
||||||
data.values
|
typeName: data.name,
|
||||||
)}"`,
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -172,15 +184,15 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
)}
|
)}
|
||||||
{isSized(data.type) && (
|
{isSized(data.type) && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold">Size</div>
|
<div className="font-semibold">{t("size")}</div>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
placeholder="Set length"
|
placeholder={t("size")}
|
||||||
value={data.size}
|
value={data.size}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateType(tid, {
|
updateType(tid, {
|
||||||
fields: types[tid].fields.map((e, id) =>
|
fields: types[tid].fields.map((e, id) =>
|
||||||
id === fid ? { ...data, size: value } : e
|
id === fid ? { ...data, size: value } : e,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -197,7 +209,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
fid: fid,
|
fid: fid,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { size: e.target.value },
|
redo: { size: e.target.value },
|
||||||
message: `Edit type field size to ${e.target.value}`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -207,10 +222,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
)}
|
)}
|
||||||
{hasPrecision(data.type) && (
|
{hasPrecision(data.type) && (
|
||||||
<>
|
<>
|
||||||
<div className="font-semibold">Precision</div>
|
<div className="font-semibold">{t("precision")}</div>
|
||||||
<Input
|
<Input
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
placeholder="Set precision: (size, d)"
|
placeholder={t("set_precision")}
|
||||||
validateStatus={
|
validateStatus={
|
||||||
/^\(\d+,\s*\d+\)$|^$/.test(data.size)
|
/^\(\d+,\s*\d+\)$|^$/.test(data.size)
|
||||||
? "default"
|
? "default"
|
||||||
@ -220,7 +235,7 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateType(tid, {
|
updateType(tid, {
|
||||||
fields: types[tid].fields.map((e, id) =>
|
fields: types[tid].fields.map((e, id) =>
|
||||||
id === fid ? { ...data, size: value } : e
|
id === fid ? { ...data, size: value } : e,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -237,7 +252,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
fid: fid,
|
fid: fid,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { size: e.target.value },
|
redo: { size: e.target.value },
|
||||||
message: `Edit type field precision to ${e.target.value}`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -259,7 +277,10 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
tid: tid,
|
tid: tid,
|
||||||
fid: fid,
|
fid: fid,
|
||||||
data: data,
|
data: data,
|
||||||
message: `Delete field`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[delete field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
updateType(tid, {
|
updateType(tid, {
|
||||||
@ -267,7 +288,7 @@ export default function TypeField({ data, tid, fid }) {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete field
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,17 @@ import {
|
|||||||
TextArea,
|
TextArea,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Toast,
|
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import { IconDeleteStroked, IconPlus } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked, IconPlus } from "@douyinfe/semi-icons";
|
||||||
import { useUndoRedo, useTypes } from "../../../hooks";
|
import { useUndoRedo, useTypes } from "../../../hooks";
|
||||||
import TypeField from "./TypeField";
|
import TypeField from "./TypeField";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TypeInfo({ index, data }) {
|
export default function TypeInfo({ index, data }) {
|
||||||
const { deleteType, updateType } = useTypes();
|
const { deleteType, updateType } = useTypes();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={`scroll_type_${index}`}>
|
<div id={`scroll_type_${index}`}>
|
||||||
@ -30,11 +31,11 @@ export default function TypeInfo({ index, data }) {
|
|||||||
itemKey={`${index}`}
|
itemKey={`${index}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center mb-2.5">
|
<div className="flex items-center mb-2.5">
|
||||||
<div className="text-md font-semibold">Name: </div>
|
<div className="text-md font-semibold break-keep">{t("name")}: </div>
|
||||||
<Input
|
<Input
|
||||||
value={data.name}
|
value={data.name}
|
||||||
validateStatus={data.name === "" ? "error" : "default"}
|
validateStatus={data.name === "" ? "error" : "default"}
|
||||||
placeholder="Name"
|
placeholder={t("name")}
|
||||||
className="ms-2"
|
className="ms-2"
|
||||||
onChange={(value) => updateType(index, { name: value })}
|
onChange={(value) => updateType(index, { name: value })}
|
||||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||||
@ -49,7 +50,10 @@ export default function TypeInfo({ index, data }) {
|
|||||||
tid: index,
|
tid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { name: e.target.value },
|
redo: { name: e.target.value },
|
||||||
message: `Edit type name to ${e.target.value}`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[name]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -65,12 +69,12 @@ export default function TypeInfo({ index, data }) {
|
|||||||
headerLine={false}
|
headerLine={false}
|
||||||
>
|
>
|
||||||
<Collapse keepDOM lazyRender>
|
<Collapse keepDOM lazyRender>
|
||||||
<Collapse.Panel header="Comment" itemKey="1">
|
<Collapse.Panel header={t("comment")} itemKey="1">
|
||||||
<TextArea
|
<TextArea
|
||||||
field="comment"
|
field="comment"
|
||||||
value={data.comment}
|
value={data.comment}
|
||||||
autosize
|
autosize
|
||||||
placeholder="Add comment"
|
placeholder={t("comment")}
|
||||||
rows={1}
|
rows={1}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateType(index, { comment: value }, false)
|
updateType(index, { comment: value }, false)
|
||||||
@ -87,7 +91,10 @@ export default function TypeInfo({ index, data }) {
|
|||||||
tid: index,
|
tid: index,
|
||||||
undo: editField,
|
undo: editField,
|
||||||
redo: { comment: e.target.value },
|
redo: { comment: e.target.value },
|
||||||
message: `Edit type comment to ${e.target.value}`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[comment]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -108,7 +115,10 @@ export default function TypeInfo({ index, data }) {
|
|||||||
element: ObjectType.TYPE,
|
element: ObjectType.TYPE,
|
||||||
component: "field_add",
|
component: "field_add",
|
||||||
tid: index,
|
tid: index,
|
||||||
message: `Add field to type`,
|
message: t("edit_type", {
|
||||||
|
typeName: data.name,
|
||||||
|
extra: "[add field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -124,20 +134,17 @@ export default function TypeInfo({ index, data }) {
|
|||||||
}}
|
}}
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Add field
|
{t("add_field")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Button
|
<Button
|
||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => {
|
onClick={() => deleteType(index)}
|
||||||
Toast.success(`Type deleted!`);
|
|
||||||
deleteType(index);
|
|
||||||
}}
|
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Delete
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -5,10 +5,12 @@ import { ObjectType } from "../../../data/constants";
|
|||||||
import Searchbar from "./SearchBar";
|
import Searchbar from "./SearchBar";
|
||||||
import Empty from "../Empty";
|
import Empty from "../Empty";
|
||||||
import TypeInfo from "./TypeInfo";
|
import TypeInfo from "./TypeInfo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TypesTab() {
|
export default function TypesTab() {
|
||||||
const { types, addType } = useTypes();
|
const { types, addType } = useTypes();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -18,30 +20,18 @@ export default function TypesTab() {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Button icon={<IconPlus />} block onClick={() => addType(true)}>
|
<Button icon={<IconPlus />} block onClick={() => addType(true)}>
|
||||||
Add type
|
{t("add_type")}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<div className="w-[240px] text-sm space-y-2 popover-theme">
|
<div className="w-[240px] text-sm space-y-2 popover-theme">
|
||||||
<div>
|
{t("types_info")
|
||||||
This feature is meant for object-relational DBMSs like{" "}
|
.split("\n")
|
||||||
<strong>PostgreSQL</strong>.
|
.map((line, index) => (
|
||||||
</div>
|
<div key={index}>{line}</div>
|
||||||
<div>
|
))}
|
||||||
If used for <strong>MySQL</strong> or <strong>MariaDB</strong>{" "}
|
|
||||||
a <code>JSON</code> type will be generated with the
|
|
||||||
corresponding json validation check.
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
If used for <strong>SQLite</strong> it will be translated to a{" "}
|
|
||||||
<code>BLOB</code>.
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
If used for <strong>MSSQL</strong> a type alias to the first
|
|
||||||
field will be generated.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
showArrow
|
showArrow
|
||||||
@ -52,7 +42,7 @@ export default function TypesTab() {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{types.length <= 0 ? (
|
{types.length <= 0 ? (
|
||||||
<Empty title="No types" text="Make your own custom data types" />
|
<Empty title={t("no_types")} text={t("no_types_text")} />
|
||||||
) : (
|
) : (
|
||||||
<Collapse
|
<Collapse
|
||||||
activeKey={
|
activeKey={
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { Divider, Tooltip } from "@douyinfe/semi-ui";
|
import { Divider, Tooltip } from "@douyinfe/semi-ui";
|
||||||
import { useTransform, useLayout } from "../hooks";
|
import { useTransform, useLayout } from "../hooks";
|
||||||
import { exitFullscreen } from "../utils/fullscreen";
|
import { exitFullscreen } from "../utils/fullscreen";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function FloatingControls() {
|
export default function FloatingControls() {
|
||||||
const { transform, setTransform } = useTransform();
|
const { transform, setTransform } = useTransform();
|
||||||
const { setLayout } = useLayout();
|
const { setLayout } = useLayout();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@ -35,7 +37,7 @@ export default function FloatingControls() {
|
|||||||
<i className="bi bi-plus-lg" />
|
<i className="bi bi-plus-lg" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip content="Exit">
|
<Tooltip content={t("exit")}>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-2 rounded-lg popover-theme"
|
className="px-3 py-2 rounded-lg popover-theme"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -3,10 +3,13 @@ import { Action, ObjectType, defaultBlue } from "../data/constants";
|
|||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
import useTransform from "../hooks/useTransform";
|
import useTransform from "../hooks/useTransform";
|
||||||
import useSelect from "../hooks/useSelect";
|
import useSelect from "../hooks/useSelect";
|
||||||
|
import { Toast } from "@douyinfe/semi-ui";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const AreasContext = createContext(null);
|
export const AreasContext = createContext(null);
|
||||||
|
|
||||||
export default function AreasContextProvider({ children }) {
|
export default function AreasContextProvider({ children }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [areas, setAreas] = useState([]);
|
const [areas, setAreas] = useState([]);
|
||||||
const { transform } = useTransform();
|
const { transform } = useTransform();
|
||||||
const { selectedElement, setSelectedElement } = useSelect();
|
const { selectedElement, setSelectedElement } = useSelect();
|
||||||
@ -39,7 +42,7 @@ export default function AreasContextProvider({ children }) {
|
|||||||
{
|
{
|
||||||
action: Action.ADD,
|
action: Action.ADD,
|
||||||
element: ObjectType.AREA,
|
element: ObjectType.AREA,
|
||||||
message: `Add new subject area`,
|
message: t("add_area"),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -48,19 +51,20 @@ export default function AreasContextProvider({ children }) {
|
|||||||
|
|
||||||
const deleteArea = (id, addToHistory = true) => {
|
const deleteArea = (id, addToHistory = true) => {
|
||||||
if (addToHistory) {
|
if (addToHistory) {
|
||||||
|
Toast.success(t("area_deleted"));
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
action: Action.DELETE,
|
action: Action.DELETE,
|
||||||
element: ObjectType.AREA,
|
element: ObjectType.AREA,
|
||||||
data: areas[id],
|
data: areas[id],
|
||||||
message: `Delete subject area`,
|
message: t("delete_area", areas[id].name),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
}
|
}
|
||||||
setAreas((prev) =>
|
setAreas((prev) =>
|
||||||
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
|
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i })),
|
||||||
);
|
);
|
||||||
if (id === selectedElement.id) {
|
if (id === selectedElement.id) {
|
||||||
setSelectedElement((prev) => ({
|
setSelectedElement((prev) => ({
|
||||||
@ -82,7 +86,7 @@ export default function AreasContextProvider({ children }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,10 +3,13 @@ import useTransform from "../hooks/useTransform";
|
|||||||
import { Action, ObjectType, defaultNoteTheme } from "../data/constants";
|
import { Action, ObjectType, defaultNoteTheme } from "../data/constants";
|
||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
import useSelect from "../hooks/useSelect";
|
import useSelect from "../hooks/useSelect";
|
||||||
|
import { Toast } from "@douyinfe/semi-ui";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const NotesContext = createContext(null);
|
export const NotesContext = createContext(null);
|
||||||
|
|
||||||
export default function NotesContextProvider({ children }) {
|
export default function NotesContextProvider({ children }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [notes, setNotes] = useState([]);
|
const [notes, setNotes] = useState([]);
|
||||||
const { transform } = useTransform();
|
const { transform } = useTransform();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
@ -39,7 +42,7 @@ export default function NotesContextProvider({ children }) {
|
|||||||
{
|
{
|
||||||
action: Action.ADD,
|
action: Action.ADD,
|
||||||
element: ObjectType.NOTE,
|
element: ObjectType.NOTE,
|
||||||
message: `Add new note`,
|
message: t("add_note"),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -48,19 +51,20 @@ export default function NotesContextProvider({ children }) {
|
|||||||
|
|
||||||
const deleteNote = (id, addToHistory = true) => {
|
const deleteNote = (id, addToHistory = true) => {
|
||||||
if (addToHistory) {
|
if (addToHistory) {
|
||||||
|
Toast.success(t("note_deleted"));
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
action: Action.DELETE,
|
action: Action.DELETE,
|
||||||
element: ObjectType.NOTE,
|
element: ObjectType.NOTE,
|
||||||
data: notes[id],
|
data: notes[id],
|
||||||
message: `Delete note`,
|
message: t("delete_note", { noteTitle: notes[id].title }),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
}
|
}
|
||||||
setNotes((prev) =>
|
setNotes((prev) =>
|
||||||
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
|
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i })),
|
||||||
);
|
);
|
||||||
if (id === selectedElement.id) {
|
if (id === selectedElement.id) {
|
||||||
setSelectedElement((prev) => ({
|
setSelectedElement((prev) => ({
|
||||||
@ -82,7 +86,7 @@ export default function NotesContextProvider({ children }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,10 +3,13 @@ import { Action, ObjectType, defaultBlue } from "../data/constants";
|
|||||||
import useTransform from "../hooks/useTransform";
|
import useTransform from "../hooks/useTransform";
|
||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
import useSelect from "../hooks/useSelect";
|
import useSelect from "../hooks/useSelect";
|
||||||
|
import { Toast } from "@douyinfe/semi-ui";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const TablesContext = createContext(null);
|
export const TablesContext = createContext(null);
|
||||||
|
|
||||||
export default function TablesContextProvider({ children }) {
|
export default function TablesContextProvider({ children }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [tables, setTables] = useState([]);
|
const [tables, setTables] = useState([]);
|
||||||
const [relationships, setRelationships] = useState([]);
|
const [relationships, setRelationships] = useState([]);
|
||||||
const { transform } = useTransform();
|
const { transform } = useTransform();
|
||||||
@ -55,7 +58,7 @@ export default function TablesContextProvider({ children }) {
|
|||||||
{
|
{
|
||||||
action: Action.ADD,
|
action: Action.ADD,
|
||||||
element: ObjectType.TABLE,
|
element: ObjectType.TABLE,
|
||||||
message: `Add new table`,
|
message: t("add_table"),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -64,13 +67,14 @@ export default function TablesContextProvider({ children }) {
|
|||||||
|
|
||||||
const deleteTable = (id, addToHistory = true) => {
|
const deleteTable = (id, addToHistory = true) => {
|
||||||
if (addToHistory) {
|
if (addToHistory) {
|
||||||
|
Toast.success(t("table_deleted"));
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
action: Action.DELETE,
|
action: Action.DELETE,
|
||||||
element: ObjectType.TABLE,
|
element: ObjectType.TABLE,
|
||||||
data: tables[id],
|
data: tables[id],
|
||||||
message: `Delete table`,
|
message: t("delete_table", { tableName: tables[id] }),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -106,7 +110,7 @@ export default function TablesContextProvider({ children }) {
|
|||||||
|
|
||||||
const updateTable = (id, updatedValues) => {
|
const updateTable = (id, updatedValues) => {
|
||||||
setTables((prev) =>
|
setTables((prev) =>
|
||||||
prev.map((t) => (t.id === id ? { ...t, ...updatedValues } : t))
|
prev.map((t) => (t.id === id ? { ...t, ...updatedValues } : t)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,12 +121,12 @@ export default function TablesContextProvider({ children }) {
|
|||||||
return {
|
return {
|
||||||
...table,
|
...table,
|
||||||
fields: table.fields.map((field, j) =>
|
fields: table.fields.map((field, j) =>
|
||||||
fid === j ? { ...field, ...updatedValues } : field
|
fid === j ? { ...field, ...updatedValues } : field,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return table;
|
return table;
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,7 +139,10 @@ export default function TablesContextProvider({ children }) {
|
|||||||
component: "field_delete",
|
component: "field_delete",
|
||||||
tid: tid,
|
tid: tid,
|
||||||
data: field,
|
data: field,
|
||||||
message: `Delete field`,
|
message: t("edit_table", {
|
||||||
|
tableName: tables[tid].name,
|
||||||
|
extra: "[delete field]",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -146,9 +153,9 @@ export default function TablesContextProvider({ children }) {
|
|||||||
!(
|
!(
|
||||||
(e.startTableId === tid && e.startFieldId === field.id) ||
|
(e.startTableId === tid && e.startFieldId === field.id) ||
|
||||||
(e.endTableId === tid && e.endFieldId === field.id)
|
(e.endTableId === tid && e.endFieldId === field.id)
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.map((e, i) => ({ ...e, id: i }))
|
.map((e, i) => ({ ...e, id: i })),
|
||||||
);
|
);
|
||||||
setRelationships((prev) => {
|
setRelationships((prev) => {
|
||||||
return prev.map((e) => {
|
return prev.map((e) => {
|
||||||
@ -185,7 +192,7 @@ export default function TablesContextProvider({ children }) {
|
|||||||
action: Action.ADD,
|
action: Action.ADD,
|
||||||
element: ObjectType.RELATIONSHIP,
|
element: ObjectType.RELATIONSHIP,
|
||||||
data: data,
|
data: data,
|
||||||
message: `Add new relationship`,
|
message: t("add_relationship"),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -208,13 +215,15 @@ export default function TablesContextProvider({ children }) {
|
|||||||
action: Action.DELETE,
|
action: Action.DELETE,
|
||||||
element: ObjectType.RELATIONSHIP,
|
element: ObjectType.RELATIONSHIP,
|
||||||
data: relationships[id],
|
data: relationships[id],
|
||||||
message: `Delete relationship`,
|
message: t("delete_relationship", {
|
||||||
|
refName: relationships[id].name,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
}
|
}
|
||||||
setRelationships((prev) =>
|
setRelationships((prev) =>
|
||||||
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i }))
|
prev.filter((e) => e.id !== id).map((e, i) => ({ ...e, id: i })),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { createContext, useState } from "react";
|
import { createContext, useState } from "react";
|
||||||
import { Action, ObjectType } from "../data/constants";
|
import { Action, ObjectType } from "../data/constants";
|
||||||
import useUndoRedo from "../hooks/useUndoRedo";
|
import useUndoRedo from "../hooks/useUndoRedo";
|
||||||
|
import { Toast } from "@douyinfe/semi-ui";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const TypesContext = createContext(null);
|
export const TypesContext = createContext(null);
|
||||||
|
|
||||||
export default function TypesContextProvider({ children }) {
|
export default function TypesContextProvider({ children }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [types, setTypes] = useState([]);
|
const [types, setTypes] = useState([]);
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
|
|
||||||
@ -31,7 +34,7 @@ export default function TypesContextProvider({ children }) {
|
|||||||
{
|
{
|
||||||
action: Action.ADD,
|
action: Action.ADD,
|
||||||
element: ObjectType.TYPE,
|
element: ObjectType.TYPE,
|
||||||
message: `Add new type`,
|
message: t("add_type"),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -40,6 +43,7 @@ export default function TypesContextProvider({ children }) {
|
|||||||
|
|
||||||
const deleteType = (id, addToHistory = true) => {
|
const deleteType = (id, addToHistory = true) => {
|
||||||
if (addToHistory) {
|
if (addToHistory) {
|
||||||
|
Toast.success(t("type_deleted"));
|
||||||
setUndoStack((prev) => [
|
setUndoStack((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
@ -47,7 +51,9 @@ export default function TypesContextProvider({ children }) {
|
|||||||
element: ObjectType.TYPE,
|
element: ObjectType.TYPE,
|
||||||
id: id,
|
id: id,
|
||||||
data: types[id],
|
data: types[id],
|
||||||
message: `Delete type`,
|
message: t("delete_type", {
|
||||||
|
typeName: types[id].name,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setRedoStack([]);
|
setRedoStack([]);
|
||||||
@ -57,7 +63,7 @@ export default function TypesContextProvider({ children }) {
|
|||||||
|
|
||||||
const updateType = (id, values) => {
|
const updateType = (id, values) => {
|
||||||
setTypes((prev) =>
|
setTypes((prev) =>
|
||||||
prev.map((e, i) => (i === id ? { ...e, ...values } : e))
|
prev.map((e, i) => (i === id ? { ...e, ...values } : e)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import i18n from "../i18n/i18n";
|
||||||
|
|
||||||
export const sqlDataTypes = [
|
export const sqlDataTypes = [
|
||||||
"INT",
|
"INT",
|
||||||
"SMALLINT",
|
"SMALLINT",
|
||||||
@ -55,9 +57,9 @@ export const tableFieldHeight = 36;
|
|||||||
export const tableColorStripHeight = 7;
|
export const tableColorStripHeight = 7;
|
||||||
|
|
||||||
export const Cardinality = {
|
export const Cardinality = {
|
||||||
ONE_TO_ONE: "One to one",
|
ONE_TO_ONE: i18n.t("one_to_one"),
|
||||||
ONE_TO_MANY: "One to many",
|
ONE_TO_MANY: i18n.t("one_to_many"),
|
||||||
MANY_TO_ONE: "Many to one",
|
MANY_TO_ONE: i18n.t("many_to_one"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Constraint = {
|
export const Constraint = {
|
||||||
@ -112,6 +114,7 @@ export const MODAL = {
|
|||||||
NEW: 7,
|
NEW: 7,
|
||||||
IMPORT_SRC: 8,
|
IMPORT_SRC: 8,
|
||||||
TABLE_WIDTH: 9,
|
TABLE_WIDTH: 9,
|
||||||
|
LANGUAGE: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const STATUS = {
|
export const STATUS = {
|
||||||
@ -125,4 +128,4 @@ export const SIDESHEET = {
|
|||||||
NONE: 0,
|
NONE: 0,
|
||||||
TODO: 1,
|
TODO: 1,
|
||||||
TIMELINE: 2,
|
TIMELINE: 2,
|
||||||
};
|
};
|
||||||
|
24
src/i18n/i18n.js
Normal file
24
src/i18n/i18n.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import i18n from "i18next";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
import { en, english } from "./locales/en";
|
||||||
|
import { zh, chinese } from "./locales/zh";
|
||||||
|
|
||||||
|
export const languages = [english, chinese];
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
fallbackLng: "en",
|
||||||
|
debug: false,
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
en,
|
||||||
|
zh,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
218
src/i18n/locales/en.js
Normal file
218
src/i18n/locales/en.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
const english = {
|
||||||
|
name: "English",
|
||||||
|
native_name: "English",
|
||||||
|
code: "en",
|
||||||
|
};
|
||||||
|
|
||||||
|
const en = {
|
||||||
|
translation: {
|
||||||
|
report_bug: "Report a bug",
|
||||||
|
import: "Import",
|
||||||
|
file: "File",
|
||||||
|
new: "New",
|
||||||
|
new_window: "New window",
|
||||||
|
open: "Open",
|
||||||
|
save: "Save",
|
||||||
|
save_as: "Save as",
|
||||||
|
save_as_template: "Save as template",
|
||||||
|
template_saved: "Template saved!",
|
||||||
|
rename: "Rename",
|
||||||
|
delete_diagram: "Delete diagram",
|
||||||
|
are_you_sure_delete_diagram:
|
||||||
|
"Are you sure you want to delete this diagram? This operation is irreversible.",
|
||||||
|
oops_smth_went_wrong: "Oops! Something went wrong.",
|
||||||
|
import_diagram: "Import diagram",
|
||||||
|
import_from_source: "Import from source",
|
||||||
|
export_as: "Export as",
|
||||||
|
export_source: "Export source",
|
||||||
|
models: "Models",
|
||||||
|
exit: "Exit",
|
||||||
|
edit: "Edit",
|
||||||
|
undo: "Undo",
|
||||||
|
redo: "Redo",
|
||||||
|
clear: "Clear",
|
||||||
|
are_you_sure_clear:
|
||||||
|
"Are you sure you want to clear the diagram? This is irreversible.",
|
||||||
|
cut: "Cut",
|
||||||
|
copy: "Copy",
|
||||||
|
paste: "Paste",
|
||||||
|
duplicate: "Duplicate",
|
||||||
|
delete: "Delete",
|
||||||
|
copy_as_image: "Copy as image",
|
||||||
|
view: "View",
|
||||||
|
header: "Header",
|
||||||
|
sidebar: "Sidebar",
|
||||||
|
issues: "Issues",
|
||||||
|
presentation_mode: "Presentation mode",
|
||||||
|
strict_mode: "Strict mode",
|
||||||
|
field_details: "Field details",
|
||||||
|
reset_view: "Reset view",
|
||||||
|
show_grid: "Show grid",
|
||||||
|
show_cardinality: "Show cardinality",
|
||||||
|
theme: "Theme",
|
||||||
|
light: "Light",
|
||||||
|
dark: "Dark",
|
||||||
|
zoom_in: "Zoom in",
|
||||||
|
zoom_out: "Zoom out",
|
||||||
|
fullscreen: "Fullscreen",
|
||||||
|
settings: "Settings",
|
||||||
|
show_timeline: "Show timeline",
|
||||||
|
autosave: "Autosave",
|
||||||
|
panning: "Panning",
|
||||||
|
table_width: "Table width",
|
||||||
|
language: "Language",
|
||||||
|
flush_storage: "Flush storage",
|
||||||
|
are_you_sure_flush_storage:
|
||||||
|
"Are you sure you want to flush the storage? This will irreversibly delete all your diagrams and custom templates.",
|
||||||
|
storage_flushed: "Storage flushed",
|
||||||
|
help: "Help",
|
||||||
|
shortcuts: "Shortcuts",
|
||||||
|
ask_on_discord: "Ast us on Discord",
|
||||||
|
feedback: "Feedback",
|
||||||
|
no_changes: "No changes",
|
||||||
|
loading: "Loading...",
|
||||||
|
last_saved: "Last saved",
|
||||||
|
saving: "Saving...",
|
||||||
|
failed_to_save: "Failed to save",
|
||||||
|
fit_window_reset: "Fit window / Reset",
|
||||||
|
zoom: "Zoom",
|
||||||
|
add_table: "Add table",
|
||||||
|
add_area: "Add area",
|
||||||
|
add_note: "Add note",
|
||||||
|
add_type: "Add type",
|
||||||
|
to_do: "To-do",
|
||||||
|
tables: "Tables",
|
||||||
|
relationships: "Relationships",
|
||||||
|
subject_areas: "Subject areas",
|
||||||
|
notes: "Notes",
|
||||||
|
types: "Types",
|
||||||
|
search: "Search...",
|
||||||
|
no_tables: "No tables",
|
||||||
|
no_tables_text: "Start building your diagram!",
|
||||||
|
no_relationships: "No relationships",
|
||||||
|
no_relationships_text: "Drag to connect fields and form relationships!",
|
||||||
|
no_subject_areas: "No subject areas",
|
||||||
|
no_subject_areas_text: "Add subject areas to group tables!",
|
||||||
|
no_notes: "No notes",
|
||||||
|
no_notes_text: "Use notes to record extra info",
|
||||||
|
no_types: "No types",
|
||||||
|
no_types_text: "Make your own custom data types",
|
||||||
|
no_issues: "No issues were detected.",
|
||||||
|
strict_mode_is_on_no_issues:
|
||||||
|
"Strict mode is off so no issues will be displayed.",
|
||||||
|
name: "Name",
|
||||||
|
type: "Type",
|
||||||
|
null: "Null",
|
||||||
|
not_null: "Not null",
|
||||||
|
primary: "Primary",
|
||||||
|
unique: "Unique",
|
||||||
|
autoincrement: "Autoincrement",
|
||||||
|
default_value: "Default",
|
||||||
|
check: "Check expression",
|
||||||
|
this_will_appear_as_is: "*This will appear in the generated script as is.",
|
||||||
|
comment: "Comment",
|
||||||
|
add_field: "Add field",
|
||||||
|
values: "values",
|
||||||
|
size: "Size",
|
||||||
|
precision: "Precision",
|
||||||
|
set_precision: "Set precision: (size, digits)",
|
||||||
|
use_for_batch_input: "Use , for batch input",
|
||||||
|
indices: "Indices",
|
||||||
|
add_index: "Add index",
|
||||||
|
select_fields: "Select fields",
|
||||||
|
title: "Title",
|
||||||
|
not_set: "Not set",
|
||||||
|
foreign: "Foreign",
|
||||||
|
cardinality: "Cardinality",
|
||||||
|
on_update: "On update",
|
||||||
|
on_delete: "On delete",
|
||||||
|
swap: "Swap",
|
||||||
|
one_to_one: "One to one",
|
||||||
|
one_to_many: "One to many",
|
||||||
|
many_to_one: "Many to one",
|
||||||
|
content: "Content",
|
||||||
|
types_info:
|
||||||
|
"This feature is meant for object-relational DBMSs like PostgreSQL.\nIf used for MySQL or MariaDB a JSON type will be generated with the corresponding json validation check.\nIf used for SQLite it will be translated to a BLOB.\nIf used for MSSQL a type alias to the first field will be generated.",
|
||||||
|
table_deleted: "Table deleted",
|
||||||
|
area_deleted: "Area deleted",
|
||||||
|
note_deleted: "Note deleted",
|
||||||
|
relationship_deleted: "Relationship deleted",
|
||||||
|
type_deleted: "Type deleted",
|
||||||
|
cannot_connect: "Cannot connect, the columns have different types",
|
||||||
|
copied_to_clipboard: "Copied to clipboard",
|
||||||
|
create_new_diagram: "Create new diagram",
|
||||||
|
cancel: "Cancel",
|
||||||
|
open_diagram: "Open diagram",
|
||||||
|
rename_diagram: "Rename diagram",
|
||||||
|
export: "Export",
|
||||||
|
export_image: "Export image",
|
||||||
|
create: "Create",
|
||||||
|
confirm: "Confirm",
|
||||||
|
last_modified: "Last modified",
|
||||||
|
drag_and_drop_files: "Drag and drop the file here or click to upload.",
|
||||||
|
support_json_and_ddb: "JSON and DDB files are supported",
|
||||||
|
upload_sql_to_generate_diagrams:
|
||||||
|
"Upload an sql file to autogenerate your tables and columns.",
|
||||||
|
overwrite_existing_diagram: "Overwrite existing diagram",
|
||||||
|
only_mysql_supported:
|
||||||
|
"*For the time being loading only MySQL scripts is supported.",
|
||||||
|
blank: "Blank",
|
||||||
|
filename: "Filename",
|
||||||
|
table_w_no_name: "Declared a table with no name",
|
||||||
|
duplicate_table_by_name: "Duplicate table by the name '{{tableName}}'",
|
||||||
|
empty_field_name: "Empty field `name` in table '{{tableName}}'",
|
||||||
|
empty_field_type: "Empty field `type` in table '{{tableName}}'",
|
||||||
|
no_values_for_field:
|
||||||
|
"'{{fieldName}}' field of table '{{tableName}}' is of type `{{type}}` but no values have been specified",
|
||||||
|
default_doesnt_match_type:
|
||||||
|
"Default value for field '{{fieldName}}' in table '{{table.name}}' does not match its type",
|
||||||
|
not_null_is_null:
|
||||||
|
"'{{fieldName}}' field of table '{{tableName}}' is NOT NULL but has default NULL",
|
||||||
|
duplicate_fields:
|
||||||
|
"Duplicate table fields by name '{{fieldName}}' in table '{{tableName}}'",
|
||||||
|
duplicate_index:
|
||||||
|
"Duplicate index by name '{{indexName}}' in table '{{tableName}}'",
|
||||||
|
empty_index: "Index in table '{{tableName}}' indexes no columns",
|
||||||
|
no_primary_key: "Table '{{tableName}}' has no primary key",
|
||||||
|
type_with_no_name: "Declared a type with no name",
|
||||||
|
duplicate_types: "Duplicate types by the name '{{typeName}}'",
|
||||||
|
type_w_no_fields: "Declared an empty type '{{typeName}}'with no fields",
|
||||||
|
empty_type_field_name: "Empty field `name` in type '{{typeName}}'",
|
||||||
|
empty_type_field_type: "Empty field `type` in type '{{typeName}}'",
|
||||||
|
no_values_for_type_field:
|
||||||
|
"'{{fieldName}}' field of type '{{typeName}}' is of type `{{type}}` but no values have been specified",
|
||||||
|
duplicate_type_fields:
|
||||||
|
"Duplicate type fields by name '{{fieldName}}' in type '{{typeName}}'",
|
||||||
|
duplicate_reference: "Duplicate reference by the name '{{refName}}'",
|
||||||
|
circular_dependency: "Circular dependency involvind table '{{refName}}'",
|
||||||
|
timeline: "Timeline",
|
||||||
|
priority: "Priority",
|
||||||
|
none: "None",
|
||||||
|
low: "Low",
|
||||||
|
medium: "Medium",
|
||||||
|
high: "High",
|
||||||
|
sort_by: "Sort by",
|
||||||
|
my_order: "My order",
|
||||||
|
completed: "Completed",
|
||||||
|
alphabetically: "Alphabetically",
|
||||||
|
add_task: "Add task",
|
||||||
|
details: "Details",
|
||||||
|
no_tasks: "You have no tasks yet.",
|
||||||
|
no_activity: "You have no activity yet.",
|
||||||
|
move_element: "Move {{name}} to {{coords}}",
|
||||||
|
edit_area: "{{extra}} Edit area {{areaName}}",
|
||||||
|
delete_area: "Delete area {{areaName}}",
|
||||||
|
edit_note: "{{extra}} Edit note {{noteTitle}}",
|
||||||
|
delete_note: "Delete note {{noteTitle}}",
|
||||||
|
edit_table: "{{extra}} Edit table {{tableName}}",
|
||||||
|
delete_table: "Delete table {{tableName}}",
|
||||||
|
edit_type: "{{extra}} Edit type {{typeName}}",
|
||||||
|
delete_type: "Delete type {{typeName}}",
|
||||||
|
add_relationship: "Add relationship",
|
||||||
|
edit_relationship: "{{extra}} Edit relationship {{refName}}",
|
||||||
|
delete_relationship: "Delete relationship {{refName}}",
|
||||||
|
not_found: "Not found",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { en, english };
|
213
src/i18n/locales/zh.js
Normal file
213
src/i18n/locales/zh.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
const chinese = {
|
||||||
|
name: "Simplified Chinese",
|
||||||
|
native_name: "简体中文",
|
||||||
|
code: "zh",
|
||||||
|
};
|
||||||
|
|
||||||
|
const zh = {
|
||||||
|
translation: {
|
||||||
|
report_bug: "报告问题",
|
||||||
|
import: "导入",
|
||||||
|
file: "文件",
|
||||||
|
new: "新建",
|
||||||
|
new_window: "新窗口",
|
||||||
|
open: "打开",
|
||||||
|
save: "保存",
|
||||||
|
save_as: "另存为",
|
||||||
|
save_as_template: "保存为模板",
|
||||||
|
template_saved: "模板已保存!",
|
||||||
|
rename: "重命名",
|
||||||
|
delete_diagram: "删除图表",
|
||||||
|
are_you_sure_delete_diagram: "确定要删除此图表吗?此操作不可逆转。",
|
||||||
|
oops_smth_went_wrong: "糟糕!出了些问题。",
|
||||||
|
import_diagram: "导入图表",
|
||||||
|
import_from_source: "从源导入",
|
||||||
|
export_as: "导出为",
|
||||||
|
export_source: "导出源",
|
||||||
|
models: "模型",
|
||||||
|
exit: "退出",
|
||||||
|
edit: "编辑",
|
||||||
|
undo: "撤销",
|
||||||
|
redo: "重做",
|
||||||
|
clear: "清除",
|
||||||
|
are_you_sure_clear: "确定要清除图表吗?此操作不可逆转。",
|
||||||
|
cut: "剪切",
|
||||||
|
copy: "复制",
|
||||||
|
paste: "粘贴",
|
||||||
|
duplicate: "复制",
|
||||||
|
delete: "删除",
|
||||||
|
copy_as_image: "复制为图像",
|
||||||
|
view: "视图",
|
||||||
|
header: "标题",
|
||||||
|
sidebar: "侧边栏",
|
||||||
|
issues: "问题",
|
||||||
|
presentation_mode: "演示模式",
|
||||||
|
strict_mode: "严格模式",
|
||||||
|
field_details: "字段详情",
|
||||||
|
reset_view: "重置视图",
|
||||||
|
show_grid: "显示网格",
|
||||||
|
show_cardinality: "显示基数",
|
||||||
|
theme: "主题",
|
||||||
|
light: "明亮",
|
||||||
|
dark: "暗黑",
|
||||||
|
zoom_in: "放大",
|
||||||
|
zoom_out: "缩小",
|
||||||
|
fullscreen: "全屏",
|
||||||
|
settings: "设置",
|
||||||
|
show_timeline: "显示时间轴",
|
||||||
|
autosave: "自动保存",
|
||||||
|
panning: "平移",
|
||||||
|
table_width: "表格宽度",
|
||||||
|
language: "语言",
|
||||||
|
flush_storage: "清除存储",
|
||||||
|
are_you_sure_flush_storage:
|
||||||
|
"确定要清除存储吗?这将不可逆地删除所有您的图表和自定义模板。",
|
||||||
|
storage_flushed: "存储已清除",
|
||||||
|
help: "帮助",
|
||||||
|
shortcuts: "快捷键",
|
||||||
|
ask_on_discord: "在 Discord 上问我们",
|
||||||
|
feedback: "反馈",
|
||||||
|
no_changes: "没有更改",
|
||||||
|
loading: "加载中...",
|
||||||
|
last_saved: "上次保存",
|
||||||
|
saving: "保存中...",
|
||||||
|
failed_to_save: "保存失败",
|
||||||
|
fit_window_reset: "适应窗口/重置",
|
||||||
|
zoom: "缩放",
|
||||||
|
add_table: "添加表",
|
||||||
|
add_area: "添加区域",
|
||||||
|
add_note: "添加注释",
|
||||||
|
add_type: "添加类型",
|
||||||
|
to_do: "待办事项",
|
||||||
|
tables: "表",
|
||||||
|
relationships: "关系",
|
||||||
|
subject_areas: "主题区域",
|
||||||
|
notes: "注释",
|
||||||
|
types: "类型",
|
||||||
|
search: "搜索...",
|
||||||
|
no_tables: "没有表",
|
||||||
|
no_tables_text: "开始构建您的图表!",
|
||||||
|
no_relationships: "没有关系",
|
||||||
|
no_relationships_text: "拖动以连接字段并形成关系!",
|
||||||
|
no_subject_areas: "没有主题区域",
|
||||||
|
no_subject_areas_text: "添加主题区域以分组表!",
|
||||||
|
no_notes: "没有注释",
|
||||||
|
no_notes_text: "使用注释记录额外信息",
|
||||||
|
no_types: "没有类型",
|
||||||
|
no_types_text: "制作您自己的自定义数据类型",
|
||||||
|
no_issues: "未检测到问题。",
|
||||||
|
strict_mode_is_on_no_issues: "严格模式已关闭,因此不会显示任何问题。",
|
||||||
|
name: "名称",
|
||||||
|
type: "类型",
|
||||||
|
null: "空",
|
||||||
|
not_null: "非空",
|
||||||
|
primary: "主键",
|
||||||
|
unique: "唯一",
|
||||||
|
autoincrement: "自增",
|
||||||
|
default_value: "默认",
|
||||||
|
check: "检查表达式",
|
||||||
|
this_will_appear_as_is: "*此内容将按原样显示在生成的脚本中。",
|
||||||
|
comment: "评论",
|
||||||
|
add_field: "添加字段",
|
||||||
|
values: "值",
|
||||||
|
size: "大小",
|
||||||
|
precision: "精度",
|
||||||
|
set_precision: "设置精度:(大小,位数)",
|
||||||
|
use_for_batch_input: "用于批量输入,使用逗号",
|
||||||
|
indices: "索引",
|
||||||
|
add_index: "添加索引",
|
||||||
|
select_fields: "选择字段",
|
||||||
|
title: "标题",
|
||||||
|
not_set: "未设置",
|
||||||
|
foreign: "外键",
|
||||||
|
cardinality: "基数",
|
||||||
|
on_update: "更新时",
|
||||||
|
on_delete: "删除时",
|
||||||
|
swap: "交换",
|
||||||
|
one_to_one: "一对一",
|
||||||
|
one_to_many: "一对多",
|
||||||
|
many_to_one: "多对一",
|
||||||
|
content: "内容",
|
||||||
|
types_info:
|
||||||
|
"此功能适用于像 PostgreSQL 这样的对象关系型数据库管理系统。\n如果用于 MySQL 或 MariaDB,将生成具有相应 JSON 验证检查的 JSON 类型。\n如果用于 SQLite,它将被转换为 BLOB。\n如果用于 MSSQL,将生成到第一个字段的类型别名。",
|
||||||
|
table_deleted: "表已删除",
|
||||||
|
area_deleted: "区域已删除",
|
||||||
|
note_deleted: "注释已删除",
|
||||||
|
relationship_deleted: "关系已删除",
|
||||||
|
type_deleted: "类型已删除",
|
||||||
|
cannot_connect: "无法连接,列具有不同的类型",
|
||||||
|
copied_to_clipboard: "已复制到剪贴板",
|
||||||
|
create_new_diagram: "创建新图表",
|
||||||
|
cancel: "取消",
|
||||||
|
open_diagram: "打开图表",
|
||||||
|
rename_diagram: "重命名图表",
|
||||||
|
export: "导出",
|
||||||
|
export_image: "导出图像",
|
||||||
|
create: "创建",
|
||||||
|
confirm: "确认",
|
||||||
|
last_modified: "最后修改",
|
||||||
|
drag_and_drop_files: "拖放文件到此处或点击上传。",
|
||||||
|
support_json_and_ddb: "支持 JSON 和 DDB 文件",
|
||||||
|
upload_sql_to_generate_diagrams: "上传 SQL 文件以自动生成表和列。",
|
||||||
|
overwrite_existing_diagram: "覆盖现有图表",
|
||||||
|
only_mysql_supported: "目前仅支持加载 MySQL 脚本。",
|
||||||
|
blank: "空",
|
||||||
|
filename: "文件名",
|
||||||
|
table_w_no_name: "声明了一个没有名称的表",
|
||||||
|
duplicate_table_by_name: "通过名称 '{{tableName}}' 重复声明了一个表",
|
||||||
|
empty_field_name: "表 '{{tableName}}' 中的字段 `name` 为空",
|
||||||
|
empty_field_type: "表 '{{tableName}}' 中的字段 `type` 为空",
|
||||||
|
no_values_for_field:
|
||||||
|
"表 '{{tableName}}' 的 '{{fieldName}}' 字段类型为 `{{type}}`,但未指定任何值",
|
||||||
|
default_doesnt_match_type:
|
||||||
|
"表 '{{table.name}}' 中字段 '{{fieldName}}' 的默认值与其类型不匹配",
|
||||||
|
not_null_is_null:
|
||||||
|
"表 '{{tableName}}' 中的 '{{fieldName}}' 字段为 NOT NULL,但默认值为 NULL",
|
||||||
|
duplicate_fields:
|
||||||
|
"在表 '{{tableName}}' 中通过名称 '{{fieldName}}' 重复声明了字段",
|
||||||
|
duplicate_index:
|
||||||
|
"在表 '{{tableName}}' 中通过名称 '{{indexName}}' 重复声明了索引",
|
||||||
|
empty_index: "在表 '{{tableName}}' 中的索引未指定任何列",
|
||||||
|
no_primary_key: "表 '{{tableName}}' 没有主键",
|
||||||
|
type_with_no_name: "声明了一个没有名称的类型",
|
||||||
|
duplicate_types: "通过名称 '{{typeName}}' 重复声明了类型",
|
||||||
|
type_w_no_fields: "声明了一个没有字段的空类型 '{{typeName}}'",
|
||||||
|
empty_type_field_name: "类型 '{{typeName}}' 中的字段 `name` 为空",
|
||||||
|
empty_type_field_type: "类型 '{{typeName}}' 中的字段 `type` 为空",
|
||||||
|
no_values_for_type_field:
|
||||||
|
"类型 '{{typeName}}' 的 '{{fieldName}}' 字段类型为 `{{type}}`,但未指定任何值",
|
||||||
|
duplicate_type_fields:
|
||||||
|
"在类型 '{{typeName}}' 中通过名称 '{{fieldName}}' 重复声明了字段",
|
||||||
|
duplicate_reference: "通过名称 '{{refName}}' 重复声明了引用",
|
||||||
|
circular_dependency: "涉及到表 '{{refName}}' 的循环依赖",
|
||||||
|
timeline: "时间轴",
|
||||||
|
priority: "优先级",
|
||||||
|
none: "无",
|
||||||
|
low: "低",
|
||||||
|
medium: "中",
|
||||||
|
high: "高",
|
||||||
|
sort_by: "排序方式",
|
||||||
|
my_order: "我的排序",
|
||||||
|
completed: "已完成",
|
||||||
|
alphabetically: "按字母顺序",
|
||||||
|
add_task: "添加任务",
|
||||||
|
details: "详情",
|
||||||
|
no_tasks: "您还没有任务。",
|
||||||
|
no_activity: "您还没有活动。",
|
||||||
|
move_element: "将 {{name}} 移动到 {{coords}}",
|
||||||
|
edit_area: "{{extra}} 编辑区域 {{areaName}}",
|
||||||
|
delete_area: "删除区域 {{areaName}}",
|
||||||
|
edit_note: "{{extra}} 编辑注释 {{noteTitle}}",
|
||||||
|
delete_note: "删除注释 {{noteTitle}}",
|
||||||
|
edit_table: "{{extra}} 编辑表格 {{tableName}}",
|
||||||
|
delete_table: "删除表格 {{tableName}}",
|
||||||
|
edit_type: "{{extra}} 编辑类型 {{typeName}}",
|
||||||
|
delete_type: "删除类型 {{typeName}}",
|
||||||
|
add_relationship: "添加关系",
|
||||||
|
edit_relationship: "{{extra}} 编辑关系 {{refName}}",
|
||||||
|
delete_relationship: "删除关系 {{refName}}",
|
||||||
|
not_found: "未找到",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { zh, chinese };
|
@ -4,11 +4,12 @@ import { Analytics } from "@vercel/analytics/react";
|
|||||||
import App from "./App.jsx";
|
import App from "./App.jsx";
|
||||||
import en_US from "@douyinfe/semi-ui/lib/es/locale/source/en_US";
|
import en_US from "@douyinfe/semi-ui/lib/es/locale/source/en_US";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import "./i18n/i18n.js";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
root.render(
|
root.render(
|
||||||
<LocaleProvider locale={en_US}>
|
<LocaleProvider locale={en_US}>
|
||||||
<App />
|
<App />
|
||||||
<Analytics />
|
<Analytics />
|
||||||
</LocaleProvider>
|
</LocaleProvider>,
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import i18n from "../i18n/i18n";
|
||||||
import { isFunction, strHasQuotes } from "./utils";
|
import { isFunction, strHasQuotes } from "./utils";
|
||||||
|
|
||||||
function validateDateStr(str) {
|
function validateDateStr(str) {
|
||||||
@ -88,11 +89,11 @@ export function getIssues(diagram) {
|
|||||||
|
|
||||||
diagram.tables.forEach((table) => {
|
diagram.tables.forEach((table) => {
|
||||||
if (table.name === "") {
|
if (table.name === "") {
|
||||||
issues.push(`Declared a table with no name`);
|
issues.push(i18n.t("table_w_no_name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateTableNames[table.name]) {
|
if (duplicateTableNames[table.name]) {
|
||||||
issues.push(`Duplicate table by the name "${table.name}"`);
|
issues.push(i18n.t("duplicate_table_by_name", { tableName: table.name }));
|
||||||
} else {
|
} else {
|
||||||
duplicateTableNames[table.name] = true;
|
duplicateTableNames[table.name] = true;
|
||||||
}
|
}
|
||||||
@ -105,33 +106,48 @@ export function getIssues(diagram) {
|
|||||||
hasPrimaryKey = true;
|
hasPrimaryKey = true;
|
||||||
}
|
}
|
||||||
if (field.name === "") {
|
if (field.name === "") {
|
||||||
issues.push(`Empty field name in table "${table.name}"`);
|
issues.push(i18n.t("empty_field_name", { tableName: table.name }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === "") {
|
if (field.type === "") {
|
||||||
issues.push(`Empty field type in table "${table.name}"`);
|
issues.push(i18n.t("empty_field_type", { tableName: table.name }));
|
||||||
} else if (field.type === "ENUM" || field.type === "SET") {
|
} else if (field.type === "ENUM" || field.type === "SET") {
|
||||||
if (!field.values || field.values.length === 0) {
|
if (!field.values || field.values.length === 0) {
|
||||||
issues.push(
|
issues.push(
|
||||||
`"${field.name}" field of table "${table.name}" is of type ${field.type} but no values have been specified`,
|
i18n.t("no_values_for_field", {
|
||||||
|
tableName: table.name,
|
||||||
|
fieldName: field.name,
|
||||||
|
type: field.type,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkDefault(field)) {
|
if (!checkDefault(field)) {
|
||||||
issues.push(
|
issues.push(
|
||||||
`Default value for field "${field.name}" in table "${table.name}" does not match its type.`,
|
i18n.t("default_doesnt_match_type", {
|
||||||
|
tableName: table.name,
|
||||||
|
fieldName: field.name,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.notNull && field.default.toLowerCase() === "null") {
|
if (field.notNull && field.default.toLowerCase() === "null") {
|
||||||
issues.push(
|
issues.push(
|
||||||
`"${field.name}" field of table "${table.name}" is NOT NULL but has default NULL`,
|
i18n.t("not_null_is_null", {
|
||||||
|
tableName: table.name,
|
||||||
|
fieldName: field.name,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateFieldNames[field.name]) {
|
if (duplicateFieldNames[field.name]) {
|
||||||
issues.push(`Duplicate table fields in table "${table.name}"`);
|
issues.push(
|
||||||
|
i18n.t("duplicate_fields", {
|
||||||
|
tableName: table.name,
|
||||||
|
fieldName: field.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
duplicateFieldNames[field.name] = true;
|
duplicateFieldNames[field.name] = true;
|
||||||
}
|
}
|
||||||
@ -140,7 +156,12 @@ export function getIssues(diagram) {
|
|||||||
const duplicateIndices = {};
|
const duplicateIndices = {};
|
||||||
table.indices.forEach((index) => {
|
table.indices.forEach((index) => {
|
||||||
if (duplicateIndices[index.name]) {
|
if (duplicateIndices[index.name]) {
|
||||||
issues.push(`Duplicate index by the name "${index.name}"`);
|
issues.push(
|
||||||
|
i18n.t("duplicate_index", {
|
||||||
|
tableName: table.name,
|
||||||
|
indexName: index.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
duplicateIndices[index.name] = true;
|
duplicateIndices[index.name] = true;
|
||||||
}
|
}
|
||||||
@ -148,50 +169,69 @@ export function getIssues(diagram) {
|
|||||||
|
|
||||||
table.indices.forEach((index) => {
|
table.indices.forEach((index) => {
|
||||||
if (index.fields.length === 0) {
|
if (index.fields.length === 0) {
|
||||||
issues.push(`Empty index type in table "${table.name}"`);
|
issues.push(
|
||||||
|
i18n.t("empty_index", {
|
||||||
|
tableName: table.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasPrimaryKey) {
|
if (!hasPrimaryKey) {
|
||||||
issues.push(`Table "${table.name}" has no primary key`);
|
issues.push(i18n.t("no_primary_key", { tableName: table.name }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const duplicateTypeNames = {};
|
const duplicateTypeNames = {};
|
||||||
diagram.types.forEach((type) => {
|
diagram.types.forEach((type) => {
|
||||||
if (type.name === "") {
|
if (type.name === "") {
|
||||||
issues.push(`Declared a type with no name`);
|
issues.push(i18n.t("type_with_no_name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateTypeNames[type.name]) {
|
if (duplicateTypeNames[type.name]) {
|
||||||
issues.push(`Duplicate types by the name "${type.name}"`);
|
issues.push(i18n.t("duplicate_types", { typeName: type.name }));
|
||||||
} else {
|
} else {
|
||||||
duplicateTypeNames[type.name] = true;
|
duplicateTypeNames[type.name] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type.fields.length === 0) {
|
if (type.fields.length === 0) {
|
||||||
issues.push(`Declared an empty type "${type.name}" with no fields`);
|
issues.push(i18n.t("type_w_no_fields", { typeName: type.name }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateFieldNames = {};
|
const duplicateFieldNames = {};
|
||||||
type.fields.forEach((field) => {
|
type.fields.forEach((field) => {
|
||||||
if (field.name === "") {
|
if (field.name === "") {
|
||||||
issues.push(`Empty field name in type "${type.name}"`);
|
issues.push(
|
||||||
|
i18n.t("empty_type_field_name", {
|
||||||
|
typeName: type.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === "") {
|
if (field.type === "") {
|
||||||
issues.push(`Empty field type in "${type.name}"`);
|
issues.push(
|
||||||
|
i18n.t("empty_type_field_type", {
|
||||||
|
typeName: type.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else if (field.type === "ENUM" || field.type === "SET") {
|
} else if (field.type === "ENUM" || field.type === "SET") {
|
||||||
if (!field.values || field.values.length === 0) {
|
if (!field.values || field.values.length === 0) {
|
||||||
issues.push(
|
issues.push(
|
||||||
`"${field.name}" field of type "${type.name}" is of type ${field.type} but no values have been specified`,
|
i18n.t("no_values_for_type_field", {
|
||||||
|
typeName: type.name,
|
||||||
|
fieldName: field.name,
|
||||||
|
type: field.type,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateFieldNames[field.name]) {
|
if (duplicateFieldNames[field.name]) {
|
||||||
issues.push(`Duplicate type fields in "${type.name}"`);
|
i18n.t("duplicate_type_fields", {
|
||||||
|
typeName: type.name,
|
||||||
|
fieldName: field.name,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
duplicateFieldNames[field.name] = true;
|
duplicateFieldNames[field.name] = true;
|
||||||
}
|
}
|
||||||
@ -201,22 +241,14 @@ export function getIssues(diagram) {
|
|||||||
const duplicateFKName = {};
|
const duplicateFKName = {};
|
||||||
diagram.relationships.forEach((r) => {
|
diagram.relationships.forEach((r) => {
|
||||||
if (duplicateFKName[r.name]) {
|
if (duplicateFKName[r.name]) {
|
||||||
issues.push(`Duplicate reference by the name "${r.name}"`);
|
issues.push(
|
||||||
|
i18n.t("duplicate_reference", {
|
||||||
|
refName: r.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
duplicateFKName[r.name] = true;
|
duplicateFKName[r.name] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
diagram.tables[r.startTableId].fields[r.startFieldId].type !==
|
|
||||||
diagram.tables[r.endTableId].fields[r.endFieldId].type
|
|
||||||
) {
|
|
||||||
issues.push(`Referencing column "${
|
|
||||||
diagram.tables[r.endTableId].fields[r.endFieldId].name
|
|
||||||
}" and referenced column "${
|
|
||||||
diagram.tables[r.startTableId].fields[r.startFieldId].name
|
|
||||||
}" are incompatible.
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const visitedTables = new Set();
|
const visitedTables = new Set();
|
||||||
@ -224,7 +256,9 @@ export function getIssues(diagram) {
|
|||||||
function checkCircularRelationships(tableId, visited = []) {
|
function checkCircularRelationships(tableId, visited = []) {
|
||||||
if (visited.includes(tableId)) {
|
if (visited.includes(tableId)) {
|
||||||
issues.push(
|
issues.push(
|
||||||
`Circular relationship involving table: "${diagram.tables[tableId].name}"`,
|
i18n.t("circular_dependency", {
|
||||||
|
refName: diagram.tables[tableId].name,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
import { MODAL } from "../data/constants";
|
import { MODAL } from "../data/constants";
|
||||||
|
import i18n from "../i18n/i18n";
|
||||||
|
|
||||||
export const getModalTitle = (modal) => {
|
export const getModalTitle = (modal) => {
|
||||||
switch (modal) {
|
switch (modal) {
|
||||||
case MODAL.IMPORT:
|
case MODAL.IMPORT:
|
||||||
case MODAL.IMPORT_SRC:
|
case MODAL.IMPORT_SRC:
|
||||||
return "Import diagram";
|
return i18n.t("import_diagram");
|
||||||
case MODAL.CODE:
|
case MODAL.CODE:
|
||||||
return "Export source";
|
return i18n.t("export_source");
|
||||||
case MODAL.IMG:
|
case MODAL.IMG:
|
||||||
return "Export image";
|
return i18n.t("export_image");
|
||||||
case MODAL.RENAME:
|
case MODAL.RENAME:
|
||||||
return "Rename diagram";
|
return i18n.t("rename_diagram");
|
||||||
case MODAL.OPEN:
|
case MODAL.OPEN:
|
||||||
return "Open diagram";
|
return i18n.t("open_diagram");
|
||||||
case MODAL.SAVEAS:
|
case MODAL.SAVEAS:
|
||||||
return "Save as";
|
return i18n.t("save_as");
|
||||||
case MODAL.NEW:
|
case MODAL.NEW:
|
||||||
return "Create new diagram";
|
return i18n.t("create_new_diagram");
|
||||||
case MODAL.TABLE_WIDTH:
|
case MODAL.TABLE_WIDTH:
|
||||||
return "Set the table width";
|
return i18n.t("table_width");
|
||||||
|
case MODAL.LANGUAGE:
|
||||||
|
return i18n.t("language");
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -28,19 +31,19 @@ export const getOkText = (modal) => {
|
|||||||
switch (modal) {
|
switch (modal) {
|
||||||
case MODAL.IMPORT:
|
case MODAL.IMPORT:
|
||||||
case MODAL.IMPORT_SRC:
|
case MODAL.IMPORT_SRC:
|
||||||
return "Import";
|
return i18n.t("import");
|
||||||
case MODAL.CODE:
|
case MODAL.CODE:
|
||||||
case MODAL.IMG:
|
case MODAL.IMG:
|
||||||
return "Export";
|
return i18n.t("export");
|
||||||
case MODAL.RENAME:
|
case MODAL.RENAME:
|
||||||
return "Rename";
|
return i18n.t("rename");
|
||||||
case MODAL.OPEN:
|
case MODAL.OPEN:
|
||||||
return "Open";
|
return i18n.t("open");
|
||||||
case MODAL.SAVEAS:
|
case MODAL.SAVEAS:
|
||||||
return "Save as";
|
return i18n.t("save_as");
|
||||||
case MODAL.NEW:
|
case MODAL.NEW:
|
||||||
return "Create";
|
return i18n.t("create");
|
||||||
default:
|
default:
|
||||||
return "Confirm";
|
return i18n.t("confirm");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user