mirror of
https://github.com/drawdb-io/drawdb.git
synced 2026-02-12 02:00:40 +08:00
Configure i18n and add simplified chinese (#99)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
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 { useAreas, useSaveState, useUndoRedo } from "../../../hooks";
|
||||
import {
|
||||
@@ -8,9 +8,11 @@ import {
|
||||
State,
|
||||
defaultBlue,
|
||||
} from "../../../data/constants";
|
||||
import ColorPalette from "../../ColorPalette";
|
||||
import ColorPalette from "../../ColorPicker";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AreaInfo({ data, i }) {
|
||||
const { t } = useTranslation();
|
||||
const { setSaveState } = useSaveState();
|
||||
const { deleteArea, updateArea } = useAreas();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
@@ -28,7 +30,7 @@ export default function AreaInfo({ data, i }) {
|
||||
<Col span={18}>
|
||||
<Input
|
||||
value={data.name}
|
||||
placeholder="Name"
|
||||
placeholder={t("name")}
|
||||
onChange={(value) => updateArea(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -41,7 +43,10 @@ export default function AreaInfo({ data, i }) {
|
||||
aid: i,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit area name to ${e.target.value}`,
|
||||
message: t("edit_area", {
|
||||
areaName: e.target.value,
|
||||
extra: "[name]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -67,7 +72,10 @@ export default function AreaInfo({ data, i }) {
|
||||
aid: i,
|
||||
undo: { color: data.color },
|
||||
redo: { color: c },
|
||||
message: `Edit area color to ${c}`,
|
||||
message: t("edit_area", {
|
||||
areaName: data.name,
|
||||
extra: "[color]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -90,10 +98,7 @@ export default function AreaInfo({ data, i }) {
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Area deleted!`);
|
||||
deleteArea(i, true);
|
||||
}}
|
||||
onClick={() => deleteArea(i, true)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -4,9 +4,11 @@ import Empty from "../Empty";
|
||||
import { useAreas } from "../../../hooks";
|
||||
import SearchBar from "./SearchBar";
|
||||
import AreaInfo from "./AreaDetails";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AreasTab() {
|
||||
const { areas, addArea } = useAreas();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -16,14 +18,14 @@ export default function AreasTab() {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button icon={<IconPlus />} block onClick={addArea}>
|
||||
Add area
|
||||
{t("add_area")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
{areas.length <= 0 ? (
|
||||
<Empty
|
||||
title="No subject areas"
|
||||
text="Add subject areas to organize tables!"
|
||||
title={t("no_subject_areas")}
|
||||
text={t("no_subject_areas_text")}
|
||||
/>
|
||||
) : (
|
||||
<div className="p-2">
|
||||
|
||||
@@ -2,18 +2,20 @@ import { useState } from "react";
|
||||
import { useAreas } from "../../../hooks";
|
||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||
import { IconSearch } from "@douyinfe/semi-icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SearchBar() {
|
||||
const { areas } = useAreas();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
areas.map((t) => t.name)
|
||||
areas.map((t) => t.name),
|
||||
);
|
||||
|
||||
const handleStringSearch = (value) => {
|
||||
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}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
emptyContent={<div className="p-3 popover-theme">No areas found</div>}
|
||||
placeholder={t("search")}
|
||||
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||
onSearch={(v) => handleStringSearch(v)}
|
||||
onChange={(v) => setSearchText(v)}
|
||||
onSelect={(v) => {
|
||||
|
||||
@@ -3,10 +3,12 @@ import { Collapse, Badge } from "@douyinfe/semi-ui";
|
||||
import { arrayIsEqual } from "../../utils/utils";
|
||||
import { getIssues } from "../../utils/issues";
|
||||
import { useSettings, useTables, useTypes } from "../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Issues() {
|
||||
const { settings } = useSettings();
|
||||
const { types } = useTypes();
|
||||
const { t } = useTranslation();
|
||||
const { settings } = useSettings();
|
||||
const { tables, relationships } = useTables();
|
||||
const [issues, setIssues] = useState([]);
|
||||
|
||||
@@ -38,7 +40,7 @@ export default function Issues() {
|
||||
>
|
||||
<div className="pe-3 select-none">
|
||||
<i className="fa-solid fa-triangle-exclamation me-2 text-yellow-500" />
|
||||
Issues
|
||||
{t("issues")}
|
||||
</div>
|
||||
</Badge>
|
||||
}
|
||||
@@ -46,9 +48,7 @@ export default function Issues() {
|
||||
>
|
||||
<div className="max-h-[160px] overflow-y-auto">
|
||||
{settings.strictMode ? (
|
||||
<div className="mb-1">
|
||||
Strict mode is off so no issues will be displayed.
|
||||
</div>
|
||||
<div className="mb-1">{t("strict_mode_is_on_no_issues")}</div>
|
||||
) : issues.length > 0 ? (
|
||||
<>
|
||||
{issues.map((e, i) => (
|
||||
@@ -58,7 +58,7 @@ export default function Issues() {
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div>No issues were detected.</div>
|
||||
<div>{t("no_issues")}</div>
|
||||
)}
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Collapse,
|
||||
TextArea,
|
||||
Popover,
|
||||
Input,
|
||||
Toast,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { Button, Collapse, TextArea, Popover, Input } from "@douyinfe/semi-ui";
|
||||
import { IconDeleteStroked, IconCheckboxTick } from "@douyinfe/semi-icons";
|
||||
import { noteThemes, Action, ObjectType } from "../../../data/constants";
|
||||
import { useNotes, useUndoRedo } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function NoteInfo({ data, nid }) {
|
||||
const { updateNote, deleteNote } = useNotes();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapse.Panel
|
||||
@@ -27,10 +22,10 @@ export default function NoteInfo({ data, nid }) {
|
||||
id={`scroll_note_${data.id}`}
|
||||
>
|
||||
<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
|
||||
value={data.title}
|
||||
placeholder="Title"
|
||||
placeholder={t("title")}
|
||||
onChange={(value) => updateNote(data.id, { title: value })}
|
||||
onFocus={(e) => setEditField({ title: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
@@ -43,7 +38,10 @@ export default function NoteInfo({ data, nid }) {
|
||||
nid: data.id,
|
||||
undo: editField,
|
||||
redo: { title: e.target.value },
|
||||
message: `Edit note title to "${e.target.name}"`,
|
||||
message: t("edit_note", {
|
||||
noteTitle: e.target.value,
|
||||
extra: "[title]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -52,7 +50,7 @@ export default function NoteInfo({ data, nid }) {
|
||||
</div>
|
||||
<div className="flex justify-between align-top">
|
||||
<TextArea
|
||||
placeholder="Add content"
|
||||
placeholder={t("content")}
|
||||
value={data.content}
|
||||
autosize
|
||||
onChange={(value) => {
|
||||
@@ -79,7 +77,10 @@ export default function NoteInfo({ data, nid }) {
|
||||
nid: nid,
|
||||
undo: editField,
|
||||
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([]);
|
||||
@@ -90,7 +91,7 @@ export default function NoteInfo({ data, nid }) {
|
||||
<Popover
|
||||
content={
|
||||
<div className="popover-theme">
|
||||
<div className="font-medium mb-1">Theme</div>
|
||||
<div className="font-medium mb-1">{t("theme")}</div>
|
||||
<hr />
|
||||
<div className="py-3">
|
||||
{noteThemes.map((c) => (
|
||||
@@ -107,7 +108,10 @@ export default function NoteInfo({ data, nid }) {
|
||||
nid: nid,
|
||||
undo: { color: data.color },
|
||||
redo: { color: c },
|
||||
message: `Edit note color to ${c}`,
|
||||
message: t("edit_note", {
|
||||
noteTitle: data.title,
|
||||
extra: "[color]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -136,10 +140,7 @@ export default function NoteInfo({ data, nid }) {
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Note deleted!`);
|
||||
deleteNote(nid, true);
|
||||
}}
|
||||
onClick={() => deleteNote(nid, true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,10 +4,12 @@ import { useNotes, useSelect } from "../../../hooks";
|
||||
import Empty from "../Empty";
|
||||
import SearchBar from "./SearchBar";
|
||||
import NoteInfo from "./NoteInfo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function NotesTab() {
|
||||
const { notes, addNote } = useNotes();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -24,12 +26,12 @@ export default function NotesTab() {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button icon={<IconPlus />} block onClick={() => addNote()}>
|
||||
Add note
|
||||
{t("add_note")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
{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
|
||||
activeKey={selectedElement.open ? `${selectedElement.id}` : ""}
|
||||
|
||||
@@ -2,17 +2,20 @@ import { useState } from "react";
|
||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||
import { IconSearch } from "@douyinfe/semi-icons";
|
||||
import { useNotes } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SearchBar({ setActiveKey }) {
|
||||
const { notes } = useNotes();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
notes.map((t) => t.title)
|
||||
notes.map((t) => t.title),
|
||||
);
|
||||
|
||||
const handleStringSearch = (value) => {
|
||||
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}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
emptyContent={<div className="p-3 popover-theme">No notes found</div>}
|
||||
placeholder={t("search")}
|
||||
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||
onSearch={(v) => handleStringSearch(v)}
|
||||
onChange={(v) => setSearchText(v)}
|
||||
onSelect={(v) => {
|
||||
|
||||
@@ -19,14 +19,16 @@ import {
|
||||
ObjectType,
|
||||
} from "../../../data/constants";
|
||||
import { useTables, useUndoRedo } from "../../../hooks";
|
||||
import i18n from "../../../i18n/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Primary",
|
||||
title: i18n.t("primary"),
|
||||
dataIndex: "primary",
|
||||
},
|
||||
{
|
||||
title: "Foreign",
|
||||
title: i18n.t("foreign"),
|
||||
dataIndex: "foreign",
|
||||
},
|
||||
];
|
||||
@@ -34,6 +36,7 @@ const columns = [
|
||||
export default function RelationshipInfo({ data }) {
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { tables, setRelationships, deleteRelationship } = useTables();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const swapKeys = () => {
|
||||
setUndoStack((prev) => [
|
||||
@@ -54,7 +57,10 @@ export default function RelationshipInfo({ data }) {
|
||||
endTableId: data.startTableId,
|
||||
endFieldId: data.startFieldId,
|
||||
},
|
||||
message: `Swap primary and foreign tables`,
|
||||
message: t("edit_relationship", {
|
||||
refName: data.name,
|
||||
extra: "[swap keys]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -71,8 +77,8 @@ export default function RelationshipInfo({ data }) {
|
||||
endTableId: e.startTableId,
|
||||
endFieldId: e.startFieldId,
|
||||
}
|
||||
: e
|
||||
)
|
||||
: e,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -85,12 +91,17 @@ export default function RelationshipInfo({ data }) {
|
||||
rid: data.id,
|
||||
undo: { cardinality: data.cardinality },
|
||||
redo: { cardinality: value },
|
||||
message: `Edit relationship cardinality`,
|
||||
message: t("edit_relationship", {
|
||||
refName: data.name,
|
||||
extra: "[cardinality]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
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,
|
||||
undo: { [undoKey]: data[undoKey] },
|
||||
redo: { [undoKey]: value },
|
||||
message: `Edit relationship ${key} constraint`,
|
||||
message: t("edit_relationship", {
|
||||
refName: data.name,
|
||||
extra: "[constraint]",
|
||||
}),
|
||||
});
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
@@ -112,12 +126,15 @@ export default function RelationshipInfo({ data }) {
|
||||
rid: data.id,
|
||||
undo: { [undoKey]: data[undoKey] },
|
||||
redo: { [undoKey]: value },
|
||||
message: `Edit relationship ${key} constraint`,
|
||||
message: t("edit_relationship", {
|
||||
refName: data.name,
|
||||
extra: "[constraint]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
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="me-3">
|
||||
<span className="font-semibold">Primary: </span>
|
||||
<span className="font-semibold">{t("primary")}: </span>
|
||||
{tables[data.endTableId].name}
|
||||
</div>
|
||||
<div className="mx-1">
|
||||
<span className="font-semibold">Foreign: </span>
|
||||
<span className="font-semibold">{t("foreign")}: </span>
|
||||
{tables[data.startTableId].name}
|
||||
</div>
|
||||
<div className="ms-1">
|
||||
@@ -168,7 +185,7 @@ export default function RelationshipInfo({ data }) {
|
||||
block
|
||||
onClick={swapKeys}
|
||||
>
|
||||
Swap
|
||||
{t("swap")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +198,7 @@ export default function RelationshipInfo({ data }) {
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-semibold my-1">Cardinality</div>
|
||||
<div className="font-semibold my-1">{t("cardinality")}:</div>
|
||||
<Select
|
||||
optionList={Object.values(Cardinality).map((v) => ({
|
||||
label: v,
|
||||
@@ -193,7 +210,7 @@ export default function RelationshipInfo({ data }) {
|
||||
/>
|
||||
<Row gutter={6} className="my-3">
|
||||
<Col span={12}>
|
||||
<div className="font-semibold">On update: </div>
|
||||
<div className="font-semibold">{t("on_update")}: </div>
|
||||
<Select
|
||||
optionList={Object.values(Constraint).map((v) => ({
|
||||
label: v,
|
||||
@@ -205,7 +222,7 @@ export default function RelationshipInfo({ data }) {
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="font-semibold">On delete: </div>
|
||||
<div className="font-semibold">{t("on_delete")}: </div>
|
||||
<Select
|
||||
optionList={Object.values(Constraint).map((v) => ({
|
||||
label: v,
|
||||
@@ -223,7 +240,7 @@ export default function RelationshipInfo({ data }) {
|
||||
type="danger"
|
||||
onClick={() => deleteRelationship(data.id, true)}
|
||||
>
|
||||
Delete
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</Collapse.Panel>
|
||||
</div>
|
||||
|
||||
@@ -4,10 +4,12 @@ import Empty from "../Empty";
|
||||
import SearchBar from "./SearchBar";
|
||||
import RelationshipInfo from "./RelationshipInfo";
|
||||
import { ObjectType } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function RelationshipsTab() {
|
||||
const { relationships } = useTables();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -33,8 +35,8 @@ export default function RelationshipsTab() {
|
||||
>
|
||||
{relationships.length <= 0 ? (
|
||||
<Empty
|
||||
title="No relationships"
|
||||
text="Drag to connect fields and form relationships!"
|
||||
title={t("no_relationships")}
|
||||
text={t("no_relationships_text")}
|
||||
/>
|
||||
) : (
|
||||
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 { IconSearch } from "@douyinfe/semi-icons";
|
||||
import { ObjectType } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SearchBar() {
|
||||
const { relationships } = useTables();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const { setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
relationships.map((t) => t.name),
|
||||
);
|
||||
@@ -24,10 +27,8 @@ export default function SearchBar() {
|
||||
value={searchText}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
emptyContent={
|
||||
<div className="p-3 popover-theme">No relationships found</div>
|
||||
}
|
||||
placeholder={t("search")}
|
||||
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||
onSearch={(v) => handleStringSearch(v)}
|
||||
onChange={(v) => setSearchText(v)}
|
||||
onSelect={(v) => {
|
||||
|
||||
@@ -7,17 +7,23 @@ import Issues from "./Issues";
|
||||
import AreasTab from "./AreasTab/AreasTab";
|
||||
import NotesTab from "./NotesTab/NotesTab";
|
||||
import TablesTab from "./TablesTab/TablesTab";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SidePanel({ width, resize, setResize }) {
|
||||
const { layout } = useLayout();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const tabList = [
|
||||
{ tab: "Tables", itemKey: Tab.TABLES, component: <TablesTab /> },
|
||||
{ tab: "Relationships", itemKey: Tab.RELATIONSHIPS, component: <RelationshipsTab /> },
|
||||
{ tab: "Subject Areas", itemKey: Tab.AREAS, component: <AreasTab /> },
|
||||
{ tab: "Notes", itemKey: Tab.NOTES, component: <NotesTab /> },
|
||||
{ tab: "Types", itemKey: Tab.TYPES, component: <TypesTab /> },
|
||||
{ tab: t("tables"), itemKey: Tab.TABLES, component: <TablesTab /> },
|
||||
{
|
||||
tab: t("relationships"),
|
||||
itemKey: Tab.RELATIONSHIPS,
|
||||
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 (
|
||||
@@ -36,13 +42,12 @@ export default function SidePanel({ width, resize, setResize }) {
|
||||
}
|
||||
collapsible
|
||||
>
|
||||
{tabList.length && tabList.map(tab =>
|
||||
<TabPane tab={tab.tab} itemKey={tab.itemKey} key={tab.itemKey}>
|
||||
<div className="p-2">
|
||||
{tab.component}
|
||||
</div>
|
||||
</TabPane>
|
||||
)}
|
||||
{tabList.length &&
|
||||
tabList.map((tab) => (
|
||||
<TabPane tab={tab.tab} itemKey={tab.itemKey} key={tab.itemKey}>
|
||||
<div className="p-2">{tab.component}</div>
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
{layout.issues && (
|
||||
|
||||
@@ -11,18 +11,21 @@ import { Action, ObjectType } from "../../../data/constants";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { hasCheck, hasPrecision, isSized } from "../../../utils/toSQL";
|
||||
import { useTables, useUndoRedo } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function FieldDetails({ data, tid, index }) {
|
||||
const { t } = useTranslation();
|
||||
const { tables } = useTables();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { updateField, deleteField } = useTables();
|
||||
const [editField, setEditField] = useState({});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="font-semibold">Default value</div>
|
||||
<div className="font-semibold">{t("default_value")}</div>
|
||||
<Input
|
||||
className="my-2"
|
||||
placeholder="Set default"
|
||||
placeholder={t("default_value")}
|
||||
value={data.default}
|
||||
disabled={
|
||||
data.type === "BLOB" ||
|
||||
@@ -45,7 +48,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
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([]);
|
||||
@@ -53,7 +59,9 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
/>
|
||||
{(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
|
||||
separator={[",", ", ", " ,"]}
|
||||
value={data.values}
|
||||
@@ -62,7 +70,7 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
}
|
||||
addOnBlur
|
||||
className="my-2"
|
||||
placeholder="Use ',' for batch input"
|
||||
placeholder={t("use_for_batch_input")}
|
||||
onChange={(v) => updateField(tid, index, { values: v })}
|
||||
onFocus={() => setEditField({ values: data.values })}
|
||||
onBlur={() => {
|
||||
@@ -80,9 +88,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
redo: { values: data.values },
|
||||
message: `Edit table field values to "${JSON.stringify(
|
||||
data.values,
|
||||
)}"`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -92,7 +101,7 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
)}
|
||||
{isSized(data.type) && (
|
||||
<>
|
||||
<div className="font-semibold">Size</div>
|
||||
<div className="font-semibold">{t("size")}</div>
|
||||
<InputNumber
|
||||
className="my-2 w-full"
|
||||
placeholder="Set length"
|
||||
@@ -111,7 +120,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
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([]);
|
||||
@@ -121,10 +133,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
)}
|
||||
{hasPrecision(data.type) && (
|
||||
<>
|
||||
<div className="font-semibold">Precision</div>
|
||||
<div className="font-semibold">{t("precision")}</div>
|
||||
<Input
|
||||
className="my-2 w-full"
|
||||
placeholder="Set precision: size, d"
|
||||
placeholder={t("set_precision")}
|
||||
validateStatus={
|
||||
!data.size || /^\d+,\s*\d+$|^$/.test(data.size)
|
||||
? "default"
|
||||
@@ -145,7 +157,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
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([]);
|
||||
@@ -155,10 +170,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
)}
|
||||
{hasCheck(data.type) && (
|
||||
<>
|
||||
<div className="font-semibold">Check Expression</div>
|
||||
<div className="font-semibold">{t("check")}</div>
|
||||
<Input
|
||||
className="mt-2"
|
||||
placeholder="Set constraint"
|
||||
placeholder={t("check")}
|
||||
value={data.check}
|
||||
disabled={data.increment}
|
||||
onChange={(value) => updateField(tid, index, { check: value })}
|
||||
@@ -175,19 +190,20 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
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([]);
|
||||
}}
|
||||
/>
|
||||
<div className="text-xs mt-1">
|
||||
*This will appear in the script as is.
|
||||
</div>
|
||||
<div className="text-xs mt-1">{t("this_will_appear_as_is")}</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between items-center my-3">
|
||||
<div className="font-medium">Unique</div>
|
||||
<div className="font-medium">{t("unique")}</div>
|
||||
<Checkbox
|
||||
value="unique"
|
||||
checked={data.unique}
|
||||
@@ -216,7 +232,7 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between items-center my-3">
|
||||
<div className="font-medium">Autoincrement</div>
|
||||
<div className="font-medium">{t("autoincrement")}</div>
|
||||
<Checkbox
|
||||
value="increment"
|
||||
checked={data.increment}
|
||||
@@ -242,9 +258,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
redo: {
|
||||
[checkedValues.target.value]: checkedValues.target.checked,
|
||||
},
|
||||
message: `Edit table field to${
|
||||
data.increment ? " not" : ""
|
||||
} auto increment`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -255,10 +272,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="font-semibold">Comment</div>
|
||||
<div className="font-semibold">{t("comment")}</div>
|
||||
<TextArea
|
||||
className="my-2"
|
||||
placeholder="Add comment"
|
||||
placeholder={t("comment")}
|
||||
value={data.comment}
|
||||
autosize
|
||||
rows={2}
|
||||
@@ -276,7 +293,10 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
redo: { comment: e.target.value },
|
||||
message: `Edit field comment to "${e.target.value}"`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -288,7 +308,7 @@ export default function FieldDetails({ data, tid, index }) {
|
||||
block
|
||||
onClick={() => deleteField(data, tid)}
|
||||
>
|
||||
Delete field
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,15 +2,17 @@ import { Action, ObjectType } from "../../../data/constants";
|
||||
import { Input, Button, Popover, Checkbox, Select } from "@douyinfe/semi-ui";
|
||||
import { IconMore, IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useTables, useUndoRedo } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
const { t } = useTranslation();
|
||||
const { tables, updateTable } = useTables();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<Select
|
||||
placeholder="Select fields"
|
||||
placeholder={t("select_fields")}
|
||||
multiple
|
||||
validateStatus={data.fields.length === 0 ? "error" : "default"}
|
||||
optionList={fields}
|
||||
@@ -33,7 +35,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
fields: [...value],
|
||||
name: `${value.join("_")}_index`,
|
||||
},
|
||||
message: `Edit index fields to "${JSON.stringify(value)}"`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[index field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -45,7 +50,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
fields: [...value],
|
||||
name: `${value.join("_")}_index`,
|
||||
}
|
||||
: index
|
||||
: index,
|
||||
),
|
||||
});
|
||||
}}
|
||||
@@ -53,10 +58,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
<Popover
|
||||
content={
|
||||
<div className="px-1 popover-theme">
|
||||
<div className="font-semibold mb-1">Index name: </div>
|
||||
<Input value={data.name} placeholder="Index name" disabled />
|
||||
<div className="font-semibold mb-1">{t("name")}: </div>
|
||||
<Input value={data.name} placeholder={t("name")} disabled />
|
||||
<div className="flex justify-between items-center my-3">
|
||||
<div className="font-medium">Unique</div>
|
||||
<div className="font-medium">{t("unique")}</div>
|
||||
<Checkbox
|
||||
value="unique"
|
||||
checked={data.unique}
|
||||
@@ -77,9 +82,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
[checkedValues.target.value]:
|
||||
checkedValues.target.checked,
|
||||
},
|
||||
message: `Edit table field to${
|
||||
data.unique ? " not" : ""
|
||||
} unique`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[index field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -91,7 +97,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
[checkedValues.target.value]:
|
||||
checkedValues.target.checked,
|
||||
}
|
||||
: index
|
||||
: index,
|
||||
),
|
||||
});
|
||||
}}
|
||||
@@ -110,7 +116,10 @@ export default function IndexDetails({ data, fields, iid, tid }) {
|
||||
component: "index_delete",
|
||||
tid: tid,
|
||||
data: data,
|
||||
message: `Delete index`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[delete index]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
|
||||
@@ -3,10 +3,12 @@ import { useSelect } from "../../../hooks";
|
||||
import { AutoComplete } from "@douyinfe/semi-ui";
|
||||
import { IconSearch } from "@douyinfe/semi-icons";
|
||||
import { ObjectType } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SearchBar({ tables }) {
|
||||
const { setSelectedElement } = useSelect();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const { t } = useTranslation();
|
||||
const filteredTable = useMemo(
|
||||
() => tables.map((t) => t.name).filter((i) => i.includes(searchText)),
|
||||
[tables, searchText],
|
||||
@@ -18,8 +20,8 @@ export default function SearchBar({ tables }) {
|
||||
value={searchText}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
emptyContent={<div className="p-3 popover-theme">No tables found</div>}
|
||||
placeholder={t("search")}
|
||||
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
|
||||
onChange={(v) => setSearchText(v)}
|
||||
onSelect={(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 { useState } from "react";
|
||||
import FieldDetails from "./FieldDetails";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TableField({ data, tid, index }) {
|
||||
const { updateField } = useTables();
|
||||
const { types } = useTypes();
|
||||
const { tables } = useTables();
|
||||
const { t } = useTranslation();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
|
||||
@@ -33,7 +36,10 @@ export default function TableField({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: editField,
|
||||
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([]);
|
||||
@@ -69,7 +75,10 @@ export default function TableField({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: { type: data.type },
|
||||
redo: { type: value },
|
||||
message: `Edit table field type to ${value}`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -123,7 +132,7 @@ export default function TableField({ data, tid, index }) {
|
||||
<Col span={3}>
|
||||
<Button
|
||||
type={data.notNull ? "primary" : "tertiary"}
|
||||
title="Not Null"
|
||||
title={t("not_null")}
|
||||
theme={data.notNull ? "solid" : "light"}
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
@@ -136,9 +145,10 @@ export default function TableField({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: { notNull: data.notNull },
|
||||
redo: { notNull: !data.notNull },
|
||||
message: `Edit table field to${
|
||||
data.notNull ? "" : " not"
|
||||
} null`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -151,7 +161,7 @@ export default function TableField({ data, tid, index }) {
|
||||
<Col span={3}>
|
||||
<Button
|
||||
type={data.primary ? "primary" : "tertiary"}
|
||||
title="Primary"
|
||||
title={t("primary")}
|
||||
theme={data.primary ? "solid" : "light"}
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
@@ -164,9 +174,10 @@ export default function TableField({ data, tid, index }) {
|
||||
fid: index,
|
||||
undo: { primary: data.primary },
|
||||
redo: { primary: !data.primary },
|
||||
message: `Edit table field to${
|
||||
data.primary ? " not" : ""
|
||||
} primary`,
|
||||
message: t("edit_table", {
|
||||
tableName: tables[tid].name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
|
||||
@@ -8,16 +8,17 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Popover,
|
||||
Toast,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import { useTables, useUndoRedo } from "../../../hooks";
|
||||
import { Action, ObjectType, defaultBlue } from "../../../data/constants";
|
||||
import ColorPalette from "../../ColorPalette";
|
||||
import ColorPalette from "../../ColorPicker";
|
||||
import TableField from "./TableField";
|
||||
import IndexDetails from "./IndexDetails";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TableInfo({ data }) {
|
||||
const { t } = useTranslation();
|
||||
const [indexActiveKey, setIndexActiveKey] = useState("");
|
||||
const { deleteTable, updateTable, updateField, setRelationships } =
|
||||
useTables();
|
||||
@@ -31,11 +32,11 @@ export default function TableInfo({ data }) {
|
||||
return (
|
||||
<div>
|
||||
<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
|
||||
value={data.name}
|
||||
validateStatus={data.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
placeholder={t("name")}
|
||||
className="ms-2"
|
||||
onChange={(value) => updateTable(data.id, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
@@ -50,7 +51,10 @@ export default function TableInfo({ data }) {
|
||||
tid: data.id,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit table name to ${e.target.value}`,
|
||||
message: t("edit_table", {
|
||||
tableName: e.target.value,
|
||||
extra: "[name]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -149,7 +153,7 @@ export default function TableInfo({ data }) {
|
||||
onChange={(itemKey) => setIndexActiveKey(itemKey)}
|
||||
accordion
|
||||
>
|
||||
<Collapse.Panel header="Indices" itemKey="1">
|
||||
<Collapse.Panel header={t("indices")} itemKey="1">
|
||||
{data.indices.map((idx, k) => (
|
||||
<IndexDetails
|
||||
key={"index_" + k}
|
||||
@@ -172,12 +176,12 @@ export default function TableInfo({ data }) {
|
||||
headerLine={false}
|
||||
>
|
||||
<Collapse keepDOM lazyRender>
|
||||
<Collapse.Panel header="Comment" itemKey="1">
|
||||
<Collapse.Panel header={t("comment")} itemKey="1">
|
||||
<TextArea
|
||||
field="comment"
|
||||
value={data.comment}
|
||||
autosize
|
||||
placeholder="Add comment"
|
||||
placeholder={t("comment")}
|
||||
rows={1}
|
||||
onChange={(value) =>
|
||||
updateTable(data.id, { comment: value }, false)
|
||||
@@ -194,7 +198,10 @@ export default function TableInfo({ data }) {
|
||||
tid: data.id,
|
||||
undo: editField,
|
||||
redo: { comment: e.target.value },
|
||||
message: `Edit table comment to ${e.target.value}`,
|
||||
message: t("edit_table", {
|
||||
tableName: e.target.value,
|
||||
extra: "[comment]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -220,7 +227,10 @@ export default function TableInfo({ data }) {
|
||||
tid: data.id,
|
||||
undo: { color: data.color },
|
||||
redo: { color: defaultBlue },
|
||||
message: `Edit table color to default`,
|
||||
message: t("edit_table", {
|
||||
tableName: data.name,
|
||||
extra: "[color]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -236,7 +246,10 @@ export default function TableInfo({ data }) {
|
||||
tid: data.id,
|
||||
undo: { color: data.color },
|
||||
redo: { color: c },
|
||||
message: `Edit table color to ${c}`,
|
||||
message: t("edit_table", {
|
||||
tableName: data.name,
|
||||
extra: "[color]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -267,7 +280,10 @@ export default function TableInfo({ data }) {
|
||||
element: ObjectType.TABLE,
|
||||
component: "index_add",
|
||||
tid: data.id,
|
||||
message: `Add index`,
|
||||
message: t("edit_table", {
|
||||
tableName: data.name,
|
||||
extra: "[add index]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -284,7 +300,7 @@ export default function TableInfo({ data }) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Add index
|
||||
{t("add_index")}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
@@ -297,7 +313,10 @@ export default function TableInfo({ data }) {
|
||||
element: ObjectType.TABLE,
|
||||
component: "field_add",
|
||||
tid: data.id,
|
||||
message: `Add field`,
|
||||
message: t("edit_table", {
|
||||
tableName: data.name,
|
||||
extra: "[add field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -321,17 +340,14 @@ export default function TableInfo({ data }) {
|
||||
}}
|
||||
block
|
||||
>
|
||||
Add field
|
||||
{t("add_field")}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Table deleted!`);
|
||||
deleteTable(data.id);
|
||||
}}
|
||||
onClick={() => deleteTable(data.id)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -5,10 +5,12 @@ import { ObjectType } from "../../../data/constants";
|
||||
import SearchBar from "./SearchBar";
|
||||
import Empty from "../Empty";
|
||||
import TableInfo from "./TableInfo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TablesTab() {
|
||||
const { tables, addTable } = useTables();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -18,12 +20,12 @@ export default function TablesTab() {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button icon={<IconPlus />} block onClick={() => addTable(true)}>
|
||||
Add table
|
||||
{t("add_table")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
{tables.length === 0 ? (
|
||||
<Empty title="No tables" text="Start building your diagram!" />
|
||||
<Empty title={t("no_tables")} text={t("no_tables_text")} />
|
||||
) : (
|
||||
<Collapse
|
||||
activeKey={
|
||||
|
||||
@@ -3,11 +3,13 @@ import { AutoComplete } from "@douyinfe/semi-ui";
|
||||
import { IconSearch } from "@douyinfe/semi-icons";
|
||||
import { useSelect, useTypes } from "../../../hooks";
|
||||
import { ObjectType } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Searchbar() {
|
||||
const { types } = useTypes();
|
||||
const [value, setValue] = useState("");
|
||||
const { setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
types.map((t) => t.name),
|
||||
@@ -25,9 +27,9 @@ export default function Searchbar() {
|
||||
value={value}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
placeholder={t("search")}
|
||||
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)}
|
||||
onSelect={(v) => {
|
||||
const i = types.findIndex((t) => t.name === v);
|
||||
|
||||
@@ -13,22 +13,25 @@ import {
|
||||
import { IconDeleteStroked, IconMore } from "@douyinfe/semi-icons";
|
||||
import { isSized, hasPrecision, getSize } from "../../../utils/toSQL";
|
||||
import { useUndoRedo, useTypes } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TypeField({ data, tid, fid }) {
|
||||
const { types, updateType } = useTypes();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Row gutter={6} className="hover-1 my-2">
|
||||
<Col span={10}>
|
||||
<Input
|
||||
value={data.name}
|
||||
validateStatus={data.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
placeholder={t("name")}
|
||||
onChange={(value) =>
|
||||
updateType(tid, {
|
||||
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,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit type field name to ${e.target.value}`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -62,7 +68,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
})),
|
||||
...types
|
||||
.filter(
|
||||
(type) => type.name.toLowerCase() !== data.name.toLowerCase()
|
||||
(type) => type.name.toLowerCase() !== data.name.toLowerCase(),
|
||||
)
|
||||
.map((type) => ({
|
||||
label: type.name.toUpperCase(),
|
||||
@@ -72,7 +78,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
filter
|
||||
value={data.type}
|
||||
validateStatus={data.type === "" ? "error" : "default"}
|
||||
placeholder="Type"
|
||||
placeholder={t("type")}
|
||||
onChange={(value) => {
|
||||
if (value === data.type) return;
|
||||
setUndoStack((prev) => [
|
||||
@@ -85,7 +91,10 @@ export default function TypeField({ data, tid, fid }) {
|
||||
fid: fid,
|
||||
undo: { type: data?.type },
|
||||
redo: { type: value },
|
||||
message: `Edit type field type to ${value}`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -98,7 +107,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
type: value,
|
||||
values: data.values ? [...data.values] : [],
|
||||
}
|
||||
: e
|
||||
: e,
|
||||
),
|
||||
});
|
||||
} else if (isSized(value) || hasPrecision(value)) {
|
||||
@@ -106,13 +115,13 @@ export default function TypeField({ data, tid, fid }) {
|
||||
fields: types[tid].fields.map((e, id) =>
|
||||
id === fid
|
||||
? { ...data, type: value, size: getSize(value) }
|
||||
: e
|
||||
: e,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
updateType(tid, {
|
||||
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]">
|
||||
{(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
|
||||
separator={[",", ", ", " ,"]}
|
||||
value={data.values}
|
||||
@@ -135,11 +146,11 @@ export default function TypeField({ data, tid, fid }) {
|
||||
: "default"
|
||||
}
|
||||
className="my-2"
|
||||
placeholder="Use ',' for batch input"
|
||||
placeholder={t("use_for_batch_input")}
|
||||
onChange={(v) =>
|
||||
updateType(tid, {
|
||||
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,
|
||||
undo: editField,
|
||||
redo: { values: data.values },
|
||||
message: `Edit type field values to "${JSON.stringify(
|
||||
data.values
|
||||
)}"`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -172,15 +184,15 @@ export default function TypeField({ data, tid, fid }) {
|
||||
)}
|
||||
{isSized(data.type) && (
|
||||
<>
|
||||
<div className="font-semibold">Size</div>
|
||||
<div className="font-semibold">{t("size")}</div>
|
||||
<InputNumber
|
||||
className="my-2 w-full"
|
||||
placeholder="Set length"
|
||||
placeholder={t("size")}
|
||||
value={data.size}
|
||||
onChange={(value) =>
|
||||
updateType(tid, {
|
||||
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,
|
||||
undo: editField,
|
||||
redo: { size: e.target.value },
|
||||
message: `Edit type field size to ${e.target.value}`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -207,10 +222,10 @@ export default function TypeField({ data, tid, fid }) {
|
||||
)}
|
||||
{hasPrecision(data.type) && (
|
||||
<>
|
||||
<div className="font-semibold">Precision</div>
|
||||
<div className="font-semibold">{t("precision")}</div>
|
||||
<Input
|
||||
className="my-2 w-full"
|
||||
placeholder="Set precision: (size, d)"
|
||||
placeholder={t("set_precision")}
|
||||
validateStatus={
|
||||
/^\(\d+,\s*\d+\)$|^$/.test(data.size)
|
||||
? "default"
|
||||
@@ -220,7 +235,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
onChange={(value) =>
|
||||
updateType(tid, {
|
||||
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,
|
||||
undo: editField,
|
||||
redo: { size: e.target.value },
|
||||
message: `Edit type field precision to ${e.target.value}`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -259,7 +277,10 @@ export default function TypeField({ data, tid, fid }) {
|
||||
tid: tid,
|
||||
fid: fid,
|
||||
data: data,
|
||||
message: `Delete field`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[delete field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
updateType(tid, {
|
||||
@@ -267,7 +288,7 @@ export default function TypeField({ data, tid, fid }) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete field
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -8,16 +8,17 @@ import {
|
||||
TextArea,
|
||||
Button,
|
||||
Card,
|
||||
Toast,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconDeleteStroked, IconPlus } from "@douyinfe/semi-icons";
|
||||
import { useUndoRedo, useTypes } from "../../../hooks";
|
||||
import TypeField from "./TypeField";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TypeInfo({ index, data }) {
|
||||
const { deleteType, updateType } = useTypes();
|
||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const [editField, setEditField] = useState({});
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div id={`scroll_type_${index}`}>
|
||||
@@ -30,11 +31,11 @@ export default function TypeInfo({ index, data }) {
|
||||
itemKey={`${index}`}
|
||||
>
|
||||
<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
|
||||
value={data.name}
|
||||
validateStatus={data.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
placeholder={t("name")}
|
||||
className="ms-2"
|
||||
onChange={(value) => updateType(index, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
@@ -49,7 +50,10 @@ export default function TypeInfo({ index, data }) {
|
||||
tid: index,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit type name to ${e.target.value}`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[name]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -65,12 +69,12 @@ export default function TypeInfo({ index, data }) {
|
||||
headerLine={false}
|
||||
>
|
||||
<Collapse keepDOM lazyRender>
|
||||
<Collapse.Panel header="Comment" itemKey="1">
|
||||
<Collapse.Panel header={t("comment")} itemKey="1">
|
||||
<TextArea
|
||||
field="comment"
|
||||
value={data.comment}
|
||||
autosize
|
||||
placeholder="Add comment"
|
||||
placeholder={t("comment")}
|
||||
rows={1}
|
||||
onChange={(value) =>
|
||||
updateType(index, { comment: value }, false)
|
||||
@@ -87,7 +91,10 @@ export default function TypeInfo({ index, data }) {
|
||||
tid: index,
|
||||
undo: editField,
|
||||
redo: { comment: e.target.value },
|
||||
message: `Edit type comment to ${e.target.value}`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[comment]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -108,7 +115,10 @@ export default function TypeInfo({ index, data }) {
|
||||
element: ObjectType.TYPE,
|
||||
component: "field_add",
|
||||
tid: index,
|
||||
message: `Add field to type`,
|
||||
message: t("edit_type", {
|
||||
typeName: data.name,
|
||||
extra: "[add field]",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
@@ -124,20 +134,17 @@ export default function TypeInfo({ index, data }) {
|
||||
}}
|
||||
block
|
||||
>
|
||||
Add field
|
||||
{t("add_field")}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Type deleted!`);
|
||||
deleteType(index);
|
||||
}}
|
||||
onClick={() => deleteType(index)}
|
||||
block
|
||||
>
|
||||
Delete
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -5,10 +5,12 @@ import { ObjectType } from "../../../data/constants";
|
||||
import Searchbar from "./SearchBar";
|
||||
import Empty from "../Empty";
|
||||
import TypeInfo from "./TypeInfo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function TypesTab() {
|
||||
const { types, addType } = useTypes();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -18,30 +20,18 @@ export default function TypesTab() {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button icon={<IconPlus />} block onClick={() => addType(true)}>
|
||||
Add type
|
||||
{t("add_type")}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Popover
|
||||
content={
|
||||
<div className="w-[240px] text-sm space-y-2 popover-theme">
|
||||
<div>
|
||||
This feature is meant for object-relational DBMSs like{" "}
|
||||
<strong>PostgreSQL</strong>.
|
||||
</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>
|
||||
{t("types_info")
|
||||
.split("\n")
|
||||
.map((line, index) => (
|
||||
<div key={index}>{line}</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
showArrow
|
||||
@@ -52,7 +42,7 @@ export default function TypesTab() {
|
||||
</Col>
|
||||
</Row>
|
||||
{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
|
||||
activeKey={
|
||||
|
||||
Reference in New Issue
Block a user