From 2888572127f6928925afbdab4adbe40f907b53d2 Mon Sep 17 00:00:00 2001 From: Matt Chaffe Date: Wed, 29 Jan 2025 19:14:16 +0000 Subject: [PATCH] add warnings and small warnings UI --- package-lock.json | 287 ++++++++++++++++++ package.json | 3 + src/processors/background.processor.ts | 16 +- .../background-processors.test.ts.snap | 8 + src/tests/background-processors.test.ts | 8 +- src/transformers/css.transformer.ts | 22 +- src/transformers/scss.transformer.ts | 22 +- src/ui.css | 169 +++++++++++ src/ui.html | 149 +-------- src/ui.ts | 133 ++++---- src/utils/error.utils.ts | 21 ++ webpack.config.js | 20 +- 12 files changed, 611 insertions(+), 247 deletions(-) create mode 100644 src/ui.css create mode 100644 src/utils/error.utils.ts diff --git a/package-lock.json b/package-lock.json index 08363e1..24df83f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,12 +17,15 @@ "@types/jest": "^29.0.0", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", + "css-loader": "^7.1.2", "esbuild": "^0.19.0", "eslint": "^8.54.0", "highlight.js": "^11.11.1", + "html-inline-css-webpack-plugin": "^1.11.2", "html-inline-script-webpack-plugin": "^3.2.1", "html-webpack-plugin": "^5.6.3", "jest": "^29.0.0", + "mini-css-extract-plugin": "^2.9.2", "ts-jest": "^29.0.0", "ts-loader": "^9.5.1", "typescript": "^5.3.2", @@ -2763,6 +2766,41 @@ "node": ">= 8" } }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -2791,6 +2829,18 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -3735,6 +3785,19 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-inline-css-webpack-plugin": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/html-inline-css-webpack-plugin/-/html-inline-css-webpack-plugin-1.11.2.tgz", + "integrity": "sha512-jzJptIAOrYQh9ITxiDeNWQvPwk1nHZIo0P9/ZlLsfSYKqggMENJdO+Hcw67xSHoxtL2t+X9QO00eHu6W/c8V0Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "peerDependencies": { + "html-webpack-plugin": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/html-inline-script-webpack-plugin": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/html-inline-script-webpack-plugin/-/html-inline-script-webpack-plugin-3.2.1.tgz", @@ -3830,6 +3893,18 @@ "node": ">=10.17.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5025,6 +5100,79 @@ "node": ">=6" } }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -5046,6 +5194,24 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5376,6 +5542,112 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5776,6 +6048,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -6272,6 +6553,12 @@ "punycode": "^2.1.0" } }, + "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 + }, "node_modules/utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", diff --git a/package.json b/package.json index 3b0681d..80caca5 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,15 @@ "@types/jest": "^29.0.0", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", + "css-loader": "^7.1.2", "esbuild": "^0.19.0", "eslint": "^8.54.0", "highlight.js": "^11.11.1", + "html-inline-css-webpack-plugin": "^1.11.2", "html-inline-script-webpack-plugin": "^3.2.1", "html-webpack-plugin": "^5.6.3", "jest": "^29.0.0", + "mini-css-extract-plugin": "^2.9.2", "ts-jest": "^29.0.0", "ts-loader": "^9.5.1", "typescript": "^5.3.2", diff --git a/src/processors/background.processor.ts b/src/processors/background.processor.ts index abde487..af9384b 100644 --- a/src/processors/background.processor.ts +++ b/src/processors/background.processor.ts @@ -10,8 +10,8 @@ export const backgroundProcessor: StyleProcessor = { const visibleFills = node.fills.filter(fill => fill.visible); if (!visibleFills.length) return null; - const warnings: string[] = []; - const errors: string[] = []; + const warningsSet = new Set(); + const errorsSet = new Set(); const backgrounds = await Promise.all(visibleFills.map(async (fill: Paint) => { if (fill.type === "SOLID") { @@ -31,8 +31,12 @@ export const backgroundProcessor: StyleProcessor = { if (fill.type.startsWith('GRADIENT_')) { const result = processGradient(fill as GradientPaint); - if (result.warnings) warnings.push(...result.warnings); - if (result.errors) errors.push(...result.errors); + if (result.warnings) { + result.warnings.forEach(warning => warningsSet.add(warning)); + } + if (result.errors) { + result.errors.forEach(error => errorsSet.add(error)); + } if (result.value) { return { @@ -46,8 +50,8 @@ export const backgroundProcessor: StyleProcessor = { })); const result: ProcessedValue = { - warnings: warnings.length > 0 ? warnings : undefined, - errors: errors.length > 0 ? errors : undefined, + warnings: warningsSet.size > 0 ? Array.from(warningsSet) : undefined, + errors: errorsSet.size > 0 ? Array.from(errorsSet) : undefined, value: null, rawValue: null }; diff --git a/src/tests/__snapshots__/background-processors.test.ts.snap b/src/tests/__snapshots__/background-processors.test.ts.snap index d750390..7e791ba 100644 --- a/src/tests/__snapshots__/background-processors.test.ts.snap +++ b/src/tests/__snapshots__/background-processors.test.ts.snap @@ -7,6 +7,14 @@ exports[`Background Processors should process background gradient correctly: gra } `; +exports[`Background Processors should process background gradient correctly: gradient warnings 1`] = ` +[ + "GRADIENT_RADIAL gradients are not yet implemented", + "GRADIENT_ANGULAR gradients are not yet implemented", + "GRADIENT_DIAMOND gradients are not yet implemented", +] +`; + exports[`Background Processors should process background solid correctly - sass: solid styles 1`] = ` "// Generated SCSS Variables $brand-primary: #00464a diff --git a/src/tests/background-processors.test.ts b/src/tests/background-processors.test.ts index 89407d6..0e02b36 100644 --- a/src/tests/background-processors.test.ts +++ b/src/tests/background-processors.test.ts @@ -120,7 +120,7 @@ describe('Background Processors', () => { }; const tokens = await collectTokens(); - const { result: css, warnings, errors } = transformToCss(tokens); + const { result: css, warnings } = transformToCss(tokens); // Test specific styles with snapshots const styles = { @@ -128,11 +128,13 @@ describe('Background Processors', () => { linearAlpha: parseCssClass(css, 'background-gradient-linear-alpha-style') }; - console.log({ warnings, errors }); - + // Snapshot all styles + expect(warnings).toMatchSnapshot('gradient warnings'); expect(styles).toMatchSnapshot('gradient styles'); + expect(warnings).toHaveLength(3); + // Keep direct assertions for critical values expect(styles.linear).toBe('background: linear-gradient(224deg, #00464A 0%, #04646A 100%);'); expect(styles.linearAlpha).toBe('background: linear-gradient(224deg, rgba(0, 70, 74, 0.5) 0%, #04646A 100%);'); diff --git a/src/transformers/css.transformer.ts b/src/transformers/css.transformer.ts index a1fd1ce..4370b25 100644 --- a/src/transformers/css.transformer.ts +++ b/src/transformers/css.transformer.ts @@ -1,20 +1,14 @@ import { StyleToken, TokenCollection, TransformerResult } from '../types'; import { groupBy } from '../utils/index'; +import { deduplicateMessages } from '../utils/error.utils'; export function transformToCss(tokens: TokenCollection): TransformerResult { let output = "/* Generated CSS */"; - const warnings: string[] = []; - const errors: string[] = []; - - // Collect warnings and errors from tokens - tokens.tokens.forEach(token => { - if ('warnings' in token && token.warnings) { - warnings.push(...token.warnings); - } - if ('errors' in token && token.errors) { - errors.push(...token.errors); - } - }); + + // Deduplicate warnings and errors + const { warnings, errors } = deduplicateMessages( + tokens.tokens.filter((token): token is StyleToken => token.type === 'style') + ); // Filter for style tokens only const styleTokens = tokens.tokens.filter((token): token is StyleToken => @@ -54,7 +48,7 @@ export function transformToCss(tokens: TokenCollection): TransformerResult { return { result: output, - warnings: warnings, - errors: errors + warnings, + errors }; } \ No newline at end of file diff --git a/src/transformers/scss.transformer.ts b/src/transformers/scss.transformer.ts index 005d735..3bf08e9 100644 --- a/src/transformers/scss.transformer.ts +++ b/src/transformers/scss.transformer.ts @@ -1,20 +1,14 @@ import { TokenCollection, StyleToken, TransformerResult } from '../types'; import { sanitizeName, groupBy } from '../utils/index'; +import { deduplicateMessages } from '../utils/error.utils'; export function transformToScss(tokens: TokenCollection): TransformerResult { let output = ""; - const warnings: string[] = []; - const errors: string[] = []; - - // Collect warnings and errors from tokens - tokens.tokens.forEach(token => { - if ('warnings' in token && token.warnings) { - warnings.push(...token.warnings); - } - if ('errors' in token && token.errors) { - errors.push(...token.errors); - } - }); + + // Deduplicate warnings and errors from style tokens only + const { warnings, errors } = deduplicateMessages( + tokens.tokens.filter((token): token is StyleToken => token.type === 'style') + ); // First, collect and output color variables const colorVariables = new Map(); @@ -98,8 +92,8 @@ export function transformToScss(tokens: TokenCollection): TransformerResult { return { result: output, - warnings: warnings, - errors: errors + warnings, + errors }; } diff --git a/src/ui.css b/src/ui.css new file mode 100644 index 0000000..d5592dd --- /dev/null +++ b/src/ui.css @@ -0,0 +1,169 @@ +body { margin: 0; padding: 20px; font-family: sans-serif; } +button { padding: 8px 16px; font-size: 14px; margin-bottom: 10px; } +#output { + width: 100%; + height: 50vh; + padding: 0; + box-sizing: border-box; + background: #1E1E1E; + color: #DCDCDC; + font-family: monospace; + overflow: auto; + white-space: pre; + line-height: 1.25; + position: relative; +} +.output-header { + position: absolute; + top: 0; + right: 0; + z-index: 1; + height: 32px; + display: flex; + align-items: stretch; +} +.copy-button { + display: flex; + align-items: center; + gap: 4px; + padding: 8px; + border: 1px solid var(--figma-color-border); + border-radius: 0; + border-top: none; + border-right: none; + border-bottom-left-radius: 6px; + background: var(--figma-color-bg); + color: var(--figma-color-text); + cursor: pointer; + opacity: 0.8; + height: 100%; + box-sizing: border-box; +} +.copy-button:hover { + background: var(--figma-color-bg-hover); + opacity: 1; +} +.copy-button svg { + width: 16px; + height: 16px; +} +.form-group { margin-bottom: 15px; } +label { display: block; margin-bottom: 5px; color: white; } +input { width: 100%; padding: 8px; box-sizing: border-box; } +pre { + margin: 0; + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 10px; +} +#status { + margin-left: 10px; + color: #DCDCDC; + font-style: italic; +} +#status a { + color: #7cc4f8; + text-decoration: none; +} +#status a:hover { + text-decoration: underline; +} + +/* Highlight.js VS2015 Theme */ +.hljs { + display: block; + overflow-x: auto; + padding: 0; + background: #1E1E1E; + color: #DCDCDC; +} +.hljs-keyword, +.hljs-literal, +.hljs-symbol, +.hljs-name { + color: #569CD6; +} +.hljs-link { + color: #569CD6; + text-decoration: underline; +} +.hljs-built_in, +.hljs-type { + color: #4EC9B0; +} +.hljs-number, +.hljs-class { + color: #B8D7A3; +} +.hljs-string, +.hljs-meta-string { + color: #D69D85; +} +.hljs-regexp, +.hljs-template-tag { + color: #9A5334; +} +.hljs-subst, +.hljs-function, +.hljs-title, +.hljs-params, +.hljs-formula { + color: #DCDCDC; +} +.hljs-comment, +.hljs-quote { + color: #57A64A; + font-style: italic; +} +.hljs-doctag { + color: #608B4E; +} +.hljs-meta, +.hljs-meta-keyword, +.hljs-tag { + color: #9B9B9B; +} +.hljs-variable, +.hljs-template-variable { + color: #BD63C5; +} +.hljs-attr, +.hljs-attribute, +.hljs-builtin-name { + color: #9CDCFE; +} +.hljs-section { + color: gold; +} +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +#warnings { + display: none; + background: #fff3cd; + border: 1px solid #ffeeba; + border-radius: 4px; + padding: 12px; + margin-bottom: 16px; +} + +#warnings h3 { + margin: 0 0 8px; + font-size: 14px; + color: #856404; +} + +#warnings ul { + margin: 0; + padding-left: 20px; + color: #856404; +} + +#warnings li { + margin: 4px 0; +} \ No newline at end of file diff --git a/src/ui.html b/src/ui.html index 3028a39..d70b28e 100644 --- a/src/ui.html +++ b/src/ui.html @@ -3,153 +3,7 @@ Eggstractor - +
@@ -181,6 +35,7 @@
+

       
diff --git a/src/ui.ts b/src/ui.ts index 947b406..46bef12 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -108,73 +108,86 @@ window.onmessage = async (event) => { switch (msg.type) { case 'output-styles': generatedScss = true; + const warnings = document.getElementById('warnings') as HTMLDivElement; const output = document.getElementById('output') as HTMLDivElement; - const highlightedCode = highlightCode(event.data.pluginMessage.styles); - output.innerHTML = ` -
- -
-
${highlightedCode}
- `; + const highlightedCode = highlightCode(msg.styles); - const copyButton = document.getElementById('copyButton'); - if (copyButton) { - copyButton.onclick = () => { - copyToClipboard(event.data.pluginMessage.styles); - copyButton.setAttribute('aria-label', 'Copied!'); - copyButton.setAttribute('title', 'Copied!'); - copyButton.innerHTML = ` - - Check mark icon - - - `; - setTimeout(() => { - copyButton.setAttribute('aria-label', 'Copy to clipboard'); - copyButton.setAttribute('title', 'Copy to clipboard'); - copyButton.innerHTML = ` + // Add warnings section if there are any + const warningsHtml = msg.warnings?.length + ? `
+

⚠️ Warnings ⚠️

+
    + ${msg.warnings.map(warning => `
  • ${warning}
  • `).join('')} +
+
` + : ''; + warnings.innerHTML = warningsHtml; + warnings.style.display = 'block'; + output.innerHTML = ` +
+ +
+
${highlightedCode}
+ `; + + const copyButton = document.getElementById('copyButton'); + if (copyButton) { + copyButton.onclick = () => { + copyToClipboard(msg.styles); + copyButton.setAttribute('aria-label', 'Copied!'); + copyButton.setAttribute('title', 'Copied!'); + copyButton.innerHTML = ` + + Check mark icon + + `; - }, 2000); - }; - } - break; - case 'config-loaded': - repoPathInput.value = event.data.pluginMessage.config.repoPath || ''; - filePathInput.value = event.data.pluginMessage.config.filePath || ''; - branchNameInput.value = event.data.pluginMessage.config.branchName || ''; - githubTokenInput.value = event.data.pluginMessage.config.githubToken || ''; - break; - case 'pr-created': - const statusEl = document.getElementById('status') as HTMLSpanElement; - statusEl.innerHTML = `PR created! View PR`; - createPRBtn.disabled = false; - break; - case 'error': - createPRBtn.disabled = false; - alert(`Error: ${event.data.pluginMessage.message}`); - break; - case 'test-data-exported': - // Create and trigger download - const blob = new Blob([msg.data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'figma-test-data.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - break; + setTimeout(() => { + copyButton.setAttribute('aria-label', 'Copy to clipboard'); + copyButton.setAttribute('title', 'Copy to clipboard'); + copyButton.innerHTML = ` + + Copy icon + + + + `; + }, 2000); + }; + } + break; + case 'config-loaded': + repoPathInput.value = event.data.pluginMessage.config.repoPath || ''; + filePathInput.value = event.data.pluginMessage.config.filePath || ''; + branchNameInput.value = event.data.pluginMessage.config.branchName || ''; + githubTokenInput.value = event.data.pluginMessage.config.githubToken || ''; + break; + case 'pr-created': + const statusEl = document.getElementById('status') as HTMLSpanElement; + statusEl.innerHTML = `PR created! View PR`; + createPRBtn.disabled = false; + break; + case 'error': + createPRBtn.disabled = false; + alert(`Error: ${event.data.pluginMessage.message}`); + break; + case 'test-data-exported': + // Create and trigger download + const blob = new Blob([msg.data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'figma-test-data.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + break; } }; diff --git a/src/utils/error.utils.ts b/src/utils/error.utils.ts new file mode 100644 index 0000000..006a560 --- /dev/null +++ b/src/utils/error.utils.ts @@ -0,0 +1,21 @@ +export function deduplicateMessages(tokens: { warnings?: string[], errors?: string[] }[]): { + warnings: string[]; + errors: string[]; +} { + const warningsSet = new Set(); + const errorsSet = new Set(); + + tokens.forEach(token => { + if (token.warnings) { + token.warnings.forEach(warning => warningsSet.add(warning)); + } + if (token.errors) { + token.errors.forEach(error => errorsSet.add(error)); + } + }); + + return { + warnings: Array.from(warningsSet), + errors: Array.from(errorsSet) + }; +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 37ad34b..93dd284 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,12 +1,14 @@ const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const HtmlInlineCssPlugin = require('html-inline-css-webpack-plugin').default; const path = require('path'); module.exports = (env, argv) => ({ mode: argv.mode === 'production' ? 'production' : 'development', devtool: argv.mode === 'production' ? false : 'inline-source-map', entry: { - ui: './src/ui.ts', + ui: ['./src/ui.ts', './src/ui.css'], code: './src/code.ts', }, module: { @@ -20,10 +22,14 @@ module.exports = (env, argv) => ({ } }, exclude: /node_modules/, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'] } ], }, - resolve: { extensions: ['.tsx', '.ts', '.js'] }, + resolve: { extensions: ['.tsx', '.ts', '.js', '.css'] }, output: { filename: (pathData) => { return pathData.chunk.name === 'code' ? 'code.js' : '[name].[contenthash].js'; @@ -32,12 +38,20 @@ module.exports = (env, argv) => ({ clean: true, }, plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].css' + }), new HtmlWebpackPlugin({ - inject: 'body', + inject: true, template: './src/ui.html', filename: 'ui.html', chunks: ['ui'], }), + new HtmlInlineCssPlugin({ + replace: { + removeTarget: true + } + }), new HtmlInlineScriptPlugin({ htmlMatchPattern: [/ui.html/], scriptMatchPattern: [/.js$/],