Skip to content

Commit bfe0e05

Browse files
committed
Implement import devup
1 parent 2f278c4 commit bfe0e05

File tree

15 files changed

+819
-322
lines changed

15 files changed

+819
-322
lines changed

manifest.json

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,35 @@
33
"id": "1412341601954480694",
44
"api": "1.0.0",
55
"main": "./dist/code.js",
6-
"capabilities": ["codegen", "inspect"],
6+
"capabilities": [
7+
"codegen",
8+
"inspect"
9+
],
710
"enableProposedApi": false,
811
"documentAccess": "dynamic-page",
9-
"editorType": ["dev", "figma"],
12+
"editorType": [
13+
"dev",
14+
"figma"
15+
],
1016
"networkAccess": {
1117
"allowedDomains": [
1218
"none"
1319
]
1420
},
1521
"codegenLanguages": [
16-
{"label": "React", "value": "devup"}
22+
{
23+
"label": "React",
24+
"value": "devup"
25+
}
1726
],
1827
"menu": [
1928
{
2029
"name": "Export Devup",
2130
"command": "export-devup"
31+
},
32+
{
33+
"name": "Import Devup",
34+
"command": "import-devup"
2235
}
2336
]
24-
}
37+
}

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
"eslint-plugin-devup": "^2.0.5",
1919
"@figma/eslint-plugin-figma-plugins": "*",
2020
"@figma/plugin-typings": "*",
21-
"@rspack/cli": "^1.3.5",
22-
"@rspack/core": "^1.3.5",
23-
"@typescript-eslint/eslint-plugin": "^8.31.0",
24-
"@typescript-eslint/parser": "^8.31.0",
25-
"@eslint/compat": "^1.2.8",
26-
"eslint": "^9.25.1",
21+
"@rspack/cli": "^1.3.9",
22+
"@rspack/core": "^1.3.9",
23+
"@typescript-eslint/eslint-plugin": "^8.32.0",
24+
"@typescript-eslint/parser": "^8.32.0",
25+
"@eslint/compat": "^1.2.9",
26+
"eslint": "^9.26.0",
2727
"typescript": "^5.8.3",
28-
"@vitest/coverage-v8": "^3.1.2",
29-
"vitest": "^3.1.2"
28+
"@vitest/coverage-v8": "^3.1.3",
29+
"vitest": "^3.1.3"
3030
}
3131
}

pnpm-lock.yaml

Lines changed: 582 additions & 273 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Element.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from './utils'
1111
import { extractKeyValueFromCssVar } from './utils/extract-key-value-from-css-var'
1212
import { textSegmentToTypography } from './utils/text-segment-to-typography'
13+
import { toCamel } from './utils/to-camel'
1314

1415
export type ComponentType =
1516
| 'Fragment'
@@ -101,16 +102,16 @@ export class Element {
101102
'width' in this.node.parent &&
102103
this.node.parent.width === this.node.width
103104
? {
104-
src: this.node.name,
105-
width: '100%',
106-
height: '',
107-
'aspect-ratio': `${Math.floor((this.node.width / this.node.height) * 100) / 100}`,
108-
}
105+
src: this.node.name,
106+
width: '100%',
107+
height: '',
108+
'aspect-ratio': `${Math.floor((this.node.width / this.node.height) * 100) / 100}`,
109+
}
109110
: {
110-
src: this.node.name,
111-
width: this.node.width + 'px',
112-
height: this.node.height + 'px',
113-
},
111+
src: this.node.name,
112+
width: this.node.width + 'px',
113+
height: this.node.height + 'px',
114+
},
114115
)
115116
}
116117

@@ -221,6 +222,10 @@ export class Element {
221222

222223
if (this.svgVarKeyValue) {
223224
value = value.replaceAll(this.svgVarKeyValue[1], 'currentColor')
225+
if (this.svgVarKeyValue[0].startsWith('$'))
226+
this.svgVarKeyValue[0] =
227+
'$' + toCamel(this.svgVarKeyValue[0].slice(1))
228+
224229
value = value.replace(
225230
'<svg',
226231
`<svg className={css({ color: "${this.svgVarKeyValue[0]}" })}`,
@@ -231,7 +236,6 @@ export class Element {
231236
}
232237

233238
const originProps = await this.getProps()
234-
console.log(originProps)
235239
const mergedProps = { ...originProps, ...this.additionalProps }
236240
const children = this.getChildren()
237241

@@ -273,22 +277,21 @@ export class Element {
273277
? await propsToPropsWithTypography(mergedProps, this.node.textStyleId)
274278
: propsToComponentProps(mergedProps, componentType, children.length),
275279
)
276-
console.log(props)
277280

278281
const hasChildren = children.length > 0 && !this.skipChildren
279282

280283
const renderChildren = hasChildren
281284
? (
282-
await Promise.all(
283-
children.map((child) =>
284-
child instanceof Element
285-
? child.render(dep + 1)
286-
: fixChildrenText(child),
287-
),
285+
await Promise.all(
286+
children.map((child) =>
287+
child instanceof Element
288+
? child.render(dep + 1)
289+
: fixChildrenText(child),
290+
),
291+
)
288292
)
289-
)
290-
.join('\n')
291-
.trim()
293+
.join('\n')
294+
.trim()
292295
: ''
293296

294297
const propsString = Object.entries(props)

src/__tests__/Element.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ describe('Element', () => {
597597
})
598598
expect(await element.getComponentType()).toEqual('Text')
599599
expect(await element.render()).toEqual(
600-
'<Text typography="button-title">\n a\n</Text>',
600+
'<Text typography="buttonTitle">\n a\n</Text>',
601601
)
602602
})
603603
it('should render many Text', async () => {

src/__tests__/code.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { exportDevup } from '../devup'
1+
import { exportDevup, importDevup } from '../devup'
22

33
vi.mock('../devup')
44

@@ -20,6 +20,18 @@ describe('figma', () => {
2020
expect(closePlugin).toBeCalledTimes(1)
2121
})
2222
})
23+
it('should import devup', async () => {
24+
const closePlugin = vi.fn()
25+
;(globalThis as any).figma = {
26+
editorType: 'figma',
27+
command: 'import-devup',
28+
closePlugin,
29+
}
30+
vi.mocked(importDevup).mockResolvedValueOnce()
31+
await import('../code')
32+
expect(importDevup).toBeCalledTimes(1)
33+
expect(closePlugin).toBeCalledTimes(1)
34+
})
2335

2436
describe('codegen', () => {
2537
it('should generate code', async () => {

src/__tests__/utils.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ describe('organizeProps', () => {
102102
bg: '$primary',
103103
})
104104

105+
expect(organizeProps({ bg: 'var(--PASCAL_CASE)' })).toEqual({
106+
bg: '$pascalCase',
107+
})
108+
105109
expect(
106110
organizeProps({
107111
bg: 'linear-gradient(202deg, var(--primary, #5B34F7) 3.96%, #6D7EDC 85.94%)',

src/code.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { exportDevup } from './devup'
1+
import { exportDevup, importDevup } from './devup'
22
import { Element } from './Element'
33

44
if (figma.editorType === 'dev' && figma.mode === 'codegen') {
@@ -12,9 +12,11 @@ if (figma.editorType === 'dev' && figma.mode === 'codegen') {
1212
]
1313
})
1414
}
15-
if (figma.editorType === 'figma')
16-
switch (figma.command) {
17-
case 'export-devup':
18-
exportDevup().finally(() => figma.closePlugin())
19-
break
20-
}
15+
switch (figma.command) {
16+
case 'export-devup':
17+
exportDevup().finally(() => figma.closePlugin())
18+
break
19+
case 'import-devup':
20+
importDevup().finally(() => figma.closePlugin())
21+
break
22+
}

src/devup/index.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { rgbaToHex } from '../utils/rgba-to-hex'
44
import { styleNameToTypography } from '../utils/style-name-to-typography'
55
import { textSegmentToTypography } from '../utils/text-segment-to-typography'
66
import { toCamel } from '../utils/to-camel'
7+
import { uploadFile } from '../utils/upload-file'
78
import { variableAliasToValue } from '../utils/variable-alias-to-value'
89
import { type Devup, DevupTypography } from './types'
910
import { getDevupColorCollection } from './utils/get-devup-color-collection'
@@ -132,3 +133,106 @@ export async function exportDevup() {
132133

133134
return downloadFile('devup.json', JSON.stringify(devup))
134135
}
136+
137+
export async function importDevup() {
138+
const devup: Devup = JSON.parse(await uploadFile('.json'))
139+
if (devup.theme?.colors) {
140+
const collection = (await getDevupColorCollection()) ?? (await figma.variables.createVariableCollection('Devup Colors'))
141+
const themes = new Set()
142+
const colors = new Set()
143+
for (const [theme, value] of Object.entries(devup.theme.colors)) {
144+
const modeId = collection.modes.find((mode) => mode.name === theme)?.modeId ?? collection.addMode(theme)
145+
146+
const variables = await figma.variables.getLocalVariablesAsync()
147+
for (const [colorKey, colorValue] of Object.entries(value)) {
148+
const variable = variables.find((variable) => variable.name === colorKey) ?? figma.variables.createVariable(colorKey, collection, 'COLOR')
149+
150+
variable.setValueForMode(modeId, figma.util.rgba(colorValue))
151+
colors.add(colorKey)
152+
}
153+
themes.add(theme)
154+
}
155+
for(const theme of collection.modes.filter((mode) => !themes.has(mode.name)))
156+
collection.removeMode(theme.modeId)
157+
158+
const variables = await figma.variables.getLocalVariablesAsync()
159+
for(const variable of variables.filter((variable) => !colors.has(variable.name)))
160+
variable.remove()
161+
}
162+
if (devup.theme?.typography) {
163+
const styles = await figma.getLocalTextStylesAsync()
164+
for(const [style, value] of Object.entries(devup.theme.typography)) {
165+
const targetStyleNames:[target:string, typography:DevupTypography][] = []
166+
if (Array.isArray(value)) {
167+
for(const v in value) {
168+
if(v&&value[v]) {
169+
170+
targetStyleNames.push([`${{
171+
0: 'mobile',
172+
2: 'tablet',
173+
4: 'desktop',
174+
}[v]}/${style}`, value[v]])
175+
}
176+
}
177+
} else {
178+
targetStyleNames.push([`mobile/${style}`, value])
179+
}
180+
181+
for(const [target, typography] of targetStyleNames) {
182+
const st = styles.find((s) => s.name === target) ?? figma.createTextStyle()
183+
st.name = target
184+
185+
if(typography.fontWeight || typography.fontStyle) {
186+
const fontFamily = {
187+
family: typography.fontFamily ?? "Inter",
188+
style: typography.fontStyle == 'italic' ? 'Italic' : 'Regular',
189+
}
190+
await figma.loadFontAsync(fontFamily)
191+
st.fontName = fontFamily
192+
}
193+
if(typography.fontSize) {
194+
st.fontSize = parseInt(typography.fontSize)
195+
}
196+
if(typography.letterSpacing) {
197+
if(typography.letterSpacing.endsWith('em')) {
198+
199+
st.letterSpacing = {
200+
unit: 'PERCENT',
201+
value: parseFloat(typography.letterSpacing),
202+
}
203+
} else {
204+
st.letterSpacing = {
205+
unit: 'PIXELS',
206+
value: parseFloat(typography.letterSpacing) * 100,
207+
}
208+
}
209+
}
210+
if(typography.lineHeight) {
211+
if(typography.lineHeight === 'normal') {
212+
st.lineHeight = {
213+
unit: 'AUTO',
214+
}
215+
} else {
216+
if(typeof typography.lineHeight === 'string') {
217+
st.lineHeight = {
218+
unit: 'PIXELS',
219+
value: parseInt(typography.lineHeight),
220+
}
221+
} else {
222+
st.lineHeight = {
223+
unit: 'PERCENT',
224+
value: Math.round(typography.lineHeight / 10) / 10,
225+
}
226+
}
227+
}
228+
}
229+
if(typography.textTransform) {
230+
st.textCase = typography.textTransform.toUpperCase() as TextCase
231+
}
232+
if(typography.textDecoration) {
233+
st.textDecoration = typography.textDecoration.toUpperCase() as TextDecoration
234+
}
235+
}
236+
}
237+
}
238+
}

src/devup/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export interface DevupTypography {
44
fontSize?: string
55
fontWeight?: number
66
lineHeight?: number | string
7-
letterSpacing?: number | string
7+
letterSpacing?: string
88
textDecoration?: string
99
textTransform?: string
1010
}

0 commit comments

Comments
 (0)