Skip to content

1981 figma plugin doesnt work for new empty file #1984

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions tools/theme-selector-plugin/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"editorType": [
"figma"
],
"permissions": ["teamlibrary"],
"ui": "src/ui.html",
"networkAccess": {
"allowedDomains": [
Expand Down
264 changes: 182 additions & 82 deletions tools/theme-selector-plugin/src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ figma.showUI(__html__, {
themeColors: true
});

let collections: VariableCollection[] = [];
type VariableCollectionWithModeId = VariableCollection & {
modeId?: string;
};

let libVariables: LibraryVariableCollection[] = [];
let variableCollections: VariableCollectionWithModeId[] = [];
const themes: Array<{
collectionId: string;
collectionModeId: string;
Expand All @@ -25,99 +30,186 @@ const themes: Array<{
name: string;
}> = [];

const getFromLibrary = async () => {
// this list is all we have access to when the variables only exist in the library, not on the current page
libVariables = await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync();
const variableCollection: VariableCollection[] = [];

const baseVariable = libVariables.find(c => c.name === 'Base');
if (!baseVariable) {
console.error('Base collection not found');
return;
}

const base = await getCollectionFromKey(baseVariable.key);
if (!base || !base.modes) {
console.log('No collection found');
return;
}
variableCollections.push(base);

// get all collections from the base collection (Collection I & Collection II level)
const collections = await getSubCollections(base);

// get all theme collections from the all collections (Theme A, B, etc level)
const themecollections = await Promise.all(
collections.filter(c => c !== undefined).flatMap(c => getSubCollections(c))
);

// get all themes from the all theme collections (Sanoma Learning, Magister, etc level)
await Promise.all(
themecollections
.flat()
.filter(c => c !== undefined)
.flatMap(c => getSubCollections(c))
);

return variableCollection;
};

const getSubCollections = async (collection: VariableCollection | VariableCollectionWithModeId) => {
return await Promise.all(
collection.modes.map(async mode => {
if (mode.name === 'Placeholder') {
// we don't want to show the placeholder theme in the UI
return;
}
const modeVariable = libVariables.find(c => c.name === mode.name);
if (!modeVariable) {
console.error('Mode variable not found for', mode.name);
return;
}
const modeCollection = (await getCollectionFromKey(modeVariable.key)) as VariableCollectionWithModeId;
if (!modeCollection) {
console.error('Mode collection not found for', mode.name);
return;
}
modeCollection.modeId = mode.modeId;
variableCollections.push(modeCollection);
return modeCollection;
})
);
};

const getCollectionFromKey = async (key: string) => {
const variables = await figma.teamLibrary.getVariablesInLibraryCollectionAsync(key);
if (variables.length === 0) {
console.log('No variables found in library collection');
return;
}
const baseId = (await figma.variables.importVariableByKeyAsync(variables[0].key)).variableCollectionId;
const variablesByKey = await figma.variables.importVariableByKeyAsync(variables[0].key);
console.log('types', variablesByKey.name, variablesByKey.resolvedType, variables);
return await figma.variables.getVariableCollectionByIdAsync(baseId);
};

getFromLibrary()
.then(variableCollection => {
if (variableCollection) {
sendCollections();
} else {
// if the variables are not in the library, we need to get them from the current page
figma.variables
.getLocalVariableCollectionsAsync()
.then(c => {
variableCollections = c;
sendCollections();
})
.catch(e => {
console.error('Error getting local variable collections', e);
});
}
})
.catch(e => {
console.error('Error getting library variables', e);
});

/** Create the list of themes with all the id's of relevant parents and children and send it to the UI */
const sendCollections = () => {
figma.variables
.getLocalVariableCollectionsAsync()
.then(c => {
collections = c;

//find all collections that have a theme as a direct child
collections
.filter(c => c.name.includes('Themes'))
.forEach(themesCollection => {
const base = findCollectionByName('Base');
if (!base) {
console.error('Base collection not found');
return;
}
//find all collections that have a theme as a direct child
variableCollections
.filter(c => c.name.includes('Themes'))
.forEach(themesCollection => {
const base = findCollectionByName('Base');
if (!base) {
console.error('Base collection not found');
return;
}

// find the parent collection of the themes collection
const collection = findParentCollection(themesCollection.name);
if (!collection) {
console.error('Collection not found for', themesCollection.name);
return;
}
// find the modeId of the collection so it can be set on the base collection
const collectionAsMode = base.modes.find(sc => sc.name === collection.name);
if (!collectionAsMode) {
console.error('Collection mode id not found for', collection.name);
return;
}
// find the parent collection of the themes collection
const collection = findParentCollection(themesCollection.name);
if (!collection) {
console.error('Collection not found for', themesCollection.name);
return;
}
// find the modeId of the collection so it can be set on the base collection
const collectionAsMode = base.modes.find(sc => sc.name === collection.name);
if (!collectionAsMode) {
console.error('Collection mode id not found for', collection.name);
return;
}

// find the modeId of the themes collection so it can be set on the parent collection
const themeCollectionAsMode = collection.modes.find(sc => sc.name === themesCollection.name);
if (!themeCollectionAsMode) {
console.error('Theme collection mode id not found for', themesCollection.name);
// find the modeId of the themes collection so it can be set on the parent collection
const themeCollectionAsMode = collection.modes.find(sc => sc.name === themesCollection.name);
if (!themeCollectionAsMode) {
console.error('Theme collection mode id not found for', themesCollection.name);
return;
}

// for all themes in this themesCollection, check if there are any variants, and create a list of all relevant id's
themesCollection.modes
.filter(theme => theme.name !== 'Placeholder')
.forEach(theme => {
const themeCollection = findCollectionByName(theme.name);
if (!themeCollection) {
console.error('Theme collection not found for', theme.name);
return;
}

// for all themes in this themesCollection, check if there are any variants, and create a list of all relevant id's
themesCollection.modes.forEach(theme => {
const themeCollection = findCollectionByName(theme.name);
if (!themeCollection) {
console.error('Theme collection not found for', theme.name);
return;
}
const themeModes = themeCollection ? themeCollection.modes : [];
if (themeModes.length > 1) {
themeModes.forEach(mode => {
themes.push({
collectionId: collection.id,
collectionModeId: collectionAsMode.modeId,
themeCollectionId: themesCollection.id,
themeCollectionModeId: themeCollectionAsMode.modeId,
themeId: themeCollection.id,
themeModeId: theme.modeId,
variantId: mode.modeId,
compoundId: `${themeCollection.id}-${mode.modeId}`,
name: `${theme.name} - ${mode.name}`
});
});
} else {
const themeModes = themeCollection ? themeCollection.modes : [];
if (themeModes.length > 1) {
themeModes.forEach(mode => {
themes.push({
collectionId: collection.id,
collectionModeId: collectionAsMode.modeId,
themeCollectionId: themesCollection.id,
themeCollectionModeId: themeCollectionAsMode.modeId,
themeId: themeCollection.id,
themeModeId: theme.modeId,
compoundId: `${themeCollection.id}`,
name: `${theme.name}`
variantId: mode.modeId,
compoundId: `${themeCollection.id}-${mode.modeId}`,
name: `${theme.name} - ${mode.name}`
});
}
});
});
} else {
themes.push({
collectionId: collection.id,
collectionModeId: collectionAsMode.modeId,
themeCollectionId: themesCollection.id,
themeCollectionModeId: themeCollectionAsMode.modeId,
themeId: themeCollection.id,
themeModeId: theme.modeId,
compoundId: `${themeCollection.id}`,
name: `${theme.name}`
});
}
});
figma.ui.postMessage(themes, { origin: '*' });
})
.catch(e => {
console.error('Error getting collections', e);
});
figma.ui.postMessage(themes, { origin: '*' });
};

/** use this to find the collection based on the mode name */
const findCollectionByName = (name: string) => {
return collections.find(c => c.name === name);
return variableCollections.find(c => c.name === name);
};

/** Find the collection based on the ID */
const findCollectionById = (id: string) => {
return collections.find(c => c.id === id);
return variableCollections.find(c => c.id === id);
};

/** Find the parent collection of a child collection by looking for the child in the modes of the parent collection, based on name or modeId */
const findParentCollection = (child: string) => {
const parent = collections.find(c => {
const parent = variableCollections.find(c => {
const collectionsWithChild = c.modes.filter(mode => {
return mode.name === child || mode.modeId === child;
});
Expand All @@ -129,7 +221,7 @@ const findParentCollection = (child: string) => {
/** Removes all explicit variable modes from the current page. */
const removeCurrentVariableModes = () => {
Object.keys(figma.currentPage.explicitVariableModes).forEach(key => {
const collectionForKey = collections.find(c => c.id === key);
const collectionForKey = variableCollections.find(c => c.id === key);

if (collectionForKey) {
figma.currentPage.clearExplicitVariableModeForCollection(collectionForKey);
Expand All @@ -143,7 +235,7 @@ figma.ui.onmessage = async (msg: { type: string; theme: string }) => {
removeCurrentVariableModes();

const themeIds = themes.find(t => t.compoundId === msg.theme);
const base = collections.find(c => c.name === 'Base');
const base = variableCollections.find(c => c.name === 'Base');

if (!themeIds) {
console.error('Theme not found for', msg.theme);
Expand All @@ -155,21 +247,29 @@ figma.ui.onmessage = async (msg: { type: string; theme: string }) => {
const themeCollection = findCollectionById(themeIds.themeCollectionId);
const theme = findCollectionById(themeIds.themeId);

if (base) {
figma.currentPage.setExplicitVariableModeForCollection(base, themeIds.collectionModeId);
}
if (collection) {
figma.currentPage.setExplicitVariableModeForCollection(collection, themeIds.themeCollectionModeId);
}
if (themeCollection) {
figma.currentPage.setExplicitVariableModeForCollection(themeCollection, themeIds.themeModeId);
}
if (theme && themeIds.variantId) {
figma.currentPage.setExplicitVariableModeForCollection(theme, themeIds.variantId);
}
figma
.loadFontAsync({ family: 'The Message', style: 'DemiBold' })
.then(() => {
console.log({ base, collection, themeCollection, theme });

if (base) {
figma.currentPage.setExplicitVariableModeForCollection(base, themeIds.collectionModeId);
}
if (collection) {
figma.currentPage.setExplicitVariableModeForCollection(collection, themeIds.themeCollectionModeId);
}
if (themeCollection) {
figma.currentPage.setExplicitVariableModeForCollection(themeCollection, themeIds.themeModeId);
}
if (theme && themeIds.variantId) {
figma.currentPage.setExplicitVariableModeForCollection(theme, themeIds.variantId);
}
})
.catch(e => {
console.error('Error loading font', e);
figma.notify('Error loading font', { error: true });
});
}

figma.closePlugin();
};

sendCollections();
34 changes: 25 additions & 9 deletions tools/theme-selector-plugin/src/ui.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
<div>
<label>Select a theme</label>
<select id="theme"></select>
</div>
<button id="select">Set theme</button>
<form>
<div>
<label>Select a theme</label>
<select id="theme"></select>
</div>
<button id="select">Set theme</button>
</form>
<span id="loading"> Loading themes...</span>

<style>
body {
color: var(--figma-color-text);
display: grid;
font-family: Inter, Roboto, sans-serif;
font-size: 11px;
margin: 0;
padding: 16px;
display: flex;
place-content: stretch;
}
form {
display: none;
flex-direction: column;
gap: 16px;
font-family: Inter, Roboto, sans-serif;
font-size: 11px;
margin: 0;
color: var(--figma-color-text-primary);
}
div {
display: flex;
flex-direction: column;
gap: 8px;
}

span {
display: grid;
place-content: center;
}

label {
color: var(--figma-color-text-secondary);
}
Expand Down Expand Up @@ -85,6 +98,9 @@
option.innerText = item.name;
theme.appendChild(option);
});

document.getElementById('loading').style.display = 'none';
document.querySelector('form').style.display = 'flex';
}
};
</script>