mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-05-24 18:39:12 +00:00
refactor: modularize and organize table component
This commit is contained in:
parent
ce85bb6680
commit
44b3d3429c
@ -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 (
|
|
||||||
<>
|
|
||||||
<foreignObject
|
|
||||||
key={tableData.id}
|
|
||||||
x={tableData.x}
|
|
||||||
y={tableData.y}
|
|
||||||
width={settings.tableWidth}
|
|
||||||
height={height}
|
|
||||||
className="group drop-shadow-lg rounded-md cursor-move"
|
|
||||||
onPointerDown={onPointerDown}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onDoubleClick={openEditor}
|
|
||||||
className={`border-2 hover:border-dashed hover:border-blue-500
|
|
||||||
select-none rounded-lg w-full ${
|
|
||||||
settings.mode === "light"
|
|
||||||
? "bg-zinc-100 text-zinc-800"
|
|
||||||
: "bg-zinc-800 text-zinc-200"
|
|
||||||
} ${
|
|
||||||
selectedElement.id === tableData.id &&
|
|
||||||
selectedElement.element === ObjectType.TABLE
|
|
||||||
? "border-solid border-blue-500"
|
|
||||||
: "border-zinc-500"
|
|
||||||
}`}
|
|
||||||
style={{ direction: "ltr" }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="h-[10px] w-full rounded-t-md"
|
|
||||||
style={{ backgroundColor: tableData.color }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={`overflow-hidden font-bold h-[40px] flex justify-between items-center border-b border-gray-400 ${
|
|
||||||
settings.mode === "light" ? "bg-zinc-200" : "bg-zinc-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className=" px-3 overflow-hidden text-ellipsis whitespace-nowrap">
|
|
||||||
{tableData.name}
|
|
||||||
</div>
|
|
||||||
<div className="hidden group-hover:block">
|
|
||||||
<div className="flex justify-end items-center mx-2">
|
|
||||||
<Button
|
|
||||||
icon={<IconEdit />}
|
|
||||||
size="small"
|
|
||||||
theme="solid"
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#2f68adb3",
|
|
||||||
marginRight: "6px",
|
|
||||||
}}
|
|
||||||
onClick={openEditor}
|
|
||||||
/>
|
|
||||||
<Popover
|
|
||||||
key={tableData.key}
|
|
||||||
content={
|
|
||||||
<div className="popover-theme">
|
|
||||||
<div className="mb-2">
|
|
||||||
<strong>{t("comment")}:</strong>{" "}
|
|
||||||
{tableData.comment === "" ? (
|
|
||||||
t("not_set")
|
|
||||||
) : (
|
|
||||||
<div>{tableData.comment}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong
|
|
||||||
className={`${
|
|
||||||
tableData.indices.length === 0 ? "" : "block"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{t("indices")}:
|
|
||||||
</strong>{" "}
|
|
||||||
{tableData.indices.length === 0 ? (
|
|
||||||
t("not_set")
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{tableData.indices.map((index, k) => (
|
|
||||||
<div
|
|
||||||
key={k}
|
|
||||||
className={`flex items-center my-1 px-2 py-1 rounded ${
|
|
||||||
settings.mode === "light"
|
|
||||||
? "bg-gray-100"
|
|
||||||
: "bg-zinc-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-thumbtack me-2 mt-1 text-slate-500"></i>
|
|
||||||
<div>
|
|
||||||
{index.fields.map((f) => (
|
|
||||||
<Tag color="blue" key={f} className="me-1">
|
|
||||||
{f}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
icon={<IconDeleteStroked />}
|
|
||||||
type="danger"
|
|
||||||
block
|
|
||||||
style={{ marginTop: "8px" }}
|
|
||||||
onClick={() => deleteTable(tableData.id)}
|
|
||||||
>
|
|
||||||
{t("delete")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
position="rightTop"
|
|
||||||
showArrow
|
|
||||||
trigger="click"
|
|
||||||
style={{ width: "200px", wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
icon={<IconMore />}
|
|
||||||
type="tertiary"
|
|
||||||
size="small"
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#808080b3",
|
|
||||||
color: "white",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{tableData.fields.map((e, i) => {
|
|
||||||
return settings.showFieldSummary ? (
|
|
||||||
<Popover
|
|
||||||
key={i}
|
|
||||||
content={
|
|
||||||
<div className="popover-theme">
|
|
||||||
<div
|
|
||||||
className="flex justify-between items-center pb-2"
|
|
||||||
style={{ direction: "ltr" }}
|
|
||||||
>
|
|
||||||
<p className="me-4 font-bold">{e.name}</p>
|
|
||||||
<p className="ms-4">
|
|
||||||
{e.type +
|
|
||||||
((dbToTypes[database][e.type].isSized ||
|
|
||||||
dbToTypes[database][e.type].hasPrecision) &&
|
|
||||||
e.size &&
|
|
||||||
e.size !== ""
|
|
||||||
? "(" + e.size + ")"
|
|
||||||
: "")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{e.primary && (
|
|
||||||
<Tag color="blue" className="me-2 my-2">
|
|
||||||
{t("primary")}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{e.unique && (
|
|
||||||
<Tag color="amber" className="me-2 my-2">
|
|
||||||
{t("unique")}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{e.notNull && (
|
|
||||||
<Tag color="purple" className="me-2 my-2">
|
|
||||||
{t("not_null")}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{e.increment && (
|
|
||||||
<Tag color="green" className="me-2 my-2">
|
|
||||||
{t("autoincrement")}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
<p>
|
|
||||||
<strong>{t("default_value")}: </strong>
|
|
||||||
{e.default === "" ? t("not_set") : e.default}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{t("comment")}: </strong>
|
|
||||||
{e.comment === "" ? t("not_set") : e.comment}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
position="right"
|
|
||||||
showArrow
|
|
||||||
style={
|
|
||||||
isRtl(i18n.language)
|
|
||||||
? { direction: "rtl" }
|
|
||||||
: { direction: "ltr" }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{field(e, i)}
|
|
||||||
</Popover>
|
|
||||||
) : (
|
|
||||||
field(e, i)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<SideSheet
|
|
||||||
title={t("edit")}
|
|
||||||
size="small"
|
|
||||||
visible={
|
|
||||||
selectedElement.element === ObjectType.TABLE &&
|
|
||||||
selectedElement.id === tableData.id &&
|
|
||||||
selectedElement.open &&
|
|
||||||
!layout.sidebar
|
|
||||||
}
|
|
||||||
onCancel={() =>
|
|
||||||
setSelectedElement((prev) => ({
|
|
||||||
...prev,
|
|
||||||
open: !prev.open,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
style={{ paddingBottom: "16px" }}
|
|
||||||
>
|
|
||||||
<div className="sidesheet-theme">
|
|
||||||
<TableInfo data={tableData} />
|
|
||||||
</div>
|
|
||||||
</SideSheet>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
function field(fieldData, index) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
index === tableData.fields.length - 1
|
|
||||||
? ""
|
|
||||||
: "border-b border-gray-400"
|
|
||||||
} group h-[36px] px-2 py-1 flex justify-between items-center gap-1 w-full overflow-hidden`}
|
|
||||||
onPointerEnter={(e) => {
|
|
||||||
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);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
hoveredField === index ? "text-zinc-400" : ""
|
|
||||||
} flex items-center gap-2 overflow-hidden`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="flex-shrink-0 w-[10px] h-[10px] bg-[#2f68adcc] rounded-full"
|
|
||||||
onPointerDown={(e) => {
|
|
||||||
if (!e.isPrimary) return;
|
|
||||||
|
|
||||||
handleGripField(index);
|
|
||||||
setLinkingLine((prev) => ({
|
|
||||||
...prev,
|
|
||||||
startFieldId: index,
|
|
||||||
startTableId: tableData.id,
|
|
||||||
startX: tableData.x + 15,
|
|
||||||
startY:
|
|
||||||
tableData.y +
|
|
||||||
index * tableFieldHeight +
|
|
||||||
tableHeaderHeight +
|
|
||||||
tableColorStripHeight +
|
|
||||||
12,
|
|
||||||
endX: tableData.x + 15,
|
|
||||||
endY:
|
|
||||||
tableData.y +
|
|
||||||
index * tableFieldHeight +
|
|
||||||
tableHeaderHeight +
|
|
||||||
tableColorStripHeight +
|
|
||||||
12,
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
|
||||||
{fieldData.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-zinc-400">
|
|
||||||
{hoveredField === index ? (
|
|
||||||
<Button
|
|
||||||
theme="solid"
|
|
||||||
size="small"
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#d42020b3",
|
|
||||||
}}
|
|
||||||
icon={<IconMinus />}
|
|
||||||
onClick={() => deleteField(fieldData, tableData.id)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="flex gap-1 items-center">
|
|
||||||
{fieldData.primary && <IconKeyStroked />}
|
|
||||||
{!fieldData.notNull && <span>?</span>}
|
|
||||||
<span>
|
|
||||||
{fieldData.type +
|
|
||||||
((dbToTypes[database][fieldData.type].isSized ||
|
|
||||||
dbToTypes[database][fieldData.type].hasPrecision) &&
|
|
||||||
fieldData.size &&
|
|
||||||
fieldData.size !== ""
|
|
||||||
? "(" + fieldData.size + ")"
|
|
||||||
: "")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
133
src/components/EditorCanvas/Table/components/TableField.jsx
Normal file
133
src/components/EditorCanvas/Table/components/TableField.jsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
// Popover children needs forwardRef and props destructuring to work with
|
||||||
|
// Functiona Components (https://semi.design/en-US/show/popover#Cautions)
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={`${
|
||||||
|
index === tableData.fields.length - 1 ? "" : "border-b border-gray-400"
|
||||||
|
} group h-[36px] px-2 py-1 flex justify-between items-center gap-1 w-full overflow-hidden`}
|
||||||
|
onPointerEnter={(e) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
hoveredField === index ? "text-zinc-400" : ""
|
||||||
|
} flex items-center gap-2 overflow-hidden`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="flex-shrink-0 w-[10px] h-[10px] bg-[#2f68adcc] rounded-full"
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
if (!e.isPrimary) return;
|
||||||
|
|
||||||
|
handleGripField(index);
|
||||||
|
setLinkingLine((prev) => ({
|
||||||
|
...prev,
|
||||||
|
startFieldId: index,
|
||||||
|
startTableId: tableData.id,
|
||||||
|
startX: tableData.x + 15,
|
||||||
|
startY:
|
||||||
|
tableData.y +
|
||||||
|
index * tableFieldHeight +
|
||||||
|
tableHeaderHeight +
|
||||||
|
tableColorStripHeight +
|
||||||
|
12,
|
||||||
|
endX: tableData.x + 15,
|
||||||
|
endY:
|
||||||
|
tableData.y +
|
||||||
|
index * tableFieldHeight +
|
||||||
|
tableHeaderHeight +
|
||||||
|
tableColorStripHeight +
|
||||||
|
12,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
{fieldData.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-zinc-400">
|
||||||
|
{hoveredField === index ? (
|
||||||
|
<Button
|
||||||
|
theme="solid"
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#d42020b3",
|
||||||
|
}}
|
||||||
|
icon={<IconMinus />}
|
||||||
|
onClick={() => {
|
||||||
|
deleteField(fieldData, tableData.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-1 items-center">
|
||||||
|
{fieldData.primary && <IconKeyStroked />}
|
||||||
|
{!fieldData.notNull && <span>?</span>}
|
||||||
|
<span>
|
||||||
|
<FieldSize field={fieldData} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TableField.displayName = "TableField";
|
||||||
|
|
||||||
|
export default TableField;
|
@ -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 <React.Fragment>{children}</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<div className="popover-theme">
|
||||||
|
<div
|
||||||
|
className="flex justify-between items-center pb-2"
|
||||||
|
style={{ direction: "ltr" }}
|
||||||
|
>
|
||||||
|
<p className="me-4 font-bold">{fieldData.name}</p>
|
||||||
|
<p className="ms-4">{<FieldSize field={fieldData} />}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
{fieldData.primary && (
|
||||||
|
<Tag color="blue" className="me-2 my-2">
|
||||||
|
{t("primary")}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{fieldData.unique && (
|
||||||
|
<Tag color="amber" className="me-2 my-2">
|
||||||
|
{t("unique")}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{fieldData.notNull && (
|
||||||
|
<Tag color="purple" className="me-2 my-2">
|
||||||
|
{t("not_null")}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{fieldData.increment && (
|
||||||
|
<Tag color="green" className="me-2 my-2">
|
||||||
|
{t("autoincrement")}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
<strong>{t("default_value")}: </strong>
|
||||||
|
{fieldData.default === "" ? t("not_set") : fieldData.default}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>{t("comment")}: </strong>
|
||||||
|
{fieldData.comment === "" ? t("not_set") : fieldData.comment}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="right"
|
||||||
|
showArrow
|
||||||
|
style={isRtl(i18n.language) ? { direction: "rtl" } : { direction: "ltr" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
105
src/components/EditorCanvas/Table/components/TableHeader.jsx
Normal file
105
src/components/EditorCanvas/Table/components/TableHeader.jsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
className={`overflow-hidden font-bold h-[40px] flex justify-between items-center border-b border-gray-400 ${
|
||||||
|
settings.mode === "light" ? "bg-zinc-200" : "bg-zinc-900"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="px-3 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
{tableData.name}
|
||||||
|
</div>
|
||||||
|
<div className="hidden group-hover:block">
|
||||||
|
<div className="flex justify-end items-center mx-2">
|
||||||
|
<Button
|
||||||
|
icon={<IconEdit />}
|
||||||
|
size="small"
|
||||||
|
theme="solid"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#2f68adb3",
|
||||||
|
marginRight: "6px",
|
||||||
|
}}
|
||||||
|
onClick={openEditor}
|
||||||
|
/>
|
||||||
|
<Popover
|
||||||
|
key={tableData.key}
|
||||||
|
content={
|
||||||
|
<div className="popover-theme">
|
||||||
|
<div className="mb-2">
|
||||||
|
<strong>{t("comment")}:</strong>{" "}
|
||||||
|
{tableData.comment === "" ? (
|
||||||
|
t("not_set")
|
||||||
|
) : (
|
||||||
|
<div>{tableData.comment}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong
|
||||||
|
className={`${
|
||||||
|
tableData.indices.length === 0 ? "" : "block"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t("indices")}:
|
||||||
|
</strong>{" "}
|
||||||
|
{tableData.indices.length === 0 ? (
|
||||||
|
t("not_set")
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{tableData.indices.map((index, k) => (
|
||||||
|
<div
|
||||||
|
key={k}
|
||||||
|
className={`flex items-center my-1 px-2 py-1 rounded ${
|
||||||
|
settings.mode === "light"
|
||||||
|
? "bg-gray-100"
|
||||||
|
: "bg-zinc-800"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-thumbtack me-2 mt-1 text-slate-500"></i>
|
||||||
|
<div>
|
||||||
|
{index.fields.map((f) => (
|
||||||
|
<Tag color="blue" key={f} className="me-1">
|
||||||
|
{f}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
icon={<IconDeleteStroked />}
|
||||||
|
type="danger"
|
||||||
|
block
|
||||||
|
style={{ marginTop: "8px" }}
|
||||||
|
onClick={() => deleteTable(tableData.id)}
|
||||||
|
>
|
||||||
|
{t("delete")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="rightTop"
|
||||||
|
showArrow
|
||||||
|
trigger="click"
|
||||||
|
style={{ width: "200px", wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<IconMore />}
|
||||||
|
type="tertiary"
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#808080b3",
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
156
src/components/EditorCanvas/Table/index.jsx
Normal file
156
src/components/EditorCanvas/Table/index.jsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
className="h-[10px] w-full rounded-t-md"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TableHeaderBand.displayName = "TableHeaderBand";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<foreignObject
|
||||||
|
key={tableData.id}
|
||||||
|
x={tableData.x}
|
||||||
|
y={tableData.y}
|
||||||
|
width={settings.tableWidth}
|
||||||
|
height={height}
|
||||||
|
className="group drop-shadow-lg rounded-md cursor-move"
|
||||||
|
onPointerDown={onPointerDown}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onDoubleClick={openEditor}
|
||||||
|
className={`border-2 hover:border-dashed hover:border-blue-500
|
||||||
|
select-none rounded-lg w-full ${
|
||||||
|
settings.mode === "light"
|
||||||
|
? "bg-zinc-100 text-zinc-800"
|
||||||
|
: "bg-zinc-800 text-zinc-200"
|
||||||
|
} ${
|
||||||
|
selectedElement.id === tableData.id &&
|
||||||
|
selectedElement.element === ObjectType.TABLE
|
||||||
|
? "border-solid border-blue-500"
|
||||||
|
: "border-zinc-500"
|
||||||
|
}`}
|
||||||
|
style={{ direction: "ltr" }}
|
||||||
|
>
|
||||||
|
<TableHeaderBand color={tableData.color} />
|
||||||
|
|
||||||
|
<TableHeader
|
||||||
|
tableData={tableData}
|
||||||
|
settings={settings}
|
||||||
|
openEditor={openEditor}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{tableData.fields.map((fieldData, index) => {
|
||||||
|
return (
|
||||||
|
<TableFieldPopover
|
||||||
|
key={index}
|
||||||
|
visible={settings.showFieldSummary}
|
||||||
|
fieldData={fieldData}
|
||||||
|
>
|
||||||
|
<TableField
|
||||||
|
key={index}
|
||||||
|
tableData={tableData}
|
||||||
|
fieldData={fieldData}
|
||||||
|
index={index}
|
||||||
|
setHoveredTable={setHoveredTable}
|
||||||
|
handleGripField={handleGripField}
|
||||||
|
setLinkingLine={setLinkingLine}
|
||||||
|
setHoveredField={setHoveredField}
|
||||||
|
hoveredField={hoveredField}
|
||||||
|
tableFieldHeight={tableFieldHeight}
|
||||||
|
tableHeaderHeight={tableHeaderHeight}
|
||||||
|
tableColorStripHeight={tableColorStripHeight}
|
||||||
|
/>
|
||||||
|
</TableFieldPopover>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
|
||||||
|
<SideSheet
|
||||||
|
title={t("edit")}
|
||||||
|
size="small"
|
||||||
|
visible={
|
||||||
|
selectedElement.element === ObjectType.TABLE &&
|
||||||
|
selectedElement.id === tableData.id &&
|
||||||
|
selectedElement.open &&
|
||||||
|
!layout.sidebar
|
||||||
|
}
|
||||||
|
onCancel={() =>
|
||||||
|
setSelectedElement((prev) => ({
|
||||||
|
...prev,
|
||||||
|
open: !prev.open,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
style={{ paddingBottom: "16px" }}
|
||||||
|
>
|
||||||
|
<div className="sidesheet-theme">
|
||||||
|
<TableInfo data={tableData} />
|
||||||
|
</div>
|
||||||
|
</SideSheet>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user