From 1684c062f638fb5bb7947dc3116e4927fc510125 Mon Sep 17 00:00:00 2001 From: ajenkins-mparticle Date: Fri, 13 Sep 2024 09:58:34 -0300 Subject: [PATCH 1/3] chore: adds storybook badge to readme (#400) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d292eda3..cece65b88 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@
+ + # mParticle Aquarium mParticle Component Library @@ -23,4 +25,4 @@ $> npm run test-storybook ## License -mParticle's Aquarium is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). See the LICENSE file for more info. \ No newline at end of file +mParticle's Aquarium is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). See the LICENSE file for more info. From 40dffbf1eb2a480d2470dc70338118934f96cfa7 Mon Sep 17 00:00:00 2001 From: Gaby Zifferman Date: Fri, 13 Sep 2024 15:04:33 +0200 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20add=20mp=20colors=20to=20storybook?= =?UTF-8?q?=20and=20remove=20token=20to=20css=20from=20pre=20com=E2=80=A6?= =?UTF-8?q?=20(#405)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .husky/pre-commit | 1 - .storybook/manager.ts | 6 ++++++ .storybook/theme.ts | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .storybook/manager.ts create mode 100644 .storybook/theme.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index 532fa1f84..d0a778429 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1 @@ -npm run tokens-to-css npx lint-staged \ No newline at end of file diff --git a/.storybook/manager.ts b/.storybook/manager.ts new file mode 100644 index 000000000..3e931eb70 --- /dev/null +++ b/.storybook/manager.ts @@ -0,0 +1,6 @@ +import { addons } from '@storybook/manager-api' +import customTheme from './theme' + +addons.setConfig({ + theme: customTheme, +}) diff --git a/.storybook/theme.ts b/.storybook/theme.ts new file mode 100644 index 000000000..7c91f4ca7 --- /dev/null +++ b/.storybook/theme.ts @@ -0,0 +1,16 @@ +import { create } from '@storybook/theming' +import { LightTheme } from '../design/LightTheme' + +export default create({ + base: 'light', + colorPrimary: LightTheme.token.colorPrimary, + colorSecondary: LightTheme.token.colorPrimaryText, + appBg: LightTheme.token.colorPrimaryBgHover, + appContentBg: LightTheme.token.colorPrimaryBg, + textColor: LightTheme.token.colorText, + textInverseColor: LightTheme.token.colorTextQuaternary, + brandTitle: 'Aquarium', + brandImage: + 'https://raw.githubusercontent.com/mParticle/aquarium/3abc3b71916ab5a7db3c4f93e06ad2bd5845e1bf/src/assets/svg/mp-logo-wordmark.svg', + brandUrl: 'https://github.com/mParticle/aquarium/', +}) From a99793f264f2d1609343d4d83c11acdb0b005668 Mon Sep 17 00:00:00 2001 From: Gaby Zifferman Date: Mon, 16 Sep 2024 18:27:15 +0200 Subject: [PATCH 3/3] feat: add prettifier (#404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gabriel Tibúrcio --- .semgrepignore | 3 + docs/icons.md | 38 ++ package-lock.json | 402 +++++++++++++++++- package.json | 8 +- server/prettify-server.cjs | 88 ++++ .../svg-prettifier/SvgPrettfier.stories.tsx | 41 ++ src/utils/svg-prettifier/SvgPrettifier.tsx | 141 ++++++ src/utils/svg-prettifier/prettifier.cjs | 93 ++++ 8 files changed, 790 insertions(+), 24 deletions(-) create mode 100644 .semgrepignore create mode 100644 docs/icons.md create mode 100644 server/prettify-server.cjs create mode 100644 src/utils/svg-prettifier/SvgPrettfier.stories.tsx create mode 100644 src/utils/svg-prettifier/SvgPrettifier.tsx create mode 100644 src/utils/svg-prettifier/prettifier.cjs diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 000000000..37b2d4417 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,3 @@ +server/prettify-server.cjs +src/utils/svg-prettifier/SvgPrettifier.tsx +src/utils/svg-prettifier/prettifier.cjs diff --git a/docs/icons.md b/docs/icons.md new file mode 100644 index 000000000..987bea588 --- /dev/null +++ b/docs/icons.md @@ -0,0 +1,38 @@ +## Icons Documentation + +### Adding and Updating Icons + +The Design team provides SVG files with the final icon names. These SVG files must be minified and follow the project's styling guidelines. You can prettify them using the `SVGPrettifier` tool, which ensures the correct size and properties are applied to the SVG. + +#### Steps to Add a New Icon: + +1. **Prettify the SVG**: + Use the SVGPrettifier Story to clean up the SVG file and ensure it meets the necessary formatting standards (size, properties, etc.). + +2. **Update the Icon Type**: + Add the new icon name to the TypeScript type definition in the [src/types/icons](https://github.com/mParticle/aquarium/blob/3abc3b71916ab5a7db3c4f93e06ad2bd5845e1bf/src/types/icons.ts) file. + +3. **Update Icon Components**: + Add the icon components in the [src/components/icons](https://github.com/mParticle/aquarium/blob/3abc3b71916ab5a7db3c4f93e06ad2bd5845e1bf/src/components/icons/index.ts) directory, where all icon components are stored. + +4. **Update the Icons Object**: + Ensure the new icons are included in the `Icons` object located in [src/constants/Icons](https://github.com/mParticle/aquarium/blob/3abc3b71916ab5a7db3c4f93e06ad2bd5845e1bf/src/constants/Icons.ts). This object is used across the project to reference all available icons. + +### The `IconTable` Component + +The `IconTable` component is used to display a table of all available icons in the project. + +### Using the `SVGPrettifier` Tool + +The `SVGPrettifier` component is used to upload and prettify SVG files. Each SVG must follow the project's styling guidelines and have a unique name to avoid overwriting existing icons. + +### Starting the Server + +To process the uploaded SVGs with the `SVGPrettifier`, a server must be running locally. Follow these steps to start the server: + +1. Navigate to the root of the project directory. +2. Run the following command to start the server: + + ```bash + npm run icons-prettifier + ``` diff --git a/package-lock.json b/package-lock.json index 5402962d4..9245ace68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "@storybook/react-vite": "8.1.10", "@storybook/test": "8.1.10", "@storybook/test-runner": "0.18.2", + "@types/jsdom": "21.1.7", + "@types/multer": "1.4.12", "@typescript-eslint/eslint-plugin": "6.19.0", "@vitejs/plugin-react": "4.2.1", "concurrently": "8.2.2", @@ -40,7 +42,8 @@ "factory.ts": "1.4.1", "http-server": "14.1.1", "husky": "9.0.11", - "jsdom": "24.0.0", + "jsdom": "24.1.3", + "multer": "1.4.5-lts.1", "prettier": "3.3.2", "storybook": "8.1.10", "stylelint": "16.2.0", @@ -3059,6 +3062,32 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/css-parser-algorithms": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.0.tgz", @@ -8058,6 +8087,38 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -8351,6 +8412,41 @@ "optional": true, "peer": true }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/jsdom/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@types/jsdom/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -8387,6 +8483,15 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "20.11.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", @@ -8482,6 +8587,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -9716,6 +9827,12 @@ "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", "dev": true }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "dev": true + }, "node_modules/append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -9734,6 +9851,14 @@ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -10497,6 +10622,18 @@ "node": ">=10" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -11292,6 +11429,57 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/concat-stream/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==", + "dev": true, + "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/concat-stream/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==", + "dev": true + }, + "node_modules/concat-stream/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/concurrently": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", @@ -11655,6 +11843,14 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -15496,9 +15692,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -18133,9 +18329,9 @@ } }, "node_modules/jsdom": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", - "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", "dev": true, "dependencies": { "cssstyle": "^4.0.1", @@ -18143,21 +18339,21 @@ "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.7", + "nwsapi": "^2.2.12", "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", + "tough-cookie": "^4.1.4", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.16.0", + "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -18220,6 +18416,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/jsdom/node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true + }, "node_modules/jsdom/node_modules/tr46": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", @@ -18792,6 +18994,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -19159,6 +19369,36 @@ "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", "dev": true }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dev": true, + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer/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==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -22195,9 +22435,9 @@ "peer": true }, "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", "dev": true }, "node_modules/nyc": { @@ -26455,6 +26695,15 @@ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "dev": true }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -27539,9 +27788,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "dependencies": { "psl": "^1.1.33", @@ -27611,6 +27860,90 @@ "node": ">=6.10" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -27775,6 +28108,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -28165,6 +28504,14 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", @@ -29233,9 +29580,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -29345,6 +29692,17 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 32001d346..d820f3a1b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "@storybook/react-vite": "8.1.10", "@storybook/test": "8.1.10", "@storybook/test-runner": "0.18.2", + "@types/jsdom": "21.1.7", + "@types/multer": "1.4.12", "@typescript-eslint/eslint-plugin": "6.19.0", "@vitejs/plugin-react": "4.2.1", "concurrently": "8.2.2", @@ -57,7 +59,8 @@ "factory.ts": "1.4.1", "http-server": "14.1.1", "husky": "9.0.11", - "jsdom": "24.0.0", + "jsdom": "24.1.3", + "multer": "1.4.5-lts.1", "prettier": "3.3.2", "storybook": "8.1.10", "stylelint": "16.2.0", @@ -90,7 +93,8 @@ "lint:fix": "concurrently \"eslint --ext .ts,.tsx \"src/**/*.{ts,tsx}\" --quiet --fix\" \"stylelint \"**/*.css\" --fix\"", "lint:staged": "eslint --quiet --fix --no-error-on-unmatched-pattern", "stylelint:staged": "stylelint --allow-empty-input --fix ", - "tokens-to-css": "npx style-dictionary build --config ./style-dictionary.json" + "tokens-to-css": "npx style-dictionary build --config ./style-dictionary.json", + "icons-prettifier": "node ./server/prettify-server.cjs" }, "lint-staged": { "*.{ts,tsx}": [ diff --git a/server/prettify-server.cjs b/server/prettify-server.cjs new file mode 100644 index 000000000..b89499357 --- /dev/null +++ b/server/prettify-server.cjs @@ -0,0 +1,88 @@ +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const multer = require('multer'); + +const { prettifySVG, savePrettifiedSVG } = require('../src/utils/svg-prettifier/prettifier.cjs'); // Ensure correct path + +const port = 8000; + +// Use memory storage for uploaded files +const storage = multer.memoryStorage(); +const upload = multer({ storage: storage }); + +const server = http.createServer((req, res) => { + // Add CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + if (req.method === 'OPTIONS') { + // Handle preflight requests + res.writeHead(204); + res.end(); + return; + } + + if (req.method === 'POST' && req.url === '/api') { + upload.array('files')(req, res, err => { + if (err) { + console.error('Error uploading file:', err); + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error uploading file'); + return; + } + + // Accumulate success or error messages + const results = []; + let hasErrors = false; // Flag to check if any file failed + + // Process each uploaded file + req.files.forEach(file => { + try { + const fileName = file.originalname; // Directly use the original filename + const content = file.buffer.toString('utf8'); + const prettifiedSVG = prettifySVG(content); + + // Destination path for the prettified SVG + const filePath = path.join(__dirname, '../src/assets/svg/', fileName); + + // Check if the file already exists + if (fs.existsSync(filePath)) { + results.push({ file: file.originalname, status: 'error', message: 'File already exists' }); + hasErrors = true; // Mark that there is an error + return; + } + + // Save the prettified SVG + savePrettifiedSVG(filePath, prettifiedSVG); + + // Add success message + results.push({ file: file.originalname, status: 'success' }); + } catch (error) { + console.error(`Error prettifying file ${file.originalname}:`, error); + hasErrors = true; // Mark that there is an error + + // Add error message + results.push({ file: file.originalname, status: 'error', message: error.message }); + } + }); + + // If there are errors, return a 400 Bad Request status + if (hasErrors) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + } else { + res.writeHead(200, { 'Content-Type': 'application/json' }); + } + + res.end(JSON.stringify(results)); // Send results as JSON + }); + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not found'); + } +}); + +server.listen(port, () => { + console.log(`Server is listening on port ${port}`); +}); \ No newline at end of file diff --git a/src/utils/svg-prettifier/SvgPrettfier.stories.tsx b/src/utils/svg-prettifier/SvgPrettfier.stories.tsx new file mode 100644 index 000000000..a9ef17729 --- /dev/null +++ b/src/utils/svg-prettifier/SvgPrettfier.stories.tsx @@ -0,0 +1,41 @@ +import { type Meta, type StoryObj } from '@storybook/react' +import { SVGPrettifier } from './SvgPrettifier' + +// SVG Prettifier Documentation +const svgPrettifierDocumentation = ` +### SVG Prettifier Documentation + +The \`SVGPrettifier\` component is used to upload and prettify SVG files. Each SVG must follow the project's styling guidelines and must have a **unique name** to avoid overwriting existing icons. +** This only works locally.** + +#### Starting the Server +To process the uploaded SVGs, a server must be running. You can start the server by following these steps: + +1. Navigate to the project root folder. +2. Run the following command to start the server: + \`\`\` #run + npm run icons-prettifier + \`\`\` +3. The server will start on **port 8000**. Ensure this port is available or update the port configuration in the server script. +4. The server handles the uploaded SVG files and saves the prettified versions to the designated folder. + +For more information on the icon component and usage, please refer to the [Icons Documentation](?path=/docs/aquarium-general-icons--documentation). +` +const meta: Meta = { + title: 'Aquarium/Other/SVGPrettifier', + component: SVGPrettifier, + parameters: { + docs: { + description: { + component: svgPrettifierDocumentation, + }, + }, + }, +} + +export default meta + +type Story = StoryObj + +// Primary story for SVG Prettifier +export const Primary: Story = {} diff --git a/src/utils/svg-prettifier/SvgPrettifier.tsx b/src/utils/svg-prettifier/SvgPrettifier.tsx new file mode 100644 index 000000000..f0f2ca76a --- /dev/null +++ b/src/utils/svg-prettifier/SvgPrettifier.tsx @@ -0,0 +1,141 @@ +import React, { useState } from 'react' +import { Upload, Button, List, Space } from 'src/components' +import type { UploadFile } from 'antd/es/upload/interface' + +type FileResult = { + file: string + status: 'success' | 'error' + message?: string // Optional message, only needed if there is an error +} + +export const SVGPrettifier = () => { + const [files, setFiles] = useState([]) // Specify the type for files + const [prettifying, setPrettifying] = useState(false) + const [statusMessage, setStatusMessage] = useState('') // State to hold success/error messages + const [statusType, setStatusType] = useState('') // State to hold success/error type (for styling) + const [fileResults, setFileResults] = useState([]) // Correctly typed file results + + const handleUpload = ({ fileList }: { fileList: UploadFile[] }) => { + setFiles(fileList ?? []) // Use nullish coalescing to ensure fileList is an array + + // Set status message below + const fileNames = (fileList ?? []).map(file => file.name).join(', ') + setStatusMessage(`Files uploaded successfully: ${fileNames}`) + setStatusType('success') // Set message type to success + } + + const handlePrettify = async () => { + if ((files ?? []).length === 0) { + setStatusMessage('No files to prettify') + setStatusType('error') + return + } + + const formData = new FormData() + ;(files ?? []).forEach(file => { + formData.append('files', file.originFileObj as File) + }) // Append all files + + setPrettifying(true) + setStatusMessage('') // Clear the message while processing + + try { + const response = await fetch('http://localhost:8000/api', { + method: 'POST', + body: formData, + }) + + const results: FileResult[] = await response.json() // Expecting an array of results + setFileResults(results) + + // Handle overall status message + if (!response.ok) { + // If 400 error, show a general error message + setStatusMessage('Some files could not be processed. See details below.') + setStatusType('error') + } else { + setStatusMessage('All files prettified successfully!') + setStatusType('success') + } + + // Clear the files from the UI if all were successful + const hasErrors = results.some((result: { status: string }) => result.status === 'error') + if (!hasErrors) setFiles([]) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred' + setStatusMessage(`Error prettifying the files: ${errorMessage}`) + setStatusType('error') + } finally { + setPrettifying(false) + } + } + + return ( +
+

Prettify Your Icons

+

+ Upload your SVG files and make them consistent with our styling guidelines. +

+ + + { + setFiles((files ?? []).filter(f => f.uid !== file.uid)) // Handle file removal + }} + showUploadList={true} // Show uploaded files automatically + style={{ width: '100%' }}> + + + + {files.length > 0 && ( + Uploaded Files
} + bordered + dataSource={files} + renderItem={file => {file.name}} + /> + )} + + {statusMessage && ( +
+ {statusMessage} +
+ )} + + {fileResults.length > 0 && ( + File Results} + bordered + dataSource={fileResults} + renderItem={result => ( + + {result.file}: {result.status === 'success' ? 'Prettified' : `Error - ${result.message}`} + + )} + /> + )} + + + + + ) +} diff --git a/src/utils/svg-prettifier/prettifier.cjs b/src/utils/svg-prettifier/prettifier.cjs new file mode 100644 index 000000000..62e3041cd --- /dev/null +++ b/src/utils/svg-prettifier/prettifier.cjs @@ -0,0 +1,93 @@ +const fs = require('fs') +const http = require('http') +const path = require('path') +const multer = require('multer') + +const { prettifySVG, savePrettifiedSVG } = require('../src/utils/svg-prettifier/prettifier.cjs') // Ensure correct path + +const port = 8000 + +// Use memory storage for uploaded files +const storage = multer.memoryStorage() +const upload = multer({ storage: storage }) + +// Custom filename sanitizer +function sanitizeFileName(filename) { + return filename.replace(/[^a-z0-9\.\-_]/gi, '_') // Replace any non-alphanumeric characters (except . - _) with an underscore +} + +const server = http.createServer((req, res) => { + // Add CORS headers + res.setHeader('Access-Control-Allow-Origin', '*') + res.setHeader('Access-Control-Allow-Methods', 'POST') + res.setHeader('Access-Control-Allow-Headers', 'Content-Type') + + if (req.method === 'OPTIONS') { + // Handle preflight requests + res.writeHead(204) + res.end() + return + } + + if (req.method === 'POST' && req.url === '/api') { + upload.array('files')(req, res, err => { + if (err) { + console.error('Error uploading file:', err) + res.writeHead(500, { 'Content-Type': 'text/plain' }) + res.end('Error uploading file') + return + } + + // Accumulate success or error messages + const results = [] + let hasErrors = false // Flag to check if any file failed + + // Process each uploaded file + req.files.forEach(file => { + try { + const safeFileName = sanitizeFileName(file.originalname) // Use custom sanitizer + const content = file.buffer.toString('utf8') + const prettifiedSVG = prettifySVG(content) + + // Destination path for the prettified SVG + const filePath = path.join(__dirname, '../src/assets/svg/', safeFileName) + + // Check if the file already exists + if (fs.existsSync(filePath)) { + results.push({ file: file.originalname, status: 'error', message: 'File already exists' }) + hasErrors = true // Mark that there is an error + return + } + + // Save the prettified SVG + savePrettifiedSVG(filePath, prettifiedSVG) + + // Add success message + results.push({ file: file.originalname, status: 'success' }) + } catch (error) { + console.error(`Error prettifying file ${file.originalname}:`, error) + hasErrors = true // Mark that there is an error + + // Add error message + results.push({ file: file.originalname, status: 'error', message: error.message }) + } + }) + + // If there are errors, return a 400 Bad Request status + if (hasErrors) { + res.writeHead(400, { 'Content-Type': 'application/json' }) + } else { + res.writeHead(200, { 'Content-Type': 'application/json' }) + } + + res.end(JSON.stringify(results)) // Send results as JSON + }) + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }) + res.end('Not found') + } +}) + +server.listen(port, () => { + console.log(`Server is listening on port ${port}`) +})