From b8a1f108deb982faa4383f57eb1cfc9cd1540e58 Mon Sep 17 00:00:00 2001 From: taoliujun Date: Fri, 19 Jan 2024 17:39:17 +0800 Subject: [PATCH 1/4] fix: update --- .gitignore | 1 + packages/parse/.eslintrc.js | 10 ++ packages/parse/package.json | 30 +++++ packages/parse/src/convert.ts | 124 ++++++++++++++++++ packages/parse/src/index.ts | 1 + packages/parse/src/react.ts | 108 +++++++++++++++ .../parse/src/template/react/component.tpl | 10 ++ packages/parse/src/template/react/helper.tpl | 20 +++ packages/parse/src/template/react/types.tpl | 7 + packages/parse/src/test.ts | 12 ++ packages/parse/tsconfig.json | 24 ++++ pnpm-lock.yaml | 80 ++++++++++- 12 files changed, 425 insertions(+), 2 deletions(-) create mode 100644 packages/parse/.eslintrc.js create mode 100644 packages/parse/package.json create mode 100644 packages/parse/src/convert.ts create mode 100644 packages/parse/src/index.ts create mode 100644 packages/parse/src/react.ts create mode 100644 packages/parse/src/template/react/component.tpl create mode 100644 packages/parse/src/template/react/helper.tpl create mode 100644 packages/parse/src/template/react/types.tpl create mode 100644 packages/parse/src/test.ts create mode 100644 packages/parse/tsconfig.json diff --git a/.gitignore b/.gitignore index 43741c2..927f1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ *.log packages/*/lib +packages/*/dist .eslintcache .DS_Store \ No newline at end of file diff --git a/packages/parse/.eslintrc.js b/packages/parse/.eslintrc.js new file mode 100644 index 0000000..d5ff062 --- /dev/null +++ b/packages/parse/.eslintrc.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + overrides: [ + { + files: ['**/*.ts', '**/*.tsx'], + parserOptions: { project: path.resolve(__dirname, './tsconfig.json') }, + }, + ], +}; diff --git a/packages/parse/package.json b/packages/parse/package.json new file mode 100644 index 0000000..979c1a8 --- /dev/null +++ b/packages/parse/package.json @@ -0,0 +1,30 @@ +{ + "name": "svg-to-component-parse", + "version": "1.0.0", + "main": "./dist/index.js", + "files": [ + "dist", + "/*.md", + "package.json" + ], + "scripts": { + "build": "rm -rf ./dist && tsc && npm run build:tpl", + "build:tpl": "cp -rf ./src/template ./dist" + }, + "engines": { + "node": "^20" + }, + "dependencies": { + "fast-xml-parser": "^4.2.2", + "lodash": "^4.17.21", + "prettier": "^2.8.8", + "validate-color": "^2.2.4" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.2", + "@types/lodash": "^4.14.195", + "@types/node": "^20.2.5", + "@types/prettier": "^2.7.3", + "@types/react": "^18.2.8" + } +} \ No newline at end of file diff --git a/packages/parse/src/convert.ts b/packages/parse/src/convert.ts new file mode 100644 index 0000000..828b5f8 --- /dev/null +++ b/packages/parse/src/convert.ts @@ -0,0 +1,124 @@ +import { XMLBuilder, XMLParser } from 'fast-xml-parser'; +import { clone } from 'lodash'; +import validateColor from 'validate-color'; + +type SVGTagNames = keyof SVGElementTagNameMap; +type SVGAttributes = Record; +type SVGObject = Record & { + ':@'?: SVGAttributes; +}; +type FormatSVGAttributes = (attributes: SVGAttributes) => SVGAttributes; + +const parser = new XMLParser({ + allowBooleanAttributes: true, + attributeNamePrefix: '', + ignoreAttributes: false, + preserveOrder: true, +}); + +const builder = new XMLBuilder({ + attributeNamePrefix: '', + ignoreAttributes: false, + suppressEmptyNode: true, + preserveOrder: true, +}); + +const rootKey = ':@'; +const colorAttributeKeys = ['fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color']; + +let originColors: string[] = []; + +// Extract the color +const formatColor = (attributes: SVGAttributes) => { + const output = clone(attributes); + + colorAttributeKeys.forEach((colorKey) => { + const colorValue = output[colorKey]; + if (colorValue && validateColor(colorValue)) { + originColors.push(colorValue); + Reflect.deleteProperty(output, colorKey); + output[`__prop__color__${colorKey}`] = `${colorValue}__${originColors.length}`; + } + }); + + return output; +}; + +// Loop the properties of the SVG +const organizeSvg = ( + obj: SVGObject[], + opt?: { + /** Ignore the processing of colors */ + ignoreColor?: boolean; + /** Handling of each element's attributes */ + formatSVGAttributes?: FormatSVGAttributes; + }, +) => { + const { ignoreColor = false, formatSVGAttributes } = opt || {}; + + if (!obj?.length) { + return obj; + } + + const newObj = obj.map((v) => { + const output: [string, unknown][] = []; + + if (v[rootKey]) { + // Handling colors + let newValue = v[rootKey]; + // Remove the width and height of the root element + if (v.svg) { + Reflect.deleteProperty(newValue, 'width'); + Reflect.deleteProperty(newValue, 'height'); + // Use em and font-size configuration to control dimensions + Reflect.set(newValue, 'width', '1em'); + } + + if (formatSVGAttributes) { + newValue = formatSVGAttributes(newValue); + } + + if (!ignoreColor) { + newValue = formatColor(newValue); + } + + output.push([rootKey, newValue]); + } + + Object.entries(v) + .filter(([key]) => { + return key !== rootKey; + }) + .forEach(([key, value]) => { + output.push([ + key, + organizeSvg(value as SVGObject[], { + ...opt, + // Don't handle the color in the mask element + ignoreColor: key === 'mask', + }), + ]); + }); + + return Object.fromEntries(output); + }); + + return newObj as typeof obj; +}; + +// Code string to xml object +const svgToObj = (content: string, opt?: Parameters[1]) => { + originColors = []; + const obj = parser.parse(content) as SVGObject[]; + const newObj = organizeSvg(obj, opt); + return newObj; +}; + +// XML object to code string +const objToSvg = (obj?: SVGObject[]): string => { + const tpl = builder.build(obj); + return tpl; +}; + +export { rootKey, svgToObj, objToSvg }; +export type { SVGObject, SVGAttributes, FormatSVGAttributes }; diff --git a/packages/parse/src/index.ts b/packages/parse/src/index.ts new file mode 100644 index 0000000..5d39580 --- /dev/null +++ b/packages/parse/src/index.ts @@ -0,0 +1 @@ +export * from './react'; diff --git a/packages/parse/src/react.ts b/packages/parse/src/react.ts new file mode 100644 index 0000000..f6da1dc --- /dev/null +++ b/packages/parse/src/react.ts @@ -0,0 +1,108 @@ +import { camelCase, clone } from 'lodash'; +import { format as prettierFormat } from 'prettier'; +import { readFileSync } from 'fs'; +import path from 'path'; +import type { FormatSVGAttributes } from './convert'; +import { objToSvg, rootKey, svgToObj } from './convert'; + +// generate utils +const generateComponentUtils: () => { + targetFilePath: string; + content: string; +}[] = () => { + return [ + ['./utils/helper.ts', './template/react/helper.tpl'], + ['./utils/types.ts', './template/react/types.tpl'], + ].map((v) => { + return { + targetFilePath: v[0], + content: readFileSync(path.resolve(__dirname, v[1]), { + encoding: 'utf-8', + }), + }; + }); +}; + +// generate component template string +const generateComponentCode = (componentName: string, content: string, colors: string[]) => { + const code = readFileSync(path.resolve(__dirname, './template/react/component.tpl'), { + encoding: 'utf-8', + }) + ?.replaceAll(`$componentName$`, componentName) + ?.replaceAll(`$content$`, content) + ?.replaceAll(`$colors$`, JSON.stringify(colors)); + + return code; +}; + +// Additional SVG attribute handling +const formatSVGAttributes: FormatSVGAttributes = (attributes) => { + const newValue = clone(attributes); + Object.keys(newValue).forEach((v1) => { + // CamelCase attribute name + const camelCaseKey = camelCase(v1); + if (camelCaseKey !== v1) { + newValue[camelCase(v1)] = newValue[v1]; + Reflect.deleteProperty(newValue, v1); + } + }); + + return newValue; +}; + +// To React +const generateReact = ( + componentName: string, + content: string, + opts?: { + isPreview?: boolean; + }, +) => { + const { isPreview = false } = opts || {}; + + const xmlObj = svgToObj(content, { + formatSVGAttributes, + }); + + if (xmlObj[0][rootKey]) { + Reflect.set(xmlObj[0][rootKey], '__props__', '{...props}'); + } + + let code = objToSvg(xmlObj); + + // inject props + code = code.replace(`__props__="{...props}"`, `{...props}`); + + const colors: string[] = []; + + // inject color + const colorMatchs = code.matchAll(/__prop__color__([^=]+)="(.+?)__([^"]+)"/g); + // eslint-disable-next-line no-restricted-syntax + for (const v of colorMatchs) { + const [matchColorOrigin, matchColorKey, matchColorValue, matchColorIndex] = v; + + let replaceCode = `${matchColorKey}={getColor(color, ${matchColorIndex}, '${matchColorValue}')}`; + if (isPreview) { + replaceCode += ` data-preview-color-${matchColorKey}-index="${matchColorIndex}"`; + replaceCode += ` data-preview-color-${matchColorKey}-value="${matchColorValue}"`; + } + code = code.replace(matchColorOrigin, replaceCode); + colors.push(matchColorValue); + } + + code = generateComponentCode(componentName, code, colors); + + code = prettierFormat(code, { + printWidth: 120, + tabWidth: 4, + semi: true, + singleQuote: true, + endOfLine: 'lf', + trailingComma: 'es5', + parser: 'babel-ts', + }); + + return code; +}; + +export { generateReact, generateComponentUtils }; diff --git a/packages/parse/src/template/react/component.tpl b/packages/parse/src/template/react/component.tpl new file mode 100644 index 0000000..5981113 --- /dev/null +++ b/packages/parse/src/template/react/component.tpl @@ -0,0 +1,10 @@ +import type { FC } from 'react'; +import { getColor } from '../utils/helper'; +import type { SVGIconProps } from '../utils/types'; + +export const $componentName$: FC = ({ color, ...props }) => { + return $content$; +}; +$componentName$.defaultProps = { + color: $colors$, +}; diff --git a/packages/parse/src/template/react/helper.tpl b/packages/parse/src/template/react/helper.tpl new file mode 100644 index 0000000..460c25c --- /dev/null +++ b/packages/parse/src/template/react/helper.tpl @@ -0,0 +1,20 @@ +const getColor = (color: string | string[] | undefined, index: number, defaultColor: string) => { + if (color === undefined) { + return defaultColor; + } + + const colorType = typeof color; + const colorLen = color?.length; + + if (colorType === 'string') { + return color as string; + } + + if (colorType === 'object' && colorLen) { + return color[index - 1] || defaultColor; + } + + return defaultColor; +}; + +export { getColor }; diff --git a/packages/parse/src/template/react/types.tpl b/packages/parse/src/template/react/types.tpl new file mode 100644 index 0000000..bf40c8d --- /dev/null +++ b/packages/parse/src/template/react/types.tpl @@ -0,0 +1,7 @@ +import type { SVGAttributes } from 'react'; + +interface SVGIconProps extends Omit, 'color'> { + color?: string | string[]; +} + +export type { SVGIconProps }; diff --git a/packages/parse/src/test.ts b/packages/parse/src/test.ts new file mode 100644 index 0000000..2e8c2ce --- /dev/null +++ b/packages/parse/src/test.ts @@ -0,0 +1,12 @@ +import { generateReact } from './react'; + +console.log( + generateReact( + 'UpOutlined', + ` + + + +`, + ), +); diff --git a/packages/parse/tsconfig.json b/packages/parse/tsconfig.json new file mode 100644 index 0000000..cfe1d01 --- /dev/null +++ b/packages/parse/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "lib": [ + "es2023", + "DOM", + ], + "outDir": "./dist", + "declaration": true, + // some lints + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noUnusedLocals": true + }, + "exclude": [ + "node_modules", + "dist" + ], + "include": [ + "./src/**/*.ts" + ] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed9abc3..9959f39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,37 @@ importers: specifier: ^5 version: 5.3.3 + packages/parse: + dependencies: + fast-xml-parser: + specifier: ^4.2.2 + version: 4.3.3 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + prettier: + specifier: ^2.8.8 + version: 2.8.8 + validate-color: + specifier: ^2.2.4 + version: 2.2.4 + devDependencies: + '@tsconfig/node20': + specifier: ^20.1.2 + version: 20.1.2 + '@types/lodash': + specifier: ^4.14.195 + version: 4.14.202 + '@types/node': + specifier: ^20.2.5 + version: 20.11.3 + '@types/prettier': + specifier: ^2.7.3 + version: 2.7.3 + '@types/react': + specifier: ^18.2.8 + version: 18.2.48 + packages: /@aashutoshrathi/word-wrap@1.2.6: @@ -771,6 +802,10 @@ packages: - typescript dev: true + /@tsconfig/node20@20.1.2: + resolution: {integrity: sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==} + dev: true + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true @@ -779,6 +814,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/lodash@4.14.202: + resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + dev: true + /@types/minimist@1.2.5: resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} dev: true @@ -797,6 +836,26 @@ packages: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true + /@types/prettier@2.7.3: + resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + dev: true + + /@types/prop-types@15.7.11: + resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} + dev: true + + /@types/react@18.2.48: + resolution: {integrity: sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==} + dependencies: + '@types/prop-types': 15.7.11 + '@types/scheduler': 0.16.8 + csstype: 3.1.3 + dev: true + + /@types/scheduler@0.16.8: + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + dev: true + /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true @@ -1393,6 +1452,10 @@ packages: which: 2.0.2 dev: true + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + /csv-generate@3.4.3: resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} dev: true @@ -2037,6 +2100,13 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fast-xml-parser@4.3.3: + resolution: {integrity: sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.16.0: resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} dependencies: @@ -2860,7 +2930,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true /log-update@6.0.0: resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} @@ -3309,7 +3378,6 @@ packages: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - dev: true /prettier@3.2.2: resolution: {integrity: sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==} @@ -3804,6 +3872,10 @@ packages: engines: {node: '>=8'} dev: true + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4054,6 +4126,10 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true + /validate-color@2.2.4: + resolution: {integrity: sha512-Znolz+b6CwW6eBXYld7MFM3O7funcdyRfjKC/X9hqYV/0VcC5LB/L45mff7m3dIn9wdGdNOAQ/fybNuD5P/HDw==} + dev: false + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: From dc2e7e94b4e57f1153c52eada6aad35d5e594b92 Mon Sep 17 00:00:00 2001 From: taoliujun Date: Fri, 19 Jan 2024 18:02:36 +0800 Subject: [PATCH 2/4] fix: update --- .eslintignore | 4 - package.json | 2 +- .eslintrc.js => packages/cli/.eslintrc.js | 3 +- packages/cli/README.md | 1 + packages/cli/package.json | 51 + packages/cli/src/index.ts | 11 + packages/cli/src/log.ts | 6 + packages/cli/src/parse.ts | 110 + packages/cli/src/preview.ts | 40 + packages/cli/tsconfig.json | 20 + packages/parse/.eslintrc.js | 10 +- packages/parse/package.json | 4 +- packages/preview/.env | 1 + packages/preview/.env.prod | 1 + packages/preview/.eslintignore | 3 + packages/preview/.eslintrc.js | 3 + packages/preview/.prettierrc | 7 + packages/preview/babel.config.json | 4 + .../preview/config/utils/getWebpackEntries.js | 68 + packages/preview/config/utils/resolve.js | 10 + packages/preview/config/webpack.analyzer.js | 30 + packages/preview/config/webpack.base.js | 148 + packages/preview/config/webpack.dev.js | 27 + packages/preview/config/webpack.prod.js | 18 + packages/preview/config/webpack.test.js | 18 + packages/preview/package.json | 57 + packages/preview/postcss.config.js | 3 + packages/preview/script/dist/index.d.ts | 3 + packages/preview/script/dist/index.js | 13 + packages/preview/script/src/index.ts | 9 + packages/preview/script/tsconfig.json | 20 + packages/preview/src/.gitignore | 1 + packages/preview/src/assets/global.css | 4 + packages/preview/src/autoload.ts | 1 + packages/preview/src/global.d.ts | 57 + packages/preview/src/index.html | 14 + packages/preview/src/index.tsx | 5 + .../src/pages/index/components/Code/index.tsx | 43 + .../index/components/ColorPicker/index.tsx | 92 + .../Colors/components/ActiveMask/index.tsx | 25 + .../Colors/components/ColorControl/index.tsx | 261 + .../Colors/components/Mode/index.tsx | 41 + .../Colors/components/Store/index.tsx | 44 + .../pages/index/components/Colors/index.tsx | 46 + .../pages/index/components/Header/index.tsx | 9 + .../index/components/PageStore/index.tsx | 62 + .../pages/index/components/Preview/index.tsx | 129 + .../pages/index/components/SvgList/index.tsx | 55 + packages/preview/src/pages/index/index.tsx | 47 + packages/preview/tailwind.config.js | 20 + packages/preview/tsconfig.json | 18 + pnpm-lock.yaml | 4253 ++++++++++++++++- 52 files changed, 5663 insertions(+), 269 deletions(-) delete mode 100644 .eslintignore rename .eslintrc.js => packages/cli/.eslintrc.js (69%) create mode 100644 packages/cli/README.md create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/src/log.ts create mode 100644 packages/cli/src/parse.ts create mode 100644 packages/cli/src/preview.ts create mode 100644 packages/cli/tsconfig.json create mode 100644 packages/preview/.env create mode 100644 packages/preview/.env.prod create mode 100644 packages/preview/.eslintignore create mode 100644 packages/preview/.eslintrc.js create mode 100644 packages/preview/.prettierrc create mode 100644 packages/preview/babel.config.json create mode 100644 packages/preview/config/utils/getWebpackEntries.js create mode 100644 packages/preview/config/utils/resolve.js create mode 100644 packages/preview/config/webpack.analyzer.js create mode 100644 packages/preview/config/webpack.base.js create mode 100644 packages/preview/config/webpack.dev.js create mode 100644 packages/preview/config/webpack.prod.js create mode 100644 packages/preview/config/webpack.test.js create mode 100644 packages/preview/package.json create mode 100644 packages/preview/postcss.config.js create mode 100644 packages/preview/script/dist/index.d.ts create mode 100644 packages/preview/script/dist/index.js create mode 100644 packages/preview/script/src/index.ts create mode 100644 packages/preview/script/tsconfig.json create mode 100644 packages/preview/src/.gitignore create mode 100644 packages/preview/src/assets/global.css create mode 100644 packages/preview/src/autoload.ts create mode 100644 packages/preview/src/global.d.ts create mode 100644 packages/preview/src/index.html create mode 100644 packages/preview/src/index.tsx create mode 100644 packages/preview/src/pages/index/components/Code/index.tsx create mode 100644 packages/preview/src/pages/index/components/ColorPicker/index.tsx create mode 100644 packages/preview/src/pages/index/components/Colors/components/ActiveMask/index.tsx create mode 100644 packages/preview/src/pages/index/components/Colors/components/ColorControl/index.tsx create mode 100644 packages/preview/src/pages/index/components/Colors/components/Mode/index.tsx create mode 100644 packages/preview/src/pages/index/components/Colors/components/Store/index.tsx create mode 100644 packages/preview/src/pages/index/components/Colors/index.tsx create mode 100644 packages/preview/src/pages/index/components/Header/index.tsx create mode 100644 packages/preview/src/pages/index/components/PageStore/index.tsx create mode 100644 packages/preview/src/pages/index/components/Preview/index.tsx create mode 100644 packages/preview/src/pages/index/components/SvgList/index.tsx create mode 100644 packages/preview/src/pages/index/index.tsx create mode 100644 packages/preview/tailwind.config.js create mode 100644 packages/preview/tsconfig.json diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1ab8463..0000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -lib/ -!/.github -/.github/**/dist \ No newline at end of file diff --git a/package.json b/package.json index 3f93c19..0a3017e 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "release": "npm exec changeset publish", "release:token": "cp -f .npmrc .npmrc.bak && echo \"\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\" >> .npmrc && npm exec changeset publish && cp -f .npmrc.bak .npmrc && rm .npmrc.bak", "build": "pnpm run -r build", + "lint": "pnpm run -r lint", "lint:ts": "pnpm run -r lint:ts", - "lint": "eslint packages --ext .ts,.js --cache", "test": "echo 'test'", "prepare": "husky install" } diff --git a/.eslintrc.js b/packages/cli/.eslintrc.js similarity index 69% rename from .eslintrc.js rename to packages/cli/.eslintrc.js index e472619..81f5f86 100644 --- a/.eslintrc.js +++ b/packages/cli/.eslintrc.js @@ -2,8 +2,7 @@ module.exports = { extends: ['@taoliujun/eslint-config'], overrides: [ { - // page store components are temporarily unused - files: ['./packages/*/src/index.ts'], + files: ['./src/index.ts'], rules: { 'import/no-unused-modules': ['off'], 'import/no-default-export': ['off'], diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..a61e081 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1 @@ +# svg-to-component \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..df330de --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,51 @@ +{ + "name": "svg-to-component", + "version": "1.0.0", + "description": "parse svg files into react components, support dynamic colors and preview.", + "keywords": [ + "svg", + "parse svg", + "svg to component", + "react svg" + ], + "homepage": "https://github.com/taoliujun/svg-to-component", + "repository": { + "type": "git", + "url": "https://github.com/taoliujun/svg-to-component.git" + }, + "bugs": { + "url": "https://github.com/taoliujun/svg-to-component/issues" + }, + "files": [ + "dist", + "/*.md", + "package.json" + ], + "main": "./dist/index.js", + "bin": { + "svg-to-component": "./dist/index.js" + }, + "scripts": { + "lint": "eslint --cache src", + "lint:ts": "tsc --noEmit", + "build": "rm -rf ./dist && tsc" + }, + "engines": { + "node": "^20" + }, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^11.1.0", + "fast-glob": "^3.3.2", + "lodash": "^4.17.21", + "progress": "^2.0.3", + "svg-to-component.parse": "workspace:^", + "svg-to-component.preview": "workspace:^" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.2", + "@types/lodash": "^4.14.195", + "@types/node": "^20", + "@types/progress": "^2.0.7" + } +} \ No newline at end of file diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 0000000..6ad0691 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,11 @@ +import { Command } from 'commander'; +import { commandParse } from './parse'; +import { commandPreview } from './preview'; + +const program = new Command(); + +program.name('svg-to-component').description('svg file parse and preview.').version('1.0.0'); +program.addCommand(commandParse); +program.addCommand(commandPreview); + +program.parse(); diff --git a/packages/cli/src/log.ts b/packages/cli/src/log.ts new file mode 100644 index 0000000..4fb61b1 --- /dev/null +++ b/packages/cli/src/log.ts @@ -0,0 +1,6 @@ +import chalk from 'chalk'; + +export const log = console.log; +export const outputMain = chalk.white; +export const outputSecond = chalk.gray; +export const outputSuccess = chalk.green; diff --git a/packages/cli/src/parse.ts b/packages/cli/src/parse.ts new file mode 100644 index 0000000..0b2735d --- /dev/null +++ b/packages/cli/src/parse.ts @@ -0,0 +1,110 @@ +import path, { dirname } from 'path'; +import fs from 'fs'; +import { glob } from 'fast-glob'; +import { Command } from 'commander'; +import Progress from 'progress'; +import { camelCase, upperFirst } from 'lodash'; +import { generateReact, generateComponentUtils } from 'svg-to-component.parse'; +import { log, outputMain, outputSecond, outputSuccess } from './log'; + +const cwdPath = path.resolve(); + +type Template = 'react' | 'vue'; + +interface GenerateParams { + sourcePath: string; + outputPath: string; + template?: Template; + debug?: boolean; + isPreview?: boolean; +} + +const generateUtils = (opts: GenerateParams) => { + const { outputPath } = opts; + + log(outputSecond(`> Generate util files:`)); + + const utilFiles = generateComponentUtils(); + utilFiles.forEach((v) => { + const { targetFilePath, content } = v; + const file = path.resolve(outputPath, targetFilePath); + fs.mkdirSync(dirname(file), { recursive: true }); + fs.writeFileSync(file, content, { + encoding: 'utf-8', + }); + log(outputMain(file)); + }); +}; + +const generateComponentFiles = async (opts: GenerateParams) => { + const { sourcePath, outputPath, template = 'react', debug = false, isPreview = false } = opts; + + log(outputSecond('> Origin SVG files path:'), outputMain(sourcePath)); + + const files = await glob(path.resolve(sourcePath, './**/*.svg')); + const filesLen = files.length; + + log(outputSecond(`> Total files:`, outputMain.bold(filesLen))); + + const bar = new Progress(':bar :percent ', { total: filesLen }); + + files.forEach((inputFile, index) => { + const fileInfo = path.parse(inputFile); + const relativePath = fileInfo.dir.replace(sourcePath, ''); + const content = fs.readFileSync(inputFile, 'utf-8'); + + let outputFileName = ''; + let outputFilePath = ''; + let outputFile = ''; + let outputContent = ''; + + if (template === 'react') { + outputFileName = upperFirst(camelCase(fileInfo.name)); + outputFilePath = path.resolve(outputPath, `./${relativePath}`, outputFileName); + outputFile = path.resolve(outputFilePath, `index.tsx`); + outputContent = generateReact(outputFileName, content, { + isPreview, + }); + } + + fs.mkdirSync(outputFilePath, { recursive: true }); + fs.writeFileSync(outputFile, outputContent, { + encoding: 'utf-8', + }); + + if (debug) { + log(`[${index + 1}/${filesLen}]`, outputMain.bold(fileInfo.base)); + log(outputSecond('source file:'), outputMain(inputFile)); + log(outputSecond('output file:'), outputMain(outputFile)); + log(`\n`); + log(outputSecond(outputContent)); + log(`\n`); + } else { + bar.tick(); + } + }); + + bar.terminate(); + + generateUtils(opts); + + log(`\n`); + log(outputSuccess(`> All done, the output ${template} components path: `), outputMain(outputPath)); +}; + +const program = new Command('parse') + .description('CLI to parse SVG files to component files') + .argument('path ', 'the directory of the SVG files') + .option('-t, --template ', 'the component type, supports react', 'react') + .option('-o, --output ', 'the output directory of the component files', './svg-component-output') + .option('--debug', 'output the debug log') + .action(async (args, opts) => { + await generateComponentFiles({ + sourcePath: path.resolve(cwdPath, args), + outputPath: path.resolve(cwdPath, opts.output), + template: opts.template, + debug: opts.debug, + }); + }); + +export { program as commandParse, generateComponentFiles }; diff --git a/packages/cli/src/preview.ts b/packages/cli/src/preview.ts new file mode 100644 index 0000000..5da5bec --- /dev/null +++ b/packages/cli/src/preview.ts @@ -0,0 +1,40 @@ +import path from 'path'; +import { Command } from 'commander'; +import { packagePath as previewPackagePath, componentsPath } from 'svg-to-component.preview'; +import { log, outputMain } from './log'; +import { generateComponentFiles } from './parse'; +import { spawn } from 'child_process'; + +const cwdPath = path.resolve(); + +const program = new Command('preview') + .description('CLI to preview the svg files, and use the coloring tool') + .argument('path ', 'the directory of the SVG files') + .option('--debug', 'output the debug log') + .action(async (args, opts) => { + await generateComponentFiles({ + sourcePath: path.resolve(cwdPath, args), + outputPath: componentsPath, + template: 'react', + debug: opts.debug, + isPreview: true, + }); + + log(outputMain(`> run ${previewPackagePath} server...`)); + + const wp = spawn(`pnpm`, ['run', 'dev'], { + cwd: previewPackagePath, + stdio: 'inherit', + }); + wp.stdout?.on('data', (data) => { + process.stdout.write(data); + }); + wp.stderr?.on('data', (data) => { + process.stderr.write(data); + }); + wp.on('exit', (code) => { + log(code); + }); + }); + +export const commandPreview = program; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..4cfaecf --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + // some lints + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noUnusedLocals": true + }, + "exclude": [ + "node_modules", + "dist" + ], + "include": [ + "./src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/parse/.eslintrc.js b/packages/parse/.eslintrc.js index d5ff062..81f5f86 100644 --- a/packages/parse/.eslintrc.js +++ b/packages/parse/.eslintrc.js @@ -1,10 +1,12 @@ -const path = require('path'); - module.exports = { + extends: ['@taoliujun/eslint-config'], overrides: [ { - files: ['**/*.ts', '**/*.tsx'], - parserOptions: { project: path.resolve(__dirname, './tsconfig.json') }, + files: ['./src/index.ts'], + rules: { + 'import/no-unused-modules': ['off'], + 'import/no-default-export': ['off'], + }, }, ], }; diff --git a/packages/parse/package.json b/packages/parse/package.json index 979c1a8..b0881d5 100644 --- a/packages/parse/package.json +++ b/packages/parse/package.json @@ -1,5 +1,5 @@ { - "name": "svg-to-component-parse", + "name": "svg-to-component.parse", "version": "1.0.0", "main": "./dist/index.js", "files": [ @@ -8,6 +8,8 @@ "package.json" ], "scripts": { + "lint": "eslint --cache src", + "lint:ts": "tsc --noEmit", "build": "rm -rf ./dist && tsc && npm run build:tpl", "build:tpl": "cp -rf ./src/template ./dist" }, diff --git a/packages/preview/.env b/packages/preview/.env new file mode 100644 index 0000000..31683c7 --- /dev/null +++ b/packages/preview/.env @@ -0,0 +1 @@ +ENV=test \ No newline at end of file diff --git a/packages/preview/.env.prod b/packages/preview/.env.prod new file mode 100644 index 0000000..190a694 --- /dev/null +++ b/packages/preview/.env.prod @@ -0,0 +1 @@ +ENV=prod \ No newline at end of file diff --git a/packages/preview/.eslintignore b/packages/preview/.eslintignore new file mode 100644 index 0000000..3234d60 --- /dev/null +++ b/packages/preview/.eslintignore @@ -0,0 +1,3 @@ +global.d.ts +*.js +script/src \ No newline at end of file diff --git a/packages/preview/.eslintrc.js b/packages/preview/.eslintrc.js new file mode 100644 index 0000000..c46fcab --- /dev/null +++ b/packages/preview/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@taoliujun/eslint-config/react'], +}; diff --git a/packages/preview/.prettierrc b/packages/preview/.prettierrc new file mode 100644 index 0000000..5ace72f --- /dev/null +++ b/packages/preview/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 4 +} diff --git a/packages/preview/babel.config.json b/packages/preview/babel.config.json new file mode 100644 index 0000000..fb930aa --- /dev/null +++ b/packages/preview/babel.config.json @@ -0,0 +1,4 @@ +{ + "sourceType": "unambiguous", + "presets": [["react-app", { "flow": false, "typescript": true, "runtime": "automatic" }]] +} diff --git a/packages/preview/config/utils/getWebpackEntries.js b/packages/preview/config/utils/getWebpackEntries.js new file mode 100644 index 0000000..c2fe0c6 --- /dev/null +++ b/packages/preview/config/utils/getWebpackEntries.js @@ -0,0 +1,68 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * + * @param root {string} + * @returns {string[]} + */ +const getHTMLFiles = (root) => { + /** + * @type {string[]} + */ + const result = []; + /** + * + * @param file {string} + */ + const getFile = (file) => { + if (fs.statSync(file).isDirectory()) { + const children = fs.readdirSync(file); + children.forEach((item) => { + getFile(path.join(file, item)); + }); + return; + } + + if (path.extname(file) === '.html') { + result.push(file); + } + }; + getFile(root); + return result; +}; + +const entryExtensions = ['.js', '.ts', '.jsx', '.tsx']; +/** + * + * @param htmlFile {string} + * @returns {string} + */ +const getEntryByHTMLFile = (htmlFile) => { + let result = ''; + entryExtensions.some((ext) => { + const file = htmlFile.replace('.html', ext); + if (fs.existsSync(file)) { + result = file; + return true; + } + return false; + }); + return result; +}; + +/** + * + * @param rootPath {string} + * @returns {{entry: string, entryName: string, html: string}[]} + */ +const getWebpackEntries = (rootPath) => { + const files = getHTMLFiles(rootPath); + return files.map((file) => ({ + entryName: path.relative(rootPath, file).replace('.html', ''), + entry: getEntryByHTMLFile(file), + html: file, + })); +}; + +module.exports = { getWebpackEntries }; diff --git a/packages/preview/config/utils/resolve.js b/packages/preview/config/utils/resolve.js new file mode 100644 index 0000000..74d376c --- /dev/null +++ b/packages/preview/config/utils/resolve.js @@ -0,0 +1,10 @@ +const path = require('path'); + +/** + * @param name {string} + * @returns {string} + */ +const resolve = (name) => { + return path.resolve(__dirname, '../..', name); +}; +module.exports = { resolve }; diff --git a/packages/preview/config/webpack.analyzer.js b/packages/preview/config/webpack.analyzer.js new file mode 100644 index 0000000..4c0478c --- /dev/null +++ b/packages/preview/config/webpack.analyzer.js @@ -0,0 +1,30 @@ +const DotenvWebpackPlugin = require('dotenv-webpack'); +const { WebpackConfiguration } = require('webpack-dev-server'); +const { resolve } = require('./utils/resolve'); +const { generate } = require('./webpack.base'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +const base = generate(false); +const basePlugins = base.plugins; + +/** + * + * @type {WebpackConfiguration} + */ +const config = { + ...base, + devServer: { + static: { + directory: resolve('dist'), + }, + compress: true, + host: 'local-ipv4', + port: 8080, + open: true, + hot: true, + historyApiFallback: true, + }, + plugins: [...basePlugins, new DotenvWebpackPlugin({ path: resolve('.env') }), new BundleAnalyzerPlugin()], +}; + +module.exports = config; diff --git a/packages/preview/config/webpack.base.js b/packages/preview/config/webpack.base.js new file mode 100644 index 0000000..890fd88 --- /dev/null +++ b/packages/preview/config/webpack.base.js @@ -0,0 +1,148 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const { getWebpackEntries } = require('./utils/getWebpackEntries'); +const { resolve } = require('./utils/resolve'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); + +const path = require('path'); +const os = require('os'); +const clientRoot = resolve('src'); + +/** + * genrate webpack config + * @param isOptimization {boolean} + * @returns {import("webpack-dev-server").WebpackConfiguration} + */ +const generate = (isOptimization = false) => { + console.log(`webpack build, isOptimization:${isOptimization}`); + /** + * @type {import("webpack").EntryObject} + */ + const entries = {}; + /** + * @type {HtmlWebpackPlugin[]} + */ + const htmlPlugins = []; + + // vendor entry + entries['vendor'] = ['react', 'react-dom']; + + // html entries + const rawEntries = getWebpackEntries(clientRoot); + console.log('entry', rawEntries); + rawEntries.forEach((entry) => { + const entryName = entry.entryName; + entries[entryName] = { + import: [`${clientRoot}/autoload.ts`, entry.entry], + dependOn: 'vendor', + }; + + /** + * @type {import('html-webpack-plugin'.Options)} + */ + const htmlPluginOptions = { + inject: true, + filename: `${entry.entryName}.html`, + template: entry.html, + chunks: entryName ? ['vendor', entryName] : [], + publicPath: '/', + }; + htmlPlugins.push(new HtmlWebpackPlugin(htmlPluginOptions)); + }); + + // html plugins + return { + mode: isOptimization ? 'production' : 'development', + devtool: isOptimization ? false : 'source-map', + entry: entries, + output: { + path: resolve('dist'), + filename: isOptimization ? '[name].[contenthash:8].js' : '[name].bundle.js', + chunkFilename: isOptimization ? '[name].[contenthash:8].chunk.js' : '[name].chunk.js', + assetModuleFilename: '[name].[contenthash:8][ext][query]', + }, + plugins: [ + ...htmlPlugins, + new MiniCssExtractPlugin({ + filename: 'css/[name].[contenthash:8].css', + chunkFilename: 'css/[id][name].[contenthash:8].chunk.css', + }), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, '../src/'), + }, + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + module: { + rules: [ + { + test: /\.(svg|png|jpg|jpeg|gif|woff|woff2|eot|ttf|mp3|webp)$/, + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: 1024, // 1kb + }, + }, + }, + { + test: /\.(js|jsx|ts|tsx)$/, + loader: 'babel-loader', + resolve: { + fullySpecified: false, + }, + options: { + cacheDirectory: true, + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'], + include: [resolve('src/assets/global.css'), resolve('node_modules')], + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + esModule: false, + }, + }, + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + localIdentName: '[name]_[local]-[hash:8]', + }, + }, + }, + 'postcss-loader', + ], + include: [clientRoot], + exclude: [resolve('src/assets/global.css')], + }, + ], + }, + optimization: { + minimize: isOptimization, + minimizer: isOptimization + ? [ + new TerserPlugin({ + parallel: os.cpus().length, + extractComments: false, + terserOptions: { + output: { + comments: false, + }, + mangle: true, + }, + }), + ] + : undefined, + }, + }; +}; + +module.exports = { generate }; diff --git a/packages/preview/config/webpack.dev.js b/packages/preview/config/webpack.dev.js new file mode 100644 index 0000000..43c4b18 --- /dev/null +++ b/packages/preview/config/webpack.dev.js @@ -0,0 +1,27 @@ +const DotenvWebpackPlugin = require('dotenv-webpack'); +const { WebpackConfiguration } = require('webpack-dev-server'); +const { resolve } = require('./utils/resolve'); +const { generate } = require('./webpack.base'); + +const base = generate(false); +const basePlugins = base.plugins; + +/** + * @type {WebpackConfiguration} + */ +const config = { + ...base, + devServer: { + static: { + directory: resolve('dist'), + }, + compress: true, + open: true, + hot: true, + historyApiFallback: true, + port: 9000 + }, + plugins: [...basePlugins, new DotenvWebpackPlugin({ path: resolve('.env') })], +}; + +module.exports = config; diff --git a/packages/preview/config/webpack.prod.js b/packages/preview/config/webpack.prod.js new file mode 100644 index 0000000..4fb3377 --- /dev/null +++ b/packages/preview/config/webpack.prod.js @@ -0,0 +1,18 @@ +const DotenvWebpackPlugin = require('dotenv-webpack'); +const { WebpackConfiguration } = require('webpack-dev-server'); +const { resolve } = require('./utils/resolve'); +const { generate } = require('./webpack.base'); + +const base = generate(true); +const basePlugins = base.plugins; + +/** + * + * @type {WebpackConfiguration} + */ +const config = { + ...base, + plugins: [...basePlugins, new DotenvWebpackPlugin({ path: resolve('.env.prod') })], +}; + +module.exports = config; diff --git a/packages/preview/config/webpack.test.js b/packages/preview/config/webpack.test.js new file mode 100644 index 0000000..201db69 --- /dev/null +++ b/packages/preview/config/webpack.test.js @@ -0,0 +1,18 @@ +const DotenvWebpackPlugin = require('dotenv-webpack'); +const { WebpackConfiguration } = require('webpack-dev-server'); +const { resolve } = require('./utils/resolve'); +const { generate } = require('./webpack.base'); + +const base = generate(true); +const basePlugins = base.plugins; + +/** + * + * @type {WebpackConfiguration} + */ +const config = { + ...base, + plugins: [...basePlugins, new DotenvWebpackPlugin({ path: resolve('.env') })], +}; + +module.exports = config; diff --git a/packages/preview/package.json b/packages/preview/package.json new file mode 100644 index 0000000..367197b --- /dev/null +++ b/packages/preview/package.json @@ -0,0 +1,57 @@ +{ + "name": "svg-to-component.preview", + "version": "1.0.0", + "main": "./script/dist/index.js", + "scripts": { + "lint": "eslint --cache src", + "lint:ts": "tsc --noEmit", + "clean": "rm -rf dist/", + "dev": "NODE_ENV=development webpack serve --config config/webpack.dev.js", + "build:test": "npm run clean && NODE_ENV=production webpack --config config/webpack.test.js", + "build:prod": "npm run clean && NODE_ENV=production webpack --config config/webpack.prod.js", + "build:no need to build react app": "pnpm run build:prod && pnpm run build:script", + "build": "pnpm run build:script", + "build:script": "cd script && tsc" + }, + "engines": { + "node": "^20", + "pnpm": ">=8" + }, + "dependencies": { + "@tsconfig/create-react-app": "^2.0.1", + "@tsconfig/node20": "^20.1.2", + "@types/lodash": "^4.14.195", + "@types/node": "^20.2.5", + "@types/react": "^18.2.8", + "@types/react-color": "^3.0.6", + "@types/react-dom": "^18.2.4", + "@types/webpack-env": "^1.18.4", + "ahooks": "^3.7.8", + "autoprefixer": "^10.4.14", + "babel-loader": "^9.1.2", + "babel-preset-react-app": "^10.0.1", + "classnames": "^2.3.2", + "css-loader": "^6.8.1", + "daisyui": "^4.0.4", + "dotenv-webpack": "^8.0.1", + "html-webpack-plugin": "^5.5.1", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.7.6", + "postcss": "^8.4.24", + "postcss-loader": "^7.3.2", + "react": "^18.2.0", + "react-color": "^2.19.3", + "react-dom": "^18.2.0", + "react-resizable-panels": "^0.0.57", + "tailwindcss": "^3.3.2", + "terser-webpack-plugin": "^5.3.9", + "typescript": "^5.1.3", + "webpack": "^5.85.0", + "webpack-cli": "^5.1.3", + "webpack-dev-server": "^4.15.0" + }, + "browserslist": [ + "> 2%", + "last 2 versions" + ] +} \ No newline at end of file diff --git a/packages/preview/postcss.config.js b/packages/preview/postcss.config.js new file mode 100644 index 0000000..3e3d521 --- /dev/null +++ b/packages/preview/postcss.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: [require('tailwindcss'), require('autoprefixer')], +}; diff --git a/packages/preview/script/dist/index.d.ts b/packages/preview/script/dist/index.d.ts new file mode 100644 index 0000000..d9c88c0 --- /dev/null +++ b/packages/preview/script/dist/index.d.ts @@ -0,0 +1,3 @@ +declare const packagePath: string; +declare const componentsPath: string; +export { packagePath, componentsPath }; diff --git a/packages/preview/script/dist/index.js b/packages/preview/script/dist/index.js new file mode 100644 index 0000000..d081827 --- /dev/null +++ b/packages/preview/script/dist/index.js @@ -0,0 +1,13 @@ +"use strict"; +// +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.componentsPath = exports.packagePath = void 0; +const path_1 = __importDefault(require("path")); +const packagePath = path_1.default.resolve(__dirname, '../..'); +exports.packagePath = packagePath; +const sourcePath = path_1.default.join(packagePath, './src'); +const componentsPath = path_1.default.resolve(sourcePath, './svgComponents'); +exports.componentsPath = componentsPath; diff --git a/packages/preview/script/src/index.ts b/packages/preview/script/src/index.ts new file mode 100644 index 0000000..7843e63 --- /dev/null +++ b/packages/preview/script/src/index.ts @@ -0,0 +1,9 @@ +// + +import path from 'path'; + +const packagePath = path.resolve(__dirname, '../..'); +const sourcePath = path.join(packagePath, './src'); +const componentsPath = path.resolve(sourcePath, './svgComponents'); + +export { packagePath, componentsPath }; diff --git a/packages/preview/script/tsconfig.json b/packages/preview/script/tsconfig.json new file mode 100644 index 0000000..4cfaecf --- /dev/null +++ b/packages/preview/script/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + // some lints + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noUnusedLocals": true + }, + "exclude": [ + "node_modules", + "dist" + ], + "include": [ + "./src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/preview/src/.gitignore b/packages/preview/src/.gitignore new file mode 100644 index 0000000..092f13d --- /dev/null +++ b/packages/preview/src/.gitignore @@ -0,0 +1 @@ +svgComponents/ \ No newline at end of file diff --git a/packages/preview/src/assets/global.css b/packages/preview/src/assets/global.css new file mode 100644 index 0000000..66505e4 --- /dev/null +++ b/packages/preview/src/assets/global.css @@ -0,0 +1,4 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +@tailwind screens; diff --git a/packages/preview/src/autoload.ts b/packages/preview/src/autoload.ts new file mode 100644 index 0000000..048c54d --- /dev/null +++ b/packages/preview/src/autoload.ts @@ -0,0 +1 @@ +import './assets/global.css'; diff --git a/packages/preview/src/global.d.ts b/packages/preview/src/global.d.ts new file mode 100644 index 0000000..a77efb5 --- /dev/null +++ b/packages/preview/src/global.d.ts @@ -0,0 +1,57 @@ +/// +/// +/// + +declare module '*.avif' { + const src: string; + export default src; +} + +declare module '*.bmp' { + const src: string; + export default src; +} + +declare module '*.gif' { + const src: string; + export default src; +} + +declare module '*.jpg' { + const src: string; + export default src; +} + +declare module '*.jpeg' { + const src: string; + export default src; +} + +declare module '*.png' { + const src: string; + export default src; +} + +declare module '*.webp' { + const src: string; + export default src; +} + +declare module '*.svg' { + import * as React from 'react'; + + export const ReactComponent: React.FunctionComponent & { title?: string }>; + + const src: string; + export default src; +} + +declare module '*.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} + +declare module '*.less' { + const classes: { readonly [key: string]: string }; + export default classes; +} diff --git a/packages/preview/src/index.html b/packages/preview/src/index.html new file mode 100644 index 0000000..110c39f --- /dev/null +++ b/packages/preview/src/index.html @@ -0,0 +1,14 @@ + + + + + React SVG Preview + + + + + +
+ + + \ No newline at end of file diff --git a/packages/preview/src/index.tsx b/packages/preview/src/index.tsx new file mode 100644 index 0000000..886995d --- /dev/null +++ b/packages/preview/src/index.tsx @@ -0,0 +1,5 @@ +import { createRoot } from 'react-dom/client'; +import { App } from './pages/index'; + +const root = createRoot(document.getElementById('root') as HTMLElement); +root.render(); diff --git a/packages/preview/src/pages/index/components/Code/index.tsx b/packages/preview/src/pages/index/components/Code/index.tsx new file mode 100644 index 0000000..4d172f7 --- /dev/null +++ b/packages/preview/src/pages/index/components/Code/index.tsx @@ -0,0 +1,43 @@ +import { useBoolean, useTimeout } from 'ahooks'; +import { FC, useMemo } from 'react'; +import { usePageStoreContext } from '../PageStore'; + +const CopySuccess: FC<{ onHide?: () => void }> = ({ onHide }) => { + useTimeout(() => { + onHide?.(); + }, 1500); + + return Copy Successfully.; +}; + +export const Code: FC = () => { + const { svgComponent, svgProps } = usePageStoreContext(); + + const colors = useMemo(() => { + return svgProps.color; + }, [svgProps.color]); + + const code = useMemo(() => { + const colorString = colors ? `color={${JSON.stringify(colors)}}` : ''; + return `<${svgComponent?.name} ${colorString} />`; + }, [colors, svgComponent?.name]); + + const [copyShow, copyShowToggle] = useBoolean(); + + return ( +
+