commit
789ab9c076
59
package-lock.json
generated
59
package-lock.json
generated
@ -15,10 +15,12 @@
|
||||
"dexie": "^3.2.4",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^10.18.0",
|
||||
"html-to-image": "^1.11.11",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"lexical": "^0.12.5",
|
||||
"node-sql-parser": "^4.17.0",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -563,6 +565,21 @@
|
||||
"react-dom": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "0.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz",
|
||||
@ -1959,6 +1976,14 @@
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.52",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
|
||||
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@ -3103,6 +3128,29 @@
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "10.18.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz",
|
||||
"integrity": "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@emotion/is-prop-valid": "^0.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@ -4083,6 +4131,17 @@
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-sql-parser": {
|
||||
"version": "4.17.0",
|
||||
"resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.17.0.tgz",
|
||||
"integrity": "sha512-3IhovpmUBpcETnoKK/KBdkz2mz53kVG5E1dnqz1QuYvtzdxYZW5xaGGEvW9u6Yyy2ivwR3eUZrn9inmEVef02w==",
|
||||
"dependencies": {
|
||||
"big-integer": "^1.6.48"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
@ -17,10 +17,12 @@
|
||||
"dexie": "^3.2.4",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^10.18.0",
|
||||
"html-to-image": "^1.11.11",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"lexical": "^0.12.5",
|
||||
"node-sql-parser": "^4.17.0",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
|
30
src/animations/Reveal.jsx
Normal file
30
src/animations/Reveal.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { motion, useInView, useAnimation } from "framer-motion";
|
||||
|
||||
export default function Reveal({ children }) {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true });
|
||||
const mainControls = useAnimation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView) {
|
||||
mainControls.start("visible");
|
||||
}
|
||||
}, [isInView, mainControls]);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: 75 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
}}
|
||||
initial="hidden"
|
||||
animate={mainControls}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -29,6 +29,8 @@ import {
|
||||
Toast,
|
||||
SideSheet,
|
||||
List,
|
||||
Select,
|
||||
Checkbox,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import timeLine from "../assets/process.png";
|
||||
import timeLineDark from "../assets/process_dark.png";
|
||||
@ -64,6 +66,7 @@ import { areaSchema, noteSchema, tableSchema } from "../data/schemas";
|
||||
import { Editor } from "@monaco-editor/react";
|
||||
import { db } from "../data/db";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { Parser } from "node-sql-parser";
|
||||
import Todo from "./Todo";
|
||||
|
||||
export default function ControlPanel({
|
||||
@ -84,6 +87,7 @@ export default function ControlPanel({
|
||||
OPEN: 5,
|
||||
SAVEAS: 6,
|
||||
NEW: 7,
|
||||
IMPORT_SRC: 8,
|
||||
};
|
||||
const STATUS = {
|
||||
NONE: 0,
|
||||
@ -823,10 +827,16 @@ export default function ControlPanel({
|
||||
.catch(() => Toast.error("Oops! Something went wrong."));
|
||||
},
|
||||
},
|
||||
Import: {
|
||||
"Import diagram": {
|
||||
function: fileImport,
|
||||
shortcut: "Ctrl+I",
|
||||
},
|
||||
"Import from source": {
|
||||
function: () => {
|
||||
setData({ src: "", overwrite: true, dbms: "MySQL" });
|
||||
setVisible(MODAL.IMPORT_SRC)
|
||||
}
|
||||
},
|
||||
"Export as": {
|
||||
children: [
|
||||
{
|
||||
@ -1172,6 +1182,7 @@ export default function ControlPanel({
|
||||
const getModalTitle = () => {
|
||||
switch (visible) {
|
||||
case MODAL.IMPORT:
|
||||
case MODAL.IMPORT_SRC:
|
||||
return "Import diagram";
|
||||
case MODAL.CODE:
|
||||
return "Export source";
|
||||
@ -1193,6 +1204,7 @@ export default function ControlPanel({
|
||||
const getOkText = () => {
|
||||
switch (visible) {
|
||||
case MODAL.IMPORT:
|
||||
case MODAL.IMPORT_SRC:
|
||||
return "Import";
|
||||
case MODAL.CODE:
|
||||
case MODAL.IMG:
|
||||
@ -1210,6 +1222,165 @@ export default function ControlPanel({
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* {
|
||||
"id": 0,
|
||||
"name": "table_4",
|
||||
"x": 50,
|
||||
"y": 83,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "INT",
|
||||
"default": "",
|
||||
"check": "",
|
||||
"primary": true,
|
||||
"unique": true,
|
||||
"notNull": true,
|
||||
"increment": true,
|
||||
"comment": "",
|
||||
"id": 0
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "NUMERIC",
|
||||
"default": "",
|
||||
"check": "",
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"notNull": false,
|
||||
"increment": false,
|
||||
"comment": "",
|
||||
"id": 1,
|
||||
"size": ""
|
||||
}
|
||||
],
|
||||
"comment": "",
|
||||
"indices": [],
|
||||
"color": "#175e7a"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "table_1",
|
||||
"x": 360,
|
||||
"y": 181,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "INT",
|
||||
"default": "",
|
||||
"check": "",
|
||||
"primary": true,
|
||||
"unique": true,
|
||||
"notNull": true,
|
||||
"increment": true,
|
||||
"comment": "",
|
||||
"id": 0
|
||||
},
|
||||
{
|
||||
"name": "kk",
|
||||
"type": "INT",
|
||||
"default": "",
|
||||
"check": "",
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"notNull": false,
|
||||
"increment": false,
|
||||
"comment": "",
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"size": "12"
|
||||
}
|
||||
],
|
||||
"comment": "",
|
||||
"indices": [],
|
||||
"color": "#175e7a"
|
||||
}
|
||||
*/
|
||||
|
||||
const parseSQLAndLoadDiagram = () => {
|
||||
const parser = new Parser();
|
||||
let ast = null;
|
||||
try {
|
||||
console.log(data.dbms)
|
||||
ast = parser.astify(data.src, { database: data.dbms });
|
||||
} catch (err) {
|
||||
Toast.error("Could not parse the sql file. Make sure there are no syntax errors.");
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
const tables = [];
|
||||
|
||||
ast.forEach(((e) => {
|
||||
console.log(JSON.stringify(e))
|
||||
if (e.type === "create" && e.keyword === "table") {
|
||||
const table = {};
|
||||
table.name = e.table[0].table;
|
||||
table.color = "#175e7a";
|
||||
table.fields = [];
|
||||
table.indices = [];
|
||||
table.x = 0;
|
||||
table.y = 0;
|
||||
e.create_definitions.forEach((d) => {
|
||||
if (d.resource === "column") {
|
||||
const field = {};
|
||||
field.name = d.column.column;
|
||||
field.type = d.definition.dataType;
|
||||
field.comment = "";
|
||||
field.unique = false;
|
||||
if (d.unique) field.unique = true;
|
||||
field.auto_increment = false;
|
||||
if (d.auto_increment) field.auto_increment = true;
|
||||
field.notNull = false;
|
||||
if (d.nullable) field.notNull = true;
|
||||
field.primary = false;
|
||||
if (d.primary_key) field.primary = true;
|
||||
field.default = "";
|
||||
if (d.default_val) field.default = d.default_val.value.value;
|
||||
if (d.definition["length"]) field.size = d.definition["length"];
|
||||
|
||||
if (d.check) {
|
||||
let check = "";
|
||||
if (d.check.definition[0].left.column) {
|
||||
check = d.check.definition[0].left.column + " " + d.check.definition[0].operator + " " + d.check.definition[0].right.value;
|
||||
} else {
|
||||
check = d.check.definition[0].left.value + " " + d.check.definition[0].operator + " " + d.check.definition[0].right.column;
|
||||
}
|
||||
field.check = check;
|
||||
}
|
||||
|
||||
table.fields.push(field);
|
||||
} else if (d.resource === "constraint") {
|
||||
if (d.constraint_type === "primary key") {
|
||||
d.definition.forEach(c => {
|
||||
table.fields.forEach((f) => {
|
||||
if (f.name === c.column && !f.primary) {
|
||||
f.primary = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tables.push(table);
|
||||
}
|
||||
}))
|
||||
|
||||
tables.forEach((e, i) => {
|
||||
e.id = i;
|
||||
e.fields.forEach((f, j) => {
|
||||
f.id = j;
|
||||
})
|
||||
})
|
||||
|
||||
setTables(tables)
|
||||
console.log(tables);
|
||||
}
|
||||
|
||||
const getModalOnOk = async () => {
|
||||
switch (visible) {
|
||||
case MODAL.IMG:
|
||||
@ -1235,6 +1406,10 @@ export default function ControlPanel({
|
||||
setRedoStack([]);
|
||||
}
|
||||
return;
|
||||
case MODAL.IMPORT_SRC:
|
||||
parseSQLAndLoadDiagram();
|
||||
setVisible(MODAL.NONE)
|
||||
return;
|
||||
case MODAL.OPEN:
|
||||
if (selectedDiagramId === 0) return;
|
||||
loadDiagram(selectedDiagramId);
|
||||
@ -1373,6 +1548,70 @@ export default function ControlPanel({
|
||||
);
|
||||
};
|
||||
|
||||
const importSrcModalBody = () => {
|
||||
return (
|
||||
<>
|
||||
<Upload
|
||||
action="#"
|
||||
beforeUpload={({ file, fileList }) => {
|
||||
const f = fileList[0].fileInstance;
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
setData(prev => ({ ...prev, src: e.target.result }))
|
||||
};
|
||||
reader.readAsText(f);
|
||||
|
||||
return {
|
||||
autoRemove: false,
|
||||
fileInstance: file.fileInstance,
|
||||
status: "success",
|
||||
shouldUpload: false,
|
||||
};
|
||||
}}
|
||||
draggable={true}
|
||||
dragMainText="Drag and drop the file here or click to upload."
|
||||
dragSubText="Upload an sql file to autogenerate your tables and columns."
|
||||
accept=".sql"
|
||||
onRemove={() => {
|
||||
setError({
|
||||
type: STATUS.NONE,
|
||||
message: "",
|
||||
});
|
||||
setData(prev => ({ ...prev, src: "" }));
|
||||
}
|
||||
}
|
||||
onFileChange={() =>
|
||||
setError({
|
||||
type: STATUS.NONE,
|
||||
message: "",
|
||||
})
|
||||
}
|
||||
limit={1}
|
||||
></Upload>
|
||||
<div className="my-2">
|
||||
<div className="text-sm font-semibold mb-1">Select DBMS</div>
|
||||
<Select defaultValue="MySQL"
|
||||
optionList={[
|
||||
{ value: 'MySQL', label: 'MySQL' },
|
||||
{ value: 'Postgresql', label: 'PostgreSQL' },
|
||||
]}
|
||||
onChange={(e) => setData(prev => ({ ...prev, dbms: e }))}
|
||||
className="w-full"></Select>
|
||||
<Checkbox aria-label="overwrite checkbox"
|
||||
checked={data.overwrite}
|
||||
defaultChecked
|
||||
onChange={e => setData(prev => ({ ...prev, overwrite: e.target.checked }))}
|
||||
className="my-2">
|
||||
Overwrite existing diagram
|
||||
</Checkbox>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const newModalBody = () => (
|
||||
<div className="h-[360px] grid grid-cols-3 gap-2 overflow-auto px-1">
|
||||
<div>
|
||||
@ -1408,6 +1647,8 @@ export default function ControlPanel({
|
||||
switch (visible) {
|
||||
case MODAL.IMPORT:
|
||||
return importModalBody();
|
||||
case MODAL.IMPORT_SRC:
|
||||
return importSrcModalBody();
|
||||
case MODAL.NEW:
|
||||
return newModalBody();
|
||||
case MODAL.RENAME:
|
||||
@ -1560,13 +1801,14 @@ export default function ControlPanel({
|
||||
closeOnEsc={true}
|
||||
okText={getOkText()}
|
||||
okButtonProps={{
|
||||
disabled:
|
||||
disabled: (error && error.type && error.type === STATUS.ERROR) ||
|
||||
(visible === MODAL.IMPORT &&
|
||||
(error.type === STATUS.ERROR || !data)) ||
|
||||
((visible === MODAL.IMG || visible === MODAL.CODE) &&
|
||||
!exportData.data) ||
|
||||
(visible === MODAL.RENAME && title === "") ||
|
||||
(visible === MODAL.SAVEAS && saveAsTitle === ""),
|
||||
(visible === MODAL.SAVEAS && saveAsTitle === "") ||
|
||||
(visible === MODAL.IMPORT_SRC && data.src === ""),
|
||||
}}
|
||||
cancelText="Cancel"
|
||||
width={600}
|
||||
|
@ -19,8 +19,7 @@ function Table({ table, grab }) {
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div
|
||||
className={`border-2 ${
|
||||
isHovered ? "border-dashed border-blue-500" : "border-zinc-300"
|
||||
className={`border-2 ${isHovered ? "border-dashed border-blue-500" : "border-zinc-300"
|
||||
} select-none rounded-lg w-full bg-zinc-100 text-zinc-800`}
|
||||
>
|
||||
<div
|
||||
@ -33,8 +32,7 @@ function Table({ table, grab }) {
|
||||
{table.fields.map((e, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`${
|
||||
i === table.fields.length - 1 ? "" : "border-b border-gray-400"
|
||||
className={`${i === table.fields.length - 1 ? "" : "border-b border-gray-400"
|
||||
} h-[36px] px-2 py-1 flex justify-between`}
|
||||
onMouseEnter={() => setHoveredField(i)}
|
||||
onMouseLeave={() => setHoveredField(-1)}
|
||||
@ -139,7 +137,7 @@ function Relationship({ relationship }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function Canvas({ diagram }) {
|
||||
export default function SimpleCanvas({ diagram, zoom }) {
|
||||
const [tables, setTables] = useState(diagram.tables);
|
||||
const [relationships, setRelationships] = useState(diagram.relationships);
|
||||
const [dragging, setDragging] = useState(-1);
|
||||
@ -233,12 +231,17 @@ export default function Canvas({ diagram }) {
|
||||
height="100%"
|
||||
fill="url(#pattern-circles)"
|
||||
></rect>
|
||||
<g style={{
|
||||
transform: `scale(${zoom})`,
|
||||
transformOrigin: "top left",
|
||||
}}>
|
||||
{tables.map((t, i) => (
|
||||
<Table key={i} table={t} grab={(e) => grabTable(e, i)} />
|
||||
))}
|
||||
{relationships.map((r, i) => (
|
||||
<Relationship key={i} relationship={r} />
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const xOffset = window.innerWidth * 0.57 * 0.09;
|
||||
const xOffset = window.innerWidth * 0.65;
|
||||
export const diagram = {
|
||||
tables: [
|
||||
{
|
@ -1,7 +1,10 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { IconCrossStroked } from "@douyinfe/semi-icons";
|
||||
import SimpleCanvas from "../components/SimpleCanvas"
|
||||
import Navbar from "../components/Navbar";
|
||||
import { diagram } from "../data/heroDiagram"
|
||||
import Reveal from "../animations/Reveal";
|
||||
|
||||
export default function LandingPage() {
|
||||
const [showSurvey, setShowSurvey] = useState(true);
|
||||
@ -14,8 +17,9 @@ export default function LandingPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col h-screen">
|
||||
{showSurvey && (
|
||||
<div className="text-white font-semibold py-1.5 px-4 text-sm text-center bg-gradient-to-r from-slate-700 from-10% via-slate-500 via-30% to-90% to-slate-700">
|
||||
<div className="text-white font-semibold py-1.5 px-4 text-sm text-center bg-gradient-to-r from-slate-700 from-10% via-slate-500 to-slate-700">
|
||||
<Link to="/survey" className="hover:underline">
|
||||
Help us improve! Share your feedback.
|
||||
</Link>
|
||||
@ -27,6 +31,37 @@ export default function LandingPage() {
|
||||
</div>
|
||||
)}
|
||||
<Navbar />
|
||||
<div className="flex-1 flex-col relative">
|
||||
<div className="h-full">
|
||||
<SimpleCanvas diagram={diagram} zoom={0.85} />
|
||||
</div>
|
||||
<div className="absolute left-0 top-[50%] translate-y-[-50%] p-8 text-zinc-800 text-center">
|
||||
<Reveal>
|
||||
<div className="text-4xl font-bold tracking-wide">
|
||||
<h1 className="py-1 bg-gradient-to-r from-slate-700 from-10% via-slate-500 to-slate-700 inline-block text-transparent bg-clip-text">
|
||||
Draw, Copy, and Paste
|
||||
</h1>
|
||||
</div>
|
||||
<div className="text-lg font-semibold mt-3">
|
||||
Free, simple, and intuitive database design tool and SQL generator.
|
||||
</div>
|
||||
</Reveal>
|
||||
<div className="mt-4 flex gap-4 justify-center font-semibold">
|
||||
<button className="bg-white shadow-lg px-9 py-2 rounded border border-zinc-200 hover:bg-zinc-100 transition-all duration-200" onClick={() => document
|
||||
.getElementById("learn-more")
|
||||
.scrollIntoView({ behavior: "smooth" })}>
|
||||
Learn more
|
||||
</button>
|
||||
<Link to="/editor" className="bg-slate-700 text-white px-4 py-2 rounded shadow-lg hover:bg-slate-600 transition-all duration-200">
|
||||
Try it for yourself
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="learn-more">
|
||||
more stuff
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -95,8 +95,8 @@ function getTypeString(field, dbms = "mysql") {
|
||||
if (isSized(field.type)) {
|
||||
return `${field.type}(${field.size})`;
|
||||
}
|
||||
if (hasPrecision(field.type) && field.size !== "") {
|
||||
return `${field.type}${field.size}`;
|
||||
if (hasPrecision(field.type)) {
|
||||
return `${field.type}${field.size ? `(${field.size})` : ""}`;
|
||||
}
|
||||
if (field.type === "SET" || field.type === "ENUM") {
|
||||
return `${field.type}(${field.values.map((v) => `"${v}"`).join(", ")})`;
|
||||
@ -161,26 +161,20 @@ function jsonToMySQL(obj) {
|
||||
return `${obj.tables
|
||||
.map(
|
||||
(table) =>
|
||||
`${
|
||||
table.comment === "" ? "" : `/* ${table.comment} */\n`
|
||||
`${table.comment === "" ? "" : `/* ${table.comment} */\n`
|
||||
}CREATE TABLE \`${table.name}\` (\n${table.fields
|
||||
.map(
|
||||
(field) =>
|
||||
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${
|
||||
field.name
|
||||
}\` ${getTypeString(field)}${field.notNull ? " NOT NULL" : ""}${
|
||||
field.increment ? " AUTO_INCREMENT" : ""
|
||||
}${field.unique ? " UNIQUE" : ""}${
|
||||
field.default !== ""
|
||||
? ` DEFAULT ${
|
||||
hasQuotes(field.type) &&
|
||||
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${field.name
|
||||
}\` ${getTypeString(field)}${field.notNull ? " NOT NULL" : ""}${field.increment ? " AUTO_INCREMENT" : ""
|
||||
}${field.unique ? " UNIQUE" : ""}${field.default !== ""
|
||||
? ` DEFAULT ${hasQuotes(field.type) &&
|
||||
field.default.toLowerCase() !== "null"
|
||||
? `"${field.default}"`
|
||||
: `${field.default}`
|
||||
}`
|
||||
: ""
|
||||
}${
|
||||
field.check === "" || !hasCheck(field.type)
|
||||
}${field.check === "" || !hasCheck(field.type)
|
||||
? !sqlDataTypes.includes(field.type)
|
||||
? ` CHECK(\n\t\tJSON_SCHEMA_VALID("${generateSchema(
|
||||
obj.types.find(
|
||||
@ -191,19 +185,16 @@ function jsonToMySQL(obj) {
|
||||
: ` CHECK(${field.check})`
|
||||
}`
|
||||
)
|
||||
.join(",\n")}${
|
||||
table.fields.filter((f) => f.primary).length > 0
|
||||
.join(",\n")}${table.fields.filter((f) => f.primary).length > 0
|
||||
? `,\n\tPRIMARY KEY(${table.fields
|
||||
.filter((f) => f.primary)
|
||||
.map((f) => `\`${f.name}\``)
|
||||
.join(", ")})`
|
||||
: ""
|
||||
}\n);\n${
|
||||
table.indices.length > 0
|
||||
}\n);\n${table.indices.length > 0
|
||||
? `\n${table.indices.map(
|
||||
(i) =>
|
||||
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${
|
||||
i.name
|
||||
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${i.name
|
||||
}\`\nON \`${table.name}\` (${i.fields
|
||||
.map((f) => `\`${f}\``)
|
||||
.join(", ")});`
|
||||
@ -214,12 +205,9 @@ function jsonToMySQL(obj) {
|
||||
.join("\n")}\n${obj.references
|
||||
.map(
|
||||
(r) =>
|
||||
`ALTER TABLE \`${
|
||||
obj.tables[r.startTableId].name
|
||||
}\`\nADD FOREIGN KEY(\`${
|
||||
obj.tables[r.startTableId].fields[r.startFieldId].name
|
||||
}\`) REFERENCES \`${obj.tables[r.endTableId].name}\`(\`${
|
||||
obj.tables[r.endTableId].fields[r.endFieldId].name
|
||||
`ALTER TABLE \`${obj.tables[r.startTableId].name
|
||||
}\`\nADD FOREIGN KEY(\`${obj.tables[r.startTableId].fields[r.startFieldId].name
|
||||
}\`) REFERENCES \`${obj.tables[r.endTableId].name}\`(\`${obj.tables[r.endTableId].fields[r.endFieldId].name
|
||||
}\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`
|
||||
)
|
||||
.join("\n")}`;
|
||||
@ -238,15 +226,13 @@ function jsonToPostgreSQL(obj) {
|
||||
if (typeStatements.length > 0) {
|
||||
return (
|
||||
typeStatements.join("") +
|
||||
`${
|
||||
type.comment === "" ? "" : `/**\n${type.comment}\n*/\n`
|
||||
`${type.comment === "" ? "" : `/**\n${type.comment}\n*/\n`
|
||||
}CREATE TYPE ${type.name} AS (\n${type.fields
|
||||
.map((f) => `\t${f.name} ${getTypeString(f, "postgres")}`)
|
||||
.join("\n")}\n);`
|
||||
);
|
||||
} else {
|
||||
return `${
|
||||
type.comment === "" ? "" : `/**\n${type.comment}\n*/\n`
|
||||
return `${type.comment === "" ? "" : `/**\n${type.comment}\n*/\n`
|
||||
}CREATE TYPE ${type.name} AS (\n${type.fields
|
||||
.map((f) => `\t${f.name} ${getTypeString(f, "postgres")}`)
|
||||
.join("\n")}\n);`;
|
||||
@ -254,8 +240,7 @@ function jsonToPostgreSQL(obj) {
|
||||
})}\n${obj.tables
|
||||
.map(
|
||||
(table) =>
|
||||
`${table.comment === "" ? "" : `/**\n${table.comment}\n*/\n`}${
|
||||
table.fields.filter((f) => f.type === "ENUM" || f.type === "SET")
|
||||
`${table.comment === "" ? "" : `/**\n${table.comment}\n*/\n`}${table.fields.filter((f) => f.type === "ENUM" || f.type === "SET")
|
||||
.length > 0
|
||||
? `${table.fields
|
||||
.filter((f) => f.type === "ENUM" || f.type === "SET")
|
||||
@ -269,38 +254,30 @@ function jsonToPostgreSQL(obj) {
|
||||
}CREATE TABLE "${table.name}" (\n${table.fields
|
||||
.map(
|
||||
(field) =>
|
||||
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${
|
||||
field.name
|
||||
}" ${getTypeString(field, "postgres")}${
|
||||
field.notNull ? " NOT NULL" : ""
|
||||
}${
|
||||
field.default !== ""
|
||||
? ` DEFAULT ${
|
||||
hasQuotes(field.type) &&
|
||||
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${field.name
|
||||
}" ${getTypeString(field, "postgres")}${field.notNull ? " NOT NULL" : ""
|
||||
}${field.default !== ""
|
||||
? ` DEFAULT ${hasQuotes(field.type) &&
|
||||
field.default.toLowerCase() !== "null"
|
||||
? `'${field.default}'`
|
||||
: `${field.default}`
|
||||
}`
|
||||
: ""
|
||||
}${
|
||||
field.check === "" || !hasCheck(field.type)
|
||||
}${field.check === "" || !hasCheck(field.type)
|
||||
? ""
|
||||
: ` CHECK(${field.check})`
|
||||
}`
|
||||
)
|
||||
.join(",\n")}${
|
||||
table.fields.filter((f) => f.primary).length > 0
|
||||
.join(",\n")}${table.fields.filter((f) => f.primary).length > 0
|
||||
? `,\n\tPRIMARY KEY(${table.fields
|
||||
.filter((f) => f.primary)
|
||||
.map((f) => `"${f.name}"`)
|
||||
.join(", ")})`
|
||||
: ""
|
||||
}\n);\n${
|
||||
table.indices.length > 0
|
||||
}\n);\n${table.indices.length > 0
|
||||
? `${table.indices.map(
|
||||
(i) =>
|
||||
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX "${
|
||||
i.name
|
||||
`\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX "${i.name
|
||||
}"\nON "${table.name}" (${i.fields
|
||||
.map((f) => `"${f}"`)
|
||||
.join(", ")});`
|
||||
@ -311,10 +288,8 @@ function jsonToPostgreSQL(obj) {
|
||||
.join("\n")}\n${obj.references
|
||||
.map(
|
||||
(r) =>
|
||||
`ALTER TABLE "${obj.tables[r.startTableId].name}"\nADD FOREIGN KEY("${
|
||||
obj.tables[r.startTableId].fields[r.startFieldId].name
|
||||
}") REFERENCES "${obj.tables[r.endTableId].name}"("${
|
||||
obj.tables[r.endTableId].fields[r.endFieldId].name
|
||||
`ALTER TABLE "${obj.tables[r.startTableId].name}"\nADD FOREIGN KEY("${obj.tables[r.startTableId].fields[r.startFieldId].name
|
||||
}") REFERENCES "${obj.tables[r.endTableId].name}"("${obj.tables[r.endTableId].fields[r.endFieldId].name
|
||||
}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`
|
||||
)
|
||||
.join("\n")}`;
|
||||
@ -325,13 +300,13 @@ function arrayIsEqual(arr1, arr2) {
|
||||
}
|
||||
|
||||
function isSized(type) {
|
||||
return ["CHAR", "VARCHAR", "BINARY", "VARBINARY", "TEXT", "FLOAT"].includes(
|
||||
return ["CHAR", "VARCHAR", "BINARY", "VARBINARY", "TEXT"].includes(
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
function hasPrecision(type) {
|
||||
return ["DOUBLE", "NUMERIC", "DECIMAL"].includes(type);
|
||||
return ["DOUBLE", "NUMERIC", "DECIMAL", "FLOAT"].includes(type);
|
||||
}
|
||||
|
||||
function hasCheck(type) {
|
||||
@ -569,10 +544,8 @@ function validateDiagram(diagram) {
|
||||
diagram.tables[r.startTableId].fields[r.startFieldId].type !==
|
||||
diagram.tables[r.endTableId].fields[r.endFieldId].type
|
||||
) {
|
||||
issues.push(`Referencing column "${
|
||||
diagram.tables[r.endTableId].fields[r.endFieldId].name
|
||||
}" and referenced column "${
|
||||
diagram.tables[r.startTableId].fields[r.startFieldId].name
|
||||
issues.push(`Referencing column "${diagram.tables[r.endTableId].fields[r.endFieldId].name
|
||||
}" and referenced column "${diagram.tables[r.startTableId].fields[r.startFieldId].name
|
||||
}" are incompatible.
|
||||
`);
|
||||
}
|
||||
@ -632,80 +605,55 @@ const calcPath = (x1, x2, y1, y2, startFieldId, endFieldId, zoom = 1) => {
|
||||
|
||||
if (y1 <= y2) {
|
||||
if (x1 + tableWidth <= x2) {
|
||||
return `M ${x1 + tableWidth - offsetX * 2} ${y1} L ${
|
||||
midX - r
|
||||
} ${y1} A ${r} ${r} 0 0 1 ${midX} ${y1 + r} L ${midX} ${
|
||||
y2 - r
|
||||
return `M ${x1 + tableWidth - offsetX * 2} ${y1} L ${midX - r
|
||||
} ${y1} A ${r} ${r} 0 0 1 ${midX} ${y1 + r} L ${midX} ${y2 - r
|
||||
} A ${r} ${r} 0 0 0 ${midX + r} ${y2} L ${endX} ${y2}`;
|
||||
} else if (x2 <= x1 + tableWidth && x1 <= x2) {
|
||||
return `M ${x1 + tableWidth - 2 * offsetX} ${y1} L ${
|
||||
x2 + tableWidth
|
||||
} ${y1} A ${r} ${r} 0 0 1 ${x2 + tableWidth + r} ${y1 + r} L ${
|
||||
x2 + tableWidth + r
|
||||
} ${y2 - r} A ${r} ${r} 0 0 1 ${x2 + tableWidth} ${y2} L ${
|
||||
x2 + tableWidth - 2 * offsetX
|
||||
return `M ${x1 + tableWidth - 2 * offsetX} ${y1} L ${x2 + tableWidth
|
||||
} ${y1} A ${r} ${r} 0 0 1 ${x2 + tableWidth + r} ${y1 + r} L ${x2 + tableWidth + r
|
||||
} ${y2 - r} A ${r} ${r} 0 0 1 ${x2 + tableWidth} ${y2} L ${x2 + tableWidth - 2 * offsetX
|
||||
} ${y2}`;
|
||||
} else if (x2 + tableWidth >= x1 && x2 + tableWidth <= x1 + tableWidth) {
|
||||
return `M ${x1} ${y1} L ${x2 - r} ${y1} A ${r} ${r} 0 0 0 ${x2 - r - r} ${
|
||||
y1 + r
|
||||
} L ${x2 - r - r} ${y2 - r} A ${r} ${r} 0 0 0 ${
|
||||
x2 - r
|
||||
return `M ${x1} ${y1} L ${x2 - r} ${y1} A ${r} ${r} 0 0 0 ${x2 - r - r} ${y1 + r
|
||||
} L ${x2 - r - r} ${y2 - r} A ${r} ${r} 0 0 0 ${x2 - r
|
||||
} ${y2} L ${x2} ${y2}`;
|
||||
} else {
|
||||
return `M ${x1} ${y1} L ${midX + r} ${y1} A ${r} ${r} 0 0 0 ${midX} ${
|
||||
y1 + r
|
||||
} L ${midX} ${y2 - r} A ${r} ${r} 0 0 1 ${
|
||||
midX - r
|
||||
return `M ${x1} ${y1} L ${midX + r} ${y1} A ${r} ${r} 0 0 0 ${midX} ${y1 + r
|
||||
} L ${midX} ${y2 - r} A ${r} ${r} 0 0 1 ${midX - r
|
||||
} ${y2} L ${endX} ${y2}`;
|
||||
}
|
||||
} else {
|
||||
if (x1 + tableWidth <= x2) {
|
||||
return `M ${x1 + tableWidth - offsetX * 2} ${y1} L ${
|
||||
midX - r
|
||||
} ${y1} A ${r} ${r} 0 0 0 ${midX} ${y1 - r} L ${midX} ${
|
||||
y2 + r
|
||||
return `M ${x1 + tableWidth - offsetX * 2} ${y1} L ${midX - r
|
||||
} ${y1} A ${r} ${r} 0 0 0 ${midX} ${y1 - r} L ${midX} ${y2 + r
|
||||
} A ${r} ${r} 0 0 1 ${midX + r} ${y2} L ${endX} ${y2}`;
|
||||
} else if (x1 + tableWidth >= x2 && x1 + tableWidth <= x2 + tableWidth) {
|
||||
// this for the overlap remember
|
||||
if (startTableY < y2) {
|
||||
return `M ${x1} ${y1} L ${x1 - r - r} ${y1} A ${r} ${r} 0 0 1 ${
|
||||
x1 - r - r - r
|
||||
} ${y1 - r} L ${x1 - r - r - r} ${y2 + r} A ${r} ${r} 0 0 1 ${
|
||||
x1 - r - r
|
||||
return `M ${x1} ${y1} L ${x1 - r - r} ${y1} A ${r} ${r} 0 0 1 ${x1 - r - r - r
|
||||
} ${y1 - r} L ${x1 - r - r - r} ${y2 + r} A ${r} ${r} 0 0 1 ${x1 - r - r
|
||||
} ${y2} L ${x1 - r - 4} ${y2}`;
|
||||
}
|
||||
return `M ${x1} ${y1} L ${x1 - r - r} ${y1} A ${r} ${r} 0 0 1 ${
|
||||
x1 - r - r - r
|
||||
} ${y1 - r} L ${x1 - r - r - r} ${y2 + r} A ${r} ${r} 0 0 1 ${
|
||||
x1 - r - r
|
||||
return `M ${x1} ${y1} L ${x1 - r - r} ${y1} A ${r} ${r} 0 0 1 ${x1 - r - r - r
|
||||
} ${y1 - r} L ${x1 - r - r - r} ${y2 + r} A ${r} ${r} 0 0 1 ${x1 - r - r
|
||||
} ${y2} L ${endX} ${y2}`;
|
||||
} else if (x1 >= x2 && x1 <= x2 + tableWidth) {
|
||||
// this for the overlap remember
|
||||
if (startTableY < y2) {
|
||||
return `M ${x1 + tableWidth - 2 * offsetX} ${y1} L ${
|
||||
x1 + tableWidth - 2 * offsetX + r
|
||||
} ${y1} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r + r} ${
|
||||
y1 - r
|
||||
} L ${x1 + tableWidth - 2 * offsetX + r + r} ${
|
||||
y2 + r
|
||||
} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r} ${y2} L ${
|
||||
x1 + tableWidth - 16
|
||||
return `M ${x1 + tableWidth - 2 * offsetX} ${y1} L ${x1 + tableWidth - 2 * offsetX + r
|
||||
} ${y1} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r + r} ${y1 - r
|
||||
} L ${x1 + tableWidth - 2 * offsetX + r + r} ${y2 + r
|
||||
} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r} ${y2} L ${x1 + tableWidth - 16
|
||||
} ${y2}`;
|
||||
}
|
||||
return `M ${x1 + tableWidth - 2 * offsetX} ${y1} L ${
|
||||
x1 + tableWidth - 2 * offsetX + r
|
||||
} ${y1} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r + r} ${
|
||||
y1 - r
|
||||
} L ${x1 + tableWidth - 2 * offsetX + r + r} ${
|
||||
y2 + r
|
||||
} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r} ${y2} L ${
|
||||
x2 + tableWidth - 2 * offsetX
|
||||
return `M ${x1 + tableWidth - 2 * offsetX} ${y1} L ${x1 + tableWidth - 2 * offsetX + r
|
||||
} ${y1} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r + r} ${y1 - r
|
||||
} L ${x1 + tableWidth - 2 * offsetX + r + r} ${y2 + r
|
||||
} A ${r} ${r} 0 0 0 ${x1 + tableWidth - 2 * offsetX + r} ${y2} L ${x2 + tableWidth - 2 * offsetX
|
||||
} ${y2}`;
|
||||
} else {
|
||||
return `M ${x1} ${y1} L ${midX + r} ${y1} A ${r} ${r} 0 0 1 ${midX} ${
|
||||
y1 - r
|
||||
} L ${midX} ${y2 + r} A ${r} ${r} 0 0 0 ${
|
||||
midX - r
|
||||
return `M ${x1} ${y1} L ${midX + r} ${y1} A ${r} ${r} 0 0 1 ${midX} ${y1 - r
|
||||
} L ${midX} ${y2 + r} A ${r} ${r} 0 0 0 ${midX - r
|
||||
} ${y2} L ${endX} ${y2}`;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user