diff --git a/src/components/EditorCanvas/Table.jsx b/src/components/EditorCanvas/Table.jsx
deleted file mode 100644
index 1e656f9a..00000000
--- a/src/components/EditorCanvas/Table.jsx
+++ /dev/null
@@ -1,376 +0,0 @@
-import { useState } from "react";
-import {
- Tab,
- ObjectType,
- tableFieldHeight,
- tableHeaderHeight,
- tableColorStripHeight,
-} from "../../data/constants";
-import {
- IconEdit,
- IconMore,
- IconMinus,
- IconDeleteStroked,
- IconKeyStroked,
-} from "@douyinfe/semi-icons";
-import { Popover, Tag, Button, SideSheet } from "@douyinfe/semi-ui";
-import { useLayout, useSettings, useDiagram, useSelect } from "../../hooks";
-import TableInfo from "../EditorSidePanel/TablesTab/TableInfo";
-import { useTranslation } from "react-i18next";
-import { dbToTypes } from "../../data/datatypes";
-import { isRtl } from "../../i18n/utils/rtl";
-import i18n from "../../i18n/i18n";
-
-export default function Table(props) {
- const [hoveredField, setHoveredField] = useState(-1);
- const { database } = useDiagram();
- const {
- tableData,
- onPointerDown,
- setHoveredTable,
- handleGripField,
- setLinkingLine,
- } = props;
- const { layout } = useLayout();
- const { deleteTable, deleteField } = useDiagram();
- const { settings } = useSettings();
- const { t } = useTranslation();
- const { selectedElement, setSelectedElement } = useSelect();
-
- const height =
- tableData.fields.length * tableFieldHeight + tableHeaderHeight + 7;
- const openEditor = () => {
- if (!layout.sidebar) {
- setSelectedElement((prev) => ({
- ...prev,
- element: ObjectType.TABLE,
- id: tableData.id,
- open: true,
- }));
- } else {
- setSelectedElement((prev) => ({
- ...prev,
- currentTab: Tab.TABLES,
- element: ObjectType.TABLE,
- id: tableData.id,
- open: true,
- }));
- if (selectedElement.currentTab !== Tab.TABLES) return;
- document
- .getElementById(`scroll_table_${tableData.id}`)
- .scrollIntoView({ behavior: "smooth" });
- }
- };
-
- return (
- <>
-
-
-
-
-
- {tableData.name}
-
-
-
-
}
- size="small"
- theme="solid"
- style={{
- backgroundColor: "#2f68adb3",
- marginRight: "6px",
- }}
- onClick={openEditor}
- />
-
-
-
{t("comment")}:{" "}
- {tableData.comment === "" ? (
- t("not_set")
- ) : (
-
{tableData.comment}
- )}
-
-
-
- {t("indices")}:
- {" "}
- {tableData.indices.length === 0 ? (
- t("not_set")
- ) : (
-
- {tableData.indices.map((index, k) => (
-
-
-
- {index.fields.map((f) => (
-
- {f}
-
- ))}
-
-
- ))}
-
- )}
-
- }
- type="danger"
- block
- style={{ marginTop: "8px" }}
- onClick={() => deleteTable(tableData.id)}
- >
- {t("delete")}
-
-
- }
- position="rightTop"
- showArrow
- trigger="click"
- style={{ width: "200px", wordBreak: "break-word" }}
- >
-
}
- type="tertiary"
- size="small"
- style={{
- backgroundColor: "#808080b3",
- color: "white",
- }}
- />
-
-
-
-
- {tableData.fields.map((e, i) => {
- return settings.showFieldSummary ? (
-
-
-
{e.name}
-
- {e.type +
- ((dbToTypes[database][e.type].isSized ||
- dbToTypes[database][e.type].hasPrecision) &&
- e.size &&
- e.size !== ""
- ? "(" + e.size + ")"
- : "")}
-
-
-
- {e.primary && (
-
- {t("primary")}
-
- )}
- {e.unique && (
-
- {t("unique")}
-
- )}
- {e.notNull && (
-
- {t("not_null")}
-
- )}
- {e.increment && (
-
- {t("autoincrement")}
-
- )}
-
- {t("default_value")}:
- {e.default === "" ? t("not_set") : e.default}
-
-
- {t("comment")}:
- {e.comment === "" ? t("not_set") : e.comment}
-
-
- }
- position="right"
- showArrow
- style={
- isRtl(i18n.language)
- ? { direction: "rtl" }
- : { direction: "ltr" }
- }
- >
- {field(e, i)}
-
- ) : (
- field(e, i)
- );
- })}
-
-
-
- setSelectedElement((prev) => ({
- ...prev,
- open: !prev.open,
- }))
- }
- style={{ paddingBottom: "16px" }}
- >
-
-
- >
- );
-
- function field(fieldData, index) {
- return (
-
{
- if (!e.isPrimary) return;
-
- setHoveredField(index);
- setHoveredTable({
- tableId: tableData.id,
- field: index,
- });
- }}
- onPointerLeave={(e) => {
- if (!e.isPrimary) return;
-
- setHoveredField(-1);
- }}
- onPointerDown={(e) => {
- // Required for onPointerLeave to trigger when a touch pointer leaves
- // https://stackoverflow.com/a/70976017/1137077
- e.target.releasePointerCapture(e.pointerId);
- }}
- >
-
-
-
- {hoveredField === index ? (
-
}
- onClick={() => deleteField(fieldData, tableData.id)}
- />
- ) : (
-
- {fieldData.primary && }
- {!fieldData.notNull && ?}
-
- {fieldData.type +
- ((dbToTypes[database][fieldData.type].isSized ||
- dbToTypes[database][fieldData.type].hasPrecision) &&
- fieldData.size &&
- fieldData.size !== ""
- ? "(" + fieldData.size + ")"
- : "")}
-
-
- )}
-
-
- );
- }
-}
diff --git a/src/components/EditorCanvas/Table/components/TableField.jsx b/src/components/EditorCanvas/Table/components/TableField.jsx
new file mode 100644
index 00000000..e96ce7b8
--- /dev/null
+++ b/src/components/EditorCanvas/Table/components/TableField.jsx
@@ -0,0 +1,133 @@
+import React, { forwardRef } from "react";
+import { dbToTypes } from "../../../../data/datatypes";
+import { useDiagram } from "../../../../hooks";
+import { Button } from "@douyinfe/semi-ui";
+import { IconMinus, IconKeyStroked } from "@douyinfe/semi-icons";
+
+const TableField = forwardRef((props, ref) => {
+ const {
+ tableData,
+ fieldData,
+ index,
+ setHoveredTable,
+ handleGripField,
+ setLinkingLine,
+ setHoveredField,
+ hoveredField,
+ tableFieldHeight,
+ tableHeaderHeight,
+ tableColorStripHeight,
+ } = props;
+ const { database, deleteField } = useDiagram();
+
+ const FieldSize = React.memo(({ field }) => {
+ let hasSize =
+ dbToTypes[database][field.type].isSized ||
+ dbToTypes[database][field.type].hasPrecision;
+ let sizeValid = field.size && field.size !== "";
+
+ if (hasSize && sizeValid) {
+ return field.type + `(${field.size})`;
+ } else {
+ return field.type;
+ }
+ });
+
+ FieldSize.displayName = "FieldSize";
+
+ return (
+ {
+ if (!e.isPrimary) return;
+
+ setHoveredField(index);
+ setHoveredTable({
+ tableId: tableData.id,
+ field: index,
+ });
+ }}
+ onPointerLeave={(e) => {
+ if (!e.isPrimary) return;
+
+ setHoveredField(-1);
+ }}
+ onPointerDown={(e) => {
+ // Required for onPointerLeave to trigger when a touch pointer leaves
+ // https://stackoverflow.com/a/70976017/1137077
+ e.target.releasePointerCapture(e.pointerId);
+ }}
+ >
+
+
+
+
+ {hoveredField === index ? (
+
}
+ onClick={() => {
+ deleteField(fieldData, tableData.id);
+ }}
+ />
+ ) : (
+
+ {fieldData.primary && }
+ {!fieldData.notNull && ?}
+
+
+
+
+ )}
+
+
+ );
+});
+
+TableField.displayName = "TableField";
+
+export default TableField;
diff --git a/src/components/EditorCanvas/Table/components/TableFieldPopover.jsx b/src/components/EditorCanvas/Table/components/TableFieldPopover.jsx
new file mode 100644
index 00000000..00d19bee
--- /dev/null
+++ b/src/components/EditorCanvas/Table/components/TableFieldPopover.jsx
@@ -0,0 +1,83 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+import i18n from "../../../../i18n/i18n";
+import { isRtl } from "../../../../i18n/utils/rtl";
+import { Popover, Tag } from "@douyinfe/semi-ui";
+import { dbToTypes } from "../../../../data/datatypes";
+import { useDiagram } from "../../../../hooks";
+
+export default function TableFieldPopover({ fieldData, children, visible }) {
+ const { database } = useDiagram();
+ const { t } = useTranslation();
+
+ if (!visible) {
+ return {children};
+ }
+
+ const FieldSize = React.memo(({ field }) => {
+ let hasSize =
+ dbToTypes[database][field.type].isSized ||
+ dbToTypes[database][field.type].hasPrecision;
+ let sizeValid = field.size && field.size !== "";
+
+ if (hasSize && sizeValid) {
+ return `(${field.size})`;
+ } else {
+ return "";
+ }
+ });
+
+ FieldSize.displayName = "FieldSize";
+
+ return (
+
+
+
{fieldData.name}
+
{}
+
+
+
+
+ {fieldData.primary && (
+
+ {t("primary")}
+
+ )}
+ {fieldData.unique && (
+
+ {t("unique")}
+
+ )}
+ {fieldData.notNull && (
+
+ {t("not_null")}
+
+ )}
+ {fieldData.increment && (
+
+ {t("autoincrement")}
+
+ )}
+
+ {t("default_value")}:
+ {fieldData.default === "" ? t("not_set") : fieldData.default}
+
+
+ {t("comment")}:
+ {fieldData.comment === "" ? t("not_set") : fieldData.comment}
+
+
+ }
+ position="right"
+ showArrow
+ style={isRtl(i18n.language) ? { direction: "rtl" } : { direction: "ltr" }}
+ >
+ {children}
+
+ );
+}
diff --git a/src/components/EditorCanvas/Table/components/TableHeader.jsx b/src/components/EditorCanvas/Table/components/TableHeader.jsx
new file mode 100644
index 00000000..7d214ac5
--- /dev/null
+++ b/src/components/EditorCanvas/Table/components/TableHeader.jsx
@@ -0,0 +1,105 @@
+import { IconEdit, IconMore, IconDeleteStroked } from "@douyinfe/semi-icons";
+import { Popover, Tag, Button } from "@douyinfe/semi-ui";
+import { useDiagram } from "../../../../hooks";
+
+export default function TableHeader({ tableData, settings, openEditor, t }) {
+ const { deleteTable } = useDiagram();
+
+ return (
+
+
+ {tableData.name}
+
+
+
+
}
+ size="small"
+ theme="solid"
+ style={{
+ backgroundColor: "#2f68adb3",
+ marginRight: "6px",
+ }}
+ onClick={openEditor}
+ />
+
+
+
{t("comment")}:{" "}
+ {tableData.comment === "" ? (
+ t("not_set")
+ ) : (
+
{tableData.comment}
+ )}
+
+
+
+ {t("indices")}:
+ {" "}
+ {tableData.indices.length === 0 ? (
+ t("not_set")
+ ) : (
+
+ {tableData.indices.map((index, k) => (
+
+
+
+ {index.fields.map((f) => (
+
+ {f}
+
+ ))}
+
+
+ ))}
+
+ )}
+
+ }
+ type="danger"
+ block
+ style={{ marginTop: "8px" }}
+ onClick={() => deleteTable(tableData.id)}
+ >
+ {t("delete")}
+
+
+ }
+ position="rightTop"
+ showArrow
+ trigger="click"
+ style={{ width: "200px", wordBreak: "break-word" }}
+ >
+
}
+ type="tertiary"
+ size="small"
+ style={{
+ backgroundColor: "#808080b3",
+ color: "white",
+ }}
+ />
+
+
+
+
+ );
+}
diff --git a/src/components/EditorCanvas/Table/index.jsx b/src/components/EditorCanvas/Table/index.jsx
new file mode 100644
index 00000000..69bc5208
--- /dev/null
+++ b/src/components/EditorCanvas/Table/index.jsx
@@ -0,0 +1,156 @@
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { SideSheet } from "@douyinfe/semi-ui";
+import { useLayout, useSettings, useSelect } from "../../../hooks";
+
+import {
+ Tab,
+ ObjectType,
+ tableFieldHeight,
+ tableHeaderHeight,
+ tableColorStripHeight,
+} from "../../../data/constants";
+
+import TableFieldPopover from "./components/TableFieldPopover";
+import TableField from "./components/TableField";
+import TableHeader from "./components/TableHeader";
+
+import TableInfo from "../../EditorSidePanel/TablesTab/TableInfo";
+
+export default function Table(props) {
+ const [hoveredField, setHoveredField] = useState(-1);
+ const {
+ tableData,
+ onPointerDown,
+ setHoveredTable,
+ handleGripField,
+ setLinkingLine,
+ } = props;
+ const { layout } = useLayout();
+ const { settings } = useSettings();
+ const { t } = useTranslation();
+ const { selectedElement, setSelectedElement } = useSelect();
+
+ const height =
+ tableData.fields.length * tableFieldHeight + tableHeaderHeight + 7;
+
+ const openEditor = () => {
+ if (!layout.sidebar) {
+ setSelectedElement((prev) => ({
+ ...prev,
+ element: ObjectType.TABLE,
+ id: tableData.id,
+ open: true,
+ }));
+ } else {
+ setSelectedElement((prev) => ({
+ ...prev,
+ currentTab: Tab.TABLES,
+ element: ObjectType.TABLE,
+ id: tableData.id,
+ open: true,
+ }));
+ if (selectedElement.currentTab !== Tab.TABLES) return;
+ document
+ .getElementById(`scroll_table_${tableData.id}`)
+ .scrollIntoView({ behavior: "smooth" });
+ }
+ };
+
+ const TableHeaderBand = React.memo(({ color }) => {
+ return (
+
+ );
+ });
+
+ TableHeaderBand.displayName = "TableHeaderBand";
+
+ return (
+ <>
+
+
+
+
+
+
+ {tableData.fields.map((fieldData, index) => {
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+ setSelectedElement((prev) => ({
+ ...prev,
+ open: !prev.open,
+ }))
+ }
+ style={{ paddingBottom: "16px" }}
+ >
+
+
+ >
+ );
+}