From 3c45476a269b8d89010d4c495ac45d1e43ecce66 Mon Sep 17 00:00:00 2001 From: pvhieu Date: Tue, 21 May 2024 23:02:11 +0700 Subject: [PATCH] Refactor component structure of modal and add export excel feature - Refactor the modal structure - Allow renaming of files when exporting to PDF and DrawDB. - Add a feature to exclude exporting relationships when exporting to SQL. - Add an export to Excel feature --- package-lock.json | 826 +++++++++++++++++- package.json | 1 + src/components/EditorHeader/ControlPanel.jsx | 214 +---- .../EditorHeader/Modal/BaseModal.jsx | 26 + .../EditorHeader/Modal/ExportDrawDB.jsx | 49 ++ .../EditorHeader/Modal/ExportExcel.jsx | 91 ++ .../EditorHeader/Modal/ExportImage.jsx | 79 ++ .../EditorHeader/Modal/ExportJson.jsx | 68 ++ .../EditorHeader/Modal/ExportModal.jsx | 35 + .../EditorHeader/Modal/ExportPdf.jsx | 50 ++ .../EditorHeader/Modal/ExportSql.jsx | 104 +++ .../EditorHeader/Modal/ImportDiagram.jsx | 181 ++-- .../EditorHeader/Modal/ImportSource.jsx | 122 ++- .../EditorHeader/Modal/Language.jsx | 49 +- src/components/EditorHeader/Modal/Modal.jsx | 341 -------- src/components/EditorHeader/Modal/New.jsx | 82 +- src/components/EditorHeader/Modal/Open.jsx | 70 +- src/components/EditorHeader/Modal/Rename.jsx | 32 +- src/components/EditorHeader/Modal/SaveAs.jsx | 30 + .../EditorHeader/Modal/SetTableWidth.jsx | 27 +- src/components/EditorHeader/ModalManager.jsx | 56 ++ src/data/constants.js | 32 +- src/i18n/locales/da.js | 2 +- src/i18n/locales/de.js | 2 +- src/i18n/locales/en.js | 17 +- src/i18n/locales/es.js | 2 +- src/i18n/locales/fa.js | 7 +- src/i18n/locales/pt-br.js | 2 +- src/i18n/locales/vi.js | 25 +- src/utils/modalTitles.js | 49 -- src/utils/toDocument.js | 176 ++++ src/utils/toSQL.js | 204 +++-- 32 files changed, 2186 insertions(+), 865 deletions(-) create mode 100644 src/components/EditorHeader/Modal/BaseModal.jsx create mode 100644 src/components/EditorHeader/Modal/ExportDrawDB.jsx create mode 100644 src/components/EditorHeader/Modal/ExportExcel.jsx create mode 100644 src/components/EditorHeader/Modal/ExportImage.jsx create mode 100644 src/components/EditorHeader/Modal/ExportJson.jsx create mode 100644 src/components/EditorHeader/Modal/ExportModal.jsx create mode 100644 src/components/EditorHeader/Modal/ExportPdf.jsx create mode 100644 src/components/EditorHeader/Modal/ExportSql.jsx delete mode 100644 src/components/EditorHeader/Modal/Modal.jsx create mode 100644 src/components/EditorHeader/Modal/SaveAs.jsx create mode 100644 src/components/EditorHeader/ModalManager.jsx delete mode 100644 src/utils/modalTitles.js create mode 100644 src/utils/toDocument.js diff --git a/package-lock.json b/package-lock.json index 54398b7a..ab8cdd8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "axios": "^1.6.2", "dexie": "^3.2.4", "dexie-react-hooks": "^1.1.7", + "exceljs": "^4.4.0", "file-saver": "^2.0.5", "framer-motion": "^10.18.0", "html-to-image": "^1.11.11", @@ -1116,6 +1117,43 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -1718,6 +1756,17 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/pegjs": { "version": "0.10.6", "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz", @@ -1971,6 +2020,75 @@ "node": ">= 8" } }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2085,6 +2203,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/async-validator": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz", @@ -2188,6 +2311,25 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bezier-easing": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", @@ -2201,6 +2343,18 @@ "node": ">=0.6" } }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2210,6 +2364,21 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2274,6 +2443,53 @@ "node": ">= 0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -2350,6 +2566,17 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "optional": true }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2465,6 +2692,20 @@ "node": ">= 6" } }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/compute-scroll-into-view": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", @@ -2503,6 +2744,34 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -2571,6 +2840,11 @@ "date-fns": ">=2.0.0" } }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2680,12 +2954,60 @@ "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", "optional": true }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.614", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz", "integrity": "sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ==", "dev": true }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es-abstract": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", @@ -3170,6 +3492,37 @@ "node": ">=0.10.0" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3372,6 +3725,11 @@ } } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3391,6 +3749,31 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3531,6 +3914,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -3670,6 +4058,25 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -3679,6 +4086,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4201,6 +4613,49 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4210,6 +4665,49 @@ "json-buffer": "3.0.1" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4248,6 +4746,14 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -4263,6 +4769,11 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4283,12 +4794,77 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4366,6 +4942,25 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4429,7 +5024,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4611,6 +5205,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4892,6 +5491,11 @@ "node": ">=6" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5121,6 +5725,46 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5310,6 +5954,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -5324,6 +5987,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -5382,6 +6056,11 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5434,6 +6113,14 @@ "node": ">=0.1.14" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -5657,6 +6344,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -5693,6 +6395,14 @@ "node": ">=0.8" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5714,6 +6424,14 @@ "node": ">=8.0" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "engines": { + "node": "*" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -5829,6 +6547,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -5885,8 +6660,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utility-types": { "version": "3.10.0", @@ -5905,6 +6679,14 @@ "base64-arraybuffer": "^1.0.2" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", @@ -6069,6 +6851,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -6112,6 +6899,39 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } } } } diff --git a/package.json b/package.json index cff6fa59..f1a03bc5 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "axios": "^1.6.2", "dexie": "^3.2.4", "dexie-react-hooks": "^1.1.7", + "exceljs": "^4.4.0", "file-saver": "^2.0.5", "framer-motion": "^10.18.0", "html-to-image": "^1.11.11", diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index 1e067918..66991423 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -21,15 +21,7 @@ import { Toast, Popconfirm, } from "@douyinfe/semi-ui"; -import { toPng, toJpeg, toSvg } from "html-to-image"; -import { saveAs } from "file-saver"; -import { - jsonToMySQL, - jsonToPostgreSQL, - jsonToSQLite, - jsonToMariaDB, - jsonToSQLServer, -} from "../../utils/toSQL"; +import { toPng } from "html-to-image"; import { ObjectType, Action, @@ -38,7 +30,6 @@ import { MODAL, SIDESHEET, } from "../../data/constants"; -import jsPDF from "jspdf"; import { useHotkeys } from "react-hotkeys-hook"; import { Validator } from "jsonschema"; import { areaSchema, noteSchema, tableSchema } from "../../data/schemas"; @@ -60,8 +51,8 @@ import useSaveState from "../../hooks/useSaveState"; import { IconAddArea, IconAddNote, IconAddTable } from "../../icons"; import LayoutDropdown from "./LayoutDropdown"; import Sidesheet from "./SideSheet/Sidesheet"; -import Modal from "./Modal/Modal"; import { useTranslation } from "react-i18next"; +import ModalManager from "./ModalManager"; export default function ControlPanel({ diagramId, @@ -72,13 +63,7 @@ export default function ControlPanel({ }) { const [modal, setModal] = useState(MODAL.NONE); const [sidesheet, setSidesheet] = useState(SIDESHEET.NONE); - const [prevTitle, setPrevTitle] = useState(title); const [showEditName, setShowEditName] = useState(false); - const [exportData, setExportData] = useState({ - data: null, - filename: `${title}_${new Date().toISOString()}`, - extension: "", - }); const { saveState, setSaveState } = useSaveState(); const { layout, setLayout } = useLayout(); const { settings, setSettings } = useSettings(); @@ -452,7 +437,7 @@ export default function ControlPanel({ } }; - const fileImport = () => setModal(MODAL.IMPORT); + const importDiagram = () => setModal(MODAL.IMPORT_DIAGRAM); const viewGrid = () => setSettings((prev) => ({ ...prev, showGrid: !prev.showGrid })); const zoomIn = () => @@ -704,7 +689,6 @@ export default function ControlPanel({ rename: { function: () => { setModal(MODAL.RENAME); - setPrevTitle(title); }, }, delete_diagram: { @@ -730,7 +714,7 @@ export default function ControlPanel({ }, }, import_diagram: { - function: fileImport, + function: importDiagram, shortcut: "Ctrl+I", }, import_from_source: { @@ -739,188 +723,33 @@ export default function ControlPanel({ export_as: { children: [ { - PNG: () => { - toPng(document.getElementById("canvas")).then(function (dataUrl) { - setExportData((prev) => ({ - ...prev, - data: dataUrl, - extension: "png", - })); - }); - setModal(MODAL.IMG); - }, - }, - { - JPEG: () => { - toJpeg(document.getElementById("canvas"), { quality: 0.95 }).then( - function (dataUrl) { - setExportData((prev) => ({ - ...prev, - data: dataUrl, - extension: "jpeg", - })); - }, - ); - setModal(MODAL.IMG); - }, - }, - { - JSON: () => { - setModal(MODAL.CODE); - const result = JSON.stringify( - { - tables: tables, - relationships: relationships, - notes: notes, - subjectAreas: areas, - types: types, - title: title, - }, - null, - 2, - ); - setExportData((prev) => ({ - ...prev, - data: result, - extension: "json", - })); - }, - }, - { - SVG: () => { - const filter = (node) => node.tagName !== "i"; - toSvg(document.getElementById("canvas"), { filter: filter }).then( - function (dataUrl) { - setExportData((prev) => ({ - ...prev, - data: dataUrl, - extension: "svg", - })); - }, - ); - setModal(MODAL.IMG); + IMAGE: () => { + setModal(MODAL.EXPORT_IMG); }, }, { 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`); - }); + setModal(MODAL.EXPORT_PDF); }, }, { - DRAWDB: () => { - const result = JSON.stringify( - { - author: "Unnamed", - title: title, - date: new Date().toISOString(), - tables: tables, - relationships: relationships, - notes: notes, - subjectAreas: areas, - types: types, - }, - null, - 2, - ); - const blob = new Blob([result], { - type: "text/plain;charset=utf-8", - }); - saveAs(blob, `${exportData.filename}.ddb`); + SQL: () => { + setModal(MODAL.EXPORT_SQL); }, }, - ], - function: () => {}, - }, - export_source: { - children: [ { - MySQL: () => { - setModal(MODAL.CODE); - const src = jsonToMySQL({ - tables: tables, - references: relationships, - types: types, - }); - setExportData((prev) => ({ - ...prev, - data: src, - extension: "sql", - })); + EXCEL: async () => { + setModal(MODAL.EXPORT_EXCEL); }, }, { - PostgreSQL: () => { - setModal(MODAL.CODE); - const src = jsonToPostgreSQL({ - tables: tables, - references: relationships, - types: types, - }); - setExportData((prev) => ({ - ...prev, - data: src, - extension: "sql", - })); - }, - }, - { - SQLite: () => { - setModal(MODAL.CODE); - const src = jsonToSQLite({ - tables: tables, - references: relationships, - types: types, - }); - setExportData((prev) => ({ - ...prev, - data: src, - extension: "sql", - })); - }, - }, - { - MariaDB: () => { - setModal(MODAL.CODE); - const src = jsonToMariaDB({ - tables: tables, - references: relationships, - types: types, - }); - setExportData((prev) => ({ - ...prev, - data: src, - extension: "sql", - })); + JSON: () => { + setModal(MODAL.EXPORT_JSON); }, }, { - MSSQL: () => { - setModal(MODAL.CODE); - const src = jsonToSQLServer({ - tables: tables, - references: relationships, - types: types, - }); - setExportData((prev) => ({ - ...prev, - data: src, - extension: "sql", - })); + DRAWDB: () => { + setModal(MODAL.EXPORT_DIAGRAM); }, }, ], @@ -1166,7 +995,7 @@ export default function ControlPanel({ }, }; - useHotkeys("ctrl+i, meta+i", fileImport, { preventDefault: true }); + useHotkeys("ctrl+i, meta+i", importDiagram, { preventDefault: true }); useHotkeys("ctrl+z, meta+z", undo, { preventDefault: true }); useHotkeys("ctrl+y, meta+y", redo, { preventDefault: true }); useHotkeys("ctrl+s, meta+s", save, { preventDefault: true }); @@ -1196,20 +1025,21 @@ export default function ControlPanel({ }); useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true }); + const hideModal = () => { + setModal(MODAL.NONE); + }; + return ( <> {layout.header && header()} {layout.toolbar && toolbar()} - + {children} + + ); +}; diff --git a/src/components/EditorHeader/Modal/ExportDrawDB.jsx b/src/components/EditorHeader/Modal/ExportDrawDB.jsx new file mode 100644 index 00000000..73f25355 --- /dev/null +++ b/src/components/EditorHeader/Modal/ExportDrawDB.jsx @@ -0,0 +1,49 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + useAreas, + useNotes, + useTables, + useTypes, +} from "../../../hooks"; +import ExportModal from "./ExportModal"; + +export default function ExportDrawDB({ title, hideModal }) { + const { t } = useTranslation(); + const { tables, relationships } = useTables(); + const { notes } = useNotes(); + const { areas } = useAreas(); + const { types } = useTypes(); + + const rawData = JSON.stringify( + { + author: "Unnamed", + title: title, + date: new Date().toISOString(), + tables: tables, + relationships: relationships, + notes: notes, + subjectAreas: areas, + types: types, + }, + null, + 2, + ); + const [exportData, setExportData] = useState({ + data: new Blob( + [rawData], + { type: "text/plain;charset=utf-8" } + ), + filename: `${title}_${new Date().toISOString()}`, + extension: "ddb", + }); + + return ( + + ) +} diff --git a/src/components/EditorHeader/Modal/ExportExcel.jsx b/src/components/EditorHeader/Modal/ExportExcel.jsx new file mode 100644 index 00000000..0a857e97 --- /dev/null +++ b/src/components/EditorHeader/Modal/ExportExcel.jsx @@ -0,0 +1,91 @@ +import { Checkbox, Select } from "@douyinfe/semi-ui"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DATABASE_TYPES } from "../../../data/constants"; +import { jsonToExcel } from "../../../utils/toDocument"; +import { useTables } from "../../../hooks"; +import ExportModal from "./ExportModal"; + +export default function ExportExcel({ title, hideModal }) { + const { t } = useTranslation(); + const { tables, relationships } = useTables(); + + const [options, setOptions] = useState({ + format: DATABASE_TYPES[0], + hasRelationship: true, + hasDiagram: true, + }); + + const [exportData, setExportData] = useState({ + data: null, + filename: `${title}_${new Date().toISOString()}`, + extension: "xlsx", + }); + + const changeOptions = (options) => { + setOptions((prev) => ({ + ...prev, + ...options + })); + } + + const getData = useCallback(async (options) => { + const content = await jsonToExcel({ + tables: tables, + dbms: options.format.toLowerCase(), + relationships: options.hasRelationship ? relationships: [], + hasDiagram: options.hasDiagram, + }); + + return new Blob([content], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + }, [tables, relationships]); + + useEffect(() => { + getData(options).then( + (data) => setExportData((prev) => ({ + ...prev, + data: data, + })) + ) + }, [options, getData]); + + return ( + +
{t("format")}:
+ ({ + label: type.toUpperCase(), + value: type, + }))} + value={exportData.extension} + onChange={changeType} + /> +
+ {exportData.data + ? Diagram + : + } +
+
+ ) +} diff --git a/src/components/EditorHeader/Modal/ExportJson.jsx b/src/components/EditorHeader/Modal/ExportJson.jsx new file mode 100644 index 00000000..a57375e2 --- /dev/null +++ b/src/components/EditorHeader/Modal/ExportJson.jsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import CodeMirror from "@uiw/react-codemirror"; +import { sql } from "@codemirror/lang-sql"; +import { vscodeDark } from "@uiw/codemirror-theme-vscode"; +import { json } from "@codemirror/lang-json"; +import { githubLight } from "@uiw/codemirror-theme-github"; +import { + useAreas, + useNotes, + useTables, + useTypes, + useSettings +} from "../../../hooks"; +import ExportModal from "./ExportModal"; + +const languageExtension = { + sql: [sql()], + json: [json()], +}; + +export default function ExportJson({ title, hideModal }) { + const { t } = useTranslation(); + const { settings } = useSettings(); + const { tables, relationships } = useTables(); + const { notes } = useNotes(); + const { areas } = useAreas(); + const { types } = useTypes(); + + const rawData = JSON.stringify( + { + tables: tables, + relationships: relationships, + notes: notes, + subjectAreas: areas, + types: types, + title: title, + }, + null, + 2, + ); + const [exportData, setExportData] = useState({ + data: new Blob( + [rawData], + { type: "application/json" } + ), + filename: `${title}_${new Date().toISOString()}`, + extension: "json", + }); + + return ( + + {}} + editable={false} + theme={settings.mode === "dark" ? vscodeDark : githubLight} + /> + + ) +} diff --git a/src/components/EditorHeader/Modal/ExportModal.jsx b/src/components/EditorHeader/Modal/ExportModal.jsx new file mode 100644 index 00000000..ccd8918e --- /dev/null +++ b/src/components/EditorHeader/Modal/ExportModal.jsx @@ -0,0 +1,35 @@ +import { Input } from "@douyinfe/semi-ui"; +import { saveAs } from "file-saver"; +import { useTranslation } from "react-i18next"; +import BaseModal from "./BaseModal"; + +export default function ExportModal({children, modalTitle, exportData, setExportData, onCancel}) { + const { t } = useTranslation(); + + const onOk = () => { + saveAs(exportData.data, `${exportData.filename}.${exportData.extension}`) + } + + return ( + +
{t("filename")}:
+ {`.${exportData.extension}`}} + onChange={(value) => + setExportData((prev) => ({ ...prev, filename: value })) + } + field="filename" + /> + {children} +
+ ) +} diff --git a/src/components/EditorHeader/Modal/ExportPdf.jsx b/src/components/EditorHeader/Modal/ExportPdf.jsx new file mode 100644 index 00000000..80127427 --- /dev/null +++ b/src/components/EditorHeader/Modal/ExportPdf.jsx @@ -0,0 +1,50 @@ +import { useEffect, useState } from "react"; +import { toJpeg } from "html-to-image"; +import jsPDF from "jspdf"; +import { useTranslation } from "react-i18next"; +import ExportModal from "./ExportModal"; + +export default function ExportPdf({ title, hideModal }) { + const { t } = useTranslation(); + const [exportData, setExportData] = useState({ + data: null, + filename: `${title}_${new Date().toISOString()}`, + extension: "pdf", + }); + + const getData = async () => { + const canvas = document.getElementById("canvas"); + const dataUrl = await toJpeg(canvas); + const doc = new jsPDF("l", "px", [ + canvas.offsetWidth, + canvas.offsetHeight, + ]); + doc.addImage( + dataUrl, + "jpeg", + 0, + 0, + canvas.offsetWidth, + canvas.offsetHeight, + ); + return doc.output('blob'); + } + + useEffect(() => { + getData().then( + (data) => setExportData((prev) => ({ + ...prev, + data: data, + })) + ) + }, []); + + return ( + + ) +} diff --git a/src/components/EditorHeader/Modal/ExportSql.jsx b/src/components/EditorHeader/Modal/ExportSql.jsx new file mode 100644 index 00000000..d59766db --- /dev/null +++ b/src/components/EditorHeader/Modal/ExportSql.jsx @@ -0,0 +1,104 @@ +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import CodeMirror from "@uiw/react-codemirror"; +import { sql } from "@codemirror/lang-sql"; +import { vscodeDark } from "@uiw/codemirror-theme-vscode"; +import { json } from "@codemirror/lang-json"; +import { githubLight } from "@uiw/codemirror-theme-github"; +import { Checkbox, Select } from "@douyinfe/semi-ui"; +import { DATABASE_TYPES } from "../../../data/constants"; +import toSQL from "../../../utils/toSQL"; +import { + useTables, + useSettings, + useTypes, +} from "../../../hooks"; +import ExportModal from "./ExportModal"; + +const languageExtension = { + sql: [sql()], + json: [json()], +}; + +export default function ExportSql({ title, hideModal }) { + const { t } = useTranslation(); + const { settings } = useSettings(); + const { tables, relationships } = useTables(); + const { types } = useTypes(); + + const [options, setOptions] = useState({ + format: DATABASE_TYPES[0], + hasRelationship: true, + }); + + const [exportData, setExportData] = useState({ + data: null, + rawData: '', + filename: `${title}_${new Date().toISOString()}`, + extension: "sql", + }); + + const changeOptions = (options) => { + setOptions((prev) => ({ + ...prev, + ...options + })); + } + + const changeData = useCallback((options) => { + const rawData = toSQL[`jsonTo${options.format}`]({ + tables: tables, + references: options.hasRelationship ? relationships : [], + types: types, + }); + + setExportData((prev) => ({ + ...prev, + data: new Blob([rawData], { + type: "application/json", + }), + rawData: rawData + })) + }, [tables, relationships, types]) + + useEffect(() => { + changeData(options) + }, [options, changeData]); + + return ( + +
{t("format")}:
+ setSaveAsTitle(v)} - /> - ); - case MODAL.CODE: - case MODAL.IMG: - if (exportData.data !== "" || exportData.data) { - return ( - <> - {modal === MODAL.IMG ? ( - Diagram - ) : ( - {}} - editable={false} - theme={settings.mode === "dark" ? vscodeDark : githubLight} - /> - )} -
{t("filename")}:
- {`.${exportData.extension}`}} - onChange={(value) => - setExportData((prev) => ({ ...prev, filename: value })) - } - field="filename" - /> - - ); - } else { - return ( -
- -
- ); - } - case MODAL.TABLE_WIDTH: - return ; - case MODAL.LANGUAGE: - return ; - default: - return <>; - } - }; - - return ( - { - setExportData(() => ({ - data: "", - extension: "", - filename: `${title}_${new Date().toISOString()}`, - })); - setError({ - type: STATUS.NONE, - message: "", - }); - setImportData(null); - setImportSource({ - src: "", - overwrite: true, - dbms: "MySQL", - }); - }} - onCancel={() => { - if (modal === MODAL.RENAME) setTitle(prevTitle); - setModal(MODAL.NONE); - }} - centered - closeOnEsc={true} - okText={getOkText(modal)} - okButtonProps={{ - disabled: - (error && error?.type === STATUS.ERROR) || - (modal === MODAL.IMPORT && - (error.type === STATUS.ERROR || !importData)) || - (modal === MODAL.RENAME && title === "") || - ((modal === MODAL.IMG || modal === MODAL.CODE) && !exportData.data) || - (modal === MODAL.SAVEAS && saveAsTitle === "") || - (modal === MODAL.IMPORT_SRC && importSource.src === ""), - }} - cancelText={t("cancel")} - width={modal === MODAL.NEW ? 740 : 600} - bodyStyle={{ maxHeight: window.innerHeight - 280, overflow: "auto" }} - > - {getModalBody()} - - ); -} diff --git a/src/components/EditorHeader/Modal/New.jsx b/src/components/EditorHeader/Modal/New.jsx index 81ffcdb2..e5919338 100644 --- a/src/components/EditorHeader/Modal/New.jsx +++ b/src/components/EditorHeader/Modal/New.jsx @@ -1,45 +1,67 @@ -import { db } from "../../../data/db"; -import { useSettings } from "../../../hooks"; import { useLiveQuery } from "dexie-react-hooks"; -import Thumbnail from "../../Thumbnail"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; +import { db } from "../../../data/db"; +import Thumbnail from "../../Thumbnail"; +import { useSettings } from "../../../hooks"; +import BaseModal from "./BaseModal"; -export default function New({ selectedTemplateId, setSelectedTemplateId }) { - const { settings } = useSettings(); +export default function New({ hideModal }) { const { t } = useTranslation(); + const { settings } = useSettings(); + const [selectedTemplateId, setSelectedTemplateId] = useState(-1); + const templates = useLiveQuery(() => db.templates.toArray()); + const createNewDiagram = (id) => { + const newWindow = window.open("/editor"); + newWindow.name = "lt " + id; + }; + + const onOk = () => { + hideModal(); + createNewDiagram(selectedTemplateId); + } + return ( -
-
setSelectedTemplateId(0)}> -
- -
-
{t("blank")}
-
- {templates?.map((temp, i) => ( -
setSelectedTemplateId(temp.id)}> + +
+
setSelectedTemplateId(0)}>
- +
-
{temp.title}
+
{t("blank")}
- ))} -
+ {templates?.map((temp, i) => ( +
setSelectedTemplateId(temp.id)}> +
+ +
+
{temp.title}
+
+ ))} +
+ ); } diff --git a/src/components/EditorHeader/Modal/Open.jsx b/src/components/EditorHeader/Modal/Open.jsx index cf7a5266..d106641d 100644 --- a/src/components/EditorHeader/Modal/Open.jsx +++ b/src/components/EditorHeader/Modal/Open.jsx @@ -1,11 +1,29 @@ -import { db } from "../../../data/db"; -import { Banner } from "@douyinfe/semi-ui"; +import { Banner, Toast } from "@douyinfe/semi-ui"; import { useLiveQuery } from "dexie-react-hooks"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; +import { db } from "../../../data/db"; +import { + useAreas, + useNotes, + useTables, + useTransform, + useTypes, + useUndoRedo, +} from "../../../hooks"; +import BaseModal from "./BaseModal"; -export default function Open({ selectedDiagramId, setSelectedDiagramId }) { - const diagrams = useLiveQuery(() => db.diagrams.toArray()); +export default function Open({ hideModal, setDiagramId, setTitle}) { const { t } = useTranslation(); + const { setTables, setRelationships } = useTables(); + const { setNotes } = useNotes(); + const { setAreas } = useAreas(); + const { setTypes } = useTypes(); + const { setTransform } = useTransform(); + const { setUndoStack, setRedoStack } = useUndoRedo(); + + const diagrams = useLiveQuery(() => db.diagrams.toArray()); + const [selectedDiagramId, setSelectedDiagramId] = useState(0); const getDiagramSize = (d) => { const size = JSON.stringify(d).length; @@ -18,8 +36,48 @@ export default function Open({ selectedDiagramId, setSelectedDiagramId }) { return sizeStr; }; + + const loadDiagram = async (id) => { + await db.diagrams + .get(id) + .then((diagram) => { + if (diagram) { + setDiagramId(diagram.id); + setTitle(diagram.name); + setTables(diagram.tables); + setTypes(diagram.types); + setRelationships(diagram.references); + setAreas(diagram.areas); + setNotes(diagram.notes); + setTransform({ + pan: diagram.pan, + zoom: diagram.zoom, + }); + setUndoStack([]); + setRedoStack([]); + window.name = `d ${diagram.id}`; + } else { + Toast.error("Oops! Something went wrong."); + } + }) + .catch(() => { + Toast.error("Oops! Couldn't load diagram."); + }); + }; + + const onOk = () => { + if (selectedDiagramId === 0) return; + loadDiagram(selectedDiagramId); + hideModal(); + }; + return ( -
+ {diagrams?.length === 0 ? (
)} -
+ ); } diff --git a/src/components/EditorHeader/Modal/Rename.jsx b/src/components/EditorHeader/Modal/Rename.jsx index d8767468..915e60c8 100644 --- a/src/components/EditorHeader/Modal/Rename.jsx +++ b/src/components/EditorHeader/Modal/Rename.jsx @@ -1,14 +1,34 @@ import { Input } from "@douyinfe/semi-ui"; +import { useRef } from "react"; import { useTranslation } from "react-i18next"; +import BaseModal from "./BaseModal" -export default function Rename({ title, setTitle }) { +export default function Rename({ hideModal, title, setTitle }) { const { t } = useTranslation(); + const originalTitle = useRef(title) + + const onCancel = () => { + setTitle(originalTitle.current); + hideModal(); + }; + + const onOk = () => { + hideModal(); + }; return ( - setTitle(v)} - /> + + setTitle(v)} + /> + ); } diff --git a/src/components/EditorHeader/Modal/SaveAs.jsx b/src/components/EditorHeader/Modal/SaveAs.jsx new file mode 100644 index 00000000..ea80c545 --- /dev/null +++ b/src/components/EditorHeader/Modal/SaveAs.jsx @@ -0,0 +1,30 @@ +import { Input } from "@douyinfe/semi-ui"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import BaseModal from "./BaseModal" + +export default function SaveAs({ hideModal, title, setTitle }) { + const { t } = useTranslation(); + const [saveAsTitle, setSaveAsTitle] = useState(title); + + const onOk = () => { + setTitle(saveAsTitle); + hideModal(); + }; + + return ( + + setSaveAsTitle(v)} + /> + + ); +} diff --git a/src/components/EditorHeader/Modal/SetTableWidth.jsx b/src/components/EditorHeader/Modal/SetTableWidth.jsx index 93482e68..2658882b 100644 --- a/src/components/EditorHeader/Modal/SetTableWidth.jsx +++ b/src/components/EditorHeader/Modal/SetTableWidth.jsx @@ -1,17 +1,26 @@ import { InputNumber } from "@douyinfe/semi-ui"; +import { useTranslation } from "react-i18next"; import { useSettings } from "../../../hooks"; +import BaseModal from "./BaseModal" -export default function SetTableWidth() { +export default function SetTableWidth({ hideModal }) { + const { t } = useTranslation(); const { settings, setSettings } = useSettings(); return ( - { - if (c < 180) return; - setSettings((prev) => ({ ...prev, tableWidth: c })); - }} - /> + + { + if (c < 180) return; + setSettings((prev) => ({ ...prev, tableWidth: c })); + }} + /> + ); } diff --git a/src/components/EditorHeader/ModalManager.jsx b/src/components/EditorHeader/ModalManager.jsx new file mode 100644 index 00000000..f5585711 --- /dev/null +++ b/src/components/EditorHeader/ModalManager.jsx @@ -0,0 +1,56 @@ +import { MODAL } from "../../data/constants"; +import Rename from "./Modal/Rename"; +import Open from "./Modal/Open"; +import New from "./Modal/New"; +import ImportDiagram from "./Modal/ImportDiagram"; +import ImportSource from "./Modal/ImportSource"; +import SetTableWidth from "./Modal/SetTableWidth"; +import Language from "./Modal/Language"; +import ExportExcel from "./Modal/ExportExcel"; +import ExportImage from "./Modal/ExportImage"; +import ExportJson from "./Modal/ExportJson"; +import ExportDrawDB from "./Modal/ExportDrawDB"; +import ExportSql from "./Modal/ExportSql"; +import ExportPdf from "./Modal/ExportPdf"; +import SaveAs from "./Modal/SaveAs"; + +export default function ModalManager({ + modal, + hideModal, + title, + setTitle, + setDiagramId, +}) { + switch (modal) { + case MODAL.EXPORT_IMG: + return ; + case MODAL.EXPORT_PDF: + return ; + case MODAL.EXPORT_SQL: + return ; + case MODAL.EXPORT_EXCEL: + return ; + case MODAL.EXPORT_JSON: + return ; + case MODAL.EXPORT_DIAGRAM: + return ; + case MODAL.IMPORT_DIAGRAM: + return ; + case MODAL.IMPORT_SRC: + return ; + case MODAL.NEW: + return ; + case MODAL.OPEN: + return ; + case MODAL.SAVEAS: + return ; + case MODAL.RENAME: + return ; + case MODAL.TABLE_WIDTH: + return ; + case MODAL.LANGUAGE: + return ; + default: + return <>; + } +} diff --git a/src/data/constants.js b/src/data/constants.js index 480b1ba4..0d4fd453 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -105,16 +105,20 @@ export const State = { export const MODAL = { NONE: 0, - IMG: 1, - CODE: 2, - IMPORT: 3, + NEW: 1, + OPEN: 2, + SAVEAS: 3, RENAME: 4, - OPEN: 5, - SAVEAS: 6, - NEW: 7, - IMPORT_SRC: 8, - TABLE_WIDTH: 9, - LANGUAGE: 10, + IMPORT_SRC: 5, + IMPORT_DIAGRAM: 6, + TABLE_WIDTH: 7, + LANGUAGE: 8, + EXPORT_EXCEL: 9, + EXPORT_IMG: 10, + EXPORT_JSON: 11, + EXPORT_DIAGRAM: 12, + EXPORT_SQL: 13, + EXPORT_PDF: 14, }; export const STATUS = { @@ -129,3 +133,13 @@ export const SIDESHEET = { TODO: 1, TIMELINE: 2, }; + +export const DATABASE_TYPES = [ + "MySQL", + "MariaDB", + "PostgreSQL", + "SQLite", + "MSSQL", +]; + +export const IMAGE_TYPES = ["png", "jpeg", "svg"]; diff --git a/src/i18n/locales/da.js b/src/i18n/locales/da.js index baca4f5b..3f775dcb 100644 --- a/src/i18n/locales/da.js +++ b/src/i18n/locales/da.js @@ -165,7 +165,7 @@ const da = { no_values_for_field: "'{{fieldName}}' felt fra tabellen '{{tableName}}' er af type `{{type}}` men ingen værdi er blevet specificeret", default_doesnt_match_type: - "Standardværdien for feltet '{{fieldName}}' i tabellen '{{table.name}}' stemmer ikke overens med dens type", + "Standardværdien for feltet '{{fieldName}}' i tabellen '{{tableName}}' stemmer ikke overens med dens type", not_null_is_null: "'{{fieldName}}' felt fra tabellen '{{tableName}}' er IKKE NUL, men har standardværdien NUL", duplicate_fields: diff --git a/src/i18n/locales/de.js b/src/i18n/locales/de.js index a1c2d776..973507f2 100644 --- a/src/i18n/locales/de.js +++ b/src/i18n/locales/de.js @@ -168,7 +168,7 @@ const de = { no_values_for_field: "Das Feld '{{fieldName}}' der Tabelle '{{tableName}}' ist vom Typ `{{type}}`, aber es wurden keine Werte angegeben", default_doesnt_match_type: - "Der Standardwert für das Feld '{{fieldName}}' in der Tabelle '{{table.name}}' entspricht nicht seinem Typ", + "Der Standardwert für das Feld '{{fieldName}}' in der Tabelle '{{tableName}}' entspricht nicht seinem Typ", not_null_is_null: "Das Feld '{{fieldName}}' der Tabelle '{{tableName}}' ist NOT NULL, hat aber standardmäßig NULL", duplicate_fields: diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js index 2bce8950..65cf62f8 100644 --- a/src/i18n/locales/en.js +++ b/src/i18n/locales/en.js @@ -165,7 +165,7 @@ const en = { no_values_for_field: "'{{fieldName}}' field of table '{{tableName}}' is of type `{{type}}` but no values have been specified", default_doesnt_match_type: - "Default value for field '{{fieldName}}' in table '{{table.name}}' does not match its type", + "Default value for field '{{fieldName}}' in table '{{tableName}}' does not match its type", not_null_is_null: "'{{fieldName}}' field of table '{{tableName}}' is NOT NULL but has default NULL", duplicate_fields: @@ -212,6 +212,21 @@ const en = { edit_relationship: "{{extra}} Edit relationship {{refName}}", delete_relationship: "Delete relationship {{refName}}", not_found: "Not found", + export_pdf: "Export pdf", + export_json: "Export json", + export_diagram: "Export diagram", + export_excel: "Export excel", + format: "Format", + fields: "Fields", + source_table: "Source table", + source_field: "Source field", + foreign_table: "Foreign table", + foreign_field: "Foreign field", + update_constraint: "Update constraint", + delete_constraint: "Delete constraint", + yes: "Yes", + diagram: "Diagram", + add_diagram: "Add diagram" }, }; diff --git a/src/i18n/locales/es.js b/src/i18n/locales/es.js index 39c00c68..173b2975 100644 --- a/src/i18n/locales/es.js +++ b/src/i18n/locales/es.js @@ -165,7 +165,7 @@ const es = { no_values_for_field: "El campo '{{fieldName}}' de la tabla '{{tableName}}' es de tipo `{{type}}` pero no se han especificado valores", default_doesnt_match_type: - "El valor predeterminado para el campo '{{fieldName}}' en la tabla '{{table.name}}' no coincide con su tipo", + "El valor predeterminado para el campo '{{fieldName}}' en la tabla '{{tableName}}' no coincide con su tipo", not_null_is_null: "El campo '{{fieldName}}' de la tabla '{{tableName}}' es NOT NULL pero tiene NULL por defecto", duplicate_fields: diff --git a/src/i18n/locales/fa.js b/src/i18n/locales/fa.js index 4e4c4414..b7214458 100644 --- a/src/i18n/locales/fa.js +++ b/src/i18n/locales/fa.js @@ -156,7 +156,7 @@ const fa = { empty_field_name: "فیلد خالی name در جدول '{{tableName}}'", empty_field_type: "فیلد خالی type در جدول '{{tableName}}'", no_values_for_field: "فیلد '{{fieldName}}' جدول '{{tableName}}' از نوع {{type}} است اما هیچ مقداری مشخص نشده است", - default_doesnt_match_type: "مقدار پیش‌فرض برای فیلد '{{fieldName}}' در جدول '{{table.name}}' با نوع آن مطابقت ندارد", + default_doesnt_match_type: "مقدار پیش‌فرض برای فیلد '{{fieldName}}' در جدول '{{tableName}}' با نوع آن مطابقت ندارد", not_null_is_null: "فیلد '{{fieldName}}' جدول '{{tableName}}' غیر خالی است اما پیش‌فرض آن خالی است", duplicate_fields: "فیلدهای تکراری جدول به نام '{{fieldName}}' در جدول '{{tableName}}'", duplicate_index: "شاخص تکراری به نام '{{indexName}}' در جدول '{{tableName}}'", @@ -198,9 +198,8 @@ const fa = { edit_relationship: "{{extra}} ویرایش ارتباط {{refName}}", delete_relationship: "حذف ارتباط {{refName}}", not_found: "یافت نشد" - + }, }; - + export { fa,persian }; - \ No newline at end of file diff --git a/src/i18n/locales/pt-br.js b/src/i18n/locales/pt-br.js index 9368201a..008bdff8 100644 --- a/src/i18n/locales/pt-br.js +++ b/src/i18n/locales/pt-br.js @@ -165,7 +165,7 @@ const pt = { no_values_for_field: "O campo '{{fieldName}}' da tabela '{{tableName}}' é do tipo `{{type}}`, mas nenhum valor foi especificado", default_doesnt_match_type: - "O valor padrão para o campo '{{fieldName}}' na tabela '{{table.name}}' não corresponde ao seu tipo", + "O valor padrão para o campo '{{fieldName}}' na tabela '{{tableName}}' não corresponde ao seu tipo", not_null_is_null: "O campo '{{fieldName}}' da tabela '{{tableName}}' é NOT NULL mas tem o valor padrão NULL", duplicate_fields: diff --git a/src/i18n/locales/vi.js b/src/i18n/locales/vi.js index 42662e47..bcfef787 100644 --- a/src/i18n/locales/vi.js +++ b/src/i18n/locales/vi.js @@ -112,7 +112,7 @@ const vi = { size: "Kích cỡ", precision: "Độ chính xác", set_precision: "Đặt độ chính xác: (kích thước, chữ số)", - use_for_batch_input: "Sử dụng , để nhập hàng loạt", + use_for_batch_input: "Sử dụng để nhập hàng loạt", indices: "Chỉ số", add_index: "Thêm chỉ mục", select_fields: "Chọn các trường", @@ -123,9 +123,9 @@ const vi = { on_update: "Khi cập nhật", on_delete: "Khi xóa", swap: "Tráo đổi", - one_to_one: "Một đối một", - one_to_many: "Một đến nhiều", - many_to_one: "Nhiều thành một", + one_to_one: "Một với một", + one_to_many: "Một với nhiều", + many_to_one: "Nhiều với một", content: "Nội dung", types_info: "Tính năng này dành cho các DBMS quan hệ đối tượng như PostgreSQL.\nNếu được sử dụng cho MySQL hoặc MariaDB, loại JSON sẽ được tạo bằng kiểm tra xác thực json tương ứng.\nNếu được sử dụng cho SQLite, nó sẽ được dịch sang BLOB.\nNếu được sử dụng cho MSSQL một bí danh loại cho trường đầu tiên sẽ được tạo.", table_deleted: "Đã xóa bảng", @@ -156,7 +156,7 @@ const vi = { empty_field_name: "Trường trống `name` trong bảng '{{tableName}}'", empty_field_type: "Trường trống `loại` trong bảng '{{tableName}}'", no_values_for_field: "Trường '{{fieldName}}' của bảng '{{tableName}}' thuộc loại `{{type}}` nhưng không có giá trị nào được chỉ định", - default_doesnt_match_type: "Giá trị mặc định cho trường '{{fieldName}}' trong bảng '{{table.name}}' không khớp với loại của nó", + default_doesnt_match_type: "Giá trị mặc định cho trường '{{fieldName}}' trong bảng '{{tableName}}' không khớp với loại của nó", not_null_is_null: "Trường '{{fieldName}}' của bảng '{{tableName}}' là NOT NULL nhưng đang là NULL", duplicate_fields: "Các trường bảng trùng lặp theo tên '{{fieldName}}' trong bảng '{{tableName}}'", duplicate_index: "Chỉ mục trùng lặp theo tên '{{indexName}}' trong bảng '{{tableName}}'", @@ -198,6 +198,21 @@ const vi = { edit_relationship: "{{extra}} Chỉnh sửa quan hệ {{refName}}", delete_relationship: "Xóa quan hệ {{refName}}", not_found: "Không tìm thấy", + export_pdf: "Xuất pdf", + export_json: "Xuất json", + export_diagram: "Xuất sơ đồ", + export_excel: "Xuất excel", + format: "Định dạng", + fields: "Trường", + source_table: "Bảng gốc", + source_field: "Trường bảng gốc", + foreign_table: "Bảng ngoại", + foreign_field: "Trường bảng ngoại", + update_constraint: "Hạn chế cập nhật", + delete_constraint: "Hạn chế xoá", + yes: "Có", + diagram: "Sơ đồ", + add_diagram: "Thêm sơ đồ" }, }; diff --git a/src/utils/modalTitles.js b/src/utils/modalTitles.js deleted file mode 100644 index 6501aa08..00000000 --- a/src/utils/modalTitles.js +++ /dev/null @@ -1,49 +0,0 @@ -import { MODAL } from "../data/constants"; -import i18n from "../i18n/i18n"; - -export const getModalTitle = (modal) => { - switch (modal) { - case MODAL.IMPORT: - case MODAL.IMPORT_SRC: - return i18n.t("import_diagram"); - case MODAL.CODE: - return i18n.t("export_source"); - case MODAL.IMG: - return i18n.t("export_image"); - case MODAL.RENAME: - return i18n.t("rename_diagram"); - case MODAL.OPEN: - return i18n.t("open_diagram"); - case MODAL.SAVEAS: - return i18n.t("save_as"); - case MODAL.NEW: - return i18n.t("create_new_diagram"); - case MODAL.TABLE_WIDTH: - return i18n.t("table_width"); - case MODAL.LANGUAGE: - return i18n.t("language"); - default: - return ""; - } -}; - -export const getOkText = (modal) => { - switch (modal) { - case MODAL.IMPORT: - case MODAL.IMPORT_SRC: - return i18n.t("import"); - case MODAL.CODE: - case MODAL.IMG: - return i18n.t("export"); - case MODAL.RENAME: - return i18n.t("rename"); - case MODAL.OPEN: - return i18n.t("open"); - case MODAL.SAVEAS: - return i18n.t("save_as"); - case MODAL.NEW: - return i18n.t("create"); - default: - return i18n.t("confirm"); - } -}; diff --git a/src/utils/toDocument.js b/src/utils/toDocument.js new file mode 100644 index 00000000..4e8c1874 --- /dev/null +++ b/src/utils/toDocument.js @@ -0,0 +1,176 @@ +import Excel from "exceljs"; +import { getTypeString } from "./toSQL"; +import { toPng } from "html-to-image"; +import i18n from "../i18n/i18n"; + +const excelTableTheme = "TableStyleLight9"; + +export async function jsonToExcel(data) { + const labels = { + tables: i18n.t("tables"), + relationships: i18n.t("relationships"), + diagram: i18n.t("diagram"), + fields: i18n.t("fields"), + indices: i18n.t("indices"), + name: i18n.t("name"), + cardinality: i18n.t("cardinality"), + type: i18n.t("type"), + not_null: i18n.t("not_null"), + primary: i18n.t("primary"), + default_value: i18n.t("default_value"), + unique: i18n.t("unique"), + autoincrement: i18n.t("autoincrement"), + comment: i18n.t("comment"), + yes: i18n.t("yes"), + source_table: i18n.t("source_table"), + source_field: i18n.t("source_field"), + foreign_table: i18n.t("foreign_table"), + foreign_field: i18n.t("foreign_field"), + update_constraint: i18n.t("update_constraint"), + delete_constraint: i18n.t("delete_constraint"), + }; + + const workbook = new Excel.Workbook(); + + // create sheet for list tables and relationships + const listTableSheet = workbook.addWorksheet(labels.tables); + listTableSheet.addTable({ + name: "Tables", + ref: "A1", + style: { + theme: excelTableTheme, + showRowStripes: true, + }, + columns: [{ name: "#" }, { name: labels.name }, { name: labels.comment }], + rows: data.tables.map((table, index) => [ + index + 1, + table.name, + table.comment, + ]), + }); + + if (data.relationships.length) { + const relationSheet = workbook.addWorksheet(labels.relationships); + relationSheet.addTable({ + name: "Relations", + ref: "A1", + style: { + theme: excelTableTheme, + showRowStripes: true, + }, + columns: [ + { name: "#" }, + { name: labels.name }, + { name: labels.cardinality }, + { name: labels.source_table }, + { name: labels.source_field }, + { name: labels.foreign_table }, + { name: labels.foreign_field }, + { name: labels.update_constraint }, + { name: labels.delete_constraint }, + ], + rows: data.relationships.map((r, index) => [ + index + 1, + r.name, + i18n.t(r.cardinality.toLowerCase().replaceAll(" ", "_")), + data.tables[r.startTableId].name, + data.tables[r.startTableId].fields[r.startFieldId].name, + data.tables[r.endTableId].name, + data.tables[r.endTableId].fields[r.endFieldId].name, + r.updateConstraint.toUpperCase(), + r.deleteConstraint.toUpperCase(), + ]), + }); + } + + // create ER diagram sheet + if (data.hasDiagram) { + const canvasElm = document.getElementById("canvas"); + const canvasBoundary = canvasElm.getBoundingClientRect(); + const diagramImageId = workbook.addImage({ + base64: await toPng(canvasElm), + extension: 'png', + }); + workbook.addWorksheet(labels.diagram).addImage(diagramImageId, { + tl: { + col: 0, + row: 0 + }, + ext: { + width: parseInt(canvasBoundary.width), + height: parseInt(canvasBoundary.height) + } + }); + } + // create table sheet + data.tables.map((table, index) => { + const tableSheet = workbook.addWorksheet(table.name); + + tableSheet.getRow(1).getCell(1).value = labels.fields; + tableSheet.getRow(1).getCell(1).style = { font: { bold: true } }; + + tableSheet.addTable({ + name: `FieldTable${index}`, + ref: "A2", + style: { + theme: excelTableTheme, + showRowStripes: true, + }, + columns: [ + { name: "#" }, + { name: labels.name }, + { name: labels.type }, + { name: labels.not_null }, + { name: labels.default_value }, + { name: labels.primary }, + { name: labels.unique }, + { name: labels.autoincrement }, + { name: labels.comment }, + ], + rows: table.fields.map((field, index) => [ + index + 1, + field.name, + getTypeString(field, data.dbms), + field.notNull ? labels.yes : "", + field.default, + field.primary ? labels.yes : "", + field.unique ? labels.yes : "", + field.increment ? labels.yes : "", + field.comment, + ]), + }); + + if (table.indices.length) { + // add 1 empty line to separate tables + const lastRowIdx = tableSheet.rowCount + 1; + + tableSheet.getRow(lastRowIdx + 1).getCell(1).value = labels.indices; + tableSheet.getRow(lastRowIdx + 1).getCell(1).style = { + font: { bold: true }, + }; + + tableSheet.addTable({ + name: `IndexTable${index}`, + ref: `A${lastRowIdx + 2}`, + style: { + theme: excelTableTheme, + showRowStripes: true, + }, + columns: [ + { name: "#" }, + { name: labels.name }, + { name: labels.fields }, + { name: labels.unique }, + ], + rows: table.indices.map((indexData, index) => [ + index + 1, + indexData.name, + indexData.fields.join(", "), + indexData.unique ? labels.yes : "", + ]), + }); + } + }); + + return await workbook.xlsx.writeBuffer(); +} diff --git a/src/utils/toSQL.js b/src/utils/toSQL.js index 2ad49616..edae5bc3 100644 --- a/src/utils/toSQL.js +++ b/src/utils/toSQL.js @@ -39,58 +39,61 @@ export function generateSchema(type) { )}\n\t\t\t},\n\t\t\t"additionalProperties": false\n\t\t}`; } -export function getTypeString(field, dbms = "mysql", baseType = false) { - if (dbms === "mysql") { - if (field.type === "UUID") { - return `VARCHAR(36)`; - } - if (hasPrecision(field.type) || isSized(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(", ")})`; - } - if (!sqlDataTypes.includes(field.type)) { - return "JSON"; - } - return field.type; - } else if (dbms === "postgres") { - if (field.type === "SMALLINT" && field.increment) { - return "smallserial"; - } - if (field.type === "INT" && field.increment) { - return "serial"; - } - if (field.type === "BIGINT" && field.increment) { - return "bigserial"; - } - if (field.type === "ENUM") { - return `${field.name}_t`; - } - if (field.type === "SET") { - return `${field.name}_t[]`; - } - if (field.type === "TIMESTAMP") { - return "TIMESTAMPTZ"; - } - if (field.type === "DATETIME") { - return `timestamp`; - } - if (isSized(field.type)) { - const type = - field.type === "BINARY" - ? "bit" - : field.type === "VARBINARY" - ? "bit varying" - : field.type.toLowerCase(); - return `${type}(${field.size})`; - } - if (hasPrecision(field.type) && field.size !== "") { - return `${field.type}${field.size}`; - } - return field.type.toLowerCase(); - } else if (dbms === "mssql") { - let type = field.type; +export function getMysqlType(field) { + if (field.type === "UUID") { + return `VARCHAR(36)`; + } + if (hasPrecision(field.type) || isSized(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(", ")})`; + } + if (!sqlDataTypes.includes(field.type)) { + return "JSON"; + } + return field.type; +} + +export function getPostgreSQLType(field) { + if (field.type === "SMALLINT" && field.increment) { + return "smallserial"; + } + if (field.type === "INT" && field.increment) { + return "serial"; + } + if (field.type === "BIGINT" && field.increment) { + return "bigserial"; + } + if (field.type === "ENUM") { + return `${field.name}_t`; + } + if (field.type === "SET") { + return `${field.name}_t[]`; + } + if (field.type === "TIMESTAMP") { + return "TIMESTAMPTZ"; + } + if (field.type === "DATETIME") { + return `timestamp`; + } + if (isSized(field.type)) { + const type = + field.type === "BINARY" + ? "bit" + : field.type === "VARBINARY" + ? "bit varying" + : field.type.toLowerCase(); + return `${type}(${field.size})`; + } + if (hasPrecision(field.type) && field.size !== "") { + return `${field.type}${field.size}`; + } + return field.type.toLowerCase(); +} + +export function getMSSQLType(field, baseType = false) { + let type = field.type; switch (field.type) { case "ENUM": return baseType @@ -126,9 +129,55 @@ export function getTypeString(field, dbms = "mysql", baseType = false) { } return type; +} + +export function getSQLiteType(field) { + switch (field.type) { + case "INT": + case "SMALLINT": + case "BIGINT": + case "BOOLEAN": + return "INTEGER"; + case "DECIMAL": + case "NUMERIC": + case "FLOAT": + case "DOUBLE": + case "REAL": + return "REAL"; + case "CHAR": + case "VARCHAR": + case "UUID": + case "TEXT": + case "DATE": + case "TIME": + case "TIMESTAMP": + case "DATETIME": + case "BINARY": + case "VARBINARY": + return "TEXT"; + case "ENUM": + return `TEXT CHECK("${field.name}" in (${field.values + .map((v) => `'${v}'`) + .join(", ")}))`; + default: + return "BLOB"; } } +export function getTypeString(field, dbms = "mysql", baseType = false) { + if (dbms === "mysql" || dbms === "mariadb") { + return getMysqlType(field); + } else if (dbms === "postgressql") { + return getPostgreSQLType(field); + } else if (dbms === "mssql") { + return getMSSQLType(field, baseType) + } else if (dbms === "sqlite") { + return getSQLiteType(field); + } + + return field.type; +} + export function hasQuotes(type) { return [ "CHAR", @@ -233,14 +282,14 @@ export function jsonToPostgreSQL(obj) { `${ type.comment === "" ? "" : `/**\n${type.comment}\n*/\n` }CREATE TYPE ${type.name} AS (\n${type.fields - .map((f) => `\t${f.name} ${getTypeString(f, "postgres")}`) + .map((f) => `\t${f.name} ${getTypeString(f, "postgressql")}`) .join("\n")}\n);` ); } else { return `${ type.comment === "" ? "" : `/**\n${type.comment}\n*/\n` }CREATE TYPE ${type.name} AS (\n${type.fields - .map((f) => `\t${f.name} ${getTypeString(f, "postgres")}`) + .map((f) => `\t${f.name} ${getTypeString(f, "postgressql")}`) .join("\n")}\n);`; } })}\n${obj.tables @@ -263,7 +312,7 @@ export function jsonToPostgreSQL(obj) { (field) => `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${ field.name - }" ${getTypeString(field, "postgres")}${ + }" ${getTypeString(field, "postgressql")}${ field.notNull ? " NOT NULL" : "" }${ field.default !== "" ? ` DEFAULT ${parseDefault(field)}` : "" @@ -305,39 +354,6 @@ export function jsonToPostgreSQL(obj) { .join("\n")}`; } -export function getSQLiteType(field) { - switch (field.type) { - case "INT": - case "SMALLINT": - case "BIGINT": - case "BOOLEAN": - return "INTEGER"; - case "DECIMAL": - case "NUMERIC": - case "FLOAT": - case "DOUBLE": - case "REAL": - return "REAL"; - case "CHAR": - case "VARCHAR": - case "UUID": - case "TEXT": - case "DATE": - case "TIME": - case "TIMESTAMP": - case "DATETIME": - case "BINARY": - case "VARBINARY": - return "TEXT"; - case "ENUM": - return `TEXT CHECK("${field.name}" in (${field.values - .map((v) => `'${v}'`) - .join(", ")}))`; - default: - return "BLOB"; - } -} - export function getInlineFK(table, obj) { let fk = ""; obj.references.forEach((r) => { @@ -364,7 +380,7 @@ export function jsonToSQLite(obj) { (field) => `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${ field.name - }" ${getSQLiteType(field)}${field.notNull ? " NOT NULL" : ""}${ + }" ${getTypeString(field, "sqlite")}${field.notNull ? " NOT NULL" : ""}${ field.unique ? " UNIQUE" : "" }${field.default !== "" ? ` DEFAULT ${parseDefault(field)}` : ""}${ field.check === "" || !hasCheck(field.type) @@ -458,7 +474,7 @@ export function jsonToMariaDB(obj) { .join("\n")}`; } -export function jsonToSQLServer(obj) { +export function jsonToMSSQL(obj) { return `${obj.types .map((type) => { return `${ @@ -560,3 +576,11 @@ export function getSize(type) { return ""; } } + +export default { + jsonToMySQL, + jsonToMariaDB, + jsonToPostgreSQL, + jsonToSQLite, + jsonToMSSQL, +}