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",
|
"codemirror": "^5.65.13",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"jsonschema": "^1.4.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
"node-sql-parser": "^4.7.0",
|
"node-sql-parser": "^4.7.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"url": "^0.11.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -12507,6 +12509,14 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/jspdf": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
|
||||||
@ -17290,6 +17300,15 @@
|
|||||||
"punycode": "^2.1.0"
|
"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": {
|
"node_modules/url-parse": {
|
||||||
"version": "1.5.10",
|
"version": "1.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||||
@ -17299,6 +17318,11 @@
|
|||||||
"requires-port": "^1.0.0"
|
"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": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"codemirror": "^5.65.13",
|
"codemirror": "^5.65.13",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
|
"jsonschema": "^1.4.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
"node-sql-parser": "^4.7.0",
|
"node-sql-parser": "^4.7.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -23,6 +24,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"url": "^0.11.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -23,10 +23,16 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Spin,
|
Spin,
|
||||||
Input,
|
Input,
|
||||||
|
Upload,
|
||||||
|
Banner,
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
import { toPng, toJpeg, toSvg } from "html-to-image";
|
import { toPng, toJpeg, toSvg } from "html-to-image";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
import { enterFullscreen, exitFullscreen } from "../utils";
|
import {
|
||||||
|
diagramObjectIsValid,
|
||||||
|
enterFullscreen,
|
||||||
|
exitFullscreen,
|
||||||
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
AreaContext,
|
AreaContext,
|
||||||
LayoutContext,
|
LayoutContext,
|
||||||
@ -45,6 +51,7 @@ export default function ControlPanel(props) {
|
|||||||
NONE: 0,
|
NONE: 0,
|
||||||
IMG: 1,
|
IMG: 1,
|
||||||
CODE: 2,
|
CODE: 2,
|
||||||
|
IMPORT: 3,
|
||||||
};
|
};
|
||||||
const [visible, setVisible] = useState(MODAL.NONE);
|
const [visible, setVisible] = useState(MODAL.NONE);
|
||||||
const [exportData, setExportData] = useState({
|
const [exportData, setExportData] = useState({
|
||||||
@ -52,6 +59,7 @@ export default function ControlPanel(props) {
|
|||||||
filename: `diagram_${new Date().toISOString()}`,
|
filename: `diagram_${new Date().toISOString()}`,
|
||||||
extension: "",
|
extension: "",
|
||||||
});
|
});
|
||||||
|
const [error, setError] = useState(null);
|
||||||
const { layout, setLayout } = useContext(LayoutContext);
|
const { layout, setLayout } = useContext(LayoutContext);
|
||||||
const { setSettings } = useContext(SettingsContext);
|
const { setSettings } = useContext(SettingsContext);
|
||||||
const { relationships, tables, setTables } = useContext(TableContext);
|
const { relationships, tables, setTables } = useContext(TableContext);
|
||||||
@ -86,7 +94,9 @@ export default function ControlPanel(props) {
|
|||||||
},
|
},
|
||||||
Import: {
|
Import: {
|
||||||
children: [],
|
children: [],
|
||||||
function: () => {},
|
function: () => {
|
||||||
|
setVisible(MODAL.IMPORT);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"Export as": {
|
"Export as": {
|
||||||
children: [
|
children: [
|
||||||
@ -125,7 +135,7 @@ export default function ControlPanel(props) {
|
|||||||
tables: tables,
|
tables: tables,
|
||||||
relationships: relationships,
|
relationships: relationships,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
"subject areas": areas,
|
subjectAreas: areas,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
@ -155,8 +165,7 @@ export default function ControlPanel(props) {
|
|||||||
{
|
{
|
||||||
PDF: () => {
|
PDF: () => {
|
||||||
const canvas = document.getElementById("canvas");
|
const canvas = document.getElementById("canvas");
|
||||||
toJpeg(canvas).then(
|
toJpeg(canvas).then(function (dataUrl) {
|
||||||
function (dataUrl) {
|
|
||||||
const doc = new jsPDF("l", "px", [
|
const doc = new jsPDF("l", "px", [
|
||||||
canvas.offsetWidth,
|
canvas.offsetWidth,
|
||||||
canvas.offsetHeight,
|
canvas.offsetHeight,
|
||||||
@ -170,8 +179,7 @@ export default function ControlPanel(props) {
|
|||||||
canvas.offsetHeight
|
canvas.offsetHeight
|
||||||
);
|
);
|
||||||
doc.save(`${exportData.filename}.pdf`);
|
doc.save(`${exportData.filename}.pdf`);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -494,7 +502,7 @@ export default function ControlPanel(props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
title="Export diagram"
|
title={`${visible === MODAL.IMPORT ? "Import" : "Export"} diagram`}
|
||||||
visible={visible !== MODAL.NONE}
|
visible={visible !== MODAL.NONE}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
if (visible === MODAL.IMG) {
|
if (visible === MODAL.IMG) {
|
||||||
@ -507,6 +515,7 @@ export default function ControlPanel(props) {
|
|||||||
type: "text/plain;charset=utf-8",
|
type: "text/plain;charset=utf-8",
|
||||||
});
|
});
|
||||||
saveAs(blob, `${exportData.filename}.${exportData.extension}`);
|
saveAs(blob, `${exportData.filename}.${exportData.extension}`);
|
||||||
|
} else if (visible === MODAL.IMPORT) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
afterClose={() => {
|
afterClose={() => {
|
||||||
@ -515,16 +524,72 @@ export default function ControlPanel(props) {
|
|||||||
extension: "",
|
extension: "",
|
||||||
filename: `diagram_${new Date().toISOString()}`,
|
filename: `diagram_${new Date().toISOString()}`,
|
||||||
}));
|
}));
|
||||||
|
setError(null);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setVisible(MODAL.NONE)}
|
onCancel={() => setVisible(MODAL.NONE)}
|
||||||
centered
|
centered
|
||||||
closeOnEsc={true}
|
closeOnEsc={true}
|
||||||
okText="Export"
|
okText={`${visible === MODAL.IMPORT ? "Import" : "Export"}`}
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
width={520}
|
width={520}
|
||||||
>
|
>
|
||||||
{exportData.data !== "" || exportData.data ? (
|
{visible === MODAL.IMPORT ? (
|
||||||
visible === MODAL.IMG ? (
|
<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} />
|
<Image src={exportData.data} alt="Diagram" height={220} />
|
||||||
) : (
|
) : (
|
||||||
<div className="max-h-[400px] overflow-auto border border-gray-200">
|
<div className="max-h-[400px] overflow-auto border border-gray-200">
|
||||||
@ -537,13 +602,8 @@ export default function ControlPanel(props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className="text-center my-3">
|
|
||||||
<Spin tip="Loading..." size="large" />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<div className="text-sm font-semibold mt-2">Filename : </div>
|
<div className="text-sm font-semibold mt-2">Filename:</div>
|
||||||
<Input
|
<Input
|
||||||
value={exportData.filename}
|
value={exportData.filename}
|
||||||
placeholder="Filename"
|
placeholder="Filename"
|
||||||
@ -553,6 +613,12 @@ export default function ControlPanel(props) {
|
|||||||
}
|
}
|
||||||
field="filename"
|
field="filename"
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center my-3">
|
||||||
|
<Spin tip="Loading..." size="large" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</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 enterFullscreen = () => {
|
||||||
const element = document.documentElement;
|
const element = document.documentElement;
|
||||||
if (element.requestFullscreen) {
|
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