clean up table state and styles

This commit is contained in:
1ilit
2025-06-23 17:09:14 +04:00
parent 5a5304073e
commit 70e0b3e5f2
5 changed files with 143 additions and 183 deletions

View File

@@ -1,4 +1,4 @@
import { useMemo, useState } from "react"; import { useMemo } from "react";
import { import {
Tab, Tab,
ObjectType, ObjectType,
@@ -21,19 +21,18 @@ import TableInfo from "../EditorSidePanel/TablesTab/TableInfo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { dbToTypes } from "../../data/datatypes"; import { dbToTypes } from "../../data/datatypes";
import { isRtl } from "../../i18n/utils/rtl"; import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
import { getTableHeight } from "../../utils/utils"; import { getTableHeight } from "../../utils/utils";
import classNames from "classnames";
import i18n from "../../i18n/i18n";
export default function Table(props) { export default function Table({
const [hoveredField, setHoveredField] = useState(null); tableData,
onPointerDown,
setHoveredTable,
handleGripField,
setLinkingLine,
}) {
const { database } = useDiagram(); const { database } = useDiagram();
const {
tableData,
onPointerDown,
setHoveredTable,
handleGripField,
setLinkingLine,
} = props;
const { layout } = useLayout(); const { layout } = useLayout();
const { deleteTable, deleteField, updateTable } = useDiagram(); const { deleteTable, deleteField, updateTable } = useDiagram();
const { settings } = useSettings(); const { settings } = useSettings();
@@ -41,21 +40,17 @@ export default function Table(props) {
const { selectedElement, setSelectedElement, bulkSelectedElements } = const { selectedElement, setSelectedElement, bulkSelectedElements } =
useSelect(); useSelect();
const borderColor = useMemo(
() => (settings.mode === "light" ? "border-zinc-300" : "border-zinc-600"),
[settings.mode],
);
const height = getTableHeight(tableData); const height = getTableHeight(tableData);
const isSelected = useMemo(() => { const isSelected = useMemo(() => {
return ( const isIndividuallySelected =
(selectedElement.id == tableData.id && selectedElement.id == tableData.id &&
selectedElement.element === ObjectType.TABLE) || selectedElement.element === ObjectType.TABLE;
bulkSelectedElements.some( const isBulkSelected = bulkSelectedElements.some(
(e) => e.type === ObjectType.TABLE && e.id === tableData.id, (e) => e.type === ObjectType.TABLE && e.id === tableData.id,
)
); );
return isIndividuallySelected || isBulkSelected;
}, [selectedElement, tableData, bulkSelectedElements]); }, [selectedElement, tableData, bulkSelectedElements]);
const lockUnlockTable = () => const lockUnlockTable = () =>
@@ -92,85 +87,71 @@ export default function Table(props) {
y={tableData.y} y={tableData.y}
width={settings.tableWidth} width={settings.tableWidth}
height={height} height={height}
className="group drop-shadow-lg rounded-md cursor-move" data-selected={isSelected}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
className="group drop-shadow-lg rounded-md cursor-move"
> >
<div <div
onDoubleClick={openEditor} onDoubleClick={openEditor}
className={`border-2 hover:border-dashed hover:border-blue-500 className={classNames(
select-none rounded-lg w-full ${ "select-none rounded-lg border-2 border-zinc-300 hover:border-dashed hover:border-blue-500",
settings.mode === "light" "dark:border-zinc-600 group-data-[selected=true]:border-solid group-data-[selected=true]:!border-blue-500",
? "bg-zinc-100 text-zinc-800" "bg-zinc-100 dark:bg-zinc-800 text-zinc-800 dark:text-zinc-200",
: "bg-zinc-800 text-zinc-200" )}
} ${isSelected ? "border-solid border-blue-500" : borderColor}`}
style={{ direction: "ltr" }} style={{ direction: "ltr" }}
> >
<div <div
className="h-[10px] w-full rounded-t-md" className="h-2.5 w-full rounded-t-md"
style={{ backgroundColor: tableData.color }} style={{ backgroundColor: tableData.color }}
/> />
<div <div className="h-10 flex justify-between items-center border-b border-gray-400 bg-zinc-200 dark:bg-zinc-900">
className={`overflow-hidden font-bold h-[40px] flex justify-between items-center border-b border-gray-400 ${ <div className="px-2 overflow-hidden text-ellipsis font-bold whitespace-nowrap">
settings.mode === "light" ? "bg-zinc-200" : "bg-zinc-900"
}`}
>
<div className="px-3 overflow-hidden text-ellipsis whitespace-nowrap">
{tableData.name} {tableData.name}
</div> </div>
<div className="hidden group-hover:block"> <div className="hidden group-hover:block">
<div className="flex justify-end items-center mx-2 space-x-1.5"> <div className="flex justify-end items-center mx-2 space-x-1.5">
<Button <Button
aria-label={t("lock")}
icon={tableData.locked ? <IconLock /> : <IconUnlock />} icon={tableData.locked ? <IconLock /> : <IconUnlock />}
size="small" size="small"
theme="solid" theme="solid"
style={{ style={{ backgroundColor: "#2f68adb3" }}
backgroundColor: "#2f68adb3",
}}
onClick={lockUnlockTable} onClick={lockUnlockTable}
/> />
<Button <Button
aria-label={t("Edit")}
icon={<IconEdit />} icon={<IconEdit />}
size="small" size="small"
theme="solid" theme="solid"
style={{ style={{ backgroundColor: "#2f68adb3" }}
backgroundColor: "#2f68adb3",
}}
onClick={openEditor} onClick={openEditor}
/> />
<Popover <Popover
key={tableData.id} key={tableData.id}
content={ content={
<div className="popover-theme"> <div className="space-y-2 popover-theme">
<div className="mb-2"> <div>
<strong>{t("comment")}:</strong>{" "} <strong>{t("comment")}: </strong>
{tableData.comment === "" ? ( {tableData.comment || t("not_set")}
t("not_set")
) : (
<div>{tableData.comment}</div>
)}
</div> </div>
<div> <div>
<strong <strong
className={`${ className={
tableData.indices.length === 0 ? "" : "block" tableData.indices.length === 0 ? "" : "block"
}`} }
> >
{t("indices")}: {t("indices")}:
</strong>{" "} </strong>{" "}
{tableData.indices.length === 0 ? ( {tableData.indices.length === 0 ? (
t("not_set") t("not_set")
) : ( ) : (
<div> <>
{tableData.indices.map((index, k) => ( {tableData.indices.map((index, k) => (
<div <div
key={k} key={k}
className={`flex items-center my-1 px-2 py-1 rounded ${ className="flex items-center my-1 px-2 py-1 rounded bg-gray-100 dark:bg-zinc-800"
settings.mode === "light"
? "bg-gray-100"
: "bg-zinc-800"
}`}
> >
<i className="fa-solid fa-thumbtack me-2 mt-1 text-slate-500"></i> <i className="fa-solid fa-thumbtack me-2 mt-1 text-slate-500" />
<div> <div>
{index.fields.map((f) => ( {index.fields.map((f) => (
<Tag color="blue" key={f} className="me-1"> <Tag color="blue" key={f} className="me-1">
@@ -180,14 +161,14 @@ export default function Table(props) {
</div> </div>
</div> </div>
))} ))}
</div> </>
)} )}
</div> </div>
<Button <Button
aria-label={t("delete")}
block
icon={<IconDeleteStroked />} icon={<IconDeleteStroked />}
type="danger" type="danger"
block
style={{ marginTop: "8px" }}
onClick={() => deleteTable(tableData.id)} onClick={() => deleteTable(tableData.id)}
> >
{t("delete")} {t("delete")}
@@ -200,86 +181,70 @@ export default function Table(props) {
style={{ width: "200px", wordBreak: "break-word" }} style={{ width: "200px", wordBreak: "break-word" }}
> >
<Button <Button
aria-label={t("see_more")}
icon={<IconMore />} icon={<IconMore />}
type="tertiary" type="tertiary"
size="small" size="small"
style={{ theme="solid"
backgroundColor: "#808080b3",
color: "white",
}}
/> />
</Popover> </Popover>
</div> </div>
</div> </div>
</div> </div>
{tableData.fields.map((e, i) => { {tableData.fields.map((fieldData, index) => {
return settings.showFieldSummary ? ( const typeInfo = dbToTypes[database][fieldData.type];
const showSummary = settings.showFieldSummary;
const typeDisplay =
(typeInfo?.isSized || typeInfo?.hasPrecision) && fieldData.size
? `${fieldData.type}(${fieldData.size})`
: fieldData.type;
const SummaryContent = () => (
<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 font-mono ${typeInfo.color}`}>
{typeDisplay}
</p>
</div>
<hr />
<div className="space-x-2 my-2">
{fieldData.primary && <Tag color="blue">{t("primary")}</Tag>}
{fieldData.unique && <Tag color="amber">{t("unique")}</Tag>}
{fieldData.notNull && (
<Tag color="purple">{t("not_null")}</Tag>
)}
{fieldData.increment && (
<Tag color="green">{t("autoincrement")}</Tag>
)}
</div>
<div>
<strong>{t("default_value")}: </strong>
{fieldData.default || t("not_set")}
</div>
<div>
<strong>{t("comment")}: </strong>
{fieldData.comment || t("not_set")}
</div>
</div>
);
return showSummary ? (
<Popover <Popover
key={i} key={index}
content={ content={<SummaryContent />}
<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 font-mono " + dbToTypes[database][e.type].color
}
>
{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" position="right"
showArrow showArrow
style={ style={{ direction: isRtl(i18n.language) ? "rtl" : "ltr" }}
isRtl(i18n.language)
? { direction: "rtl" }
: { direction: "ltr" }
}
> >
{field(e, i)} {field(fieldData, index)}
</Popover> </Popover>
) : ( ) : (
field(e, i) field(fieldData, index)
); );
})} })}
</div> </div>
@@ -311,15 +276,10 @@ export default function Table(props) {
function field(fieldData, index) { function field(fieldData, index) {
return ( return (
<div <div
className={`${ className="border-b border-gray-400 last:border-b-0 group/field h-9 px-2 flex justify-between items-center gap-1 w-full overflow-hidden"
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) => { onPointerEnter={(e) => {
if (!e.isPrimary) return; if (!e.isPrimary) return;
setHoveredField(index);
setHoveredTable({ setHoveredTable({
tableId: tableData.id, tableId: tableData.id,
fieldId: fieldData.id, fieldId: fieldData.id,
@@ -328,7 +288,6 @@ export default function Table(props) {
onPointerLeave={(e) => { onPointerLeave={(e) => {
if (!e.isPrimary) return; if (!e.isPrimary) return;
setHoveredField(null);
setHoveredTable({ setHoveredTable({
tableId: null, tableId: null,
fieldId: null, fieldId: null,
@@ -340,13 +299,9 @@ export default function Table(props) {
e.target.releasePointerCapture(e.pointerId); e.target.releasePointerCapture(e.pointerId);
}} }}
> >
<div <div className="group-hover/field:text-zinc-400 flex items-center gap-2 overflow-hidden">
className={`${
hoveredField === index ? "text-zinc-400" : ""
} flex items-center gap-2 overflow-hidden`}
>
<button <button
className="shrink-0 w-[10px] h-[10px] bg-[#2f68adcc] rounded-full" className="shrink-0 w-2.5 h-2.5 bg-[#2f68adcc] rounded-full"
onPointerDown={(e) => { onPointerDown={(e) => {
if (!e.isPrimary) return; if (!e.isPrimary) return;
@@ -376,37 +331,32 @@ export default function Table(props) {
{fieldData.name} {fieldData.name}
</span> </span>
</div> </div>
<div className="text-zinc-400"> <div className="hidden group-hover/field:inline-block">
{hoveredField === index ? ( <Button
<Button theme="solid"
theme="solid" size="small"
size="small" style={{ backgroundColor: "#d42020b3" }}
style={{ icon={<IconMinus />}
backgroundColor: "#d42020b3", onClick={() => deleteField(fieldData, tableData.id)}
}} />
icon={<IconMinus />}
onClick={() => deleteField(fieldData, tableData.id)}
/>
) : settings.showDataTypes ? (
<div className="flex gap-1 items-center">
{fieldData.primary && <IconKeyStroked />}
{!fieldData.notNull && <span className="font-mono">?</span>}
<span
className={
"font-mono " + dbToTypes[database][fieldData.type].color
}
>
{fieldData.type +
((dbToTypes[database][fieldData.type].isSized ||
dbToTypes[database][fieldData.type].hasPrecision) &&
fieldData.size &&
fieldData.size !== ""
? `(${fieldData.size})`
: "")}
</span>
</div>
) : null}
</div> </div>
{settings.showDataTypes && (
<div className="flex gap-1 items-center group-hover/field:hidden">
{fieldData.primary && <IconKeyStroked className="text-zinc-400" />}
{!fieldData.notNull && <span className="font-mono">?</span>}
<span
className={`font-mono ${dbToTypes[database][fieldData.type].color}`}
>
{fieldData.type +
((dbToTypes[database][fieldData.type].isSized ||
dbToTypes[database][fieldData.type].hasPrecision) &&
fieldData.size
? `(${fieldData.size})`
: "")}
</span>
</div>
)}
</div> </div>
); );
} }

View File

@@ -29,6 +29,11 @@ export default function SettingsContextProvider({ children }) {
useEffect(() => { useEffect(() => {
document.body.setAttribute("theme-mode", settings.mode); document.body.setAttribute("theme-mode", settings.mode);
const removeClass = settings.mode === "light" ? "dark" : "light";
document.documentElement.classList.remove(removeClass);
document.documentElement.classList.add(settings.mode);
}, [settings.mode]); }, [settings.mode]);
useEffect(() => { useEffect(() => {

View File

@@ -9,5 +9,10 @@ export default function useThemedPage() {
useLayoutEffect(() => { useLayoutEffect(() => {
document.body.setAttribute("theme-mode", settings.mode); document.body.setAttribute("theme-mode", settings.mode);
const removeClass = settings.mode === "light" ? "dark" : "light";
document.documentElement.classList.remove(removeClass);
document.documentElement.classList.add(settings.mode);
}, [settings]); }, [settings]);
} }

View File

@@ -254,6 +254,8 @@ const en = {
export_saved_data: "Export saved data", export_saved_data: "Export saved data",
dbml_view: "DBML view", dbml_view: "DBML view",
tab_view: "Tab view", tab_view: "Tab view",
lock: "Lock", // the verb
see_more: "See more",
}, },
}; };

View File

@@ -1,19 +1,17 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: ["./src/**/*.{js,jsx,ts,tsx}"],
"./src/**/*.{js,jsx,ts,tsx}", darkMode: "class",
],
theme: { theme: {
screens: { screens: {
'3xl': {'max': '2047px'}, "3xl": { max: "2047px" },
'2xl': {'max': '1535px'}, "2xl": { max: "1535px" },
'xl': {'min': '1024px'}, xl: { min: "1024px" },
'lg': {'max': '1023px'}, lg: { max: "1023px" },
'md': {'max': '820px'}, md: { max: "820px" },
'sm': {'max': '639px'} sm: { max: "639px" },
}, },
extend: {} extend: {},
}, },
plugins: [], plugins: [],
} };