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 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/tokens-studio-for-figma/src/app/components/Tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ function Tokens({ isActive }: { isActive: boolean }) {
}
}, [activeTokenSet]);

React.useEffect(() => {
function handleTokenSync(event: MessageEvent) {
if (event.data.pluginMessage?.type === 'sync_tokens') {
console.log('Received token update:', event.data.pluginMessage.tokens);
dispatch.tokenState.setTokenData({ values: event.data.pluginMessage.tokens });
}
}

window.addEventListener('message', handleTokenSync);

return () => {
window.removeEventListener('message', handleTokenSync);
};
});

Comment on lines +113 to +127
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets not do this inside Tokens.tsx, but rather inside packages/tokens-studio-for-figma/src/app/components/Initiator.tsx

this is where we have all the plugin message listeners - thats where we can emit this

const resolvedTokens = React.useMemo(
() => defaultTokenResolver.setTokens(mergeTokenGroups(tokens, usedTokenSet, {}, activeTokenSet)),
[tokens, usedTokenSet, activeTokenSet],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() };
};
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
Expand Up @@ -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,
Expand Down
19 changes: 15 additions & 4 deletions packages/tokens-studio-for-figma/src/plugin/sendDocumentChange.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { sendSelectionChange } from './sendSelectionChange';
import { ValuesProperty } from '@/figmaStorage/ValuesProperty';
import { postToUI } from './notifiers';
import { MessageFromPluginTypes } from '@/types/messages';

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you verify once if this only includes changes from another user? I want to make sure the changes you as a user make to not trigger this event

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will check for any other filters that we can leverage

// console.log('relevantChanges', relevantChanges);

if (relevantChanges.length > 0) {
const sharedTokens = await ValuesProperty.read();
console.log('sharedTokens', sharedTokens?.global);
postToUI({
type: MessageFromPluginTypes.SYNC_TOKENS,
tokens: sharedTokens?.global,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats the global here? I think we should sync all sets, not just the global set - i might be wrong here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the token data sent inside an object called global here, will check if its the set or in general its sent in that manner

});
}
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();
Expand Down
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
Expand Up @@ -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 };
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -393,8 +400,8 @@ export type AsyncMessages =
| UpdateVariablesAsyncMessage
| PreviewRequestStartupAsyncMessage
| RemoveRelaunchDataMessage
| RemoveStylesWithoutConnectionMessage;

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

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

export enum MessageFromPluginTypes {
SELECTION = 'selection',
Expand All @@ -24,6 +25,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 };
Expand Down Expand Up @@ -128,6 +130,11 @@ export type TrackFromPluginMessage = {
opts?: Record<string, unknown>;
};

export type SyncTokensFromPluginMessage = {
type: MessageFromPluginTypes.SYNC_TOKENS;
tokens?: AnyTokenList;
};

export type PostToUIMessage =
| NoSelectionFromPluginMessage
| SelectionFromPluginMessage
Expand All @@ -143,4 +150,5 @@ export type PostToUIMessage =
| CompleteJobTasksFromPluginMessage
| SetTokensFromPluginMessage
| NotifyExceptionFromPluginMessage
| TrackFromPluginMessage;
| TrackFromPluginMessage
| SyncTokensFromPluginMessage;
Loading