File validation
This commit is contained in:
parent
423e4e9b2d
commit
db843f32f3
24
package-lock.json
generated
24
package-lock.json
generated
@ -20,6 +20,7 @@
|
||||
"codemirror": "^5.65.13",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-to-image": "^1.11.11",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"node-sql-parser": "^4.7.0",
|
||||
"react": "^18.2.0",
|
||||
@ -28,6 +29,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"url": "^0.11.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -12507,6 +12509,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonschema": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
|
||||
"integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
|
||||
@ -17290,6 +17300,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz",
|
||||
"integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==",
|
||||
"dependencies": {
|
||||
"punycode": "^1.4.1",
|
||||
"qs": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
@ -17299,6 +17318,11 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url/node_modules/punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"codemirror": "^5.65.13",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-to-image": "^1.11.11",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"node-sql-parser": "^4.7.0",
|
||||
"react": "^18.2.0",
|
||||
@ -23,6 +24,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"url": "^0.11.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -23,10 +23,16 @@ import {
|
||||
Modal,
|
||||
Spin,
|
||||
Input,
|
||||
Upload,
|
||||
Banner,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { toPng, toJpeg, toSvg } from "html-to-image";
|
||||
import { saveAs } from "file-saver";
|
||||
import { enterFullscreen, exitFullscreen } from "../utils";
|
||||
import {
|
||||
diagramObjectIsValid,
|
||||
enterFullscreen,
|
||||
exitFullscreen,
|
||||
} from "../utils";
|
||||
import {
|
||||
AreaContext,
|
||||
LayoutContext,
|
||||
@ -45,6 +51,7 @@ export default function ControlPanel(props) {
|
||||
NONE: 0,
|
||||
IMG: 1,
|
||||
CODE: 2,
|
||||
IMPORT: 3,
|
||||
};
|
||||
const [visible, setVisible] = useState(MODAL.NONE);
|
||||
const [exportData, setExportData] = useState({
|
||||
@ -52,6 +59,7 @@ export default function ControlPanel(props) {
|
||||
filename: `diagram_${new Date().toISOString()}`,
|
||||
extension: "",
|
||||
});
|
||||
const [error, setError] = useState(null);
|
||||
const { layout, setLayout } = useContext(LayoutContext);
|
||||
const { setSettings } = useContext(SettingsContext);
|
||||
const { relationships, tables, setTables } = useContext(TableContext);
|
||||
@ -86,7 +94,9 @@ export default function ControlPanel(props) {
|
||||
},
|
||||
Import: {
|
||||
children: [],
|
||||
function: () => {},
|
||||
function: () => {
|
||||
setVisible(MODAL.IMPORT);
|
||||
},
|
||||
},
|
||||
"Export as": {
|
||||
children: [
|
||||
@ -125,7 +135,7 @@ export default function ControlPanel(props) {
|
||||
tables: tables,
|
||||
relationships: relationships,
|
||||
notes: notes,
|
||||
"subject areas": areas,
|
||||
subjectAreas: areas,
|
||||
},
|
||||
null,
|
||||
2
|
||||
@ -155,8 +165,7 @@ export default function ControlPanel(props) {
|
||||
{
|
||||
PDF: () => {
|
||||
const canvas = document.getElementById("canvas");
|
||||
toJpeg(canvas).then(
|
||||
function (dataUrl) {
|
||||
toJpeg(canvas).then(function (dataUrl) {
|
||||
const doc = new jsPDF("l", "px", [
|
||||
canvas.offsetWidth,
|
||||
canvas.offsetHeight,
|
||||
@ -170,8 +179,7 @@ export default function ControlPanel(props) {
|
||||
canvas.offsetHeight
|
||||
);
|
||||
doc.save(`${exportData.filename}.pdf`);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -494,7 +502,7 @@ export default function ControlPanel(props) {
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
title="Export diagram"
|
||||
title={`${visible === MODAL.IMPORT ? "Import" : "Export"} diagram`}
|
||||
visible={visible !== MODAL.NONE}
|
||||
onOk={() => {
|
||||
if (visible === MODAL.IMG) {
|
||||
@ -507,6 +515,7 @@ export default function ControlPanel(props) {
|
||||
type: "text/plain;charset=utf-8",
|
||||
});
|
||||
saveAs(blob, `${exportData.filename}.${exportData.extension}`);
|
||||
} else if (visible === MODAL.IMPORT) {
|
||||
}
|
||||
}}
|
||||
afterClose={() => {
|
||||
@ -515,16 +524,72 @@ export default function ControlPanel(props) {
|
||||
extension: "",
|
||||
filename: `diagram_${new Date().toISOString()}`,
|
||||
}));
|
||||
setError(null);
|
||||
}}
|
||||
onCancel={() => setVisible(MODAL.NONE)}
|
||||
centered
|
||||
closeOnEsc={true}
|
||||
okText="Export"
|
||||
okText={`${visible === MODAL.IMPORT ? "Import" : "Export"}`}
|
||||
cancelText="Cancel"
|
||||
width={520}
|
||||
>
|
||||
{exportData.data !== "" || exportData.data ? (
|
||||
visible === MODAL.IMG ? (
|
||||
{visible === MODAL.IMPORT ? (
|
||||
<div>
|
||||
<Upload
|
||||
action="#"
|
||||
beforeUpload={({ file, fileList }) => {
|
||||
const f = fileList[0].fileInstance;
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
if (f.type === "application/json") {
|
||||
let jsonObject = null;
|
||||
try {
|
||||
jsonObject = JSON.parse(event.target.result);
|
||||
} catch (error) {
|
||||
setError("Invalid JSON. The file contains an error.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!diagramObjectIsValid(jsonObject)) {
|
||||
setError(
|
||||
"The file is missing necessary properties for a diagram."
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.readAsText(f);
|
||||
|
||||
return {
|
||||
autoRemove: false,
|
||||
fileInstance: file.fileInstance,
|
||||
status: "success",
|
||||
shouldUpload: false,
|
||||
};
|
||||
}}
|
||||
draggable={true}
|
||||
dragMainText="Click to upload the file or drag and drop the file here"
|
||||
dragSubText="Support json"
|
||||
accept="application/json,.txt"
|
||||
onRemove={() => setError(null)}
|
||||
onFileChange={() => setError(null)}
|
||||
limit={1}
|
||||
></Upload>
|
||||
{error && (
|
||||
<Banner
|
||||
type="danger"
|
||||
fullMode={false}
|
||||
description={<div className="text-red-800">{error}</div>}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : exportData.data !== "" || exportData.data ? (
|
||||
<>
|
||||
{visible === MODAL.IMG ? (
|
||||
<Image src={exportData.data} alt="Diagram" height={220} />
|
||||
) : (
|
||||
<div className="max-h-[400px] overflow-auto border border-gray-200">
|
||||
@ -537,11 +602,6 @@ export default function ControlPanel(props) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-center my-3">
|
||||
<Spin tip="Loading..." size="large" />
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm font-semibold mt-2">Filename:</div>
|
||||
<Input
|
||||
@ -553,6 +613,12 @@ export default function ControlPanel(props) {
|
||||
}
|
||||
field="filename"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center my-3">
|
||||
<Spin tip="Loading..." size="large" />
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
144
src/schemas/index.js
Normal file
144
src/schemas/index.js
Normal file
@ -0,0 +1,144 @@
|
||||
const jsonSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
tables: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "integer" },
|
||||
name: { type: "string" },
|
||||
x: { type: "number" },
|
||||
y: { type: "number" },
|
||||
fields: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
type: { type: "string" },
|
||||
default: { type: "string" },
|
||||
check: { type: "string" },
|
||||
primary: { type: "boolean" },
|
||||
unique: { type: "boolean" },
|
||||
notNull: { type: "boolean" },
|
||||
increment: { type: "boolean" },
|
||||
comment: { type: "string" },
|
||||
},
|
||||
required: [
|
||||
"name",
|
||||
"type",
|
||||
"default",
|
||||
"check",
|
||||
"primary",
|
||||
"unique",
|
||||
"notNull",
|
||||
"increment",
|
||||
"comment",
|
||||
],
|
||||
},
|
||||
},
|
||||
comment: { type: "string" },
|
||||
indices: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
fields: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
},
|
||||
},
|
||||
required: ["name", "fields"],
|
||||
},
|
||||
},
|
||||
color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$" },
|
||||
},
|
||||
required: [
|
||||
"id",
|
||||
"name",
|
||||
"x",
|
||||
"y",
|
||||
"fields",
|
||||
"comment",
|
||||
"indices",
|
||||
"color",
|
||||
],
|
||||
},
|
||||
},
|
||||
relationships: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
startTableId: { type: "integer" },
|
||||
startFieldId: { type: "integer" },
|
||||
endTableId: { type: "integer" },
|
||||
endFieldId: { type: "integer" },
|
||||
startX: { type: "number" },
|
||||
startY: { type: "number" },
|
||||
endX: { type: "number" },
|
||||
endY: { type: "number" },
|
||||
name: { type: "string" },
|
||||
cardinality: { type: "string" },
|
||||
updateConstraint: { type: "string" },
|
||||
deleteConstraint: { type: "string" },
|
||||
mandatory: { type: "boolean" },
|
||||
id: { type: "integer" },
|
||||
},
|
||||
required: [
|
||||
"startTableId",
|
||||
"startFieldId",
|
||||
"endTableId",
|
||||
"endFieldId",
|
||||
"startX",
|
||||
"startY",
|
||||
"endX",
|
||||
"endY",
|
||||
"name",
|
||||
"cardinality",
|
||||
"updateConstraint",
|
||||
"deleteConstraint",
|
||||
"mandatory",
|
||||
"id",
|
||||
],
|
||||
},
|
||||
},
|
||||
notes: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "integer" },
|
||||
x: { type: "number" },
|
||||
y: { type: "number" },
|
||||
title: { type: "string" },
|
||||
content: { type: "string" },
|
||||
color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$" },
|
||||
height: { type: "number" },
|
||||
},
|
||||
required: ["id", "x", "y", "title", "content", "color", "height"],
|
||||
},
|
||||
},
|
||||
subjectAreas: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "integer" },
|
||||
name: { type: "string" },
|
||||
x: { type: "number" },
|
||||
y: { type: "number" },
|
||||
width: { type: "number" },
|
||||
height: { type: "number" },
|
||||
color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$" },
|
||||
},
|
||||
required: ["id", "name", "x", "y", "width", "height", "color"],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["tables", "relationships", "notes", "subjectAreas"],
|
||||
};
|
||||
|
||||
export { jsonSchema };
|
@ -1,3 +1,6 @@
|
||||
import { Validator } from "jsonschema";
|
||||
import { jsonSchema } from "../schemas";
|
||||
|
||||
const enterFullscreen = () => {
|
||||
const element = document.documentElement;
|
||||
if (element.requestFullscreen) {
|
||||
@ -23,4 +26,8 @@ const exitFullscreen = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export { enterFullscreen, exitFullscreen };
|
||||
const diagramObjectIsValid = (obj) => {
|
||||
return new Validator().validate(obj, jsonSchema).valid;
|
||||
};
|
||||
|
||||
export { enterFullscreen, exitFullscreen, diagramObjectIsValid };
|
||||
|
Loading…
Reference in New Issue
Block a user