@mp3wizard/figma-console-mcp 1.15.3 → 1.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -37
- package/dist/cloudflare/core/cloud-websocket-relay.js +2 -3
- package/dist/cloudflare/core/config.js +1 -1
- package/dist/cloudflare/core/figma-api.js +6 -2
- package/dist/cloudflare/core/figma-desktop-connector.js +1 -2
- package/dist/cloudflare/core/port-discovery.js +73 -2
- package/dist/cloudflare/core/websocket-server.js +151 -21
- package/dist/cloudflare/core/write-tools.js +10 -2
- package/dist/cloudflare/index.js +701 -746
- package/dist/core/config.js +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/figjam-tools.d.ts +8 -0
- package/dist/core/figjam-tools.d.ts.map +1 -0
- package/dist/core/figjam-tools.js +486 -0
- package/dist/core/figjam-tools.js.map +1 -0
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +6 -2
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/figma-connector.d.ts +97 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.d.ts +23 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.js +25 -0
- package/dist/core/figma-desktop-connector.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +90 -29
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/port-discovery.d.ts.map +1 -1
- package/dist/core/port-discovery.js +2 -8
- package/dist/core/port-discovery.js.map +1 -1
- package/dist/core/slides-tools.d.ts +8 -0
- package/dist/core/slides-tools.d.ts.map +1 -0
- package/dist/core/slides-tools.js +608 -0
- package/dist/core/slides-tools.js.map +1 -0
- package/dist/core/websocket-connector.d.ts +97 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +75 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/websocket-server.d.ts +6 -0
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +11 -0
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +80 -32
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +1103 -5
- package/figma-desktop-bridge/icon.png +0 -0
- package/figma-desktop-bridge/manifest.json +1 -1
- package/figma-desktop-bridge/ui.html +1300 -173
- package/package.json +84 -85
|
@@ -66,8 +66,33 @@ figma.showUI(__html__, { width: 140, height: 50, visible: true, themeColors: tru
|
|
|
66
66
|
}
|
|
67
67
|
})();
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Detect editor type (figma | figjam | slides | dev)
|
|
70
|
+
var __editorType = figma.editorType || 'figma';
|
|
71
|
+
console.log('🌉 [Desktop Bridge] Editor type:', __editorType);
|
|
72
|
+
|
|
73
|
+
// Shared sticky color map — used by CREATE_STICKY and CREATE_STICKIES
|
|
74
|
+
var __stickyColors = {
|
|
75
|
+
'YELLOW': { r: 1, g: 0.85, b: 0.4 },
|
|
76
|
+
'BLUE': { r: 0.53, g: 0.78, b: 1 },
|
|
77
|
+
'GREEN': { r: 0.55, g: 0.87, b: 0.53 },
|
|
78
|
+
'PINK': { r: 1, g: 0.6, b: 0.78 },
|
|
79
|
+
'ORANGE': { r: 1, g: 0.71, b: 0.42 },
|
|
80
|
+
'PURPLE': { r: 0.78, g: 0.65, b: 1 },
|
|
81
|
+
'RED': { r: 1, g: 0.55, b: 0.55 },
|
|
82
|
+
'LIGHT_GRAY': { r: 0.9, g: 0.9, b: 0.9 },
|
|
83
|
+
'GRAY': { r: 0.7, g: 0.7, b: 0.7 }
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Immediately fetch and send variables data to UI (skip in FigJam — no variables API)
|
|
70
87
|
(async () => {
|
|
88
|
+
if (__editorType === 'figjam' || __editorType === 'slides') {
|
|
89
|
+
console.log('🌉 [Desktop Bridge] ' + __editorType + ' mode — skipping variables fetch');
|
|
90
|
+
figma.ui.postMessage({
|
|
91
|
+
type: 'VARIABLES_DATA',
|
|
92
|
+
data: { success: true, timestamp: Date.now(), fileKey: figma.fileKey || null, variables: [], variableCollections: [], editorType: __editorType }
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
71
96
|
try {
|
|
72
97
|
console.log('🌉 [Desktop Bridge] Fetching variables...');
|
|
73
98
|
|
|
@@ -90,6 +115,7 @@ figma.showUI(__html__, { width: 140, height: 50, visible: true, themeColors: tru
|
|
|
90
115
|
valuesByMode: v.valuesByMode,
|
|
91
116
|
variableCollectionId: v.variableCollectionId,
|
|
92
117
|
scopes: v.scopes,
|
|
118
|
+
codeSyntax: v.codeSyntax || {},
|
|
93
119
|
description: v.description,
|
|
94
120
|
hiddenFromPublishing: v.hiddenFromPublishing
|
|
95
121
|
})),
|
|
@@ -131,6 +157,7 @@ function serializeVariable(v) {
|
|
|
131
157
|
valuesByMode: v.valuesByMode,
|
|
132
158
|
variableCollectionId: v.variableCollectionId,
|
|
133
159
|
scopes: v.scopes,
|
|
160
|
+
codeSyntax: v.codeSyntax || {},
|
|
134
161
|
description: v.description,
|
|
135
162
|
hiddenFromPublishing: v.hiddenFromPublishing
|
|
136
163
|
};
|
|
@@ -218,7 +245,7 @@ figma.ui.onmessage = async (msg) => {
|
|
|
218
245
|
variables: variables.map(function(v) { return {
|
|
219
246
|
id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType,
|
|
220
247
|
valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId,
|
|
221
|
-
scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing
|
|
248
|
+
scopes: v.scopes, codeSyntax: v.codeSyntax || {}, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing
|
|
222
249
|
}; }),
|
|
223
250
|
variableCollections: collections.map(function(c) { return {
|
|
224
251
|
id: c.id, name: c.name, key: c.key, modes: c.modes,
|
|
@@ -1082,10 +1109,38 @@ figma.ui.onmessage = async (msg) => {
|
|
|
1082
1109
|
|
|
1083
1110
|
// Try published library first (by key), then fall back to local component (by nodeId)
|
|
1084
1111
|
if (msg.componentKey) {
|
|
1112
|
+
// Try importComponentByKeyAsync first (for COMPONENT nodes)
|
|
1085
1113
|
try {
|
|
1086
|
-
|
|
1114
|
+
var importResult = await Promise.race([
|
|
1115
|
+
figma.importComponentByKeyAsync(msg.componentKey),
|
|
1116
|
+
new Promise(function(_, reject) {
|
|
1117
|
+
setTimeout(function() { reject(new Error('Import timed out after 15s — component may not be published to a team library')); }, 15000);
|
|
1118
|
+
})
|
|
1119
|
+
]);
|
|
1120
|
+
component = importResult;
|
|
1087
1121
|
} catch (importError) {
|
|
1088
|
-
|
|
1122
|
+
var importErrMsg = importError && importError.message ? importError.message : String(importError);
|
|
1123
|
+
console.log('🌉 [Desktop Bridge] importComponentByKeyAsync failed: ' + importErrMsg);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// If that failed, try importComponentSetByKeyAsync (for COMPONENT_SET nodes)
|
|
1127
|
+
if (!component) {
|
|
1128
|
+
try {
|
|
1129
|
+
var setResult = await Promise.race([
|
|
1130
|
+
figma.importComponentSetByKeyAsync(msg.componentKey),
|
|
1131
|
+
new Promise(function(_, reject) {
|
|
1132
|
+
setTimeout(function() { reject(new Error('ComponentSet import timed out after 15s')); }, 15000);
|
|
1133
|
+
})
|
|
1134
|
+
]);
|
|
1135
|
+
// Got the component set — use its default variant (first child)
|
|
1136
|
+
if (setResult && setResult.type === 'COMPONENT_SET') {
|
|
1137
|
+
console.log('🌉 [Desktop Bridge] Imported component set "' + setResult.name + '" with ' + setResult.children.length + ' variants');
|
|
1138
|
+
component = setResult.defaultVariant || setResult.children[0];
|
|
1139
|
+
}
|
|
1140
|
+
} catch (setError) {
|
|
1141
|
+
var setErrMsg = setError && setError.message ? setError.message : String(setError);
|
|
1142
|
+
console.log('🌉 [Desktop Bridge] importComponentSetByKeyAsync also failed: ' + setErrMsg + ', trying local...');
|
|
1143
|
+
}
|
|
1089
1144
|
}
|
|
1090
1145
|
}
|
|
1091
1146
|
|
|
@@ -2100,7 +2155,8 @@ figma.ui.onmessage = async (msg) => {
|
|
|
2100
2155
|
currentPage: figma.currentPage.name,
|
|
2101
2156
|
currentPageId: figma.currentPage.id,
|
|
2102
2157
|
selectionCount: selection ? selection.length : 0,
|
|
2103
|
-
pluginVersion: PLUGIN_VERSION
|
|
2158
|
+
pluginVersion: PLUGIN_VERSION,
|
|
2159
|
+
editorType: __editorType
|
|
2104
2160
|
}
|
|
2105
2161
|
});
|
|
2106
2162
|
} catch (error) {
|
|
@@ -2783,6 +2839,1048 @@ figma.ui.onmessage = async (msg) => {
|
|
|
2783
2839
|
});
|
|
2784
2840
|
}
|
|
2785
2841
|
}
|
|
2842
|
+
|
|
2843
|
+
// ============================================================================
|
|
2844
|
+
// FIGJAM TOOLS — Only functional when editorType === 'figjam'
|
|
2845
|
+
// ============================================================================
|
|
2846
|
+
|
|
2847
|
+
// CREATE_STICKY - Create a sticky note
|
|
2848
|
+
else if (msg.type === 'CREATE_STICKY') {
|
|
2849
|
+
try {
|
|
2850
|
+
if (__editorType !== 'figjam') {
|
|
2851
|
+
throw new Error('CREATE_STICKY is only available in FigJam files');
|
|
2852
|
+
}
|
|
2853
|
+
console.log('🌉 [Desktop Bridge] Creating sticky note');
|
|
2854
|
+
|
|
2855
|
+
var sticky = figma.createSticky();
|
|
2856
|
+
await figma.loadFontAsync(sticky.text.fontName);
|
|
2857
|
+
sticky.text.characters = msg.text || '';
|
|
2858
|
+
|
|
2859
|
+
if (typeof msg.x === 'number') sticky.x = msg.x;
|
|
2860
|
+
if (typeof msg.y === 'number') sticky.y = msg.y;
|
|
2861
|
+
|
|
2862
|
+
// Set sticky color if provided
|
|
2863
|
+
if (msg.color) {
|
|
2864
|
+
var stickyColor = __stickyColors[msg.color.toUpperCase()];
|
|
2865
|
+
if (stickyColor) {
|
|
2866
|
+
sticky.fills = [{ type: 'SOLID', color: stickyColor }];
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
figma.ui.postMessage({
|
|
2871
|
+
type: 'CREATE_STICKY_RESULT',
|
|
2872
|
+
requestId: msg.requestId,
|
|
2873
|
+
success: true,
|
|
2874
|
+
data: { id: sticky.id, type: sticky.type, name: sticky.name, x: sticky.x, y: sticky.y }
|
|
2875
|
+
});
|
|
2876
|
+
|
|
2877
|
+
} catch (error) {
|
|
2878
|
+
console.error('🌉 [Desktop Bridge] Create sticky error:', error);
|
|
2879
|
+
figma.ui.postMessage({
|
|
2880
|
+
type: 'CREATE_STICKY_RESULT',
|
|
2881
|
+
requestId: msg.requestId,
|
|
2882
|
+
success: false,
|
|
2883
|
+
error: error.message || String(error)
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// CREATE_STICKIES - Batch create sticky notes
|
|
2889
|
+
else if (msg.type === 'CREATE_STICKIES') {
|
|
2890
|
+
try {
|
|
2891
|
+
if (__editorType !== 'figjam') {
|
|
2892
|
+
throw new Error('CREATE_STICKIES is only available in FigJam files');
|
|
2893
|
+
}
|
|
2894
|
+
console.log('🌉 [Desktop Bridge] Batch creating sticky notes:', msg.stickies.length);
|
|
2895
|
+
|
|
2896
|
+
var created = [];
|
|
2897
|
+
var failed = [];
|
|
2898
|
+
|
|
2899
|
+
// Load font once for all stickies (they all share the same default font)
|
|
2900
|
+
var stickyFontLoaded = false;
|
|
2901
|
+
|
|
2902
|
+
for (var si = 0; si < msg.stickies.length; si++) {
|
|
2903
|
+
try {
|
|
2904
|
+
var spec = msg.stickies[si];
|
|
2905
|
+
var sticky = figma.createSticky();
|
|
2906
|
+
if (!stickyFontLoaded) {
|
|
2907
|
+
await figma.loadFontAsync(sticky.text.fontName);
|
|
2908
|
+
stickyFontLoaded = true;
|
|
2909
|
+
}
|
|
2910
|
+
sticky.text.characters = spec.text || '';
|
|
2911
|
+
|
|
2912
|
+
if (typeof spec.x === 'number') sticky.x = spec.x;
|
|
2913
|
+
if (typeof spec.y === 'number') sticky.y = spec.y;
|
|
2914
|
+
|
|
2915
|
+
if (spec.color) {
|
|
2916
|
+
var sc = __stickyColors[spec.color.toUpperCase()];
|
|
2917
|
+
if (sc) {
|
|
2918
|
+
sticky.fills = [{ type: 'SOLID', color: sc }];
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
created.push({ id: sticky.id, type: sticky.type, name: sticky.name, x: sticky.x, y: sticky.y });
|
|
2923
|
+
} catch (e) {
|
|
2924
|
+
failed.push({ index: si, error: e.message || String(e) });
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
figma.ui.postMessage({
|
|
2929
|
+
type: 'CREATE_STICKIES_RESULT',
|
|
2930
|
+
requestId: msg.requestId,
|
|
2931
|
+
success: failed.length === 0,
|
|
2932
|
+
data: { created: created.length, failed: failed.length, results: created, errors: failed }
|
|
2933
|
+
});
|
|
2934
|
+
|
|
2935
|
+
} catch (error) {
|
|
2936
|
+
console.error('🌉 [Desktop Bridge] Batch create stickies error:', error);
|
|
2937
|
+
figma.ui.postMessage({
|
|
2938
|
+
type: 'CREATE_STICKIES_RESULT',
|
|
2939
|
+
requestId: msg.requestId,
|
|
2940
|
+
success: false,
|
|
2941
|
+
error: error.message || String(error)
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
// CREATE_CONNECTOR - Connect two nodes with a connector
|
|
2947
|
+
else if (msg.type === 'CREATE_CONNECTOR') {
|
|
2948
|
+
try {
|
|
2949
|
+
if (__editorType !== 'figjam') {
|
|
2950
|
+
throw new Error('CREATE_CONNECTOR is only available in FigJam files');
|
|
2951
|
+
}
|
|
2952
|
+
console.log('🌉 [Desktop Bridge] Creating connector');
|
|
2953
|
+
|
|
2954
|
+
var connector = figma.createConnector();
|
|
2955
|
+
|
|
2956
|
+
// Set start and end endpoints
|
|
2957
|
+
var startNode = await figma.getNodeByIdAsync(msg.startNodeId);
|
|
2958
|
+
var endNode = await figma.getNodeByIdAsync(msg.endNodeId);
|
|
2959
|
+
|
|
2960
|
+
if (!startNode) throw new Error('Start node not found: ' + msg.startNodeId);
|
|
2961
|
+
if (!endNode) throw new Error('End node not found: ' + msg.endNodeId);
|
|
2962
|
+
|
|
2963
|
+
connector.connectorStart = {
|
|
2964
|
+
endpointNodeId: msg.startNodeId,
|
|
2965
|
+
magnet: 'AUTO'
|
|
2966
|
+
};
|
|
2967
|
+
connector.connectorEnd = {
|
|
2968
|
+
endpointNodeId: msg.endNodeId,
|
|
2969
|
+
magnet: 'AUTO'
|
|
2970
|
+
};
|
|
2971
|
+
|
|
2972
|
+
// Set label text if provided
|
|
2973
|
+
if (msg.label) {
|
|
2974
|
+
try {
|
|
2975
|
+
await figma.loadFontAsync(connector.text.fontName);
|
|
2976
|
+
} catch (e) {
|
|
2977
|
+
// Connector default font may not be loadable — fall back to Inter
|
|
2978
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
|
|
2979
|
+
connector.text.fontName = { family: 'Inter', style: 'Medium' };
|
|
2980
|
+
}
|
|
2981
|
+
connector.text.characters = msg.label;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
figma.ui.postMessage({
|
|
2985
|
+
type: 'CREATE_CONNECTOR_RESULT',
|
|
2986
|
+
requestId: msg.requestId,
|
|
2987
|
+
success: true,
|
|
2988
|
+
data: { id: connector.id, type: connector.type, name: connector.name }
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2991
|
+
} catch (error) {
|
|
2992
|
+
console.error('🌉 [Desktop Bridge] Create connector error:', error);
|
|
2993
|
+
figma.ui.postMessage({
|
|
2994
|
+
type: 'CREATE_CONNECTOR_RESULT',
|
|
2995
|
+
requestId: msg.requestId,
|
|
2996
|
+
success: false,
|
|
2997
|
+
error: error.message || String(error)
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
// CREATE_SHAPE_WITH_TEXT - Create a labeled shape
|
|
3003
|
+
else if (msg.type === 'CREATE_SHAPE_WITH_TEXT') {
|
|
3004
|
+
try {
|
|
3005
|
+
if (__editorType !== 'figjam') {
|
|
3006
|
+
throw new Error('CREATE_SHAPE_WITH_TEXT is only available in FigJam files');
|
|
3007
|
+
}
|
|
3008
|
+
console.log('🌉 [Desktop Bridge] Creating shape with text');
|
|
3009
|
+
|
|
3010
|
+
var shape = figma.createShapeWithText();
|
|
3011
|
+
|
|
3012
|
+
// Set shape type if provided
|
|
3013
|
+
if (msg.shapeType) {
|
|
3014
|
+
shape.shapeType = msg.shapeType;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// Set text
|
|
3018
|
+
if (msg.text) {
|
|
3019
|
+
try {
|
|
3020
|
+
await figma.loadFontAsync(shape.text.fontName);
|
|
3021
|
+
} catch (e) {
|
|
3022
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
|
|
3023
|
+
shape.text.fontName = { family: 'Inter', style: 'Medium' };
|
|
3024
|
+
}
|
|
3025
|
+
shape.text.characters = msg.text;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
if (typeof msg.x === 'number') shape.x = msg.x;
|
|
3029
|
+
if (typeof msg.y === 'number') shape.y = msg.y;
|
|
3030
|
+
|
|
3031
|
+
figma.ui.postMessage({
|
|
3032
|
+
type: 'CREATE_SHAPE_WITH_TEXT_RESULT',
|
|
3033
|
+
requestId: msg.requestId,
|
|
3034
|
+
success: true,
|
|
3035
|
+
data: { id: shape.id, type: shape.type, name: shape.name, x: shape.x, y: shape.y }
|
|
3036
|
+
});
|
|
3037
|
+
|
|
3038
|
+
} catch (error) {
|
|
3039
|
+
console.error('🌉 [Desktop Bridge] Create shape with text error:', error);
|
|
3040
|
+
figma.ui.postMessage({
|
|
3041
|
+
type: 'CREATE_SHAPE_WITH_TEXT_RESULT',
|
|
3042
|
+
requestId: msg.requestId,
|
|
3043
|
+
success: false,
|
|
3044
|
+
error: error.message || String(error)
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
// CREATE_TABLE - Create a table with data
|
|
3050
|
+
else if (msg.type === 'CREATE_TABLE') {
|
|
3051
|
+
try {
|
|
3052
|
+
if (__editorType !== 'figjam') {
|
|
3053
|
+
throw new Error('CREATE_TABLE is only available in FigJam files');
|
|
3054
|
+
}
|
|
3055
|
+
console.log('🌉 [Desktop Bridge] Creating table:', msg.rows, 'x', msg.columns);
|
|
3056
|
+
|
|
3057
|
+
var table = figma.createTable(msg.rows, msg.columns);
|
|
3058
|
+
|
|
3059
|
+
if (typeof msg.x === 'number') table.x = msg.x;
|
|
3060
|
+
if (typeof msg.y === 'number') table.y = msg.y;
|
|
3061
|
+
|
|
3062
|
+
// Populate cells if data provided
|
|
3063
|
+
if (msg.data && Array.isArray(msg.data)) {
|
|
3064
|
+
for (var row = 0; row < msg.data.length && row < msg.rows; row++) {
|
|
3065
|
+
for (var col = 0; col < msg.data[row].length && col < msg.columns; col++) {
|
|
3066
|
+
var cell = table.cellAt(row, col);
|
|
3067
|
+
if (cell && msg.data[row][col] != null) {
|
|
3068
|
+
await figma.loadFontAsync(cell.text.fontName);
|
|
3069
|
+
cell.text.characters = String(msg.data[row][col]);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
figma.ui.postMessage({
|
|
3076
|
+
type: 'CREATE_TABLE_RESULT',
|
|
3077
|
+
requestId: msg.requestId,
|
|
3078
|
+
success: true,
|
|
3079
|
+
data: { id: table.id, type: table.type, name: table.name, rows: msg.rows, columns: msg.columns }
|
|
3080
|
+
});
|
|
3081
|
+
|
|
3082
|
+
} catch (error) {
|
|
3083
|
+
console.error('🌉 [Desktop Bridge] Create table error:', error);
|
|
3084
|
+
figma.ui.postMessage({
|
|
3085
|
+
type: 'CREATE_TABLE_RESULT',
|
|
3086
|
+
requestId: msg.requestId,
|
|
3087
|
+
success: false,
|
|
3088
|
+
error: error.message || String(error)
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
// CREATE_CODE_BLOCK - Create a code block
|
|
3094
|
+
else if (msg.type === 'CREATE_CODE_BLOCK') {
|
|
3095
|
+
try {
|
|
3096
|
+
if (__editorType !== 'figjam') {
|
|
3097
|
+
throw new Error('CREATE_CODE_BLOCK is only available in FigJam files');
|
|
3098
|
+
}
|
|
3099
|
+
console.log('🌉 [Desktop Bridge] Creating code block');
|
|
3100
|
+
|
|
3101
|
+
var codeBlock = figma.createCodeBlock();
|
|
3102
|
+
|
|
3103
|
+
// Code blocks require Source Code Pro font, fall back to Inter if unavailable
|
|
3104
|
+
try {
|
|
3105
|
+
await figma.loadFontAsync({ family: 'Source Code Pro', style: 'Medium' });
|
|
3106
|
+
} catch (e) {
|
|
3107
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
if (msg.code) {
|
|
3111
|
+
codeBlock.code = msg.code;
|
|
3112
|
+
}
|
|
3113
|
+
if (msg.language) {
|
|
3114
|
+
codeBlock.codeLanguage = msg.language;
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
if (typeof msg.x === 'number') codeBlock.x = msg.x;
|
|
3118
|
+
if (typeof msg.y === 'number') codeBlock.y = msg.y;
|
|
3119
|
+
|
|
3120
|
+
figma.ui.postMessage({
|
|
3121
|
+
type: 'CREATE_CODE_BLOCK_RESULT',
|
|
3122
|
+
requestId: msg.requestId,
|
|
3123
|
+
success: true,
|
|
3124
|
+
data: { id: codeBlock.id, type: codeBlock.type, name: codeBlock.name, x: codeBlock.x, y: codeBlock.y }
|
|
3125
|
+
});
|
|
3126
|
+
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
console.error('🌉 [Desktop Bridge] Create code block error:', error);
|
|
3129
|
+
figma.ui.postMessage({
|
|
3130
|
+
type: 'CREATE_CODE_BLOCK_RESULT',
|
|
3131
|
+
requestId: msg.requestId,
|
|
3132
|
+
success: false,
|
|
3133
|
+
error: error.message || String(error)
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// GET_BOARD_CONTENTS - Read all FigJam nodes from the current page
|
|
3139
|
+
else if (msg.type === 'GET_BOARD_CONTENTS') {
|
|
3140
|
+
try {
|
|
3141
|
+
if (__editorType !== 'figjam') {
|
|
3142
|
+
throw new Error('GET_BOARD_CONTENTS is only available in FigJam files');
|
|
3143
|
+
}
|
|
3144
|
+
console.log('🌉 [Desktop Bridge] Reading board contents');
|
|
3145
|
+
|
|
3146
|
+
var maxNodes = msg.maxNodes || 500;
|
|
3147
|
+
var filterTypes = msg.nodeTypes || null;
|
|
3148
|
+
|
|
3149
|
+
// FigJam node types we care about
|
|
3150
|
+
var figjamTypes = ['STICKY', 'SHAPE_WITH_TEXT', 'CONNECTOR', 'TABLE', 'CODE_BLOCK', 'SECTION', 'FRAME', 'TEXT'];
|
|
3151
|
+
|
|
3152
|
+
var allNodes = figma.currentPage.children;
|
|
3153
|
+
var results = [];
|
|
3154
|
+
var truncated = false;
|
|
3155
|
+
|
|
3156
|
+
for (var ni = 0; ni < allNodes.length && results.length < maxNodes; ni++) {
|
|
3157
|
+
var node = allNodes[ni];
|
|
3158
|
+
|
|
3159
|
+
// Skip if filtering and this type isn't in the filter list
|
|
3160
|
+
if (filterTypes && filterTypes.indexOf(node.type) === -1) continue;
|
|
3161
|
+
// Skip if not a FigJam-relevant type
|
|
3162
|
+
if (!filterTypes && figjamTypes.indexOf(node.type) === -1) continue;
|
|
3163
|
+
|
|
3164
|
+
var entry = {
|
|
3165
|
+
id: node.id,
|
|
3166
|
+
type: node.type,
|
|
3167
|
+
name: node.name,
|
|
3168
|
+
x: node.x,
|
|
3169
|
+
y: node.y,
|
|
3170
|
+
width: node.width,
|
|
3171
|
+
height: node.height
|
|
3172
|
+
};
|
|
3173
|
+
|
|
3174
|
+
// Extract text content based on node type
|
|
3175
|
+
if (node.type === 'STICKY') {
|
|
3176
|
+
entry.text = node.text ? node.text.characters : '';
|
|
3177
|
+
if (node.fills && node.fills.length > 0 && node.fills[0].color) {
|
|
3178
|
+
entry.color = node.fills[0].color;
|
|
3179
|
+
}
|
|
3180
|
+
} else if (node.type === 'SHAPE_WITH_TEXT') {
|
|
3181
|
+
entry.text = node.text ? node.text.characters : '';
|
|
3182
|
+
entry.shapeType = node.shapeType || 'ROUNDED_RECTANGLE';
|
|
3183
|
+
} else if (node.type === 'CONNECTOR') {
|
|
3184
|
+
entry.connectorStart = node.connectorStart || null;
|
|
3185
|
+
entry.connectorEnd = node.connectorEnd || null;
|
|
3186
|
+
entry.text = node.text ? node.text.characters : '';
|
|
3187
|
+
} else if (node.type === 'CODE_BLOCK') {
|
|
3188
|
+
entry.code = node.code || '';
|
|
3189
|
+
entry.codeLanguage = node.codeLanguage || '';
|
|
3190
|
+
} else if (node.type === 'TABLE') {
|
|
3191
|
+
entry.numRows = node.numRows;
|
|
3192
|
+
entry.numColumns = node.numColumns;
|
|
3193
|
+
// Read first 10 rows of cell data to avoid huge payloads
|
|
3194
|
+
var cellData = [];
|
|
3195
|
+
var maxCellRows = Math.min(node.numRows, 10);
|
|
3196
|
+
for (var row = 0; row < maxCellRows; row++) {
|
|
3197
|
+
var rowData = [];
|
|
3198
|
+
for (var col = 0; col < node.numColumns; col++) {
|
|
3199
|
+
try {
|
|
3200
|
+
var cell = node.cellAt(row, col);
|
|
3201
|
+
rowData.push(cell && cell.text ? cell.text.characters : '');
|
|
3202
|
+
} catch (e) {
|
|
3203
|
+
rowData.push('');
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
cellData.push(rowData);
|
|
3207
|
+
}
|
|
3208
|
+
entry.cellData = cellData;
|
|
3209
|
+
if (node.numRows > 10) entry.cellDataTruncated = true;
|
|
3210
|
+
} else if (node.type === 'SECTION') {
|
|
3211
|
+
entry.childCount = node.children ? node.children.length : 0;
|
|
3212
|
+
} else if (node.type === 'TEXT') {
|
|
3213
|
+
entry.text = node.characters || '';
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
results.push(entry);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
if (results.length >= maxNodes) truncated = true;
|
|
3220
|
+
|
|
3221
|
+
figma.ui.postMessage({
|
|
3222
|
+
type: 'GET_BOARD_CONTENTS_RESULT',
|
|
3223
|
+
requestId: msg.requestId,
|
|
3224
|
+
success: true,
|
|
3225
|
+
data: {
|
|
3226
|
+
nodes: results,
|
|
3227
|
+
totalFound: results.length,
|
|
3228
|
+
truncated: truncated,
|
|
3229
|
+
page: figma.currentPage.name
|
|
3230
|
+
}
|
|
3231
|
+
});
|
|
3232
|
+
|
|
3233
|
+
} catch (error) {
|
|
3234
|
+
console.error('🌉 [Desktop Bridge] Get board contents error:', error);
|
|
3235
|
+
figma.ui.postMessage({
|
|
3236
|
+
type: 'GET_BOARD_CONTENTS_RESULT',
|
|
3237
|
+
requestId: msg.requestId,
|
|
3238
|
+
success: false,
|
|
3239
|
+
error: error.message || String(error)
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
// GET_CONNECTIONS - Read the connection graph from the board
|
|
3245
|
+
else if (msg.type === 'GET_CONNECTIONS') {
|
|
3246
|
+
try {
|
|
3247
|
+
if (__editorType !== 'figjam') {
|
|
3248
|
+
throw new Error('GET_CONNECTIONS is only available in FigJam files');
|
|
3249
|
+
}
|
|
3250
|
+
console.log('🌉 [Desktop Bridge] Reading connection graph');
|
|
3251
|
+
|
|
3252
|
+
var connectors = figma.currentPage.findAll(function(n) { return n.type === 'CONNECTOR'; });
|
|
3253
|
+
var edges = [];
|
|
3254
|
+
var nodeMap = {};
|
|
3255
|
+
|
|
3256
|
+
for (var ci = 0; ci < connectors.length; ci++) {
|
|
3257
|
+
var conn = connectors[ci];
|
|
3258
|
+
var startId = conn.connectorStart ? conn.connectorStart.endpointNodeId : null;
|
|
3259
|
+
var endId = conn.connectorEnd ? conn.connectorEnd.endpointNodeId : null;
|
|
3260
|
+
var label = conn.text ? conn.text.characters : '';
|
|
3261
|
+
|
|
3262
|
+
edges.push({
|
|
3263
|
+
connectorId: conn.id,
|
|
3264
|
+
startNodeId: startId,
|
|
3265
|
+
endNodeId: endId,
|
|
3266
|
+
label: label
|
|
3267
|
+
});
|
|
3268
|
+
|
|
3269
|
+
// Build a lookup of connected nodes with their names/types
|
|
3270
|
+
if (startId && !nodeMap[startId]) {
|
|
3271
|
+
var startNode = await figma.getNodeByIdAsync(startId);
|
|
3272
|
+
if (startNode) {
|
|
3273
|
+
nodeMap[startId] = {
|
|
3274
|
+
id: startId,
|
|
3275
|
+
type: startNode.type,
|
|
3276
|
+
name: startNode.name,
|
|
3277
|
+
text: startNode.text ? startNode.text.characters : (startNode.characters || '')
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
if (endId && !nodeMap[endId]) {
|
|
3282
|
+
var endNode = await figma.getNodeByIdAsync(endId);
|
|
3283
|
+
if (endNode) {
|
|
3284
|
+
nodeMap[endId] = {
|
|
3285
|
+
id: endId,
|
|
3286
|
+
type: endNode.type,
|
|
3287
|
+
name: endNode.name,
|
|
3288
|
+
text: endNode.text ? endNode.text.characters : (endNode.characters || '')
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
figma.ui.postMessage({
|
|
3295
|
+
type: 'GET_CONNECTIONS_RESULT',
|
|
3296
|
+
requestId: msg.requestId,
|
|
3297
|
+
success: true,
|
|
3298
|
+
data: {
|
|
3299
|
+
edges: edges,
|
|
3300
|
+
connectedNodes: nodeMap,
|
|
3301
|
+
totalConnectors: connectors.length,
|
|
3302
|
+
totalConnectedNodes: Object.keys(nodeMap).length
|
|
3303
|
+
}
|
|
3304
|
+
});
|
|
3305
|
+
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
console.error('🌉 [Desktop Bridge] Get connections error:', error);
|
|
3308
|
+
figma.ui.postMessage({
|
|
3309
|
+
type: 'GET_CONNECTIONS_RESULT',
|
|
3310
|
+
requestId: msg.requestId,
|
|
3311
|
+
success: false,
|
|
3312
|
+
error: error.message || String(error)
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
// ==========================================================================
|
|
3317
|
+
// SLIDES TOOLS — Figma Slides command handlers
|
|
3318
|
+
// ==========================================================================
|
|
3319
|
+
|
|
3320
|
+
// LIST_SLIDES - List all slides in the presentation
|
|
3321
|
+
else if (msg.type === 'LIST_SLIDES') {
|
|
3322
|
+
try {
|
|
3323
|
+
if (__editorType !== 'slides') {
|
|
3324
|
+
throw new Error('LIST_SLIDES is only available in Slides files');
|
|
3325
|
+
}
|
|
3326
|
+
console.log('🌉 [Desktop Bridge] Listing slides');
|
|
3327
|
+
|
|
3328
|
+
var grid = figma.getSlideGrid();
|
|
3329
|
+
var slides = [];
|
|
3330
|
+
for (var rowIdx = 0; rowIdx < grid.length; rowIdx++) {
|
|
3331
|
+
var row = grid[rowIdx];
|
|
3332
|
+
// SlideGrid rows are array-like (iterable with numeric indices), not objects with .children
|
|
3333
|
+
for (var colIdx = 0; colIdx < row.length; colIdx++) {
|
|
3334
|
+
var slide = row[colIdx];
|
|
3335
|
+
slides.push({
|
|
3336
|
+
id: slide.id,
|
|
3337
|
+
name: slide.name,
|
|
3338
|
+
row: rowIdx,
|
|
3339
|
+
col: colIdx,
|
|
3340
|
+
isSkippedSlide: slide.isSkippedSlide,
|
|
3341
|
+
childCount: slide.children ? slide.children.length : 0
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
figma.ui.postMessage({
|
|
3347
|
+
type: 'LIST_SLIDES_RESULT',
|
|
3348
|
+
requestId: msg.requestId,
|
|
3349
|
+
success: true,
|
|
3350
|
+
data: { slides: slides, totalSlides: slides.length, totalRows: grid.length }
|
|
3351
|
+
});
|
|
3352
|
+
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
console.error('🌉 [Desktop Bridge] List slides error:', error);
|
|
3355
|
+
figma.ui.postMessage({
|
|
3356
|
+
type: 'LIST_SLIDES_RESULT',
|
|
3357
|
+
requestId: msg.requestId,
|
|
3358
|
+
success: false,
|
|
3359
|
+
error: error.message || String(error)
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
// GET_SLIDE_CONTENT - Get node tree of a slide
|
|
3365
|
+
else if (msg.type === 'GET_SLIDE_CONTENT') {
|
|
3366
|
+
try {
|
|
3367
|
+
if (__editorType !== 'slides') {
|
|
3368
|
+
throw new Error('GET_SLIDE_CONTENT is only available in Slides files');
|
|
3369
|
+
}
|
|
3370
|
+
var slideNode = await figma.getNodeByIdAsync(msg.slideId);
|
|
3371
|
+
if (!slideNode || slideNode.type !== 'SLIDE') {
|
|
3372
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3373
|
+
}
|
|
3374
|
+
console.log('🌉 [Desktop Bridge] Getting slide content:', slideNode.name);
|
|
3375
|
+
|
|
3376
|
+
function serializeNode(n) {
|
|
3377
|
+
var result = { id: n.id, type: n.type, name: n.name, x: n.x, y: n.y, width: n.width, height: n.height };
|
|
3378
|
+
if (n.type === 'TEXT') {
|
|
3379
|
+
result.characters = n.characters;
|
|
3380
|
+
result.fontSize = n.fontSize;
|
|
3381
|
+
}
|
|
3382
|
+
if (n.children && n.children.length > 0) {
|
|
3383
|
+
result.children = [];
|
|
3384
|
+
for (var i = 0; i < n.children.length; i++) {
|
|
3385
|
+
result.children.push(serializeNode(n.children[i]));
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
return result;
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
figma.ui.postMessage({
|
|
3392
|
+
type: 'GET_SLIDE_CONTENT_RESULT',
|
|
3393
|
+
requestId: msg.requestId,
|
|
3394
|
+
success: true,
|
|
3395
|
+
data: serializeNode(slideNode)
|
|
3396
|
+
});
|
|
3397
|
+
|
|
3398
|
+
} catch (error) {
|
|
3399
|
+
console.error('🌉 [Desktop Bridge] Get slide content error:', error);
|
|
3400
|
+
figma.ui.postMessage({
|
|
3401
|
+
type: 'GET_SLIDE_CONTENT_RESULT',
|
|
3402
|
+
requestId: msg.requestId,
|
|
3403
|
+
success: false,
|
|
3404
|
+
error: error.message || String(error)
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// CREATE_SLIDE - Create a new slide
|
|
3410
|
+
else if (msg.type === 'CREATE_SLIDE') {
|
|
3411
|
+
try {
|
|
3412
|
+
if (__editorType !== 'slides') {
|
|
3413
|
+
throw new Error('CREATE_SLIDE is only available in Slides files');
|
|
3414
|
+
}
|
|
3415
|
+
console.log('🌉 [Desktop Bridge] Creating slide');
|
|
3416
|
+
|
|
3417
|
+
var newSlide;
|
|
3418
|
+
if (typeof msg.row === 'number' && typeof msg.col === 'number') {
|
|
3419
|
+
newSlide = figma.createSlide({ row: msg.row, col: msg.col });
|
|
3420
|
+
} else {
|
|
3421
|
+
newSlide = figma.createSlide();
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
figma.ui.postMessage({
|
|
3425
|
+
type: 'CREATE_SLIDE_RESULT',
|
|
3426
|
+
requestId: msg.requestId,
|
|
3427
|
+
success: true,
|
|
3428
|
+
data: { id: newSlide.id, name: newSlide.name }
|
|
3429
|
+
});
|
|
3430
|
+
|
|
3431
|
+
} catch (error) {
|
|
3432
|
+
console.error('🌉 [Desktop Bridge] Create slide error:', error);
|
|
3433
|
+
figma.ui.postMessage({
|
|
3434
|
+
type: 'CREATE_SLIDE_RESULT',
|
|
3435
|
+
requestId: msg.requestId,
|
|
3436
|
+
success: false,
|
|
3437
|
+
error: error.message || String(error)
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// DELETE_SLIDE - Delete a slide
|
|
3443
|
+
else if (msg.type === 'DELETE_SLIDE') {
|
|
3444
|
+
try {
|
|
3445
|
+
if (__editorType !== 'slides') {
|
|
3446
|
+
throw new Error('DELETE_SLIDE is only available in Slides files');
|
|
3447
|
+
}
|
|
3448
|
+
var delSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3449
|
+
if (!delSlide || delSlide.type !== 'SLIDE') {
|
|
3450
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3451
|
+
}
|
|
3452
|
+
console.log('🌉 [Desktop Bridge] Deleting slide:', delSlide.name);
|
|
3453
|
+
|
|
3454
|
+
var delName = delSlide.name;
|
|
3455
|
+
delSlide.remove();
|
|
3456
|
+
|
|
3457
|
+
figma.ui.postMessage({
|
|
3458
|
+
type: 'DELETE_SLIDE_RESULT',
|
|
3459
|
+
requestId: msg.requestId,
|
|
3460
|
+
success: true,
|
|
3461
|
+
data: { deleted: msg.slideId, name: delName }
|
|
3462
|
+
});
|
|
3463
|
+
|
|
3464
|
+
} catch (error) {
|
|
3465
|
+
console.error('🌉 [Desktop Bridge] Delete slide error:', error);
|
|
3466
|
+
figma.ui.postMessage({
|
|
3467
|
+
type: 'DELETE_SLIDE_RESULT',
|
|
3468
|
+
requestId: msg.requestId,
|
|
3469
|
+
success: false,
|
|
3470
|
+
error: error.message || String(error)
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// DUPLICATE_SLIDE - Clone a slide
|
|
3476
|
+
else if (msg.type === 'DUPLICATE_SLIDE') {
|
|
3477
|
+
try {
|
|
3478
|
+
if (__editorType !== 'slides') {
|
|
3479
|
+
throw new Error('DUPLICATE_SLIDE is only available in Slides files');
|
|
3480
|
+
}
|
|
3481
|
+
var srcSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3482
|
+
if (!srcSlide || srcSlide.type !== 'SLIDE') {
|
|
3483
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3484
|
+
}
|
|
3485
|
+
console.log('🌉 [Desktop Bridge] Duplicating slide:', srcSlide.name);
|
|
3486
|
+
|
|
3487
|
+
var clone = srcSlide.clone();
|
|
3488
|
+
|
|
3489
|
+
figma.ui.postMessage({
|
|
3490
|
+
type: 'DUPLICATE_SLIDE_RESULT',
|
|
3491
|
+
requestId: msg.requestId,
|
|
3492
|
+
success: true,
|
|
3493
|
+
data: { originalId: msg.slideId, newId: clone.id, name: clone.name }
|
|
3494
|
+
});
|
|
3495
|
+
|
|
3496
|
+
} catch (error) {
|
|
3497
|
+
console.error('🌉 [Desktop Bridge] Duplicate slide error:', error);
|
|
3498
|
+
figma.ui.postMessage({
|
|
3499
|
+
type: 'DUPLICATE_SLIDE_RESULT',
|
|
3500
|
+
requestId: msg.requestId,
|
|
3501
|
+
success: false,
|
|
3502
|
+
error: error.message || String(error)
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
// GET_SLIDE_GRID - Get 2D grid layout
|
|
3508
|
+
else if (msg.type === 'GET_SLIDE_GRID') {
|
|
3509
|
+
try {
|
|
3510
|
+
if (__editorType !== 'slides') {
|
|
3511
|
+
throw new Error('GET_SLIDE_GRID is only available in Slides files');
|
|
3512
|
+
}
|
|
3513
|
+
console.log('🌉 [Desktop Bridge] Getting slide grid');
|
|
3514
|
+
|
|
3515
|
+
var slideGrid = figma.getSlideGrid();
|
|
3516
|
+
var gridData = [];
|
|
3517
|
+
for (var ri = 0; ri < slideGrid.length; ri++) {
|
|
3518
|
+
var gridRow = slideGrid[ri];
|
|
3519
|
+
var rowSlides = [];
|
|
3520
|
+
// SlideGrid rows are array-like (iterable with numeric indices)
|
|
3521
|
+
for (var ci = 0; ci < gridRow.length; ci++) {
|
|
3522
|
+
var gs = gridRow[ci];
|
|
3523
|
+
rowSlides.push({ id: gs.id, name: gs.name, col: ci, isSkippedSlide: gs.isSkippedSlide });
|
|
3524
|
+
}
|
|
3525
|
+
gridData.push({ rowIndex: ri, slides: rowSlides });
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
figma.ui.postMessage({
|
|
3529
|
+
type: 'GET_SLIDE_GRID_RESULT',
|
|
3530
|
+
requestId: msg.requestId,
|
|
3531
|
+
success: true,
|
|
3532
|
+
data: { grid: gridData, totalRows: gridData.length }
|
|
3533
|
+
});
|
|
3534
|
+
|
|
3535
|
+
} catch (error) {
|
|
3536
|
+
console.error('🌉 [Desktop Bridge] Get slide grid error:', error);
|
|
3537
|
+
figma.ui.postMessage({
|
|
3538
|
+
type: 'GET_SLIDE_GRID_RESULT',
|
|
3539
|
+
requestId: msg.requestId,
|
|
3540
|
+
success: false,
|
|
3541
|
+
error: error.message || String(error)
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// REORDER_SLIDES - Reorder slides via 2D grid of slide IDs
|
|
3547
|
+
else if (msg.type === 'REORDER_SLIDES') {
|
|
3548
|
+
try {
|
|
3549
|
+
if (__editorType !== 'slides') {
|
|
3550
|
+
throw new Error('REORDER_SLIDES is only available in Slides files');
|
|
3551
|
+
}
|
|
3552
|
+
console.log('🌉 [Desktop Bridge] Reordering slides');
|
|
3553
|
+
|
|
3554
|
+
var newGrid = msg.grid; // 2D array of slide IDs
|
|
3555
|
+
|
|
3556
|
+
// Build a lookup: slideId → SlideNode from the current grid
|
|
3557
|
+
var currentGrid = figma.getSlideGrid();
|
|
3558
|
+
var slideMap = {};
|
|
3559
|
+
for (var gri = 0; gri < currentGrid.length; gri++) {
|
|
3560
|
+
var grow = currentGrid[gri];
|
|
3561
|
+
for (var gci = 0; gci < grow.length; gci++) {
|
|
3562
|
+
slideMap[grow[gci].id] = grow[gci];
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
// Build new grid as arrays of SlideNode references
|
|
3567
|
+
var reorderedRows = [];
|
|
3568
|
+
for (var rri = 0; rri < newGrid.length; rri++) {
|
|
3569
|
+
var rowIds = newGrid[rri];
|
|
3570
|
+
var rowSlides = [];
|
|
3571
|
+
for (var cci = 0; cci < rowIds.length; cci++) {
|
|
3572
|
+
var slideRef = slideMap[rowIds[cci]];
|
|
3573
|
+
if (!slideRef) throw new Error('Slide not found in current grid: ' + rowIds[cci]);
|
|
3574
|
+
rowSlides.push(slideRef);
|
|
3575
|
+
}
|
|
3576
|
+
reorderedRows.push(rowSlides);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
figma.setSlideGrid(reorderedRows);
|
|
3580
|
+
|
|
3581
|
+
figma.ui.postMessage({
|
|
3582
|
+
type: 'REORDER_SLIDES_RESULT',
|
|
3583
|
+
requestId: msg.requestId,
|
|
3584
|
+
success: true,
|
|
3585
|
+
data: { success: true, rows: reorderedRows.length }
|
|
3586
|
+
});
|
|
3587
|
+
|
|
3588
|
+
} catch (error) {
|
|
3589
|
+
console.error('🌉 [Desktop Bridge] Reorder slides error:', error);
|
|
3590
|
+
figma.ui.postMessage({
|
|
3591
|
+
type: 'REORDER_SLIDES_RESULT',
|
|
3592
|
+
requestId: msg.requestId,
|
|
3593
|
+
success: false,
|
|
3594
|
+
error: error.message || String(error)
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
// SET_SLIDE_TRANSITION - Set transition on a slide
|
|
3600
|
+
else if (msg.type === 'SET_SLIDE_TRANSITION') {
|
|
3601
|
+
try {
|
|
3602
|
+
if (__editorType !== 'slides') {
|
|
3603
|
+
throw new Error('SET_SLIDE_TRANSITION is only available in Slides files');
|
|
3604
|
+
}
|
|
3605
|
+
var transSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3606
|
+
if (!transSlide || transSlide.type !== 'SLIDE') {
|
|
3607
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3608
|
+
}
|
|
3609
|
+
console.log('🌉 [Desktop Bridge] Setting slide transition:', transSlide.name);
|
|
3610
|
+
|
|
3611
|
+
var transConfig = {
|
|
3612
|
+
style: msg.style,
|
|
3613
|
+
duration: msg.duration,
|
|
3614
|
+
curve: msg.curve,
|
|
3615
|
+
timing: { type: 'ON_CLICK' }
|
|
3616
|
+
};
|
|
3617
|
+
if (msg.timing) {
|
|
3618
|
+
transConfig.timing = msg.timing;
|
|
3619
|
+
}
|
|
3620
|
+
transSlide.setSlideTransition(transConfig);
|
|
3621
|
+
|
|
3622
|
+
figma.ui.postMessage({
|
|
3623
|
+
type: 'SET_SLIDE_TRANSITION_RESULT',
|
|
3624
|
+
requestId: msg.requestId,
|
|
3625
|
+
success: true,
|
|
3626
|
+
data: { id: transSlide.id, transition: transSlide.getSlideTransition() }
|
|
3627
|
+
});
|
|
3628
|
+
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
console.error('🌉 [Desktop Bridge] Set slide transition error:', error);
|
|
3631
|
+
figma.ui.postMessage({
|
|
3632
|
+
type: 'SET_SLIDE_TRANSITION_RESULT',
|
|
3633
|
+
requestId: msg.requestId,
|
|
3634
|
+
success: false,
|
|
3635
|
+
error: error.message || String(error)
|
|
3636
|
+
});
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// GET_SLIDE_TRANSITION - Read transition from a slide
|
|
3641
|
+
else if (msg.type === 'GET_SLIDE_TRANSITION') {
|
|
3642
|
+
try {
|
|
3643
|
+
if (__editorType !== 'slides') {
|
|
3644
|
+
throw new Error('GET_SLIDE_TRANSITION is only available in Slides files');
|
|
3645
|
+
}
|
|
3646
|
+
var readTransSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3647
|
+
if (!readTransSlide || readTransSlide.type !== 'SLIDE') {
|
|
3648
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3649
|
+
}
|
|
3650
|
+
console.log('🌉 [Desktop Bridge] Getting slide transition:', readTransSlide.name);
|
|
3651
|
+
|
|
3652
|
+
figma.ui.postMessage({
|
|
3653
|
+
type: 'GET_SLIDE_TRANSITION_RESULT',
|
|
3654
|
+
requestId: msg.requestId,
|
|
3655
|
+
success: true,
|
|
3656
|
+
data: { id: readTransSlide.id, transition: readTransSlide.getSlideTransition() }
|
|
3657
|
+
});
|
|
3658
|
+
|
|
3659
|
+
} catch (error) {
|
|
3660
|
+
console.error('🌉 [Desktop Bridge] Get slide transition error:', error);
|
|
3661
|
+
figma.ui.postMessage({
|
|
3662
|
+
type: 'GET_SLIDE_TRANSITION_RESULT',
|
|
3663
|
+
requestId: msg.requestId,
|
|
3664
|
+
success: false,
|
|
3665
|
+
error: error.message || String(error)
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
// SET_SLIDES_VIEW_MODE - Toggle between grid and single-slide view
|
|
3671
|
+
else if (msg.type === 'SET_SLIDES_VIEW_MODE') {
|
|
3672
|
+
try {
|
|
3673
|
+
if (__editorType !== 'slides') {
|
|
3674
|
+
throw new Error('SET_SLIDES_VIEW_MODE is only available in Slides files');
|
|
3675
|
+
}
|
|
3676
|
+
console.log('🌉 [Desktop Bridge] Setting slides view mode:', msg.mode);
|
|
3677
|
+
|
|
3678
|
+
figma.viewport.slidesView = msg.mode;
|
|
3679
|
+
|
|
3680
|
+
figma.ui.postMessage({
|
|
3681
|
+
type: 'SET_SLIDES_VIEW_MODE_RESULT',
|
|
3682
|
+
requestId: msg.requestId,
|
|
3683
|
+
success: true,
|
|
3684
|
+
data: { mode: figma.viewport.slidesView }
|
|
3685
|
+
});
|
|
3686
|
+
|
|
3687
|
+
} catch (error) {
|
|
3688
|
+
console.error('🌉 [Desktop Bridge] Set slides view mode error:', error);
|
|
3689
|
+
figma.ui.postMessage({
|
|
3690
|
+
type: 'SET_SLIDES_VIEW_MODE_RESULT',
|
|
3691
|
+
requestId: msg.requestId,
|
|
3692
|
+
success: false,
|
|
3693
|
+
error: error.message || String(error)
|
|
3694
|
+
});
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
// GET_FOCUSED_SLIDE - Get currently focused slide
|
|
3699
|
+
else if (msg.type === 'GET_FOCUSED_SLIDE') {
|
|
3700
|
+
try {
|
|
3701
|
+
if (__editorType !== 'slides') {
|
|
3702
|
+
throw new Error('GET_FOCUSED_SLIDE is only available in Slides files');
|
|
3703
|
+
}
|
|
3704
|
+
console.log('🌉 [Desktop Bridge] Getting focused slide');
|
|
3705
|
+
|
|
3706
|
+
var focused = figma.currentPage.focusedSlide;
|
|
3707
|
+
var focusData = focused ? { id: focused.id, name: focused.name } : { focused: null };
|
|
3708
|
+
|
|
3709
|
+
figma.ui.postMessage({
|
|
3710
|
+
type: 'GET_FOCUSED_SLIDE_RESULT',
|
|
3711
|
+
requestId: msg.requestId,
|
|
3712
|
+
success: true,
|
|
3713
|
+
data: focusData
|
|
3714
|
+
});
|
|
3715
|
+
|
|
3716
|
+
} catch (error) {
|
|
3717
|
+
console.error('🌉 [Desktop Bridge] Get focused slide error:', error);
|
|
3718
|
+
figma.ui.postMessage({
|
|
3719
|
+
type: 'GET_FOCUSED_SLIDE_RESULT',
|
|
3720
|
+
requestId: msg.requestId,
|
|
3721
|
+
success: false,
|
|
3722
|
+
error: error.message || String(error)
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
// FOCUS_SLIDE - Navigate to a specific slide
|
|
3728
|
+
else if (msg.type === 'FOCUS_SLIDE') {
|
|
3729
|
+
try {
|
|
3730
|
+
if (__editorType !== 'slides') {
|
|
3731
|
+
throw new Error('FOCUS_SLIDE is only available in Slides files');
|
|
3732
|
+
}
|
|
3733
|
+
var focusTarget = await figma.getNodeByIdAsync(msg.slideId);
|
|
3734
|
+
if (!focusTarget || focusTarget.type !== 'SLIDE') {
|
|
3735
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3736
|
+
}
|
|
3737
|
+
console.log('🌉 [Desktop Bridge] Focusing slide:', focusTarget.name);
|
|
3738
|
+
|
|
3739
|
+
figma.viewport.slidesView = 'single-slide';
|
|
3740
|
+
figma.currentPage.focusedSlide = focusTarget;
|
|
3741
|
+
|
|
3742
|
+
figma.ui.postMessage({
|
|
3743
|
+
type: 'FOCUS_SLIDE_RESULT',
|
|
3744
|
+
requestId: msg.requestId,
|
|
3745
|
+
success: true,
|
|
3746
|
+
data: { focused: focusTarget.id, name: focusTarget.name }
|
|
3747
|
+
});
|
|
3748
|
+
|
|
3749
|
+
} catch (error) {
|
|
3750
|
+
console.error('🌉 [Desktop Bridge] Focus slide error:', error);
|
|
3751
|
+
figma.ui.postMessage({
|
|
3752
|
+
type: 'FOCUS_SLIDE_RESULT',
|
|
3753
|
+
requestId: msg.requestId,
|
|
3754
|
+
success: false,
|
|
3755
|
+
error: error.message || String(error)
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
// SKIP_SLIDE - Toggle skip on a slide
|
|
3761
|
+
else if (msg.type === 'SKIP_SLIDE') {
|
|
3762
|
+
try {
|
|
3763
|
+
if (__editorType !== 'slides') {
|
|
3764
|
+
throw new Error('SKIP_SLIDE is only available in Slides files');
|
|
3765
|
+
}
|
|
3766
|
+
var skipSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3767
|
+
if (!skipSlide || skipSlide.type !== 'SLIDE') {
|
|
3768
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3769
|
+
}
|
|
3770
|
+
console.log('🌉 [Desktop Bridge] Toggling slide skip:', skipSlide.name, '→', msg.skip);
|
|
3771
|
+
|
|
3772
|
+
skipSlide.isSkippedSlide = !!msg.skip;
|
|
3773
|
+
|
|
3774
|
+
figma.ui.postMessage({
|
|
3775
|
+
type: 'SKIP_SLIDE_RESULT',
|
|
3776
|
+
requestId: msg.requestId,
|
|
3777
|
+
success: true,
|
|
3778
|
+
data: { id: skipSlide.id, isSkippedSlide: skipSlide.isSkippedSlide }
|
|
3779
|
+
});
|
|
3780
|
+
|
|
3781
|
+
} catch (error) {
|
|
3782
|
+
console.error('🌉 [Desktop Bridge] Skip slide error:', error);
|
|
3783
|
+
figma.ui.postMessage({
|
|
3784
|
+
type: 'SKIP_SLIDE_RESULT',
|
|
3785
|
+
requestId: msg.requestId,
|
|
3786
|
+
success: false,
|
|
3787
|
+
error: error.message || String(error)
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
// ADD_TEXT_TO_SLIDE - Add a text node to a slide
|
|
3793
|
+
else if (msg.type === 'ADD_TEXT_TO_SLIDE') {
|
|
3794
|
+
try {
|
|
3795
|
+
if (__editorType !== 'slides') {
|
|
3796
|
+
throw new Error('ADD_TEXT_TO_SLIDE is only available in Slides files');
|
|
3797
|
+
}
|
|
3798
|
+
var textSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3799
|
+
if (!textSlide || textSlide.type !== 'SLIDE') {
|
|
3800
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3801
|
+
}
|
|
3802
|
+
console.log('🌉 [Desktop Bridge] Adding text to slide:', textSlide.name);
|
|
3803
|
+
|
|
3804
|
+
var textNode = figma.createText();
|
|
3805
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
|
|
3806
|
+
textNode.characters = msg.text || '';
|
|
3807
|
+
textNode.fontSize = msg.fontSize || 24;
|
|
3808
|
+
textNode.x = typeof msg.x === 'number' ? msg.x : 100;
|
|
3809
|
+
textNode.y = typeof msg.y === 'number' ? msg.y : 100;
|
|
3810
|
+
textSlide.appendChild(textNode);
|
|
3811
|
+
|
|
3812
|
+
figma.ui.postMessage({
|
|
3813
|
+
type: 'ADD_TEXT_TO_SLIDE_RESULT',
|
|
3814
|
+
requestId: msg.requestId,
|
|
3815
|
+
success: true,
|
|
3816
|
+
data: { id: textNode.id, text: textNode.characters }
|
|
3817
|
+
});
|
|
3818
|
+
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
console.error('🌉 [Desktop Bridge] Add text to slide error:', error);
|
|
3821
|
+
figma.ui.postMessage({
|
|
3822
|
+
type: 'ADD_TEXT_TO_SLIDE_RESULT',
|
|
3823
|
+
requestId: msg.requestId,
|
|
3824
|
+
success: false,
|
|
3825
|
+
error: error.message || String(error)
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
// ADD_SHAPE_TO_SLIDE - Add a shape to a slide
|
|
3831
|
+
else if (msg.type === 'ADD_SHAPE_TO_SLIDE') {
|
|
3832
|
+
try {
|
|
3833
|
+
if (__editorType !== 'slides') {
|
|
3834
|
+
throw new Error('ADD_SHAPE_TO_SLIDE is only available in Slides files');
|
|
3835
|
+
}
|
|
3836
|
+
var shapeSlide = await figma.getNodeByIdAsync(msg.slideId);
|
|
3837
|
+
if (!shapeSlide || shapeSlide.type !== 'SLIDE') {
|
|
3838
|
+
throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
|
|
3839
|
+
}
|
|
3840
|
+
console.log('🌉 [Desktop Bridge] Adding shape to slide:', shapeSlide.name);
|
|
3841
|
+
|
|
3842
|
+
var shape;
|
|
3843
|
+
if (msg.shapeType === 'ELLIPSE') {
|
|
3844
|
+
shape = figma.createEllipse();
|
|
3845
|
+
} else {
|
|
3846
|
+
shape = figma.createRectangle();
|
|
3847
|
+
}
|
|
3848
|
+
shape.x = typeof msg.x === 'number' ? msg.x : 100;
|
|
3849
|
+
shape.y = typeof msg.y === 'number' ? msg.y : 100;
|
|
3850
|
+
shape.resize(typeof msg.width === 'number' ? msg.width : 200, typeof msg.height === 'number' ? msg.height : 200);
|
|
3851
|
+
|
|
3852
|
+
if (msg.color && typeof msg.color === 'string') {
|
|
3853
|
+
var hex = msg.color.replace('#', '');
|
|
3854
|
+
if (/^[0-9a-fA-F]{6}$/.test(hex)) {
|
|
3855
|
+
shape.fills = [{
|
|
3856
|
+
type: 'SOLID',
|
|
3857
|
+
color: {
|
|
3858
|
+
r: parseInt(hex.substring(0, 2), 16) / 255,
|
|
3859
|
+
g: parseInt(hex.substring(2, 4), 16) / 255,
|
|
3860
|
+
b: parseInt(hex.substring(4, 6), 16) / 255
|
|
3861
|
+
}
|
|
3862
|
+
}];
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
shapeSlide.appendChild(shape);
|
|
3866
|
+
|
|
3867
|
+
figma.ui.postMessage({
|
|
3868
|
+
type: 'ADD_SHAPE_TO_SLIDE_RESULT',
|
|
3869
|
+
requestId: msg.requestId,
|
|
3870
|
+
success: true,
|
|
3871
|
+
data: { id: shape.id, type: shape.type }
|
|
3872
|
+
});
|
|
3873
|
+
|
|
3874
|
+
} catch (error) {
|
|
3875
|
+
console.error('🌉 [Desktop Bridge] Add shape to slide error:', error);
|
|
3876
|
+
figma.ui.postMessage({
|
|
3877
|
+
type: 'ADD_SHAPE_TO_SLIDE_RESULT',
|
|
3878
|
+
requestId: msg.requestId,
|
|
3879
|
+
success: false,
|
|
3880
|
+
error: error.message || String(error)
|
|
3881
|
+
});
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
2786
3884
|
};
|
|
2787
3885
|
|
|
2788
3886
|
// ============================================================================
|