diff --git a/package.json b/package.json index 02d5e097b..5ff8d81f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tokens-studio-for-figma", "version": "1.0.0", - "plugin_version": "130", + "plugin_version": "131", "description": "Tokens Studio for Figma", "license": "MIT", "scripts": { diff --git a/script/release.sh b/script/release.sh index c8f281e71..cd2c6c6fa 100755 --- a/script/release.sh +++ b/script/release.sh @@ -1,4 +1,4 @@ -VERSION=figma-tokens@130 +VERSION=figma-tokens@131 sentry-cli releases -p figma-tokens files "$VERSION" upload-sourcemaps --ext ts --ext tsx --ext map --ext js --ignore-file .sentryignore . sentry-cli releases set-commits "$VERSION" --auto sentry-cli releases finalize "$VERSION" \ No newline at end of file diff --git a/src/app/components/AppContainer/AppContainer.tsx b/src/app/components/AppContainer/AppContainer.tsx index 565783e75..e4f1cedce 100644 --- a/src/app/components/AppContainer/AppContainer.tsx +++ b/src/app/components/AppContainer/AppContainer.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useState, } from 'react'; import { useDispatch } from 'react-redux'; -import * as pjs from '../../../../package.json'; import App from '../App'; import FigmaLoading from '../FigmaLoading'; import { AsyncMessageTypes, StartupMessage } from '@/types/AsyncMessages'; @@ -11,7 +10,6 @@ import { Tabs } from '@/constants/Tabs'; import { AsyncMessageChannel } from '@/AsyncMessageChannel'; import { useStartupProcess } from './useStartupProcess'; import { ProcessStepStatus } from '@/hooks'; -import { track } from '@/utils/analytics'; import { withLDProviderWrapper } from '../LaunchDarkly'; import { ApplicationInitSteps } from './ApplicationInitSteps'; import ConfirmDialog from '../ConfirmDialog'; @@ -59,7 +57,6 @@ export const AppContainer = withLDProviderWrapper((params: Props) => { && startupProcess.currentStatus !== ProcessStepStatus.CANCELED ) { if (startupProcess.currentStep === null) { - track('Launched', { version: pjs.plugin_version }); await startupProcess.start(); } else if (startupProcess.currentStatus === ProcessStepStatus.DONE) { await startupProcess.next(); diff --git a/src/app/components/AppContainer/startupProcessSteps/__tests__/savePluginDataFactory.test.ts b/src/app/components/AppContainer/startupProcessSteps/__tests__/savePluginDataFactory.test.ts index 506e34c3a..10be5b564 100644 --- a/src/app/components/AppContainer/startupProcessSteps/__tests__/savePluginDataFactory.test.ts +++ b/src/app/components/AppContainer/startupProcessSteps/__tests__/savePluginDataFactory.test.ts @@ -12,7 +12,7 @@ describe('savePluginDataFactory', () => { const mockParams = { user: { - userId: 'uid:1234', + userId: 'figma:1234', figmaId: 'figma:1234', name: 'Jan Six', }, diff --git a/src/app/components/AppContainer/startupProcessSteps/pullTokensFactory.ts b/src/app/components/AppContainer/startupProcessSteps/pullTokensFactory.ts index 801ff2f2e..159dbd77c 100644 --- a/src/app/components/AppContainer/startupProcessSteps/pullTokensFactory.ts +++ b/src/app/components/AppContainer/startupProcessSteps/pullTokensFactory.ts @@ -13,6 +13,7 @@ import { notifyToUI } from '@/plugin/notifiers'; import type useRemoteTokens from '@/app/store/remoteTokens'; import { hasTokenValues } from '@/utils/hasTokenValues'; import { BackgroundJobs } from '@/constants/BackgroundJobs'; +import { isGitProvider } from '@/utils/is'; export function pullTokensFactory( store: Store, @@ -50,7 +51,8 @@ export function pullTokensFactory( if (matchingSet) { // found API credentials try { - track('Fetched from remote', { provider: matchingSet.provider }); + const isMultifile = isGitProvider(matchingSet) && 'filePath' in matchingSet && !matchingSet.filePath.endsWith('.json'); + track('Fetched from remote', { provider: matchingSet.provider, isMultifile }); if (!matchingSet.internalId) { track('missingInternalId', { provider: matchingSet.provider }); } diff --git a/src/app/components/AppContainer/startupProcessSteps/savePluginDataFactory.ts b/src/app/components/AppContainer/startupProcessSteps/savePluginDataFactory.ts index 51b1f3ed6..c2c95c159 100644 --- a/src/app/components/AppContainer/startupProcessSteps/savePluginDataFactory.ts +++ b/src/app/components/AppContainer/startupProcessSteps/savePluginDataFactory.ts @@ -1,11 +1,21 @@ import type { Dispatch } from '@/app/store'; import type { StartupMessage } from '@/types/AsyncMessages'; -import { identify } from '@/utils/analytics'; +import { identify, track } from '@/utils/analytics'; +import * as pjs from '../../../../../package.json'; export function savePluginDataFactory(dispatch: Dispatch, params: StartupMessage) { return async () => { const { user } = params; if (user) { + // initiate analytics + if (user.userId) { + identify({ + userId: user.userId, + figmaId: user.userId, + name: user.name, + }); + } + track('Launched', { version: pjs.plugin_version }); const { width, height, showEmptyGroups, ...rest } = params.settings; @@ -24,7 +34,6 @@ export function savePluginDataFactory(dispatch: Dispatch, params: StartupMessage dispatch.uiState.setOnboardingExplainerSyncProviders(params.onboardingExplainer.syncProviders); dispatch.uiState.setOnboardingExplainerInspect(params.onboardingExplainer.inspect); dispatch.settings.setUISettings(settings); - identify(user); } else { throw new Error('User not found'); } diff --git a/src/app/components/BranchSelector.tsx b/src/app/components/BranchSelector.tsx index 8cdd6e4f7..bf32768dc 100644 --- a/src/app/components/BranchSelector.tsx +++ b/src/app/components/BranchSelector.tsx @@ -38,6 +38,7 @@ import ProBadge from './ProBadge'; import { AsyncMessageChannel } from '@/AsyncMessageChannel'; import { AsyncMessageTypes } from '@/types/AsyncMessages'; import { StorageTypeCredentials } from '@/types/StorageType'; +import { track } from '@/utils/analytics'; const BranchSwitchMenuItemElement: React.FC<{ branch: string @@ -121,6 +122,7 @@ export default function BranchSelector() { }, [confirm]); const createBranchByChange = React.useCallback(() => { + track('Create new branch from current changes'); setMenuOpened(false); setIsCurrentChanges(true); setStartBranch(currentBranch ?? null); @@ -128,6 +130,7 @@ export default function BranchSelector() { }, [currentBranch]); const createNewBranchFrom = React.useCallback(async (branch: string) => { + track('Create new branch from specific branch'); setMenuOpened(false); if (hasChanges && await askUserIfPushChanges()) { @@ -154,6 +157,7 @@ export default function BranchSelector() { }, [apiData, localApiState, pullTokens, usedTokenSet, activeTheme, dispatch]); const onBranchSelected = React.useCallback(async (branch: string) => { + track('Branch changed'); if (hasChanges) { if (await askUserIfPushChanges()) { await changeAndPull(branch); diff --git a/src/app/components/Footer.tsx b/src/app/components/Footer.tsx index ab27273ad..0729f040d 100644 --- a/src/app/components/Footer.tsx +++ b/src/app/components/Footer.tsx @@ -106,7 +106,7 @@ export default function Footer() { - {`V ${pjs.plugin_version}`} + {`V ${pjs.plugin_version}`} diff --git a/src/app/components/StartScreen.tsx b/src/app/components/StartScreen.tsx index e651372e6..93d8e6b97 100644 --- a/src/app/components/StartScreen.tsx +++ b/src/app/components/StartScreen.tsx @@ -17,6 +17,7 @@ import { Tabs } from '@/constants/Tabs'; import { StorageProviderType } from '@/constants/StorageProviderType'; import Box from './Box'; import { transformProviderName } from '@/utils/transformProviderName'; +import { track } from '@/utils/analytics'; const StyledFigmaTokensLogo = styled(FigmaLetter, { width: '130px', @@ -46,11 +47,18 @@ function StartScreen() { const storageType = useSelector(storageTypeSelector); const apiProviders = useSelector(apiProvidersSelector); - const onSetDefaultTokens = React.useCallback(() => { + const onSetEmptyTokens = React.useCallback(() => { + track('Start with empty set'); dispatch.uiState.setActiveTab(Tabs.TOKENS); dispatch.tokenState.setEmptyTokens(); }, [dispatch]); + const onSetDefaultTokens = React.useCallback(() => { + track('Start with exmaple set'); + dispatch.uiState.setActiveTab(Tabs.TOKENS); + dispatch.tokenState.setDefaultTokens(); + }, [dispatch]); + const onSetSyncClick = React.useCallback(() => { if (storageType.provider === StorageProviderType.LOCAL) { return; @@ -85,11 +93,11 @@ function StartScreen() { Guides - + Getting started - + Documentation @@ -110,9 +118,14 @@ function StartScreen() { }} /> ) : ( - + + + + )} diff --git a/src/app/hooks/usePropertiesForType.ts b/src/app/hooks/usePropertiesForType.ts index b80a4187e..314cd3419 100644 --- a/src/app/hooks/usePropertiesForType.ts +++ b/src/app/hooks/usePropertiesForType.ts @@ -31,6 +31,24 @@ export function usePropertiesForTokenType(type: TokenTypes, value?: SingleToken[ { label: 'Bottom Left', name: Properties.borderRadiusBottomLeft, disabled }, ); break; + case TokenTypes.BORDER: + properties.push( + { + label: 'All', + name: Properties.border, + clear: [ + Properties.borderTop, + Properties.borderRight, + Properties.borderBottom, + Properties.borderLeft, + ], + }, + { label: 'Top', name: Properties.borderTop, disabled }, + { label: 'Right', name: Properties.borderRight, disabled }, + { label: 'Bottom', name: Properties.borderBottom, disabled }, + { label: 'Left', name: Properties.borderLeft, disabled }, + ); + break; case TokenTypes.BORDER_WIDTH: properties.push( { diff --git a/src/constants/Properties.ts b/src/constants/Properties.ts index 732537783..7c076efc2 100644 --- a/src/constants/Properties.ts +++ b/src/constants/Properties.ts @@ -12,6 +12,11 @@ export enum Properties { itemSpacing = 'itemSpacing', fill = 'fill', backgroundBlur = 'backgroundBlur', + border = 'border', + borderTop = 'borderTop', + borderRight = 'borderRight', + borderBottom = 'borderBottom', + borderLeft = 'borderLeft', borderColor = 'borderColor', borderRadius = 'borderRadius', borderRadiusTopLeft = 'borderRadiusTopLeft', @@ -35,7 +40,6 @@ export enum Properties { paragraphSpacing = 'paragraphSpacing', textCase = 'textCase', dimension = 'dimension', - border = 'border', textDecoration = 'textDecoration', asset = 'asset', tokenValue = 'tokenValue', diff --git a/src/plugin/node.ts b/src/plugin/node.ts index dc8d5d2b8..ac169c95b 100644 --- a/src/plugin/node.ts +++ b/src/plugin/node.ts @@ -179,7 +179,7 @@ async function migrateTokens(entry: NodeManagerNode, values: MapValuesToTokensRe if (typeof values.border === 'string' && typeof tokens.border !== 'undefined') { values.borderColor = values.border; await updatePluginData({ - entries: [entry], values: { [Properties.borderColor]: tokens.border, [Properties.border]: 'delete' }, + entries: [entry], values: { [Properties.borderColor]: tokens.border, [Properties.border]: 'delete' }, shouldRemove: false, }); await defaultNodeManager.updateNode(entry.node, (t) => ( omit(t, [Properties.border]) diff --git a/src/plugin/setBorderValuesOnTarget.ts b/src/plugin/setBorderValuesOnTarget.ts index 37a56c8ec..f1f440dde 100644 --- a/src/plugin/setBorderValuesOnTarget.ts +++ b/src/plugin/setBorderValuesOnTarget.ts @@ -4,13 +4,26 @@ import { isPrimitiveValue } from '@/utils/is'; import { transformValue } from './helpers'; import setColorValuesOnTarget from './setColorValuesOnTarget'; -export default function setBorderValuesOnTarget(target: BaseNode, token: Pick) { +export default function setBorderValuesOnTarget(target: BaseNode, token: Pick, side?: 'top' | 'right' | 'bottom' | 'left') { const { value } = token; const { color, width, style } = value; try { - if ('strokeWeight' in target && typeof width !== 'undefined' && isPrimitiveValue(width)) { + if ('strokeWeight' in target && typeof width !== 'undefined' && isPrimitiveValue(width) && !side) { target.strokeWeight = transformValue(String(width), 'borderWidth'); } + if ('strokeTopWeight' in target && typeof width !== 'undefined' && isPrimitiveValue(width) && side === 'top') { + target.strokeTopWeight = transformValue(String(width), 'borderWidth'); + } + if ('strokeRightWeight' in target && typeof width !== 'undefined' && isPrimitiveValue(width) && side === 'right') { + target.strokeRightWeight = transformValue(String(width), 'borderWidth'); + } + if ('strokeBottomWeight' in target && typeof width !== 'undefined' && isPrimitiveValue(width) && side === 'bottom') { + target.strokeBottomWeight = transformValue(String(width), 'borderWidth'); + } + if ('strokeLeftWeight' in target && typeof width !== 'undefined' && isPrimitiveValue(width) && side === 'left') { + target.strokeLeftWeight = transformValue(String(width), 'borderWidth'); + } + if (typeof color !== 'undefined' && typeof color === 'string') { setColorValuesOnTarget(target, { value: color }, 'strokes'); } diff --git a/src/plugin/setValuesOnNode.ts b/src/plugin/setValuesOnNode.ts index 9c4125f5e..8db2cb687 100644 --- a/src/plugin/setValuesOnNode.ts +++ b/src/plugin/setValuesOnNode.ts @@ -53,6 +53,18 @@ export default async function setValuesOnNode( if (values.border && isCompositeBorderValue(values.border)) { setBorderValuesOnTarget(node, { value: values.border }); } + if (values.borderTop && isCompositeBorderValue(values.borderTop)) { + setBorderValuesOnTarget(node, { value: values.borderTop }, 'top'); + } + if (values.borderRight && isCompositeBorderValue(values.borderRight)) { + setBorderValuesOnTarget(node, { value: values.borderRight }, 'right'); + } + if (values.borderBottom && isCompositeBorderValue(values.borderBottom)) { + setBorderValuesOnTarget(node, { value: values.borderBottom }, 'bottom'); + } + if (values.borderLeft && isCompositeBorderValue(values.borderLeft)) { + setBorderValuesOnTarget(node, { value: values.borderLeft }, 'left'); + } // if applied border is just a string, it's the older version where border was just a color. apply color then. if (values.border && typeof values.border === 'string' && typeof data.border !== 'undefined') {