Support custom types
This commit is contained in:
parent
36ad44f0db
commit
273d398dd2
@ -48,6 +48,7 @@ import {
|
||||
SettingsContext,
|
||||
TabContext,
|
||||
TableContext,
|
||||
TypeContext,
|
||||
UndoRedoContext,
|
||||
} from "../pages/editor";
|
||||
import { IconAddTable, IconAddArea, IconAddNote } from "./custom_icons";
|
||||
@ -96,6 +97,8 @@ export default function ControlPanel(props) {
|
||||
addRelationship,
|
||||
deleteRelationship,
|
||||
} = useContext(TableContext);
|
||||
const { types, addType, deleteType, updateType, setTypes } =
|
||||
useContext(TypeContext);
|
||||
const { notes, setNotes, updateNote, addNote, deleteNote } =
|
||||
useContext(NoteContext);
|
||||
const { areas, setAreas, updateArea, addArea, deleteArea } =
|
||||
@ -136,6 +139,8 @@ export default function ControlPanel(props) {
|
||||
deleteNote(notes[notes.length - 1].id, false);
|
||||
} else if (a.element === ObjectType.RELATIONSHIP) {
|
||||
deleteRelationship(a.data.id, false);
|
||||
} else if (a.element === ObjectType.TYPE) {
|
||||
deleteType(types.length - 1, false);
|
||||
}
|
||||
setRedoStack((prev) => [...prev, a]);
|
||||
} else if (a.action === Action.MOVE) {
|
||||
@ -167,6 +172,8 @@ export default function ControlPanel(props) {
|
||||
addNote(false, a.data);
|
||||
} else if (a.element === ObjectType.AREA) {
|
||||
addArea(false, a.data);
|
||||
} else if (a.element === ObjectType.TYPE) {
|
||||
addType(false, { id: a.id, ...a.data });
|
||||
}
|
||||
setRedoStack((prev) => [...prev, a]);
|
||||
} else if (a.action === Action.EDIT) {
|
||||
@ -232,6 +239,34 @@ export default function ControlPanel(props) {
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) => (idx === a.rid ? { ...e, ...a.undo } : e))
|
||||
);
|
||||
} else if (a.element === ObjectType.TYPE) {
|
||||
if (a.component === "field_add") {
|
||||
updateType(a.tid, {
|
||||
fields: types[a.tid].fields.filter(
|
||||
(e, i) => i !== types[a.tid].fields.length - 1
|
||||
),
|
||||
});
|
||||
}
|
||||
if (a.component === "field") {
|
||||
updateType(a.tid, {
|
||||
fields: types[a.tid].fields.map((e, i) =>
|
||||
i === a.fid ? { ...e, ...a.undo } : e
|
||||
),
|
||||
});
|
||||
} else if (a.component === "field_delete") {
|
||||
setTypes((prev) =>
|
||||
prev.map((t, i) => {
|
||||
if (i === a.tid) {
|
||||
const temp = t.fields.slice();
|
||||
temp.splice(a.fid, 0, a.data);
|
||||
return { ...t, fields: temp };
|
||||
}
|
||||
return t;
|
||||
})
|
||||
);
|
||||
} else if (a.component === "self") {
|
||||
updateType(a.tid, a.undo);
|
||||
}
|
||||
}
|
||||
setRedoStack((prev) => [...prev, a]);
|
||||
} else if (a.action === Action.PAN) {
|
||||
@ -255,6 +290,8 @@ export default function ControlPanel(props) {
|
||||
addNote(false);
|
||||
} else if (a.element === ObjectType.RELATIONSHIP) {
|
||||
addRelationship(false, a.data);
|
||||
} else if (a.element === ObjectType.TYPE) {
|
||||
addType(false);
|
||||
}
|
||||
setUndoStack((prev) => [...prev, a]);
|
||||
} else if (a.action === Action.MOVE) {
|
||||
@ -286,6 +323,8 @@ export default function ControlPanel(props) {
|
||||
deleteNote(a.data.id, false);
|
||||
} else if (a.element === ObjectType.AREA) {
|
||||
deleteArea(a.data.id, false);
|
||||
} else if (a.element === ObjectType.TYPE) {
|
||||
deleteType(a.id, false);
|
||||
}
|
||||
setUndoStack((prev) => [...prev, a]);
|
||||
} else if (a.action === Action.EDIT) {
|
||||
@ -363,6 +402,30 @@ export default function ControlPanel(props) {
|
||||
setRelationships((prev) =>
|
||||
prev.map((e, idx) => (idx === a.rid ? { ...e, ...a.redo } : e))
|
||||
);
|
||||
} else if (a.element === ObjectType.TYPE) {
|
||||
if (a.component === "field_add") {
|
||||
updateType(a.tid, {
|
||||
fields: [
|
||||
...types[a.tid].fields,
|
||||
{
|
||||
name: "",
|
||||
type: "",
|
||||
},
|
||||
],
|
||||
});
|
||||
} else if (a.component === "field") {
|
||||
updateType(a.tid, {
|
||||
fields: types[a.tid].fields.map((e, i) =>
|
||||
i === a.fid ? { ...e, ...a.redo } : e
|
||||
),
|
||||
});
|
||||
} else if (a.component === "field_delete") {
|
||||
updateType(a.tid, {
|
||||
fields: types[a.tid].fields.filter((field, i) => i !== a.fid),
|
||||
});
|
||||
} else if (a.component === "self") {
|
||||
updateType(a.tid, a.redo);
|
||||
}
|
||||
}
|
||||
setUndoStack((prev) => [...prev, a]);
|
||||
} else if (a.action === Action.PAN) {
|
||||
|
@ -7,6 +7,7 @@ import { Tab } from "../data/data";
|
||||
import { LayoutContext, TabContext } from "../pages/editor";
|
||||
import NotesOverview from "./notes_overview";
|
||||
import Issues from "./issues";
|
||||
import TypesOverview from "./types_overview";
|
||||
|
||||
const EditorPanel = (props) => {
|
||||
const { tab, setTab } = useContext(TabContext);
|
||||
@ -17,12 +18,14 @@ const EditorPanel = (props) => {
|
||||
{ tab: "Relationships", itemKey: Tab.relationships },
|
||||
{ tab: "Subject Areas", itemKey: Tab.subject_areas },
|
||||
{ tab: "Notes", itemKey: Tab.notes },
|
||||
{ tab: "Types", itemKey: Tab.types },
|
||||
];
|
||||
const contentList = [
|
||||
<TableOverview />,
|
||||
<ReferenceOverview />,
|
||||
<AreaOverview />,
|
||||
<NotesOverview />,
|
||||
<TypesOverview />,
|
||||
];
|
||||
|
||||
return (
|
||||
|
329
src/components/types_overview.jsx
Normal file
329
src/components/types_overview.jsx
Normal file
@ -0,0 +1,329 @@
|
||||
import { React, useContext, useState } from "react";
|
||||
import { Action, ObjectType, sqlDataTypes } from "../data/data";
|
||||
import {
|
||||
Collapse,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
TextArea,
|
||||
Button,
|
||||
Card,
|
||||
Select,
|
||||
AutoComplete,
|
||||
Toast,
|
||||
Empty,
|
||||
Popover,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import {
|
||||
IconDeleteStroked,
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
IconInfoCircle,
|
||||
} from "@douyinfe/semi-icons";
|
||||
import {
|
||||
IllustrationNoContent,
|
||||
IllustrationNoContentDark,
|
||||
} from "@douyinfe/semi-illustrations";
|
||||
import { TypeContext, UndoRedoContext } from "../pages/editor";
|
||||
|
||||
export default function TableOverview(props) {
|
||||
const [value, setValue] = useState("");
|
||||
const { types, addType, deleteType, updateType } = useContext(TypeContext);
|
||||
const { setUndoStack, setRedoStack } = useContext(UndoRedoContext);
|
||||
const [editField, setEditField] = useState({});
|
||||
const [filteredResult, setFilteredResult] = useState(
|
||||
types.map((t) => {
|
||||
return t.name;
|
||||
})
|
||||
);
|
||||
|
||||
const handleStringSearch = (value) => {
|
||||
setFilteredResult(
|
||||
types
|
||||
.map((t) => {
|
||||
return t.name;
|
||||
})
|
||||
.filter((i) => i.includes(value))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={6}>
|
||||
<Col span={13}>
|
||||
<AutoComplete
|
||||
data={filteredResult}
|
||||
value={value}
|
||||
showClear
|
||||
prefix={<IconSearch />}
|
||||
placeholder="Search..."
|
||||
onSearch={(v) => handleStringSearch(v)}
|
||||
emptyContent={
|
||||
<div className="p-3 popover-theme">No types found</div>
|
||||
}
|
||||
onChange={(v) => setValue(v)}
|
||||
onSelect={(v) => {
|
||||
const i = types.findIndex((t) => t.name === v);
|
||||
document
|
||||
.getElementById(`scroll_type_${i}`)
|
||||
.scrollIntoView({ behavior: "smooth" });
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button icon={<IconPlus />} block onClick={() => addType(true)}>
|
||||
Add type
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Popover
|
||||
content={
|
||||
<div className="w-[300px] text-sm popover-theme">
|
||||
This feature is meant for object-relational DMSMs like
|
||||
PostgreSQL. However, if used for relational DMSMs, a JSON type
|
||||
will be generated with the custom type schema.
|
||||
</div>
|
||||
}
|
||||
showArrow
|
||||
position="right"
|
||||
>
|
||||
<Button theme="borderless" icon={<IconInfoCircle />} />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Collapse accordion>
|
||||
{types.length <= 0 ? (
|
||||
<div className="select-none mt-2">
|
||||
<Empty
|
||||
image={
|
||||
<IllustrationNoContent style={{ width: 154, height: 154 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoContentDark
|
||||
style={{ width: 154, height: 154 }}
|
||||
/>
|
||||
}
|
||||
title="No types"
|
||||
description={<div>Make your own custom data types.</div>}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
types.map((t, i) => (
|
||||
<div id={`scroll_type_${i}`} key={i}>
|
||||
<Collapse.Panel header={<div>{t.name}</div>} itemKey={`${i}`}>
|
||||
<div className="flex items-center mb-2.5">
|
||||
<div className="text-md font-semibold">Name: </div>
|
||||
<Input
|
||||
value={t.name}
|
||||
validateStatus={t.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
className="ms-2"
|
||||
onChange={(value) => updateType(i, { name: value })}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.name) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "self",
|
||||
tid: i,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit type name to ${e.target.value}`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{t.fields.map((f, j) => (
|
||||
<Row gutter={6} key={j} className="hover-1 my-2">
|
||||
<Col span={10}>
|
||||
<Input
|
||||
value={f.name}
|
||||
validateStatus={f.name === "" ? "error" : "default"}
|
||||
placeholder="Name"
|
||||
onChange={(value) =>
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
id === j ? { ...f, name: value } : e
|
||||
),
|
||||
})
|
||||
}
|
||||
onFocus={(e) => setEditField({ name: e.target.value })}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.name) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
fid: j,
|
||||
undo: editField,
|
||||
redo: { name: e.target.value },
|
||||
message: `Edit type field name to ${e.target.value}`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={11}>
|
||||
<Select
|
||||
className="w-full"
|
||||
optionList={sqlDataTypes.map((value) => {
|
||||
return {
|
||||
label: value,
|
||||
value: value,
|
||||
};
|
||||
})}
|
||||
filter
|
||||
value={f.type}
|
||||
validateStatus={f.type === "" ? "error" : "default"}
|
||||
placeholder="Type"
|
||||
onChange={(value) => {
|
||||
if (value === f.type) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field",
|
||||
tid: i,
|
||||
fid: j,
|
||||
undo: { type: f.type },
|
||||
redo: { type: value },
|
||||
message: `Edit type field type to ${value}`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
updateType(i, {
|
||||
fields: t.fields.map((e, id) =>
|
||||
id === j ? { ...f, type: value } : e
|
||||
),
|
||||
});
|
||||
}}
|
||||
></Select>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field_delete",
|
||||
tid: i,
|
||||
fid: j,
|
||||
data: f,
|
||||
message: `Delete field`,
|
||||
},
|
||||
]);
|
||||
updateType(i, {
|
||||
fields: t.fields.filter((field, k) => k !== j),
|
||||
});
|
||||
}}
|
||||
></Button>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<Card
|
||||
bodyStyle={{ padding: "4px" }}
|
||||
style={{ marginTop: "12px", marginBottom: "12px" }}
|
||||
headerLine={false}
|
||||
>
|
||||
<Collapse>
|
||||
<Collapse.Panel header="Comment" itemKey="1">
|
||||
<TextArea
|
||||
field="comment"
|
||||
value={t.comment}
|
||||
autosize
|
||||
placeholder="Add comment"
|
||||
rows={1}
|
||||
onChange={(value) =>
|
||||
updateType(i, { comment: value }, false)
|
||||
}
|
||||
onFocus={(e) =>
|
||||
setEditField({ comment: e.target.value })
|
||||
}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === editField.comment) return;
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "self",
|
||||
tid: i,
|
||||
undo: editField,
|
||||
redo: { comment: e.target.value },
|
||||
message: `Edit type comment to ${e.target.value}`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
}}
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</Card>
|
||||
<Row gutter={6} className="mt-2">
|
||||
<Col span={12}>
|
||||
<Button
|
||||
icon={<IconPlus />}
|
||||
onClick={() => {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.EDIT,
|
||||
element: ObjectType.TYPE,
|
||||
component: "field_add",
|
||||
tid: i,
|
||||
message: `Add field to type`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
updateType(i, {
|
||||
fields: [
|
||||
...t.fields,
|
||||
{
|
||||
name: "",
|
||||
type: "",
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
block
|
||||
>
|
||||
Add field
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
icon={<IconDeleteStroked />}
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
Toast.success(`Type deleted!`);
|
||||
deleteType(i);
|
||||
}}
|
||||
block
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Collapse.Panel>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
}
|
@ -84,6 +84,7 @@ const Tab = {
|
||||
relationships: "2",
|
||||
subject_areas: "3",
|
||||
notes: "4",
|
||||
types: "5",
|
||||
};
|
||||
|
||||
const ObjectType = {
|
||||
@ -92,6 +93,7 @@ const ObjectType = {
|
||||
AREA: 2,
|
||||
NOTE: 3,
|
||||
RELATIONSHIP: 4,
|
||||
TYPE: 5,
|
||||
};
|
||||
|
||||
const Action = {
|
||||
|
@ -24,12 +24,14 @@ export const UndoRedoContext = createContext();
|
||||
export const SelectContext = createContext();
|
||||
export const TaskContext = createContext();
|
||||
export const MessageContext = createContext();
|
||||
export const TypeContext = createContext();
|
||||
|
||||
export default function Editor(props) {
|
||||
const [tables, setTables] = useState([]);
|
||||
const [relationships, setRelationships] = useState([]);
|
||||
const [areas, setAreas] = useState([]);
|
||||
const [notes, setNotes] = useState([]);
|
||||
const [types, setTypes] = useState([]);
|
||||
const [resize, setResize] = useState(false);
|
||||
const [width, setWidth] = useState(340);
|
||||
const [tab, setTab] = useState(Tab.tables);
|
||||
@ -118,6 +120,59 @@ export default function Editor(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const addType = (addToHistory = true, data) => {
|
||||
if (data) {
|
||||
setTypes((prev) => {
|
||||
const temp = prev.slice();
|
||||
temp.splice(data.id, 0, data);
|
||||
return temp;
|
||||
});
|
||||
} else {
|
||||
setTypes((prev) => [
|
||||
...prev,
|
||||
{
|
||||
name: `type_${prev.length}`,
|
||||
fields: [],
|
||||
comment: "",
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (addToHistory) {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.ADD,
|
||||
element: ObjectType.TYPE,
|
||||
message: `Add new type`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteType = (id, addToHistory = true) => {
|
||||
if (addToHistory) {
|
||||
setUndoStack((prev) => [
|
||||
...prev,
|
||||
{
|
||||
action: Action.DELETE,
|
||||
element: ObjectType.TYPE,
|
||||
id: id,
|
||||
data: types[id],
|
||||
message: `Delete type`,
|
||||
},
|
||||
]);
|
||||
setRedoStack([]);
|
||||
}
|
||||
setTypes((prev) => prev.filter((e, i) => i !== id));
|
||||
};
|
||||
|
||||
const updateType = (id, values) => {
|
||||
setTypes((prev) =>
|
||||
prev.map((e, i) => (i === id ? { ...e, ...values } : e))
|
||||
);
|
||||
};
|
||||
|
||||
const updateField = (tid, fid, updatedValues) => {
|
||||
setTables((prev) =>
|
||||
prev.map((table, i) => {
|
||||
@ -477,34 +532,44 @@ export default function Editor(props) {
|
||||
<TaskContext.Provider
|
||||
value={{ tasks, setTasks, updateTask }}
|
||||
>
|
||||
<div className="h-[100vh] overflow-hidden theme">
|
||||
<ControlPanel />
|
||||
<div
|
||||
className={
|
||||
layout.header
|
||||
? `flex h-[calc(100vh-120px)]`
|
||||
: `flex h-[calc(100vh-52px)]`
|
||||
}
|
||||
onMouseUp={() => setResize(false)}
|
||||
onMouseMove={dragHandler}
|
||||
>
|
||||
{layout.sidebar && (
|
||||
<EditorPanel
|
||||
resize={resize}
|
||||
setResize={setResize}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
<Canvas />
|
||||
{layout.services && (
|
||||
<MessageContext.Provider
|
||||
value={{ messages, setMessages }}
|
||||
>
|
||||
<Sidebar />
|
||||
</MessageContext.Provider>
|
||||
)}
|
||||
<TypeContext.Provider
|
||||
value={{
|
||||
types,
|
||||
setTypes,
|
||||
addType,
|
||||
updateType,
|
||||
deleteType,
|
||||
}}
|
||||
>
|
||||
<div className="h-[100vh] overflow-hidden theme">
|
||||
<ControlPanel />
|
||||
<div
|
||||
className={
|
||||
layout.header
|
||||
? `flex h-[calc(100vh-120px)]`
|
||||
: `flex h-[calc(100vh-52px)]`
|
||||
}
|
||||
onMouseUp={() => setResize(false)}
|
||||
onMouseMove={dragHandler}
|
||||
>
|
||||
{layout.sidebar && (
|
||||
<EditorPanel
|
||||
resize={resize}
|
||||
setResize={setResize}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
<Canvas />
|
||||
{layout.services && (
|
||||
<MessageContext.Provider
|
||||
value={{ messages, setMessages }}
|
||||
>
|
||||
<Sidebar />
|
||||
</MessageContext.Provider>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TypeContext.Provider>
|
||||
</TaskContext.Provider>
|
||||
</SelectContext.Provider>
|
||||
</UndoRedoContext.Provider>
|
||||
|
Loading…
Reference in New Issue
Block a user