layout context
This commit is contained in:
parent
23e58a0110
commit
1eea618faa
@ -1,11 +1,13 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useContext, useRef, useState } from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import Table from "./table";
|
||||
import { defaultTableTheme, Cardinality, Constraint } from "../data/data";
|
||||
import Area from "./area";
|
||||
import Relationship from "./relationship";
|
||||
import { TableContext } from "../pages/editor";
|
||||
|
||||
export default function Canvas(props) {
|
||||
const { tables, setTables } = useContext(TableContext);
|
||||
const ObjectType = {
|
||||
NONE: 0,
|
||||
TABLE: 1,
|
||||
@ -51,7 +53,7 @@ export default function Canvas(props) {
|
||||
const handleMouseDownRect = (e, id, type) => {
|
||||
const { clientX, clientY } = e;
|
||||
if (type === ObjectType.TABLE) {
|
||||
const table = props.tables.find((t) => t.id === id);
|
||||
const table = tables.find((t) => t.id === id);
|
||||
setOffset({
|
||||
x: clientX - table.x,
|
||||
y: clientY - table.y,
|
||||
@ -87,8 +89,8 @@ export default function Canvas(props) {
|
||||
const dy = e.clientY - panOffset.y;
|
||||
setPanOffset({ x: e.clientX, y: e.clientY });
|
||||
|
||||
props.setTables(
|
||||
props.tables.map((t) => ({
|
||||
setTables(
|
||||
tables.map((t) => ({
|
||||
...t,
|
||||
x: t.x + dx,
|
||||
y: t.y + dy,
|
||||
@ -113,7 +115,7 @@ export default function Canvas(props) {
|
||||
}))
|
||||
);
|
||||
} else if (dragging[0] === ObjectType.TABLE && dragging[1] >= 0) {
|
||||
const updatedTables = props.tables.map((t) => {
|
||||
const updatedTables = tables.map((t) => {
|
||||
if (t.id === dragging[1]) {
|
||||
const updatedTable = {
|
||||
...t,
|
||||
@ -124,20 +126,19 @@ export default function Canvas(props) {
|
||||
}
|
||||
return t;
|
||||
});
|
||||
props.setTables(updatedTables);
|
||||
setTables(updatedTables);
|
||||
const updatedRelationShips = props.relationships.map((r) => {
|
||||
if (r.startTableId === dragging[1]) {
|
||||
return {
|
||||
...r,
|
||||
startX: props.tables[r.startTableId].x + 15,
|
||||
startY:
|
||||
props.tables[r.startTableId].y + r.startFieldId * 36 + 50 + 19,
|
||||
startX: tables[r.startTableId].x + 15,
|
||||
startY: tables[r.startTableId].y + r.startFieldId * 36 + 50 + 19,
|
||||
};
|
||||
} else if (r.endTableId === dragging[1]) {
|
||||
return {
|
||||
...r,
|
||||
endX: props.tables[r.endTableId].x + 15,
|
||||
endY: props.tables[r.endTableId].y + r.endFieldId * 36 + 50 + 19,
|
||||
endX: tables[r.endTableId].x + 15,
|
||||
endY: tables[r.endTableId].y + r.endFieldId * 36 + 50 + 19,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
@ -246,10 +247,10 @@ export default function Canvas(props) {
|
||||
...line,
|
||||
endTableId: onRect.tableId,
|
||||
endFieldId: onRect.field,
|
||||
endX: props.tables[onRect.tableId].x + 15,
|
||||
endY: props.tables[onRect.tableId].y + onRect.field * 36 + 50 + 19,
|
||||
name: `${props.tables[line.startTableId].name}_to_${
|
||||
props.tables[onRect.tableId].name
|
||||
endX: tables[onRect.tableId].x + 15,
|
||||
endY: tables[onRect.tableId].y + onRect.field * 36 + 50 + 19,
|
||||
name: `${tables[line.startTableId].name}_to_${
|
||||
tables[onRect.tableId].name
|
||||
}`,
|
||||
id: prev.length,
|
||||
},
|
||||
@ -265,8 +266,8 @@ export default function Canvas(props) {
|
||||
const x = offset.x - canvasRect.left - 100 * 0.5;
|
||||
const y = offset.y - canvasRect.top - 100 * 0.5;
|
||||
const newTable = {
|
||||
id: props.tables.length,
|
||||
name: `table_${props.tables.length}`,
|
||||
id: tables.length,
|
||||
name: `table_${tables.length}`,
|
||||
x: x,
|
||||
y: y,
|
||||
fields: [
|
||||
@ -286,7 +287,7 @@ export default function Canvas(props) {
|
||||
indices: [],
|
||||
color: defaultTableTheme,
|
||||
};
|
||||
props.setTables((prev) => [...prev, newTable]);
|
||||
setTables((prev) => [...prev, newTable]);
|
||||
props.setCode((prev) =>
|
||||
prev === ""
|
||||
? `CREATE TABLE \`${newTable.name}\`;`
|
||||
@ -297,7 +298,7 @@ export default function Canvas(props) {
|
||||
isOver: !!monitor.isOver(),
|
||||
}),
|
||||
}),
|
||||
[props.tables]
|
||||
[tables]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -352,13 +353,10 @@ export default function Canvas(props) {
|
||||
setAreas={props.setAreas}
|
||||
></Area>
|
||||
))}
|
||||
{props.tables.map((table, i) => (
|
||||
{tables.map((table, i) => (
|
||||
<Table
|
||||
key={table.id}
|
||||
id={table.id}
|
||||
tableData={table}
|
||||
tables={props.tables}
|
||||
setTables={props.setTables}
|
||||
setOnRect={setOnRect}
|
||||
handleGripField={handleGripField}
|
||||
setLine={setLine}
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { React, useState, useRef } from "react";
|
||||
import { React, useState } from "react";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { createTheme } from "@uiw/codemirror-themes";
|
||||
import { sql } from "@codemirror/lang-sql";
|
||||
import { tags as t } from "@lezer/highlight";
|
||||
import Shape from "./shape";
|
||||
import { Parser } from "node-sql-parser";
|
||||
// import { Parser } from "node-sql-parser";
|
||||
import { Tabs } from "@douyinfe/semi-ui";
|
||||
import TableOverview from "./table_overview";
|
||||
import ReferenceOverview from "./reference_overview";
|
||||
import { defaultTableTheme } from "../data/data";
|
||||
// import { TableContext } from "../pages/editor";
|
||||
|
||||
const myTheme = createTheme({
|
||||
dark: "light",
|
||||
@ -26,7 +27,8 @@ const myTheme = createTheme({
|
||||
|
||||
const EditorPanel = (props) => {
|
||||
const [tab, setTab] = useState("1");
|
||||
const map = useRef(new Map());
|
||||
// const map = useRef(new Map());
|
||||
// const {tables, setTables} = useContext(TableContext);
|
||||
|
||||
const tabList = [
|
||||
{ tab: "Tables", itemKey: "1" },
|
||||
@ -35,11 +37,10 @@ const EditorPanel = (props) => {
|
||||
{ tab: "Editor", itemKey: "4" },
|
||||
];
|
||||
const contentList = [
|
||||
<TableOverview tables={props.tables} setTables={props.setTables} />,
|
||||
<TableOverview />,
|
||||
<ReferenceOverview
|
||||
relationships={props.relationships}
|
||||
setRelationships={props.setRelationships}
|
||||
tables={props.tables}
|
||||
/>,
|
||||
<Shape />,
|
||||
<CodeMirror
|
||||
@ -99,44 +100,44 @@ const EditorPanel = (props) => {
|
||||
<br />
|
||||
<button
|
||||
onClick={() => {
|
||||
try {
|
||||
const parser = new Parser();
|
||||
const ast = parser.astify(props.code);
|
||||
console.log(ast);
|
||||
ast.forEach((e) => {
|
||||
e.table.forEach((t) => {
|
||||
if (map.current.has(t.table)) {
|
||||
return;
|
||||
}
|
||||
map.current.set(t.table, t);
|
||||
const newTable = {
|
||||
id: props.tables.length,
|
||||
name: `table_${props.tables.length}`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fields: [
|
||||
{
|
||||
name: "id",
|
||||
type: "UUID",
|
||||
default: "",
|
||||
check: "",
|
||||
primary: true,
|
||||
unique: true,
|
||||
notNull: true,
|
||||
increment: true,
|
||||
comment: "",
|
||||
},
|
||||
],
|
||||
comment: "",
|
||||
indices: [],
|
||||
color: defaultTableTheme,
|
||||
};
|
||||
props.setTables((prev) => [...prev, newTable]);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
alert("parsing error");
|
||||
}
|
||||
// try {
|
||||
// const parser = new Parser();
|
||||
// const ast = parser.astify(props.code);
|
||||
// console.log(ast);
|
||||
// ast.forEach((e) => {
|
||||
// e.table.forEach((t) => {
|
||||
// if (map.current.has(t.table)) {
|
||||
// return;
|
||||
// }
|
||||
// map.current.set(t.table, t);
|
||||
// const newTable = {
|
||||
// id: props.tables.length,
|
||||
// name: `table_${props.tables.length}`,
|
||||
// x: 0,
|
||||
// y: 0,
|
||||
// fields: [
|
||||
// {
|
||||
// name: "id",
|
||||
// type: "UUID",
|
||||
// default: "",
|
||||
// check: "",
|
||||
// primary: true,
|
||||
// unique: true,
|
||||
// notNull: true,
|
||||
// increment: true,
|
||||
// comment: "",
|
||||
// },
|
||||
// ],
|
||||
// comment: "",
|
||||
// indices: [],
|
||||
// color: defaultTableTheme,
|
||||
// };
|
||||
// props.setTables((prev) => [...prev, newTable]);
|
||||
// });
|
||||
// });
|
||||
// } catch (e) {
|
||||
// alert("parsing error");
|
||||
// }
|
||||
}}
|
||||
>
|
||||
parse
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
AutoComplete,
|
||||
Collapse,
|
||||
@ -23,6 +23,7 @@ import {
|
||||
IllustrationNoContentDark,
|
||||
} from "@douyinfe/semi-illustrations";
|
||||
import { Cardinality, Constraint } from "../data/data";
|
||||
import { TableContext } from "../pages/editor";
|
||||
|
||||
export default function ReferenceOverview(props) {
|
||||
const columns = [
|
||||
@ -35,6 +36,7 @@ export default function ReferenceOverview(props) {
|
||||
dataIndex: "foreign",
|
||||
},
|
||||
];
|
||||
const { tables } = useContext(TableContext);
|
||||
const [refActiveIndex, setRefActiveIndex] = useState("");
|
||||
const [value, setValue] = useState("");
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
@ -108,11 +110,11 @@ export default function ReferenceOverview(props) {
|
||||
<div className="flex justify-between items-center my-1">
|
||||
<div className="me-3">
|
||||
<strong>Primary: </strong>
|
||||
{props.tables[r.endTableId].name}
|
||||
{tables[r.endTableId].name}
|
||||
</div>
|
||||
<div className="mx-1">
|
||||
<strong>Foreign: </strong>
|
||||
{props.tables[r.startTableId].name}
|
||||
{tables[r.startTableId].name}
|
||||
</div>
|
||||
<div className="ms-1">
|
||||
<Popover
|
||||
@ -123,19 +125,18 @@ export default function ReferenceOverview(props) {
|
||||
dataSource={[
|
||||
{
|
||||
key: "1",
|
||||
foreign: props.tables[r.startTableId].name,
|
||||
primary: props.tables[r.endTableId].name,
|
||||
foreign: tables[r.startTableId].name,
|
||||
primary: tables[r.endTableId].name,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
foreign:
|
||||
props.tables[r.startTableId].fields[
|
||||
tables[r.startTableId].fields[
|
||||
r.startFieldId
|
||||
].name,
|
||||
primary:
|
||||
props.tables[r.endTableId].fields[
|
||||
r.endFieldId
|
||||
].name,
|
||||
tables[r.endTableId].fields[r.endFieldId]
|
||||
.name,
|
||||
},
|
||||
]}
|
||||
pagination={false}
|
||||
|
@ -12,13 +12,14 @@ import {
|
||||
Button,
|
||||
SideSheet,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { LayoutContext } from "../pages/editor";
|
||||
import { LayoutContext, TableContext } from "../pages/editor";
|
||||
|
||||
export default function Table(props) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [hoveredField, setHoveredField] = useState(-1);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const {layout} = useContext(LayoutContext);
|
||||
const {setTables} = useContext(TableContext);
|
||||
// const [editFieldVisible, setEditFieldVisible] = useState(-1);
|
||||
// const [field, setField] = useState({
|
||||
// name: "",
|
||||
@ -36,7 +37,7 @@ export default function Table(props) {
|
||||
return (
|
||||
<g>
|
||||
<foreignObject
|
||||
key={props.id}
|
||||
key={props.tableData.id}
|
||||
x={props.tableData.x}
|
||||
y={props.tableData.y}
|
||||
width={200}
|
||||
@ -199,7 +200,7 @@ export default function Table(props) {
|
||||
onMouseEnter={() => {
|
||||
setHoveredField(i);
|
||||
props.setOnRect({
|
||||
tableId: props.id,
|
||||
tableId: props.tableData.id,
|
||||
field: i,
|
||||
});
|
||||
}}
|
||||
@ -217,7 +218,7 @@ export default function Table(props) {
|
||||
props.setLine((prev) => ({
|
||||
...prev,
|
||||
startFieldId: i,
|
||||
startTableId: props.id,
|
||||
startTableId: props.tableData.id,
|
||||
startX: props.tableData.x + 15,
|
||||
startY: props.tableData.y + i * 36 + 50 + 19,
|
||||
endX: props.tableData.x + 15,
|
||||
@ -237,13 +238,13 @@ export default function Table(props) {
|
||||
backgroundColor: "#d42020",
|
||||
}}
|
||||
onClick={(ev) => {
|
||||
props.setTables((prev) => {
|
||||
setTables((prev) => {
|
||||
const updatedTables = [...prev];
|
||||
const updatedFields = [
|
||||
...updatedTables[props.id].fields,
|
||||
...updatedTables[props.tableData.id].fields,
|
||||
];
|
||||
updatedFields.splice(i, 1);
|
||||
updatedTables[props.id].fields = [...updatedFields];
|
||||
updatedTables[props.tableData.id].fields = [...updatedFields];
|
||||
return updatedTables;
|
||||
});
|
||||
}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { React, useState } from "react";
|
||||
import { React, useContext, useState } from "react";
|
||||
import { defaultTableTheme, sqlDataTypes, tableThemes } from "../data/data";
|
||||
import {
|
||||
Collapse,
|
||||
@ -27,20 +27,22 @@ import {
|
||||
IllustrationNoContent,
|
||||
IllustrationNoContentDark,
|
||||
} from "@douyinfe/semi-illustrations";
|
||||
import { TableContext } from "../pages/editor";
|
||||
|
||||
export default function TableOverview(props) {
|
||||
const [indexActiveKey, setIndexActiveKey] = useState("");
|
||||
const [tableActiveKey, setTableActiveKey] = useState("");
|
||||
const [value, setValue] = useState("");
|
||||
const { tables, setTables } = useContext(TableContext);
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
props.tables.map((t) => {
|
||||
tables.map((t) => {
|
||||
return t.name;
|
||||
})
|
||||
);
|
||||
|
||||
const handleStringSearch = (value) => {
|
||||
setFilteredResult(
|
||||
props.tables
|
||||
tables
|
||||
.map((t) => {
|
||||
return t.name;
|
||||
})
|
||||
@ -49,7 +51,7 @@ export default function TableOverview(props) {
|
||||
};
|
||||
|
||||
const updatedField = (tid, fid, updatedValues) => {
|
||||
props.setTables((prev) =>
|
||||
setTables((prev) =>
|
||||
prev.map((table, i) => {
|
||||
if (tid === i) {
|
||||
return {
|
||||
@ -65,7 +67,7 @@ export default function TableOverview(props) {
|
||||
};
|
||||
|
||||
const updateTable = (tid, updatedValues) => {
|
||||
props.setTables((prev) =>
|
||||
setTables((prev) =>
|
||||
prev.map((table, i) => {
|
||||
if (tid === i) {
|
||||
return {
|
||||
@ -92,7 +94,7 @@ export default function TableOverview(props) {
|
||||
onSearch={(v) => handleStringSearch(v)}
|
||||
onChange={(v) => setValue(v)}
|
||||
onSelect={(v) => {
|
||||
const { id } = props.tables.find((t) => t.name === v);
|
||||
const { id } = tables.find((t) => t.name === v);
|
||||
setTableActiveKey(`${id}`);
|
||||
document
|
||||
.getElementById(`scroll_table_${id}`)
|
||||
@ -107,9 +109,7 @@ export default function TableOverview(props) {
|
||||
block
|
||||
onClick={() => {
|
||||
const id =
|
||||
props.tables.length === 0
|
||||
? 0
|
||||
: props.tables[props.tables.length - 1].id + 1;
|
||||
tables.length === 0 ? 0 : tables[tables.length - 1].id + 1;
|
||||
const newTable = {
|
||||
id: id,
|
||||
name: `table_${id}`,
|
||||
@ -132,7 +132,7 @@ export default function TableOverview(props) {
|
||||
indices: [],
|
||||
color: defaultTableTheme,
|
||||
};
|
||||
props.setTables((prev) => [...prev, newTable]);
|
||||
setTables((prev) => [...prev, newTable]);
|
||||
}}
|
||||
>
|
||||
Add table
|
||||
@ -144,7 +144,7 @@ export default function TableOverview(props) {
|
||||
onChange={(k) => setTableActiveKey(k)}
|
||||
accordion
|
||||
>
|
||||
{props.tables.length <= 0 ? (
|
||||
{tables.length <= 0 ? (
|
||||
<div className="select-none">
|
||||
<Empty
|
||||
image={
|
||||
@ -160,7 +160,7 @@ export default function TableOverview(props) {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
props.tables.map((t, i) => (
|
||||
tables.map((t, i) => (
|
||||
<div id={`scroll_table_${t.id}`} key={t.id}>
|
||||
<Collapse.Panel header={<div>{t.name}</div>} itemKey={`${t.id}`}>
|
||||
{t.fields.map((f, j) => (
|
||||
@ -296,7 +296,7 @@ export default function TableOverview(props) {
|
||||
type="danger"
|
||||
block
|
||||
onClick={(ev) => {
|
||||
props.setTables((prev) => {
|
||||
setTables((prev) => {
|
||||
const updatedTables = [...prev];
|
||||
const updatedFields = [
|
||||
...updatedTables[t.id].fields,
|
||||
@ -349,7 +349,7 @@ export default function TableOverview(props) {
|
||||
className="w-full"
|
||||
defaultValue={idx.fields}
|
||||
onChange={(value) => {
|
||||
const updatedTables = [...props.tables];
|
||||
const updatedTables = [...tables];
|
||||
const updatedIndices = [...t.indices];
|
||||
updatedIndices[k] = {
|
||||
name: `${value.join("_")}_index`,
|
||||
@ -359,7 +359,7 @@ export default function TableOverview(props) {
|
||||
...t,
|
||||
indices: [...updatedIndices],
|
||||
};
|
||||
props.setTables(updatedTables);
|
||||
setTables(updatedTables);
|
||||
}}
|
||||
/>
|
||||
<Popover
|
||||
@ -379,14 +379,14 @@ export default function TableOverview(props) {
|
||||
type="danger"
|
||||
block
|
||||
onClick={() => {
|
||||
const updatedTables = [...props.tables];
|
||||
const updatedTables = [...tables];
|
||||
const updatedIndices = [...t.indices];
|
||||
updatedIndices.splice(k, 1);
|
||||
updatedTables[i] = {
|
||||
...t,
|
||||
indices: [...updatedIndices],
|
||||
};
|
||||
props.setTables(updatedTables);
|
||||
setTables(updatedTables);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
@ -506,7 +506,7 @@ export default function TableOverview(props) {
|
||||
block
|
||||
onClick={() => {
|
||||
setIndexActiveKey("1");
|
||||
const updatedTables = [...props.tables];
|
||||
const updatedTables = [...tables];
|
||||
updatedTables[i] = {
|
||||
...t,
|
||||
indices: [
|
||||
@ -514,7 +514,7 @@ export default function TableOverview(props) {
|
||||
{ name: `index_${t.indices.length}`, fields: [] },
|
||||
],
|
||||
};
|
||||
props.setTables(updatedTables);
|
||||
setTables(updatedTables);
|
||||
}}
|
||||
>
|
||||
Add index
|
||||
@ -523,7 +523,7 @@ export default function TableOverview(props) {
|
||||
<Col span={6}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const updatedTables = [...props.tables];
|
||||
const updatedTables = [...tables];
|
||||
updatedTables[i].fields = [
|
||||
...updatedTables[i].fields,
|
||||
{
|
||||
@ -537,7 +537,7 @@ export default function TableOverview(props) {
|
||||
comment: "",
|
||||
},
|
||||
];
|
||||
props.setTables(updatedTables);
|
||||
setTables(updatedTables);
|
||||
}}
|
||||
block
|
||||
>
|
||||
@ -550,7 +550,7 @@ export default function TableOverview(props) {
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Table deleted!`);
|
||||
props.setTables((prev) =>
|
||||
setTables((prev) =>
|
||||
prev
|
||||
.filter((e) => e.id !== i)
|
||||
.map((e, idx) => ({ ...e, id: idx }))
|
||||
|
@ -7,6 +7,7 @@ import Canvas from "../components/canvas";
|
||||
import EditorPanel from "../components/editor_panel";
|
||||
|
||||
export const LayoutContext = createContext();
|
||||
export const TableContext = createContext();
|
||||
|
||||
export default function Editor(props) {
|
||||
const [code, setCode] = useState("");
|
||||
@ -33,12 +34,14 @@ export default function Editor(props) {
|
||||
if (w > 340) setWidth(w);
|
||||
};
|
||||
|
||||
const value = {layout, setLayout};
|
||||
const layoutValue = { layout, setLayout };
|
||||
const tableValue = { tables, setTables };
|
||||
|
||||
return (
|
||||
<LayoutContext.Provider value={value}>
|
||||
<LayoutContext.Provider value={layoutValue}>
|
||||
<TableContext.Provider value={tableValue}>
|
||||
<div className="h-[100vh] overflow-hidden">
|
||||
<ControlPanel/>
|
||||
<ControlPanel />
|
||||
<div
|
||||
className={
|
||||
layout.header
|
||||
@ -51,8 +54,6 @@ export default function Editor(props) {
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
{layout.sidebar && (
|
||||
<EditorPanel
|
||||
tables={tables}
|
||||
setTables={setTables}
|
||||
code={code}
|
||||
setCode={setCode}
|
||||
relationships={relationships}
|
||||
@ -65,8 +66,6 @@ export default function Editor(props) {
|
||||
/>
|
||||
)}
|
||||
<Canvas
|
||||
tables={tables}
|
||||
setTables={setTables}
|
||||
code={code}
|
||||
setCode={setCode}
|
||||
relationships={relationships}
|
||||
@ -78,6 +77,7 @@ export default function Editor(props) {
|
||||
{layout.services && <Sidebar />}
|
||||
</div>
|
||||
</div>
|
||||
</TableContext.Provider>
|
||||
</LayoutContext.Provider>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user