File validation

This commit is contained in:
1ilit
2023-09-19 15:49:28 +03:00
parent 423e4e9b2d
commit db843f32f3
5 changed files with 290 additions and 47 deletions

View File

@@ -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,23 +165,21 @@ export default function ControlPanel(props) {
{
PDF: () => {
const canvas = document.getElementById("canvas");
toJpeg(canvas).then(
function (dataUrl) {
const doc = new jsPDF("l", "px", [
canvas.offsetWidth,
canvas.offsetHeight,
]);
doc.addImage(
dataUrl,
"jpeg",
0,
0,
canvas.offsetWidth,
canvas.offsetHeight
);
doc.save(`${exportData.filename}.pdf`);
}
);
toJpeg(canvas).then(function (dataUrl) {
const doc = new jsPDF("l", "px", [
canvas.offsetWidth,
canvas.offsetHeight,
]);
doc.addImage(
dataUrl,
"jpeg",
0,
0,
canvas.offsetWidth,
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,44 +524,101 @@ 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 ? (
<Image src={exportData.data} alt="Diagram" height={220} />
) : (
<div className="max-h-[400px] overflow-auto border border-gray-200">
<CodeMirror
value={exportData.data}
extensions={[json()]}
style={{
width: "100%",
height: "100%",
}}
{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>
)
)}
</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">
<CodeMirror
value={exportData.data}
extensions={[json()]}
style={{
width: "100%",
height: "100%",
}}
/>
</div>
)}
<div className="text-sm font-semibold mt-2">Filename:</div>
<Input
value={exportData.filename}
placeholder="Filename"
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
onChange={(value) =>
setExportData((prev) => ({ ...prev, filename: value }))
}
field="filename"
/>
</>
) : (
<div className="text-center my-3">
<Spin tip="Loading..." size="large" />
</div>
)}
<div className="text-sm font-semibold mt-2">Filename : </div>
<Input
value={exportData.filename}
placeholder="Filename"
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
onChange={(value) =>
setExportData((prev) => ({ ...prev, filename: value }))
}
field="filename"
/>
</Modal>
</div>
);

144
src/schemas/index.js Normal file
View 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 };

View File

@@ -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 };