Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi user sync local storage #3254

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { MessageFromPluginTypes, PostToUIMessage } from '@/types/messages';
import useRemoteTokens from '../store/remoteTokens';
import { Dispatch } from '../store';
import { Dispatch, store } from '../store';
import useStorage from '../store/useStorage';
import { sortSelectionValueByProperties } from '@/utils/sortSelectionValueByProperties';
import { convertToOrderObj } from '@/utils/convertToOrderObj';
@@ -13,6 +13,8 @@ import { hasTokenValues } from '@/utils/hasTokenValues';
import { track } from '@/utils/analytics';
import { AsyncMessageChannel } from '@/AsyncMessageChannel';
import { AsyncMessageChannelPreview } from '@/AsyncMessageChannelPreview';
import { TokenFormatOptions } from '@/plugin/TokenFormatStoreClass';
import { INTERNAL_THEMES_NO_GROUP } from '@/constants/InternalTokenGroup';

// @README this component is not the "Initiator" anymore - as it is named
// but solely acts as the interface between the plugin and the UI
@@ -159,6 +161,27 @@ export function Initiator() {
});
break;
}
case MessageFromPluginTypes.SYNC_TOKENS: {
const currentState = store.getState();
console.log('currentState.tokenState', currentState.tokenState);
console.log('pluginMessage', pluginMessage);
const hasStateChanged = JSON.stringify(currentState.tokenState.tokens?.values) !== JSON.stringify(pluginMessage.values)
|| String(currentState.tokenState.activeTheme) !== String(pluginMessage.activeTheme)
|| JSON.stringify(currentState.tokenState.themes) !== JSON.stringify(pluginMessage.themes)
|| currentState.tokenState.tokenFormat !== pluginMessage.tokenFormat;

if (hasStateChanged) {
dispatch.tokenState.setTokenData({ values: pluginMessage.values ?? { global: [] } });
dispatch.tokenState.setActiveTheme({
newActiveTheme: typeof pluginMessage.activeTheme === 'string'
? { [INTERNAL_THEMES_NO_GROUP]: pluginMessage.activeTheme }
: pluginMessage.activeTheme ?? {},
});
dispatch.tokenState.setThemes(pluginMessage.themes ?? []);
dispatch.tokenState.setTokenFormat((pluginMessage.tokenFormat as TokenFormatOptions) ?? 'json');
}
break;
}
default:
break;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FigmaStorageProperty, FigmaStorageType } from './FigmaStorageProperty';

export const LastModifiedByProperty = new FigmaStorageProperty<string>(
FigmaStorageType.CLIENT_STORAGE,
'lastModifiedBy',
);
Original file line number Diff line number Diff line change
@@ -40,3 +40,4 @@ export * from './updateVariables';
export * from './setInitialLoad';
export * from './preview';
export * from './removeStylesWithoutConnection';
export * from './syncSharedTokens';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AsyncMessageChannelHandlers } from '@/AsyncMessageChannel';
import { AsyncMessageTypes } from '@/types/AsyncMessages';
import { ValuesProperty } from '@/figmaStorage/ValuesProperty';

export const syncSharedTokens: AsyncMessageChannelHandlers[AsyncMessageTypes.SYNC_SHARED_TOKENS] = async () => {
const sharedTokens = await ValuesProperty.read();
return { sharedTokens: Object.values(sharedTokens || {}).flat() };
};
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ export const update: AsyncMessageChannelHandlers[AsyncMessageTypes.UPDATE] = asy
checkForChanges: msg.checkForChanges ?? false,
collapsedTokenSets: msg.collapsedTokenSets,
tokenFormat: msg.tokenFormat || TokenFormatOptions.Legacy,
lastModifiedBy: figma.currentUser?.id ?? '',
});
}
if (msg.tokens) {
1 change: 1 addition & 0 deletions packages/tokens-studio-for-figma/src/plugin/controller.ts
Original file line number Diff line number Diff line change
@@ -65,6 +65,7 @@ AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.SET_AUTH_DATA, async
AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.CREATE_LOCAL_VARIABLES, asyncHandlers.createLocalVariables);
AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.CREATE_LOCAL_VARIABLES_WITHOUT_MODES, asyncHandlers.createLocalVariablesWithoutModes);
AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.RESOLVE_VARIABLE_INFO, asyncHandlers.resolveVariableInfo);
AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.SYNC_SHARED_TOKENS, asyncHandlers.syncSharedTokens);
AsyncMessageChannel.PluginInstance.handle(
AsyncMessageTypes.ATTACH_LOCAL_VARIABLES_TO_THEME,
asyncHandlers.attachLocalVariablesToTheme,
65 changes: 62 additions & 3 deletions packages/tokens-studio-for-figma/src/plugin/sendDocumentChange.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,71 @@
import { sendSelectionChange } from './sendSelectionChange';
import { ValuesProperty } from '@/figmaStorage/ValuesProperty';
import { postToUI } from './notifiers';
import { MessageFromPluginTypes } from '@/types/messages';
import { CheckForChangesProperty } from '@/figmaStorage/CheckForChangesProperty';
import { ThemesProperty } from '@/figmaStorage/ThemesProperty';
import { TokenFormatProperty } from '@/figmaStorage/TokenFormatProperty';
import { ThemeObjectsList } from '@/types/ThemeObjectsList';
import { LastModifiedByProperty } from '@/figmaStorage/LastModifiedBy';
import { UpdatedAtProperty } from '@/figmaStorage/UpdatedAtProperty';

export async function sendDocumentChange(event: DocumentChangeEvent) {
if (event.documentChanges.length === 1 && event.documentChanges[0].type === 'PROPERTY_CHANGE' && event.documentChanges[0].id === '0:0') {
const relevantChanges = event.documentChanges.filter((change) => change.type === 'PROPERTY_CHANGE'
&& change.origin === 'REMOTE'
&& change.properties?.includes('pluginData'));

console.log('updatedAT before return ', await UpdatedAtProperty.read());

if (relevantChanges.length === 0) return;

const { currentUser } = figma;

const hasChanges = await CheckForChangesProperty.read();
console.log('updatedAT ', await UpdatedAtProperty.read());

const sourceUserId = await LastModifiedByProperty.read();
const targetUserId = currentUser?.id;

console.log('Change detection:', {
sourceUserId,
targetUserId,
origin: event.documentChanges[0].origin,
hasChanges,
changes: relevantChanges.map((c) => ({
type: c.type,
...(c.type === 'PROPERTY_CHANGE' ? { properties: c.properties } : {}),
})),
});

if (hasChanges
&& relevantChanges.length > 2
&& event.documentChanges[0].origin === 'REMOTE'
&& event.documentChanges[0].type === 'PROPERTY_CHANGE'
&& event.documentChanges[0].properties?.includes('pluginData')) {
console.log('Plugin data change detected:', {
sourceUserId,
targetUserId,
properties: event.documentChanges[0].properties,
});

const sharedTokens = await ValuesProperty.read();

await CheckForChangesProperty.write(null);

postToUI({
type: MessageFromPluginTypes.SYNC_TOKENS,
tokens: sharedTokens ?? {},
values: await ValuesProperty.read() ?? { global: [] },
themes: (await ThemesProperty.read() as ThemeObjectsList) ?? [],
tokenFormat: await TokenFormatProperty.read() ?? 'json',
});

return;
}
const changeNodeIds = event.documentChanges.filter((change) => change.origin === 'REMOTE' && change.type === 'PROPERTY_CHANGE').map((change) => change.id);
if (!changeNodeIds.length) {

if (event.documentChanges.length === 1 && event.documentChanges[0].type === 'PROPERTY_CHANGE' && event.documentChanges[0].id === '0:0') {
return;
}

await sendSelectionChange();
}
14 changes: 11 additions & 3 deletions packages/tokens-studio-for-figma/src/types/AsyncMessages.ts
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@ export enum AsyncMessageTypes {
UPDATE_VARIABLES = 'async/update-variables',
SET_INITIAL_LOAD = 'async/set-initial-load',
PREVIEW_REQUEST_STARTUP = 'async/preview-request-startup',
SYNC_SHARED_TOKENS = 'async/sync-shared-tokens',
}

export type AsyncMessage<T extends AsyncMessageTypes, P = unknown> = P & { type: T };
@@ -348,6 +349,12 @@ AsyncMessageTypes.REMOVE_RELAUNCH_DATA,
export type PreviewRequestStartupAsyncMessage = AsyncMessage<AsyncMessageTypes.PREVIEW_REQUEST_STARTUP>;
export type PreviewRequestStartupAsyncMessageResult = AsyncMessage<AsyncMessageTypes.PREVIEW_REQUEST_STARTUP>;

export type SyncSharedTokensAsyncMessage = AsyncMessage<AsyncMessageTypes.SYNC_SHARED_TOKENS>;

export type SyncSharedTokensAsyncMessageResult = AsyncMessage<AsyncMessageTypes.SYNC_SHARED_TOKENS, {
sharedTokens: AnyTokenList
}>;

export type AsyncMessages =
CreateStylesAsyncMessage
| RenameStylesAsyncMessage
@@ -393,8 +400,8 @@ export type AsyncMessages =
| UpdateVariablesAsyncMessage
| PreviewRequestStartupAsyncMessage
| RemoveRelaunchDataMessage
| RemoveStylesWithoutConnectionMessage;

| RemoveStylesWithoutConnectionMessage
| SyncSharedTokensAsyncMessage;
export type AsyncMessageResults =
CreateStylesAsyncMessageResult
| RenameStylesAsyncMessageResult
@@ -440,7 +447,8 @@ export type AsyncMessageResults =
| UpdateVariablesAsyncMessageResult
| PreviewRequestStartupAsyncMessageResult
| RemoveRelaunchDataMessageResult
| RemoveStylesWithoutConnectionResult;
| RemoveStylesWithoutConnectionResult
| SyncSharedTokensAsyncMessageResult;

export type AsyncMessagesMap = {
[K in AsyncMessageTypes]: Extract<AsyncMessages, { type: K }>
15 changes: 14 additions & 1 deletion packages/tokens-studio-for-figma/src/types/messages.tsx
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ import type { StorageTypeCredentials } from './StorageType';
import { StyleToCreateToken, VariableToCreateToken } from './payloads';
import { TokenFormatOptions } from '@/plugin/TokenFormatStoreClass';
import { ApplyVariablesStylesOrRawValues } from '@/constants/ApplyVariablesStyleOrder';
import { AnyTokenList } from './tokens';
import type { ThemeObjectsList } from './ThemeObjectsList';

export enum MessageFromPluginTypes {
SELECTION = 'selection',
@@ -24,6 +26,7 @@ export enum MessageFromPluginTypes {
SET_TOKENS = 'set_tokens',
NOTIFY_EXCEPTION = 'notify_exception',
TRACK_FROM_PLUGIN = 'track_from_plugin',
SYNC_TOKENS = 'sync_tokens',
}

export type NoSelectionFromPluginMessage = { type: MessageFromPluginTypes.NO_SELECTION };
@@ -128,6 +131,15 @@ export type TrackFromPluginMessage = {
opts?: Record<string, unknown>;
};

export type SyncTokensFromPluginMessage = {
type: MessageFromPluginTypes.SYNC_TOKENS;
tokens?: Record<string, AnyTokenList>;
values?: Record<string, AnyTokenList>;
activeTheme?: string | Record<string, string>
themes?: ThemeObjectsList;
tokenFormat?: string;
};

export type PostToUIMessage =
| NoSelectionFromPluginMessage
| SelectionFromPluginMessage
@@ -143,4 +155,5 @@ export type PostToUIMessage =
| CompleteJobTasksFromPluginMessage
| SetTokensFromPluginMessage
| NotifyExceptionFromPluginMessage
| TrackFromPluginMessage;
| TrackFromPluginMessage
| SyncTokensFromPluginMessage;
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import {
} from '@/figmaStorage';
import { CollapsedTokenSetsProperty } from '@/figmaStorage/CollapsedTokenSetsProperty';
import { TokenFormatOptions } from '@/plugin/TokenFormatStoreClass';
import { LastModifiedByProperty } from '@/figmaStorage/LastModifiedBy';

type Payload = {
tokens: Record<string, AnyTokenList>
@@ -18,6 +19,7 @@ type Payload = {
checkForChanges: boolean
collapsedTokenSets: string[]
tokenFormat: TokenFormatOptions
lastModifiedBy: string
};

export async function updateLocalTokensData(payload: Payload) {
@@ -30,4 +32,6 @@ export async function updateLocalTokensData(payload: Payload) {
await CheckForChangesProperty.write(payload.checkForChanges);
await CollapsedTokenSetsProperty.write(payload.collapsedTokenSets);
await TokenFormatProperty.write(payload.tokenFormat);
// console.log('payload.lastModifiedBy in updateLocalTokensData', payload.lastModifiedBy);
await LastModifiedByProperty.write(payload.lastModifiedBy);
}
Loading