Merge branch 'main' into select-db

# Conflicts:
#	src/i18n/locales/en.js
#	src/utils/exportSQL/generic.js
This commit is contained in:
1ilit 2024-07-07 17:57:33 +03:00
commit 322bb6e988
13 changed files with 143 additions and 60 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
open_collective: drawdb

View File

@ -247,7 +247,7 @@ export default function Todo() {
<TextArea <TextArea
placeholder={t("details")} placeholder={t("details")}
onChange={(v) => updateTask(i, { details: v })} onChange={(v) => updateTask(i, { details: v })}
value={t.details} value={task.details}
onBlur={() => setSaveState(State.SAVING)} onBlur={() => setSaveState(State.SAVING)}
></TextArea> ></TextArea>
</Col> </Col>

View File

@ -15,7 +15,7 @@ export default function AreasTab() {
<div className="flex gap-2"> <div className="flex gap-2">
<SearchBar /> <SearchBar />
<div> <div>
<Button icon={<IconPlus />} block onClick={addArea}> <Button icon={<IconPlus />} block onClick={() => addArea()}>
{t("add_area")} {t("add_area")}
</Button> </Button>
</div> </div>

View File

@ -98,7 +98,7 @@ export default function NoteInfo({ data, nid }) {
<button <button
key={c} key={c}
style={{ backgroundColor: c }} style={{ backgroundColor: c }}
className="p-3 rounded-full mx-1" className="w-10 h-10 p-3 rounded-full mx-1"
onClick={() => { onClick={() => {
setUndoStack((prev) => [ setUndoStack((prev) => [
...prev, ...prev,

View File

@ -3,11 +3,13 @@ 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 { useDiagram, useUndoRedo } from "../../../hooks"; import { useDiagram, useUndoRedo } from "../../../hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState } from "react";
export default function IndexDetails({ data, fields, iid, tid }) { export default function IndexDetails({ data, fields, iid, tid }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { tables, updateTable } = useDiagram(); const { tables, updateTable } = useDiagram();
const { setUndoStack, setRedoStack } = useUndoRedo(); const { setUndoStack, setRedoStack } = useUndoRedo();
const [editField, setEditField] = useState({});
return ( return (
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
@ -29,11 +31,9 @@ export default function IndexDetails({ data, fields, iid, tid }) {
iid: iid, iid: iid,
undo: { undo: {
fields: [...data.fields], fields: [...data.fields],
name: `${data.fields.join("_")}_index`,
}, },
redo: { redo: {
fields: [...value], fields: [...value],
name: `${value.join("_")}_index`,
}, },
message: t("edit_table", { message: t("edit_table", {
tableName: tables[tid].name, tableName: tables[tid].name,
@ -48,7 +48,6 @@ export default function IndexDetails({ data, fields, iid, tid }) {
? { ? {
...index, ...index,
fields: [...value], fields: [...value],
name: `${value.join("_")}_index`,
} }
: index, : index,
), ),
@ -59,7 +58,48 @@ export default function IndexDetails({ data, fields, iid, tid }) {
content={ content={
<div className="px-1 popover-theme"> <div className="px-1 popover-theme">
<div className="font-semibold mb-1">{t("name")}: </div> <div className="font-semibold mb-1">{t("name")}: </div>
<Input value={data.name} placeholder={t("name")} disabled /> <Input
value={data.name}
placeholder={t("name")}
validateStatus={data.name.trim() === "" ? "error" : "default"}
onFocus={() =>
setEditField({
name: data.name,
})
}
onChange={(value) =>
updateTable(tid, {
indices: tables[tid].indices.map((index) =>
index.id === iid
? {
...index,
name: value,
}
: index,
),
})
}
onBlur={(e) => {
if (e.target.value === editField.name) return;
setUndoStack((prev) => [
...prev,
{
action: Action.EDIT,
element: ObjectType.TABLE,
component: "index",
tid: tid,
iid: iid,
undo: editField,
redo: { name: e.target.value },
message: t("edit_table", {
tableName: tables[tid].name,
extra: "[index]",
}),
},
]);
setRedoStack([]);
}}
/>
<div className="flex justify-between items-center my-3"> <div className="flex justify-between items-center my-3">
<div className="font-medium">{t("unique")}</div> <div className="font-medium">{t("unique")}</div>
<Checkbox <Checkbox
@ -133,7 +173,7 @@ export default function IndexDetails({ data, fields, iid, tid }) {
}); });
}} }}
> >
Delete {t("delete")}
</Button> </Button>
</div> </div>
} }

View File

@ -1,40 +1,64 @@
import { useMemo, useState } from "react"; import { useMemo } from "react";
import { useSelect } from "../../../hooks"; import { useSelect } from "../../../hooks";
import { AutoComplete } from "@douyinfe/semi-ui"; import { TreeSelect } 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"; 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 { t } = useTranslation(); const { t } = useTranslation();
const filteredTable = useMemo(
() => tables.map((t) => t.name).filter((i) => i.includes(searchText)), const treeData = useMemo(() => {
[tables, searchText], return tables.map(({ id, name: parentName, fields }, i) => {
); const children = fields.map(({ name }, j) => ({
tableId: id,
id: `${j}`,
label: name,
value: name,
key: `${i}-${j}`,
}));
return {
tableId: id,
id: `${i}`,
label: parentName,
value: parentName,
key: `${i}`,
children,
};
});
}, [tables]);
return ( return (
<AutoComplete <TreeSelect
data={filteredTable} searchPosition="trigger"
value={searchText} dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
showClear treeData={treeData}
prefix={<IconSearch />} prefix={<IconSearch />}
placeholder={t("search")}
emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>} emptyContent={<div className="p-3 popover-theme">{t("not_found")}</div>}
onChange={(v) => setSearchText(v)} filterTreeNode
onSelect={(v) => { placeholder={t("search")}
const { id } = tables.find((t) => t.name === v); onChange={(node) => {
const { tableId, id, children } = node;
setSelectedElement((prev) => ({ setSelectedElement((prev) => ({
...prev, ...prev,
id: id, id: tableId,
open: true, open: true,
element: ObjectType.TABLE, element: ObjectType.TABLE,
})); }));
document document
.getElementById(`scroll_table_${id}`) .getElementById(`scroll_table_${tableId}`)
.scrollIntoView({ behavior: "smooth" }); .scrollIntoView({ behavior: "smooth" });
if (!children) {
document
.getElementById(`scroll_table_${tableId}_input_${id}`)
.focus();
}
}} }}
onChangeWithObject
className="w-full" className="w-full"
/> />
); );

View File

@ -20,8 +20,9 @@ export default function TableField({ data, tid, index }) {
<Row gutter={6} className="hover-1 my-2"> <Row gutter={6} className="hover-1 my-2">
<Col span={7}> <Col span={7}>
<Input <Input
id={`scroll_table_${tid}_input_${index}`}
value={data.name} value={data.name}
validateStatus={data.name === "" ? "error" : "default"} validateStatus={data.name.trim() === "" ? "error" : "default"}
placeholder="Name" placeholder="Name"
onChange={(value) => updateField(tid, index, { name: value })} onChange={(value) => updateField(tid, index, { name: value })}
onFocus={(e) => setEditField({ name: e.target.value })} onFocus={(e) => setEditField({ name: e.target.value })}

View File

@ -33,7 +33,7 @@ export default function TableInfo({ data }) {
<div className="text-md font-semibold break-keep">{t("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.trim() === "" ? "error" : "default"}
placeholder={t("name")} placeholder={t("name")}
className="ms-2" className="ms-2"
onChange={(value) => updateTable(data.id, { name: value })} onChange={(value) => updateTable(data.id, { name: value })}
@ -290,7 +290,7 @@ export default function TableInfo({ data }) {
...data.indices, ...data.indices,
{ {
id: data.indices.length, id: data.indices.length,
name: `index_${data.indices.length}`, name: `${data.name}_index_${data.indices.length}`,
unique: false, unique: false,
fields: [], fields: [],
}, },

View File

@ -46,10 +46,17 @@ export default function TablesTab() {
{tables.map((t) => ( {tables.map((t) => (
<div id={`scroll_table_${t.id}`} key={t.id}> <div id={`scroll_table_${t.id}`} key={t.id}>
<Collapse.Panel <Collapse.Panel
className="relative"
header={ header={
<>
<div className="overflow-hidden text-ellipsis whitespace-nowrap"> <div className="overflow-hidden text-ellipsis whitespace-nowrap">
{t.name} {t.name}
</div> </div>
<div
className="w-1 h-full absolute top-0 left-0 bottom-0"
style={{ backgroundColor: t.color }}
/>
</>
} }
itemKey={`${t.id}`} itemKey={`${t.id}`}
> >

View File

@ -226,6 +226,7 @@ const en = {
no_enums: "No enums", no_enums: "No enums",
no_enums_text: "Define enums here", no_enums_text: "Define enums here",
declare_array: "Declare array", declare_array: "Declare array",
empty_index_name: "Declared an index with no name in table '{{tableName}}'",
}, },
}; };

View File

@ -176,14 +176,14 @@ export function jsonToMySQL(obj) {
: "" : ""
}\n)${table.comment ? ` COMMENT='${table.comment}'` : ""};\n${ }\n)${table.comment ? ` COMMENT='${table.comment}'` : ""};\n${
table.indices.length > 0 table.indices.length > 0
? `\n${table.indices.map( ? `\n${table.indices
.map(
(i) => (i) =>
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${ `CREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${i.name}\`\nON \`${table.name}\` (${i.fields
i.name
}\`\nON \`${table.name}\` (${i.fields
.map((f) => `\`${f}\``) .map((f) => `\`${f}\``)
.join(", ")});`, .join(", ")});`,
)}` )
.join("\n")}`
: "" : ""
}`, }`,
) )
@ -251,10 +251,8 @@ export function jsonToPostgreSQL(obj) {
field.name field.name
}" ${getTypeString(field, obj.database, "postgres")}${ }" ${getTypeString(field, obj.database, "postgres")}${
field.notNull ? " NOT NULL" : "" field.notNull ? " NOT NULL" : ""
}${ }${field.unique ? " UNIQUE" : ""}${
field.default !== "" field.default !== "" ? ` DEFAULT ${parseDefault(field)}` : ""
? ` DEFAULT ${parseDefault(field, obj.database)}`
: ""
}${ }${
field.check === "" || field.check === "" ||
!dbToTypes[obj.database][field.type].hasCheck !dbToTypes[obj.database][field.type].hasCheck
@ -271,14 +269,16 @@ export function jsonToPostgreSQL(obj) {
: "" : ""
}\n);\n${ }\n);\n${
table.indices.length > 0 table.indices.length > 0
? `${table.indices.map( ? `${table.indices
.map(
(i) => (i) =>
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX "${ `CREATE ${i.unique ? "UNIQUE " : ""}INDEX "${
i.name i.name
}"\nON "${table.name}" (${i.fields }"\nON "${table.name}" (${i.fields
.map((f) => `"${f}"`) .map((f) => `"${f}"`)
.join(", ")});`, .join(", ")});`,
)}` )
.join("\n")}`
: "" : ""
}`, }`,
) )
@ -426,14 +426,16 @@ export function jsonToMariaDB(obj) {
: "" : ""
}\n);${ }\n);${
table.indices.length > 0 table.indices.length > 0
? `\n${table.indices.map( ? `\n${table.indices
.map(
(i) => (i) =>
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${ `CREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${
i.name i.name
}\`\nON \`${table.name}\` (${i.fields }\`\nON \`${table.name}\` (${i.fields
.map((f) => `\`${f}\``) .map((f) => `\`${f}\``)
.join(", ")});`, .join(", ")});`,
)}` )
.join("\n")}`
: "" : ""
}`, }`,
) )

View File

@ -99,6 +99,13 @@ export function getIssues(diagram) {
}); });
table.indices.forEach((index) => { table.indices.forEach((index) => {
if (index.name.trim() === "") {
issues.push(
i18n.t("empty_index_name", {
tableName: table.name,
}),
);
}
if (index.fields.length === 0) { if (index.fields.length === 0) {
issues.push( issues.push(
i18n.t("empty_index", { i18n.t("empty_index", {

View File

@ -7,7 +7,7 @@ export const getModalTitle = (modal) => {
case MODAL.IMPORT_SRC: case MODAL.IMPORT_SRC:
return i18n.t("import_diagram"); return i18n.t("import_diagram");
case MODAL.CODE: case MODAL.CODE:
return i18n.t("export_source"); return i18n.t("export");
case MODAL.IMG: case MODAL.IMG:
return i18n.t("export_image"); return i18n.t("export_image");
case MODAL.RENAME: case MODAL.RENAME: