From 1e9a3f26648b00c5fba4ee47794db8febb309c83 Mon Sep 17 00:00:00 2001 From: Masataka Hirano Date: Mon, 4 Dec 2023 12:05:15 +0100 Subject: [PATCH] refactor: migrate variables sample to TS --- variables-import-export/.gitignore | 2 + variables-import-export/{code.js => code.ts} | 108 +++++++++++++------ variables-import-export/package-lock.json | 50 +++++++++ variables-import-export/package.json | 15 +++ variables-import-export/tsconfig.json | 11 ++ 5 files changed, 155 insertions(+), 31 deletions(-) create mode 100644 variables-import-export/.gitignore rename variables-import-export/{code.js => code.ts} (73%) create mode 100644 variables-import-export/package-lock.json create mode 100644 variables-import-export/package.json create mode 100644 variables-import-export/tsconfig.json diff --git a/variables-import-export/.gitignore b/variables-import-export/.gitignore new file mode 100644 index 0000000..7d8c710 --- /dev/null +++ b/variables-import-export/.gitignore @@ -0,0 +1,2 @@ +node_modules +code.js diff --git a/variables-import-export/code.js b/variables-import-export/code.ts similarity index 73% rename from variables-import-export/code.js rename to variables-import-export/code.ts index 9cfe355..7b85fa9 100755 --- a/variables-import-export/code.js +++ b/variables-import-export/code.ts @@ -1,18 +1,49 @@ console.clear(); -function createCollection(name) { +interface Alias { + key: string, + type: string, + valueKey: string, +} + +interface Aliases { + [key: string]: Alias +} + +interface Tokens { + [key: string]: Variable +} + +interface File { + fileName: string, + body: any, +} + +function createCollection(name: string): { collection: VariableCollection, modeId: string } { const collection = figma.variables.createVariableCollection(name); const modeId = collection.modes[0].modeId; return { collection, modeId }; } -function createToken(collection, modeId, type, name, value) { +function createToken( + collection: VariableCollection, + modeId: string, + type: VariableResolvedDataType, + name: string, + value: VariableValue +): Variable { const token = figma.variables.createVariable(name, collection.id, type); token.setValueForMode(modeId, value); return token; } -function createVariable(collection, modeId, key, valueKey, tokens) { +function createVariable( + collection: VariableCollection, + modeId: string, + key: string, + valueKey: string, + tokens: Tokens +): Variable { const token = tokens[valueKey]; return createToken(collection, modeId, token.resolvedType, key, { type: "VARIABLE_ALIAS", @@ -20,7 +51,7 @@ function createVariable(collection, modeId, key, valueKey, tokens) { }); } -function importJSONFile({ fileName, body }) { +function importJSONFile({ fileName, body }: { fileName: string, body: string }) { const json = JSON.parse(body); const { collection, modeId } = createCollection(fileName); const aliases = {}; @@ -39,7 +70,12 @@ function importJSONFile({ fileName, body }) { processAliases({ collection, modeId, aliases, tokens }); } -function processAliases({ collection, modeId, aliases, tokens }) { +function processAliases({ collection, modeId, aliases, tokens }: { + collection: VariableCollection, + modeId: string, + aliases: Aliases | Alias[], + tokens: Tokens +}) { aliases = Object.values(aliases); let generations = aliases.length; while (aliases.length && generations > 0) { @@ -55,18 +91,18 @@ function processAliases({ collection, modeId, aliases, tokens }) { } } -function isAlias(value) { +function isAlias(value: string | number): boolean { return value.toString().trim().charAt(0) === "{"; } -function traverseToken({ - collection, - modeId, - type, - key, - object, - tokens, - aliases, +function traverseToken({ collection, modeId, type, key, object, tokens, aliases }: { + collection: VariableCollection, + modeId: string, + type: string, + key: string, + object: any, + tokens: Tokens, + aliases: Aliases }) { type = type || object.$type; // if key is a meta field, move on @@ -126,20 +162,20 @@ function traverseToken({ function exportToJSON() { const collections = figma.variables.getLocalVariableCollections(); - const files = []; + const files: File[] = []; collections.forEach((collection) => files.push(...processCollection(collection)) ); figma.ui.postMessage({ type: "EXPORT_RESULT", files }); } -function processCollection({ name, modes, variableIds }) { - const files = []; +function processCollection({ name, modes, variableIds }: VariableCollection): File[] { + const files: File[] = []; modes.forEach((mode) => { - const file = { fileName: `${name}.${mode.name}.tokens.json`, body: {} }; + const file: File = { fileName: `${name}.${mode.name}.tokens.json`, body: {} }; variableIds.forEach((variableId) => { const { name, resolvedType, valuesByMode } = - figma.variables.getVariableById(variableId); + figma.variables.getVariableById(variableId)!; const value = valuesByMode[mode.modeId]; if (value !== undefined && ["COLOR", "FLOAT"].includes(resolvedType)) { let obj = file.body; @@ -148,12 +184,14 @@ function processCollection({ name, modes, variableIds }) { obj = obj[groupName]; }); obj.$type = resolvedType === "COLOR" ? "color" : "number"; - if (value.type === "VARIABLE_ALIAS") { + if (isVariableAlias(value)) { obj.$value = `{${figma.variables - .getVariableById(value.id) + .getVariableById(value.id)! .name.replace(/\//g, ".")}}`; + } else if (isRGBA(value)) { + obj.$value = rgbToHex(value); } else { - obj.$value = resolvedType === "COLOR" ? rgbToHex(value) : value; + obj.$value = value; } } }); @@ -185,13 +223,13 @@ if (figma.command === "import") { }); } -function rgbToHex({ r, g, b, a }) { +function rgbToHex({ r, g, b, a }: RGBA): string { if (a !== 1) { return `rgba(${[r, g, b] .map((n) => Math.round(n * 255)) .join(", ")}, ${a.toFixed(4)})`; } - const toHex = (value) => { + const toHex = (value: number) => { const hex = Math.round(value * 255).toString(16); return hex.length === 1 ? "0" + hex : hex; }; @@ -200,7 +238,7 @@ function rgbToHex({ r, g, b, a }) { return `#${hex}`; } -function parseColor(color) { +function parseColor(color: string): RGB | RGBA { color = color.trim(); const rgbRegex = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/; const rgbaRegex = @@ -213,10 +251,10 @@ function parseColor(color) { /^\{\s*r:\s*[\d\.]+,\s*g:\s*[\d\.]+,\s*b:\s*[\d\.]+(,\s*opacity:\s*[\d\.]+)?\s*\}$/; if (rgbRegex.test(color)) { - const [, r, g, b] = color.match(rgbRegex); + const [, r, g, b] = color.match(rgbRegex)!; return { r: parseInt(r) / 255, g: parseInt(g) / 255, b: parseInt(b) / 255 }; } else if (rgbaRegex.test(color)) { - const [, r, g, b, a] = color.match(rgbaRegex); + const [, r, g, b, a] = color.match(rgbaRegex)!; return { r: parseInt(r) / 255, g: parseInt(g) / 255, @@ -224,10 +262,10 @@ function parseColor(color) { a: parseFloat(a), }; } else if (hslRegex.test(color)) { - const [, h, s, l] = color.match(hslRegex); + const [, h, s, l] = color.match(hslRegex)!; return hslToRgbFloat(parseInt(h), parseInt(s) / 100, parseInt(l) / 100); } else if (hslaRegex.test(color)) { - const [, h, s, l, a] = color.match(hslaRegex); + const [, h, s, l, a] = color.match(hslaRegex)!; return Object.assign( hslToRgbFloat(parseInt(h), parseInt(s) / 100, parseInt(l) / 100), { a: parseFloat(a) } @@ -253,8 +291,8 @@ function parseColor(color) { } } -function hslToRgbFloat(h, s, l) { - const hue2rgb = (p, q, t) => { +function hslToRgbFloat(h: number, s: number, l: number): RGB { + const hue2rgb = (p: number, q: number, t: number): number => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; @@ -275,3 +313,11 @@ function hslToRgbFloat(h, s, l) { return { r, g, b }; } + +function isVariableAlias(value: VariableValue): value is VariableAlias { + return (value as VariableAlias).id !== undefined; +} + +function isRGBA(value: VariableValue): value is RGBA { + return (value as RGBA).a !== undefined; +} diff --git a/variables-import-export/package-lock.json b/variables-import-export/package-lock.json new file mode 100644 index 0000000..8e92d80 --- /dev/null +++ b/variables-import-export/package-lock.json @@ -0,0 +1,50 @@ +{ + "name": "variables", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "variables", + "version": "1.0.0", + "license": "MIT License", + "devDependencies": { + "@figma/plugin-typings": "*", + "typescript": "^5.3.2" + } + }, + "node_modules/@figma/plugin-typings": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@figma/plugin-typings/-/plugin-typings-1.81.0.tgz", + "integrity": "sha512-gy/q4Aa/oVmN9PxdrUyuiMpoBpXAkGIpbe4nqHiKK1TDjrxW3ZBwmYzCM3uGUTC63xhwk99vExbBxaHsoSrAGw==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + }, + "dependencies": { + "@figma/plugin-typings": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@figma/plugin-typings/-/plugin-typings-1.81.0.tgz", + "integrity": "sha512-gy/q4Aa/oVmN9PxdrUyuiMpoBpXAkGIpbe4nqHiKK1TDjrxW3ZBwmYzCM3uGUTC63xhwk99vExbBxaHsoSrAGw==", + "dev": true + }, + "typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true + } + } +} diff --git a/variables-import-export/package.json b/variables-import-export/package.json new file mode 100644 index 0000000..85d6380 --- /dev/null +++ b/variables-import-export/package.json @@ -0,0 +1,15 @@ +{ + "name": "variables", + "version": "1.0.0", + "description": "variables", + "scripts": { + "build": "tsc -p tsconfig.json", + "watch": "npm run build -- --watch" + }, + "author": "Figma", + "license": "MIT License", + "devDependencies": { + "@figma/plugin-typings": "*", + "typescript": "^5.3.2" + } +} diff --git a/variables-import-export/tsconfig.json b/variables-import-export/tsconfig.json new file mode 100644 index 0000000..c64c572 --- /dev/null +++ b/variables-import-export/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES6", + "lib": ["ES2017"], + "strict": true, + "typeRoots": [ + "./node_modules/@types", + "./node_modules/@figma" + ] + } +}