mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-08-29 10:43:56 +00:00
clean up table state and styles
This commit is contained in:
@@ -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);
|
|
||||||
const { database } = useDiagram();
|
|
||||||
const {
|
|
||||||
tableData,
|
tableData,
|
||||||
onPointerDown,
|
onPointerDown,
|
||||||
setHoveredTable,
|
setHoveredTable,
|
||||||
handleGripField,
|
handleGripField,
|
||||||
setLinkingLine,
|
setLinkingLine,
|
||||||
} = props;
|
}) {
|
||||||
|
const { database } = useDiagram();
|
||||||
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];
|
||||||
<Popover
|
const showSummary = settings.showFieldSummary;
|
||||||
key={i}
|
|
||||||
content={
|
const typeDisplay =
|
||||||
|
(typeInfo?.isSized || typeInfo?.hasPrecision) && fieldData.size
|
||||||
|
? `${fieldData.type}(${fieldData.size})`
|
||||||
|
: fieldData.type;
|
||||||
|
|
||||||
|
const SummaryContent = () => (
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
<div
|
<div
|
||||||
className="flex justify-between items-center pb-2"
|
className="flex justify-between items-center pb-2"
|
||||||
style={{ direction: "ltr" }}
|
style={{ direction: "ltr" }}
|
||||||
>
|
>
|
||||||
<p className="me-4 font-bold">{e.name}</p>
|
<p className="me-4 font-bold">{fieldData.name}</p>
|
||||||
<p
|
<p className={`ms-4 font-mono ${typeInfo.color}`}>
|
||||||
className={
|
{typeDisplay}
|
||||||
"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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
{e.primary && (
|
<div className="space-x-2 my-2">
|
||||||
<Tag color="blue" className="me-2 my-2">
|
{fieldData.primary && <Tag color="blue">{t("primary")}</Tag>}
|
||||||
{t("primary")}
|
{fieldData.unique && <Tag color="amber">{t("unique")}</Tag>}
|
||||||
</Tag>
|
{fieldData.notNull && (
|
||||||
|
<Tag color="purple">{t("not_null")}</Tag>
|
||||||
)}
|
)}
|
||||||
{e.unique && (
|
{fieldData.increment && (
|
||||||
<Tag color="amber" className="me-2 my-2">
|
<Tag color="green">{t("autoincrement")}</Tag>
|
||||||
{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>
|
</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
|
||||||
|
key={index}
|
||||||
|
content={<SummaryContent />}
|
||||||
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={{
|
style={{ backgroundColor: "#d42020b3" }}
|
||||||
backgroundColor: "#d42020b3",
|
|
||||||
}}
|
|
||||||
icon={<IconMinus />}
|
icon={<IconMinus />}
|
||||||
onClick={() => deleteField(fieldData, tableData.id)}
|
onClick={() => deleteField(fieldData, tableData.id)}
|
||||||
/>
|
/>
|
||||||
) : settings.showDataTypes ? (
|
</div>
|
||||||
<div className="flex gap-1 items-center">
|
|
||||||
{fieldData.primary && <IconKeyStroked />}
|
{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>}
|
{!fieldData.notNull && <span className="font-mono">?</span>}
|
||||||
<span
|
<span
|
||||||
className={
|
className={`font-mono ${dbToTypes[database][fieldData.type].color}`}
|
||||||
"font-mono " + dbToTypes[database][fieldData.type].color
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{fieldData.type +
|
{fieldData.type +
|
||||||
((dbToTypes[database][fieldData.type].isSized ||
|
((dbToTypes[database][fieldData.type].isSized ||
|
||||||
dbToTypes[database][fieldData.type].hasPrecision) &&
|
dbToTypes[database][fieldData.type].hasPrecision) &&
|
||||||
fieldData.size &&
|
fieldData.size
|
||||||
fieldData.size !== ""
|
|
||||||
? `(${fieldData.size})`
|
? `(${fieldData.size})`
|
||||||
: "")}
|
: "")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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(() => {
|
||||||
|
@@ -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]);
|
||||||
}
|
}
|
||||||
|
@@ -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",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user