From 0fb37f6b3d7a84f8378e34dff4a6b1b2816431fe Mon Sep 17 00:00:00 2001 From: Barsnes Date: Thu, 28 Nov 2024 13:05:44 +0100 Subject: [PATCH 1/3] fix(figma-plugin): update color regex --- plugins/figma/src/ui/pages/Theme/Theme.tsx | 137 ++++++++++++++------- 1 file changed, 92 insertions(+), 45 deletions(-) diff --git a/plugins/figma/src/ui/pages/Theme/Theme.tsx b/plugins/figma/src/ui/pages/Theme/Theme.tsx index cc12e8d4e3..d97939b533 100644 --- a/plugins/figma/src/ui/pages/Theme/Theme.tsx +++ b/plugins/figma/src/ui/pages/Theme/Theme.tsx @@ -10,12 +10,11 @@ import { import { useEffect, useId, useState } from 'react'; import { Link as RouterLink, useParams } from 'react-router-dom'; -import { type ColorTheme, useThemeStore } from '../../../common/store'; - import type { CssColor } from '@adobe/leonardo-contrast-colors'; import { getDummyTheme } from '@common/dummyTheme'; +import { colorCliOptions } from '@digdir/designsystemet'; import { generateThemeForColor } from '@digdir/designsystemet/color'; -import { colorCliOptions } from '@digdir/designsystemet/tokens'; +import { type ColorTheme, useThemeStore } from '../../../common/store'; import { themeToFigmaFormat } from '../../../common/utils'; import classes from './Theme.module.css'; @@ -43,56 +42,104 @@ function Theme() { }); const handleClick = () => { - const pattern = new RegExp( - `--${colorCliOptions.main}\s+"accent:(#\w{6})"\s+--${colorCliOptions.neutral}\s+"(#\w{6})"\s+--${colorCliOptions.support}\s+"brand1:(#\w{6})"\s+"brand2:(#\w{6})"\s+"brand3:(#\w{6})"`, - ); - const matches = command.replace(/\\/g, '').match(pattern); + // split input into lines + const lines = command.split('\\\n'); - if (matches) { - const accent = matches[1] as CssColor; - const neutral = matches[2] as CssColor; - const brand1 = matches[3] as CssColor; - const brand2 = matches[4] as CssColor; - const brand3 = matches[5] as CssColor; + // helper regex for extracting color info + const colorRegex = /"([^:]+):(#\w{6})"/g; - console.log( - `Accent: ${accent}, Neutral: ${neutral}, Brand1: ${brand1}, Brand2: ${brand2}, Brand3: ${brand3}`, - ); + const result: { + mainColors: { name: string; hex: CssColor }[]; + neutralColor: CssColor | null; + supportColors: { name: string; hex: CssColor }[]; + } = { + mainColors: [], + neutralColor: null, + supportColors: [], + }; - const newArray = Array.from(themes); - newArray[themeIndex] = { - ...newArray[themeIndex], - colors: { - ...newArray[themeIndex].colors, - accent: themeToFigmaFormat(generateThemeForColor(accent)), - neutral: themeToFigmaFormat(generateThemeForColor(neutral)), - brand1: themeToFigmaFormat(generateThemeForColor(brand1)), - brand2: themeToFigmaFormat(generateThemeForColor(brand2)), - brand3: themeToFigmaFormat(generateThemeForColor(brand3)), - }, - }; - - setThemes(newArray); - setLoading(true); - setCommand(''); - - setTimeout(() => { - parent.postMessage( - { - pluginMessage: { - type: 'updateVariables', - themes: newArray, - }, - }, - '*', - ); - }, 500); - } else { + for (const line of lines) { + if (line.includes(`--${colorCliOptions.main}`)) { + const matches = [...line.matchAll(colorRegex)]; + result.mainColors = matches.map((match) => ({ + name: match[1], + hex: match[2] as CssColor, + })); + } else if (line.includes(`--${colorCliOptions.neutral}`)) { + const match = line.match(/#\w{6}/); + if (match) result.neutralColor = match[0] as CssColor; + } else if (line.includes(`--${colorCliOptions.support}`)) { + const matches = [...line.matchAll(colorRegex)]; + result.supportColors = matches.map((match) => ({ + name: match[1], + hex: match[2] as CssColor, + })); + } + } + + if (!result.mainColors.length || !result.neutralColor) { console.log('No match'); setCodeSnippetError( 'Koden du limte inn er ikke gyldig. Prøv å lim inn på nytt.', ); + return; + } + + /* For now we check that we have accent, brand1, brand2, brand3 */ + const accent = result.mainColors.find( + (color) => color.name === 'accent', + )?.hex; + const brand1 = result.supportColors.find( + (color) => color.name === 'brand1', + )?.hex; + const brand2 = result.supportColors.find( + (color) => color.name === 'brand2', + )?.hex; + const brand3 = result.supportColors.find( + (color) => color.name === 'brand3', + )?.hex; + + const neutral = result.neutralColor; + + if (!accent || !brand1 || !brand2 || !brand3) { + setCodeSnippetError( + 'Koden du limte inn er ikke gyldig. Prøv å lim inn på nytt.', + ); + return; } + + console.log( + `Accent: ${accent}, Neutral: ${neutral}, Brand1: ${brand1}, Brand2: ${brand2}, Brand3: ${brand3}`, + ); + + const newArray = Array.from(themes); + newArray[themeIndex] = { + ...newArray[themeIndex], + colors: { + ...newArray[themeIndex].colors, + accent: themeToFigmaFormat(generateThemeForColor(accent)), + neutral: themeToFigmaFormat(generateThemeForColor(neutral)), + brand1: themeToFigmaFormat(generateThemeForColor(brand1)), + brand2: themeToFigmaFormat(generateThemeForColor(brand2)), + brand3: themeToFigmaFormat(generateThemeForColor(brand3)), + }, + }; + + setThemes(newArray); + setLoading(true); + setCommand(''); + + setTimeout(() => { + parent.postMessage( + { + pluginMessage: { + type: 'updateVariables', + themes: newArray, + }, + }, + '*', + ); + }, 500); }; return ( From 90e6f8a0bdb1f556ab804903925bd65ea3ec214a Mon Sep 17 00:00:00 2001 From: Barsnes Date: Thu, 28 Nov 2024 13:18:19 +0100 Subject: [PATCH 2/3] add allowed domains --- plugins/figma/manifest.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/figma/manifest.json b/plugins/figma/manifest.json index b4ce2f54cf..535feafe3a 100644 --- a/plugins/figma/manifest.json +++ b/plugins/figma/manifest.json @@ -8,5 +8,11 @@ "enableProposedApi": false, "editorType": ["figma"], "documentAccess": "dynamic-page", - "networkAccess": { "allowedDomains": ["https://api.github.com"] } + "networkAccess": { + "allowedDomains": [ + "https://api.github.com", + "https://fonts.googleapis.com", + "https://fonts.gstatic.com" + ] + } } From 1706961a432e2c4553ccdb17137d703c3913ebd3 Mon Sep 17 00:00:00 2001 From: Barsnes Date: Fri, 29 Nov 2024 09:28:47 +0100 Subject: [PATCH 3/3] chore: test new variable update logic --- plugins/figma/package.json | 2 +- plugins/figma/src/plugin/figma/themes.ts | 19 ++++- .../figma/src/plugin/figma/updateVariables.ts | 76 ++++++++----------- plugins/figma/src/plugin/plugin.ts | 10 ++- plugins/figma/src/ui/pages/Theme/Theme.tsx | 71 ++++++++++++----- 5 files changed, 110 insertions(+), 68 deletions(-) diff --git a/plugins/figma/package.json b/plugins/figma/package.json index 28310ddbe0..62b10ba9cb 100644 --- a/plugins/figma/package.json +++ b/plugins/figma/package.json @@ -1,7 +1,7 @@ { "name": "figma-plugin", "private": true, - "version": "0.2.0", + "version": "0.3.0", "type": "module", "scripts": { "dev": "run-s watch", diff --git a/plugins/figma/src/plugin/figma/themes.ts b/plugins/figma/src/plugin/figma/themes.ts index 508bbc487a..375e92f954 100644 --- a/plugins/figma/src/plugin/figma/themes.ts +++ b/plugins/figma/src/plugin/figma/themes.ts @@ -3,21 +3,28 @@ import { getDummyTheme } from '../../common/dummyTheme'; import type { StoreThemes } from '../../common/store'; export const getThemes = async () => { - const collections = await figma.variables.getLocalVariableCollectionsAsync(); + const collections = ( + await figma.variables.getLocalVariableCollectionsAsync() + ).filter((collection) => { + return collection.name === 'Color scheme' || collection.name === 'Theme'; + }); + console.log({ collections }); const modeColModes = collections.find( - (collection) => collection.name === 'Mode', + (collection) => collection.name === 'Color scheme', )?.modes; const themeModes = collections.find( (collection) => collection.name === 'Theme', )?.modes; const modeColId = collections.find( - (collection) => collection.name === 'Mode', + (collection) => collection.name === 'Color scheme', )?.id; const variables = await figma.variables.getLocalVariablesAsync('COLOR'); const themes: StoreThemes = []; + console.log({ collections, variables, modeColModes, themeModes, modeColId }); + if (themeModes) { for (const themeMode of themeModes) { themes.push({ @@ -29,6 +36,8 @@ export const getThemes = async () => { } } + console.log('first variable', variables[0]); + for (const variable of variables) { if (variable.variableCollectionId === modeColId) { const nameSplitArr = variable.name.split('/'); @@ -37,6 +46,8 @@ export const getThemes = async () => { const ThemeType = nameSplitArr[1] as string; const ThemeIndex = nameSplitArr[2] as string; + /* console.log({ themeName, ThemeType, ThemeIndex }); */ + if (themeName !== 'global' && modeColModes) { for (const mode of modeColModes) { const modeName = mode.name.toLocaleLowerCase() as @@ -59,5 +70,7 @@ export const getThemes = async () => { } } + console.log({ themes }); + return themes; }; diff --git a/plugins/figma/src/plugin/figma/updateVariables.ts b/plugins/figma/src/plugin/figma/updateVariables.ts index 24bbd74d5f..8ed440ba52 100644 --- a/plugins/figma/src/plugin/figma/updateVariables.ts +++ b/plugins/figma/src/plugin/figma/updateVariables.ts @@ -1,34 +1,30 @@ import { hexToRgb, rgbToHex } from '@digdir/designsystemet/color'; -import type { ColorIndex, StoreThemes } from '../../common/store'; +import type { ColorIndex, StoreTheme } from '../../common/store'; import type { FigmaModeName, ThemeColors } from '../../common/types'; const updateColors = ( - themes: StoreThemes, + theme: StoreTheme, variable: Variable, lightModeId: string, darkModeId: string, modeCollection: VariableCollection, - themeCollection: VariableCollection, - variables: Variable[], ) => { const themeName = variable.name.split('/')[0] as FigmaModeName; const themeType = variable.name.split('/')[1] as ThemeColors; const themeIndex = variable.name.split('/')[2] as ColorIndex; + if ( variable.variableCollectionId === modeCollection.id && !variable.name.startsWith('global') ) { const oldLightHex = rgbToHex(variable.valuesByMode[lightModeId] as RGB); - const newLightHex = themes.find((theme) => theme.themeId === themeName) - ?.colors[themeType].light[themeIndex]; - + const newLightHex = theme.colors[themeType].light[themeIndex]; const oldDarkHex = rgbToHex(variable.valuesByMode[darkModeId] as RGB); - const newDarkHex = themes.find((theme) => theme.themeId === themeName) - ?.colors[themeType].dark[themeIndex]; + const newDarkHex = theme.colors[themeType].dark[themeIndex]; + if (newLightHex && oldLightHex !== newLightHex) { const lightRGB = hexToRgb(newLightHex, '1'); - if (lightRGB) { variable.setValueForMode(lightModeId, lightRGB); } @@ -40,49 +36,43 @@ const updateColors = ( } } } - - // if (variable.name.startsWith(themeName + '/')) { - // const varr = variables.find((variable) => - // variable.name.startsWith('color/' + themeType + '/' + themeIndex), - // ); - - // const themeId = themeCollection.modes.find( - // (mode) => mode.name === themeName, - // ); - // if (varr && themeId) { - // varr.setValueForMode(themeId.modeId, { r: 0, g: 0, b: 0 }); - - // const alias = figma.variables.createVariableAlias(variable); - // if (alias) { - // varr.setValueForMode(themeId.modeId, alias); - // } - // } - // } }; -export const updateVariables = async (themes: StoreThemes) => { +export const updateVariables = async (theme: StoreTheme) => { + console.log({ theme }); const collections = await figma.variables.getLocalVariableCollectionsAsync(); - const modeCollection = collections.find((col) => col.name === 'Mode'); + const modeCollection = collections.find((col) => col.name === 'Color scheme'); const themeCollection = collections.find((col) => col.name === 'Theme'); - console.log('themes', themes); + console.log('theme', theme); + console.log({ modeCollection, themeCollection }); + + const vars = figma.variables.getVariableCollectionById(theme.themeId); + console.log({ vars }); if (modeCollection && themeCollection) { - const lightModeId: string = modeCollection.modes[0].modeId; - const darkModeId: string = modeCollection.modes[1].modeId; + let lightModeId = ''; + let darkModeId = ''; + + for (const mode of modeCollection.modes) { + if (mode.name === 'Light') { + lightModeId = mode.modeId; + } else if (mode.name === 'Dark') { + darkModeId = mode.modeId; + } + } const variables = await figma.variables.getLocalVariablesAsync('COLOR'); + const filteredVariables = variables.filter((variable) => { + const themeName = variable.name.split('/')[0] as FigmaModeName; + return themeName === theme.themeModeId; + }); + + console.log({ filteredVariables }); + try { - for (const variable of variables) { - updateColors( - themes, - variable, - lightModeId, - darkModeId, - modeCollection, - themeCollection, - variables, - ); + for (const variable of filteredVariables) { + updateColors(theme, variable, lightModeId, darkModeId, modeCollection); } figma.ui.postMessage({ type: 'updateVariables' }); } catch (error) { diff --git a/plugins/figma/src/plugin/plugin.ts b/plugins/figma/src/plugin/plugin.ts index dcd735788c..588b9a130a 100644 --- a/plugins/figma/src/plugin/plugin.ts +++ b/plugins/figma/src/plugin/plugin.ts @@ -1,5 +1,5 @@ import { Messages } from '@common/types'; -import type { ColorTheme, StoreThemes } from '../common/store'; +import type { ColorTheme, StoreTheme } from '../common/store'; import { getVariables } from './figma/getVariables'; import { getThemes } from './figma/themes'; @@ -9,7 +9,7 @@ figma.showUI(__html__, { width: 710, height: 550, themeColors: true }); figma.ui.onmessage = (msg: { type: Messages; - themes?: StoreThemes; + theme?: StoreTheme; themeId?: string; renameTheme?: { newName: string; @@ -28,7 +28,11 @@ figma.ui.onmessage = (msg: { void (async () => { switch (msg.type) { case Messages.UpdateVariables: { - if (msg.themes) await updateVariables(msg.themes); + console.log({ msg }); + if (msg.theme) { + console.log('updating variables with themes', msg.theme); + await updateVariables(msg.theme); + } break; } diff --git a/plugins/figma/src/ui/pages/Theme/Theme.tsx b/plugins/figma/src/ui/pages/Theme/Theme.tsx index d97939b533..9634ebd88b 100644 --- a/plugins/figma/src/ui/pages/Theme/Theme.tsx +++ b/plugins/figma/src/ui/pages/Theme/Theme.tsx @@ -14,7 +14,11 @@ import type { CssColor } from '@adobe/leonardo-contrast-colors'; import { getDummyTheme } from '@common/dummyTheme'; import { colorCliOptions } from '@digdir/designsystemet'; import { generateThemeForColor } from '@digdir/designsystemet/color'; -import { type ColorTheme, useThemeStore } from '../../../common/store'; +import { + type ColorTheme, + type StoreTheme, + useThemeStore, +} from '../../../common/store'; import { themeToFigmaFormat } from '../../../common/utils'; import classes from './Theme.module.css'; @@ -33,6 +37,8 @@ function Theme() { (state) => state.setCodeSnippetError, ); + console.log({ themeId, themes }); + useEffect(() => { setThemeIndex(themes.findIndex((theme) => theme.themeModeId === themeId)); setTheme( @@ -41,7 +47,7 @@ function Theme() { ); }); - const handleClick = () => { + const handleClick = async () => { // split input into lines const lines = command.split('\\\n'); @@ -112,11 +118,11 @@ function Theme() { `Accent: ${accent}, Neutral: ${neutral}, Brand1: ${brand1}, Brand2: ${brand2}, Brand3: ${brand3}`, ); - const newArray = Array.from(themes); - newArray[themeIndex] = { - ...newArray[themeIndex], + console.log('making new theme'); + const newArray: StoreTheme = { + ...themes[themeIndex], colors: { - ...newArray[themeIndex].colors, + ...themes[themeIndex].colors, accent: themeToFigmaFormat(generateThemeForColor(accent)), neutral: themeToFigmaFormat(generateThemeForColor(neutral)), brand1: themeToFigmaFormat(generateThemeForColor(brand1)), @@ -125,21 +131,32 @@ function Theme() { }, }; - setThemes(newArray); setLoading(true); setCommand(''); - setTimeout(() => { - parent.postMessage( - { - pluginMessage: { - type: 'updateVariables', - themes: newArray, - }, - }, - '*', + console.log({ newArray }); + + try { + await updateTheme(newArray) + .then(() => { + setLoading(false); + console.log('done'); + }) + .then(() => { + setLoading(false); + console.log('done'); + }); + } catch (error) { + console.error(error); + + const newThemes = [...themes, newArray]; + setThemes(newThemes); + + setLoading(false); + setCodeSnippetError( + 'Kunne ikke oppdatere tema. Prøv igjen eller kontakt support.', ); - }, 500); + } }; return ( @@ -184,7 +201,13 @@ function Theme() { ) : null}
-
@@ -195,3 +218,15 @@ function Theme() { } export default Theme; + +async function updateTheme(theme: StoreTheme) { + parent.postMessage( + { + pluginMessage: { + type: 'updateVariables', + theme, + }, + }, + '*', + ); +}