@growgroup/visual-editor 0.1.0
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/LICENSE +21 -0
- package/NOTICE +52 -0
- package/README.md +143 -0
- package/dist/editor.css +2 -0
- package/package.json +94 -0
- package/src/components/DeckSlideRender.tsx +21 -0
- package/src/components/SaveNote.tsx +21 -0
- package/src/components/auth/AuthProvider.tsx +44 -0
- package/src/components/ui/accordion.tsx +57 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +11 -0
- package/src/components/ui/context-menu.tsx +200 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/scrubbable-label.tsx +95 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/viewer/useDeck.ts +58 -0
- package/src/editor/EditorContext.tsx +632 -0
- package/src/editor/EditorToolbar.tsx +446 -0
- package/src/editor/FrontendVisualEditor.tsx +2595 -0
- package/src/editor/autosave.ts +34 -0
- package/src/editor/components/AiPromptPopover.tsx +378 -0
- package/src/editor/components/BreakpointGuides.tsx +323 -0
- package/src/editor/components/BreakpointSelector.tsx +180 -0
- package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
- package/src/editor/components/CssEditorDialog.tsx +168 -0
- package/src/editor/components/EditorCanvas.tsx +987 -0
- package/src/editor/components/EditorContextMenu.tsx +515 -0
- package/src/editor/components/EditorFooter.tsx +81 -0
- package/src/editor/components/EditorHeader.tsx +524 -0
- package/src/editor/components/EditorLayerPanel.tsx +1197 -0
- package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
- package/src/editor/components/EditorSidebar.tsx +226 -0
- package/src/editor/components/FigmaColorPicker.tsx +1577 -0
- package/src/editor/components/HtmlEditorDialog.tsx +71 -0
- package/src/editor/components/HtmlImportDialog.tsx +346 -0
- package/src/editor/components/JsEditorDialog.tsx +175 -0
- package/src/editor/components/LayerTreeItem.tsx +494 -0
- package/src/editor/components/MasterComponentEditor.tsx +846 -0
- package/src/editor/components/MediaLibraryDialog.tsx +447 -0
- package/src/editor/components/PageSettingsDialog.tsx +776 -0
- package/src/editor/components/VariablePicker.tsx +355 -0
- package/src/editor/components/VariablesDialog.tsx +564 -0
- package/src/editor/components/VariantEditor.tsx +687 -0
- package/src/editor/components/VariantMatrix.tsx +507 -0
- package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
- package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
- package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
- package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
- package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
- package/src/editor/components/component-panel/VariantList.tsx +260 -0
- package/src/editor/components/component-panel/index.ts +12 -0
- package/src/editor/components/index.ts +48 -0
- package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
- package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
- package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
- package/src/editor/components/multi-page/PageLabel.tsx +34 -0
- package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
- package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
- package/src/editor/components/multi-page/index.ts +7 -0
- package/src/editor/components/ppt/PptChrome.tsx +2508 -0
- package/src/editor/components/ppt/PptComments.tsx +589 -0
- package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
- package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
- package/src/editor/components/ppt/PptNotes.tsx +246 -0
- package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
- package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
- package/src/editor/components/property-panel/BorderSection.tsx +104 -0
- package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
- package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
- package/src/editor/components/property-panel/EffectSection.tsx +157 -0
- package/src/editor/components/property-panel/FillSection.tsx +69 -0
- package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
- package/src/editor/components/property-panel/ImageSection.tsx +206 -0
- package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
- package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
- package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
- package/src/editor/components/property-panel/LinkSection.tsx +103 -0
- package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
- package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
- package/src/editor/components/property-panel/TypographySection.tsx +230 -0
- package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
- package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
- package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
- package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
- package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
- package/src/editor/components/property-panel/index.ts +24 -0
- package/src/editor/components/property-panel/unit-utils.ts +261 -0
- package/src/editor/components/shell/EditorTopBar.tsx +392 -0
- package/src/editor/components/shell/LeftPanel.tsx +47 -0
- package/src/editor/components/shell/PagesPanel.tsx +118 -0
- package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
- package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
- package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
- package/src/editor/components/variables-panel/index.ts +7 -0
- package/src/editor/constants.ts +468 -0
- package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
- package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
- package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
- package/src/editor/contexts/EditorRefsContext.tsx +204 -0
- package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
- package/src/editor/contexts/EditorToolContext.tsx +58 -0
- package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
- package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
- package/src/editor/contexts/EditorViewContext.tsx +101 -0
- package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
- package/src/editor/contexts/index.ts +17 -0
- package/src/editor/editor-skin.css +122 -0
- package/src/editor/hooks/index.ts +38 -0
- package/src/editor/hooks/useAiReplace.ts +397 -0
- package/src/editor/hooks/useAltMeasure.ts +67 -0
- package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
- package/src/editor/hooks/useCanvasControls.ts +1222 -0
- package/src/editor/hooks/useComponentEditMode.ts +658 -0
- package/src/editor/hooks/useComponentInstances.ts +515 -0
- package/src/editor/hooks/useContextMenuHandler.ts +157 -0
- package/src/editor/hooks/useCoordinateTransform.ts +207 -0
- package/src/editor/hooks/useDragResize.ts +1317 -0
- package/src/editor/hooks/useDrawingMode.ts +472 -0
- package/src/editor/hooks/useEditorColors.ts +144 -0
- package/src/editor/hooks/useEditorMessages.ts +108 -0
- package/src/editor/hooks/useElementActions.ts +1754 -0
- package/src/editor/hooks/useElementSelection.ts +1573 -0
- package/src/editor/hooks/useFocusManagement.ts +249 -0
- package/src/editor/hooks/useGoogleFonts.ts +305 -0
- package/src/editor/hooks/useIframeInitializer.ts +584 -0
- package/src/editor/hooks/useIframeSetup.ts +360 -0
- package/src/editor/hooks/useImageUpload.ts +270 -0
- package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
- package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
- package/src/editor/hooks/useMarqueeSelection.ts +388 -0
- package/src/editor/hooks/useMediaLibrary.ts +129 -0
- package/src/editor/hooks/usePageSettingsManager.ts +739 -0
- package/src/editor/hooks/useResizablePanel.ts +163 -0
- package/src/editor/hooks/useRichPaste.ts +255 -0
- package/src/editor/hooks/useTouchGestures.ts +197 -0
- package/src/editor/index.ts +27 -0
- package/src/editor/types/page-settings.ts +513 -0
- package/src/editor/types.ts +382 -0
- package/src/editor/utils/align-elements.ts +169 -0
- package/src/editor/utils/api-json.ts +21 -0
- package/src/editor/utils/aspect-lock.ts +50 -0
- package/src/editor/utils/component-renderer.ts +702 -0
- package/src/editor/utils/component-sync.ts +845 -0
- package/src/editor/utils/crop-mode.ts +265 -0
- package/src/editor/utils/dom-utils.ts +2238 -0
- package/src/editor/utils/element-effects.ts +218 -0
- package/src/editor/utils/eyedropper.ts +35 -0
- package/src/editor/utils/figma-export.ts +560 -0
- package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
- package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
- package/src/editor/utils/figma-paste.ts +2262 -0
- package/src/editor/utils/flex-reorder.ts +231 -0
- package/src/editor/utils/flex-utils.ts +1230 -0
- package/src/editor/utils/geometry.ts +232 -0
- package/src/editor/utils/grid-layout.ts +210 -0
- package/src/editor/utils/html-utils.ts +470 -0
- package/src/editor/utils/index.ts +11 -0
- package/src/editor/utils/ink-style.ts +57 -0
- package/src/editor/utils/inline-format.ts +236 -0
- package/src/editor/utils/measure-distance.ts +120 -0
- package/src/editor/utils/override-apply.ts +526 -0
- package/src/editor/utils/override-detection.ts +613 -0
- package/src/editor/utils/paste-processors.ts +609 -0
- package/src/editor/utils/paste-sanitizer.ts +244 -0
- package/src/editor/utils/restore-flow.ts +188 -0
- package/src/editor/utils/shape-library.ts +171 -0
- package/src/editor/utils/slide-root.ts +29 -0
- package/src/editor/utils/smart-guides.ts +218 -0
- package/src/editor/utils/style-utils.ts +685 -0
- package/src/editor/utils/table-edit.ts +151 -0
- package/src/editor/utils/tailwind-mappings.ts +778 -0
- package/src/editor/utils/tailwind-utils.ts +804 -0
- package/src/editor/utils/text-highlight.ts +216 -0
- package/src/editor/utils/theme-to-variables.ts +341 -0
- package/src/editor/utils/variant-resolver.ts +456 -0
- package/src/editor/utils/viewport-utils.ts +295 -0
- package/src/hooks/useEditorHistory.ts +195 -0
- package/src/hooks/useEditorShortcuts.ts +260 -0
- package/src/index.ts +40 -0
- package/src/io.ts +134 -0
- package/src/lib/agent/slide-agent/types.ts +547 -0
- package/src/lib/agent/website-agent/types.ts +805 -0
- package/src/lib/api/auth-fetch.ts +58 -0
- package/src/lib/deck.ts +69 -0
- package/src/lib/export.ts +47 -0
- package/src/lib/firebase/config.ts +29 -0
- package/src/lib/firebase/css-variables.ts +378 -0
- package/src/lib/firebase/editor-components.ts +677 -0
- package/src/lib/firebase/storage.ts +469 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/editor.css +52 -0
- package/src/styles/skin.css +122 -0
- package/src/types/css-variables.ts +645 -0
- package/src/types/editor-components.ts +944 -0
- package/src/types/editor.ts +420 -0
- package/src/types/page-master.ts +197 -0
- package/src/types/page.ts +419 -0
- package/src/types/slide.ts +402 -0
- package/src/types/website-theme.ts +788 -0
- package/src/types/website.ts +374 -0
- package/src/vendor/firebase-firestore.ts +13 -0
- package/src/vendor/firebase-functions.ts +17 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* figma-kiwi-decoder.ts
|
|
3
|
+
*
|
|
4
|
+
* Custom Figma kiwi decoder that handles newer schema versions.
|
|
5
|
+
* Implements full kiwi binary format parsing with extended primitive types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as pako from 'pako';
|
|
9
|
+
import { cacheSchema } from './figma-kiwi-encoder';
|
|
10
|
+
|
|
11
|
+
// Extended types array
|
|
12
|
+
const EXTENDED_TYPES = [
|
|
13
|
+
'bool', // -1 -> 0
|
|
14
|
+
'byte', // -2 -> 1
|
|
15
|
+
'int', // -3 -> 2
|
|
16
|
+
'uint', // -4 -> 3
|
|
17
|
+
'float', // -5 -> 4
|
|
18
|
+
'string', // -6 -> 5
|
|
19
|
+
'int64', // -7 -> 6
|
|
20
|
+
'uint64', // -8 -> 7
|
|
21
|
+
'double', // -9 -> 8
|
|
22
|
+
'bytes', // -10 -> 9
|
|
23
|
+
'type10', // -11 -> 10
|
|
24
|
+
'type11', // -12 -> 11
|
|
25
|
+
'type12', // -13 -> 12
|
|
26
|
+
'type13', // -14 -> 13
|
|
27
|
+
'type14', // -15 -> 14
|
|
28
|
+
'type15', // -16 -> 15
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const KINDS = ['ENUM', 'STRUCT', 'MESSAGE'];
|
|
32
|
+
|
|
33
|
+
interface Field {
|
|
34
|
+
name: string;
|
|
35
|
+
line: number;
|
|
36
|
+
column: number;
|
|
37
|
+
type: string | number | null;
|
|
38
|
+
isArray: boolean;
|
|
39
|
+
isDeprecated: boolean;
|
|
40
|
+
value: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Definition {
|
|
44
|
+
name: string;
|
|
45
|
+
line: number;
|
|
46
|
+
column: number;
|
|
47
|
+
kind: string;
|
|
48
|
+
fields: Field[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface Schema {
|
|
52
|
+
package: null;
|
|
53
|
+
definitions: Definition[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* ByteBuffer for reading binary data (matching kiwi-schema's implementation)
|
|
58
|
+
*/
|
|
59
|
+
class ByteBuffer {
|
|
60
|
+
private data: Uint8Array;
|
|
61
|
+
private index: number = 0;
|
|
62
|
+
|
|
63
|
+
constructor(data: Uint8Array | ArrayBuffer) {
|
|
64
|
+
this.data = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
readByte(): number {
|
|
68
|
+
if (this.index >= this.data.length) {
|
|
69
|
+
throw new Error('Read past end of buffer');
|
|
70
|
+
}
|
|
71
|
+
return this.data[this.index++];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
readVarUint(): number {
|
|
75
|
+
let value = 0;
|
|
76
|
+
let shift = 0;
|
|
77
|
+
let byte: number;
|
|
78
|
+
do {
|
|
79
|
+
byte = this.readByte();
|
|
80
|
+
value |= (byte & 0x7F) << shift;
|
|
81
|
+
shift += 7;
|
|
82
|
+
} while (byte & 0x80);
|
|
83
|
+
return value >>> 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
readVarInt(): number {
|
|
87
|
+
const value = this.readVarUint();
|
|
88
|
+
return (value & 1) ? ~(value >>> 1) : (value >>> 1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read null-terminated UTF-8 string (kiwi format)
|
|
93
|
+
*/
|
|
94
|
+
readString(): string {
|
|
95
|
+
let result = '';
|
|
96
|
+
while (true) {
|
|
97
|
+
let codePoint: number;
|
|
98
|
+
// Decode UTF-8
|
|
99
|
+
const a = this.readByte();
|
|
100
|
+
if (a < 0xC0) {
|
|
101
|
+
codePoint = a;
|
|
102
|
+
} else {
|
|
103
|
+
const b = this.readByte();
|
|
104
|
+
if (a < 0xE0) {
|
|
105
|
+
codePoint = ((a & 0x1F) << 6) | (b & 0x3F);
|
|
106
|
+
} else {
|
|
107
|
+
const c = this.readByte();
|
|
108
|
+
if (a < 0xF0) {
|
|
109
|
+
codePoint = ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F);
|
|
110
|
+
} else {
|
|
111
|
+
const d = this.readByte();
|
|
112
|
+
codePoint = ((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Strings are null-terminated
|
|
117
|
+
if (codePoint === 0) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
// Encode UTF-16
|
|
121
|
+
if (codePoint < 0x10000) {
|
|
122
|
+
result += String.fromCharCode(codePoint);
|
|
123
|
+
} else {
|
|
124
|
+
codePoint -= 0x10000;
|
|
125
|
+
result += String.fromCharCode((codePoint >> 10) + 0xD800, (codePoint & ((1 << 10) - 1)) + 0xDC00);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get position(): number {
|
|
132
|
+
return this.index;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get remaining(): number {
|
|
136
|
+
return this.data.length - this.index;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Read length-prefixed byte array
|
|
141
|
+
*/
|
|
142
|
+
readByteArray(): Uint8Array {
|
|
143
|
+
const length = this.readVarUint();
|
|
144
|
+
const start = this.index;
|
|
145
|
+
const end = start + length;
|
|
146
|
+
if (end > this.data.length) {
|
|
147
|
+
throw new Error('Read array out of bounds');
|
|
148
|
+
}
|
|
149
|
+
this.index = end;
|
|
150
|
+
const result = new Uint8Array(length);
|
|
151
|
+
result.set(this.data.subarray(start, end));
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Read variable-length float (kiwi optimization format)
|
|
157
|
+
*/
|
|
158
|
+
readVarFloat(): number {
|
|
159
|
+
const index = this.index;
|
|
160
|
+
const data = this.data;
|
|
161
|
+
const length = data.length;
|
|
162
|
+
|
|
163
|
+
// Optimization: use a single byte to store zero
|
|
164
|
+
if (index + 1 > length) {
|
|
165
|
+
throw new Error('Index out of bounds');
|
|
166
|
+
}
|
|
167
|
+
const first = data[index];
|
|
168
|
+
if (first === 0) {
|
|
169
|
+
this.index = index + 1;
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Endian-independent 32-bit read
|
|
174
|
+
if (index + 4 > length) {
|
|
175
|
+
throw new Error('Index out of bounds');
|
|
176
|
+
}
|
|
177
|
+
let bits = first | (data[index + 1] << 8) | (data[index + 2] << 16) | (data[index + 3] << 24);
|
|
178
|
+
this.index = index + 4;
|
|
179
|
+
|
|
180
|
+
// Move the exponent back into place
|
|
181
|
+
bits = (bits << 23) | (bits >>> 9);
|
|
182
|
+
|
|
183
|
+
// Reinterpret as a floating-point number
|
|
184
|
+
const int32 = new Int32Array(1);
|
|
185
|
+
const float32 = new Float32Array(int32.buffer);
|
|
186
|
+
int32[0] = bits;
|
|
187
|
+
return float32[0];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Custom implementation of decodeBinarySchema with extended type support
|
|
193
|
+
*/
|
|
194
|
+
function customDecodeBinarySchema(buffer: Uint8Array, types: string[]): Schema {
|
|
195
|
+
const bb = new ByteBuffer(buffer);
|
|
196
|
+
const definitionCount = bb.readVarUint();
|
|
197
|
+
const definitions: Definition[] = [];
|
|
198
|
+
|
|
199
|
+
console.log('[figma-kiwi-decoder] Custom decode: definition count =', definitionCount);
|
|
200
|
+
|
|
201
|
+
// Read in the schema
|
|
202
|
+
for (let i = 0; i < definitionCount; i++) {
|
|
203
|
+
const definitionName = bb.readString();
|
|
204
|
+
const kind = bb.readByte();
|
|
205
|
+
const fieldCount = bb.readVarUint();
|
|
206
|
+
const fields: Field[] = [];
|
|
207
|
+
|
|
208
|
+
for (let j = 0; j < fieldCount; j++) {
|
|
209
|
+
const fieldName = bb.readString();
|
|
210
|
+
const type = bb.readVarInt();
|
|
211
|
+
const isArray = !!(bb.readByte() & 1);
|
|
212
|
+
const value = bb.readVarUint();
|
|
213
|
+
|
|
214
|
+
fields.push({
|
|
215
|
+
name: fieldName,
|
|
216
|
+
line: 0,
|
|
217
|
+
column: 0,
|
|
218
|
+
type: KINDS[kind] === 'ENUM' ? null : type,
|
|
219
|
+
isArray,
|
|
220
|
+
isDeprecated: false,
|
|
221
|
+
value,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
definitions.push({
|
|
226
|
+
name: definitionName,
|
|
227
|
+
line: 0,
|
|
228
|
+
column: 0,
|
|
229
|
+
kind: KINDS[kind] || 'MESSAGE',
|
|
230
|
+
fields,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Bind type names afterwards
|
|
235
|
+
for (let i = 0; i < definitionCount; i++) {
|
|
236
|
+
const fields = definitions[i].fields;
|
|
237
|
+
for (let j = 0; j < fields.length; j++) {
|
|
238
|
+
const field = fields[j];
|
|
239
|
+
const type = field.type;
|
|
240
|
+
if (type !== null && typeof type === 'number') {
|
|
241
|
+
if (type < 0) {
|
|
242
|
+
const typeIndex = ~type;
|
|
243
|
+
if (typeIndex >= types.length) {
|
|
244
|
+
// Use a placeholder for unknown types
|
|
245
|
+
field.type = `unknown_type_${typeIndex}`;
|
|
246
|
+
} else {
|
|
247
|
+
field.type = types[typeIndex];
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
if (type >= definitions.length) {
|
|
251
|
+
field.type = `ref_${type}`;
|
|
252
|
+
} else {
|
|
253
|
+
field.type = definitions[type].name;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
package: null,
|
|
262
|
+
definitions,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Parse Figma archive format (fig-kiwi header)
|
|
268
|
+
*/
|
|
269
|
+
function parseArchive(buffer: Uint8Array): { schema: Uint8Array; data: Uint8Array } {
|
|
270
|
+
const prelude = 'fig-kiwi';
|
|
271
|
+
const preludeBytes = new TextEncoder().encode(prelude);
|
|
272
|
+
|
|
273
|
+
// Check prelude
|
|
274
|
+
const actualPrelude = new TextDecoder().decode(buffer.slice(0, preludeBytes.length));
|
|
275
|
+
if (actualPrelude !== prelude) {
|
|
276
|
+
throw new Error(`Invalid fig-kiwi prelude: got "${actualPrelude}"`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let offset = preludeBytes.length;
|
|
280
|
+
|
|
281
|
+
// Read version (4 bytes, little endian)
|
|
282
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset);
|
|
283
|
+
const version = view.getUint32(offset, true);
|
|
284
|
+
console.log('[figma-kiwi-decoder] Archive version:', version);
|
|
285
|
+
offset += 4;
|
|
286
|
+
|
|
287
|
+
const files: Uint8Array[] = [];
|
|
288
|
+
|
|
289
|
+
// Read chunks
|
|
290
|
+
while (offset + 4 <= buffer.length) {
|
|
291
|
+
const size = view.getUint32(offset, true);
|
|
292
|
+
offset += 4;
|
|
293
|
+
|
|
294
|
+
if (offset + size > buffer.length) {
|
|
295
|
+
files.push(buffer.slice(offset));
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
files.push(buffer.slice(offset, offset + size));
|
|
300
|
+
offset += size;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (files.length < 2) {
|
|
304
|
+
throw new Error(`Invalid archive: expected at least 2 chunks, got ${files.length}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
schema: files[0],
|
|
309
|
+
data: files[1],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Parse Figma clipboard HTML and extract data using custom decoder
|
|
315
|
+
*/
|
|
316
|
+
export interface FigmaClipboardData {
|
|
317
|
+
meta: {
|
|
318
|
+
fileKey: string;
|
|
319
|
+
pasteID: number;
|
|
320
|
+
dataType: string;
|
|
321
|
+
};
|
|
322
|
+
message: Record<string, unknown>;
|
|
323
|
+
schema: Schema;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function parseFigmaClipboardToJson(html: string): FigmaClipboardData {
|
|
327
|
+
// Extract metadata
|
|
328
|
+
const metaMatch = html.match(/\(figmeta\)([A-Za-z0-9+/=]+)\(\/figmeta\)/);
|
|
329
|
+
if (!metaMatch) {
|
|
330
|
+
throw new Error('Figma metadata not found');
|
|
331
|
+
}
|
|
332
|
+
const meta = JSON.parse(atob(metaMatch[1]));
|
|
333
|
+
console.log('[figma-kiwi-decoder] Meta:', meta);
|
|
334
|
+
|
|
335
|
+
// Extract binary data
|
|
336
|
+
const dataMatch = html.match(/\(figma\)([A-Za-z0-9+/=]+)\(\/figma\)/);
|
|
337
|
+
if (!dataMatch) {
|
|
338
|
+
throw new Error('Figma data not found');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Decode base64 to Uint8Array
|
|
342
|
+
const binaryString = atob(dataMatch[1]);
|
|
343
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
344
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
345
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
346
|
+
}
|
|
347
|
+
console.log('[figma-kiwi-decoder] Binary data size:', bytes.length);
|
|
348
|
+
|
|
349
|
+
// Parse archive
|
|
350
|
+
const archive = parseArchive(bytes);
|
|
351
|
+
console.log('[figma-kiwi-decoder] Schema chunk size:', archive.schema.length);
|
|
352
|
+
console.log('[figma-kiwi-decoder] Data chunk size:', archive.data.length);
|
|
353
|
+
|
|
354
|
+
// Decompress schema and data
|
|
355
|
+
const schemaDecompressed = pako.inflateRaw(archive.schema);
|
|
356
|
+
const dataDecompressed = pako.inflateRaw(archive.data);
|
|
357
|
+
console.log('[figma-kiwi-decoder] Schema decompressed size:', schemaDecompressed.length);
|
|
358
|
+
console.log('[figma-kiwi-decoder] Data decompressed size:', dataDecompressed.length);
|
|
359
|
+
|
|
360
|
+
// Decode schema with extended types
|
|
361
|
+
const schema = customDecodeBinarySchema(schemaDecompressed, EXTENDED_TYPES);
|
|
362
|
+
console.log('[figma-kiwi-decoder] Schema definitions:', schema.definitions.length);
|
|
363
|
+
|
|
364
|
+
// Cache schema for later use in encoding (Figma export)
|
|
365
|
+
cacheSchema(schema, archive.schema);
|
|
366
|
+
|
|
367
|
+
// Export raw schema for bundling (one-time capture)
|
|
368
|
+
if (typeof window !== 'undefined') {
|
|
369
|
+
const schemaBase64 = btoa(String.fromCharCode(...archive.schema));
|
|
370
|
+
console.log('[figma-kiwi-decoder] RAW SCHEMA BASE64 (copy this for bundling):');
|
|
371
|
+
console.log(schemaBase64);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Find Message definition
|
|
375
|
+
const messageDefIndex = schema.definitions.findIndex(d => d.name === 'Message');
|
|
376
|
+
if (messageDefIndex === -1) {
|
|
377
|
+
throw new Error('Message definition not found in schema');
|
|
378
|
+
}
|
|
379
|
+
console.log('[figma-kiwi-decoder] Message definition found at index:', messageDefIndex);
|
|
380
|
+
|
|
381
|
+
// Find and log NodeChange definition to see available fields
|
|
382
|
+
const nodeChangeDef = schema.definitions.find(d => d.name === 'NodeChange');
|
|
383
|
+
if (nodeChangeDef) {
|
|
384
|
+
console.log('[figma-kiwi-decoder] NodeChange fields:',
|
|
385
|
+
nodeChangeDef.fields.map(f => `${f.name}(${f.value})`).join(', '));
|
|
386
|
+
// Look for parent-related fields
|
|
387
|
+
const parentFields = nodeChangeDef.fields.filter(f =>
|
|
388
|
+
f.name.toLowerCase().includes('parent') || f.name.toLowerCase().includes('index'));
|
|
389
|
+
if (parentFields.length > 0) {
|
|
390
|
+
console.log('[figma-kiwi-decoder] Parent-related fields:', parentFields.map(f => f.name));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Log Paint-related definitions to see what fields are available
|
|
395
|
+
const paintDefs = schema.definitions.filter(d =>
|
|
396
|
+
d.name.toLowerCase().includes('paint') || d.name.toLowerCase().includes('image'));
|
|
397
|
+
for (const def of paintDefs) {
|
|
398
|
+
console.log(`[figma-kiwi-decoder] ${def.name} (${def.kind}): ${def.fields.map(f => `${f.name}:${f.type}(${f.value})`).join(', ')}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Log PaintType enum if it exists
|
|
402
|
+
const paintTypeEnum = schema.definitions.find(d => d.name === 'PaintType');
|
|
403
|
+
if (paintTypeEnum) {
|
|
404
|
+
console.log('[figma-kiwi-decoder] PaintType enum values:', paintTypeEnum.fields.map(f => `${f.name}=${f.value}`).join(', '));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Compile schema and decode message using our custom implementation
|
|
408
|
+
const message = decodeMessage(dataDecompressed, schema);
|
|
409
|
+
|
|
410
|
+
console.log('[figma-kiwi-decoder] Message decoded, type:', message.type);
|
|
411
|
+
|
|
412
|
+
return { meta, message, schema };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Decode a message using the compiled schema
|
|
417
|
+
*/
|
|
418
|
+
function decodeMessage(buffer: Uint8Array, schema: Schema): Record<string, unknown> {
|
|
419
|
+
const bb = new ByteBuffer(buffer);
|
|
420
|
+
|
|
421
|
+
// Build definition map
|
|
422
|
+
const defMap = new Map<string, Definition>();
|
|
423
|
+
for (const def of schema.definitions) {
|
|
424
|
+
defMap.set(def.name, def);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Find Message definition
|
|
428
|
+
const messageDef = defMap.get('Message');
|
|
429
|
+
if (!messageDef) {
|
|
430
|
+
throw new Error('Message definition not found');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const result = decodeStruct(bb, messageDef, defMap);
|
|
434
|
+
// Message should always be a struct, not an enum
|
|
435
|
+
if (typeof result === 'string') {
|
|
436
|
+
throw new Error('Message definition is unexpectedly an enum');
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Decode a value based on its type
|
|
443
|
+
*/
|
|
444
|
+
function decodeValue(
|
|
445
|
+
bb: ByteBuffer,
|
|
446
|
+
typeName: string | null,
|
|
447
|
+
isArray: boolean,
|
|
448
|
+
defMap: Map<string, Definition>
|
|
449
|
+
): unknown {
|
|
450
|
+
if (isArray) {
|
|
451
|
+
const length = bb.readVarUint();
|
|
452
|
+
const arr: unknown[] = [];
|
|
453
|
+
for (let i = 0; i < length; i++) {
|
|
454
|
+
arr.push(decodeValue(bb, typeName, false, defMap));
|
|
455
|
+
}
|
|
456
|
+
return arr;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
switch (typeName) {
|
|
460
|
+
case 'bool':
|
|
461
|
+
return bb.readByte() !== 0;
|
|
462
|
+
case 'byte':
|
|
463
|
+
return bb.readByte();
|
|
464
|
+
case 'int':
|
|
465
|
+
case 'int64':
|
|
466
|
+
return bb.readVarInt();
|
|
467
|
+
case 'uint':
|
|
468
|
+
case 'uint64':
|
|
469
|
+
return bb.readVarUint();
|
|
470
|
+
case 'float':
|
|
471
|
+
return bb.readVarFloat();
|
|
472
|
+
case 'double':
|
|
473
|
+
return readDouble(bb);
|
|
474
|
+
case 'string':
|
|
475
|
+
return bb.readString();
|
|
476
|
+
case 'bytes':
|
|
477
|
+
return bb.readByteArray();
|
|
478
|
+
default:
|
|
479
|
+
// Check if it's a reference to another definition
|
|
480
|
+
if (typeName && defMap.has(typeName)) {
|
|
481
|
+
const def = defMap.get(typeName)!;
|
|
482
|
+
// Handle ENUM types - resolve to name
|
|
483
|
+
if (def.kind === 'ENUM') {
|
|
484
|
+
const enumValue = bb.readVarUint();
|
|
485
|
+
// Find the enum field with this value
|
|
486
|
+
for (const field of def.fields) {
|
|
487
|
+
if (field.value === enumValue) {
|
|
488
|
+
return field.name;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Return raw value if no matching field found
|
|
492
|
+
return `UNKNOWN_ENUM_${enumValue}`;
|
|
493
|
+
}
|
|
494
|
+
return decodeStruct(bb, def, defMap);
|
|
495
|
+
}
|
|
496
|
+
// Unknown type - try to skip or return null
|
|
497
|
+
console.warn(`[figma-kiwi-decoder] Unknown type: ${typeName}`);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Read a double from the buffer (8 bytes, little endian)
|
|
504
|
+
*/
|
|
505
|
+
function readDouble(bb: ByteBuffer): number {
|
|
506
|
+
const bytes = new Uint8Array(8);
|
|
507
|
+
for (let i = 0; i < 8; i++) {
|
|
508
|
+
bytes[i] = bb.readByte();
|
|
509
|
+
}
|
|
510
|
+
return new DataView(bytes.buffer).getFloat64(0, true);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Decode a struct or message
|
|
515
|
+
*/
|
|
516
|
+
function decodeStruct(
|
|
517
|
+
bb: ByteBuffer,
|
|
518
|
+
def: Definition,
|
|
519
|
+
defMap: Map<string, Definition>
|
|
520
|
+
): Record<string, unknown> | string {
|
|
521
|
+
const result: Record<string, unknown> = {};
|
|
522
|
+
|
|
523
|
+
// ENUMs should be handled in decodeValue, but handle here as fallback
|
|
524
|
+
if (def.kind === 'ENUM') {
|
|
525
|
+
const enumValue = bb.readVarUint();
|
|
526
|
+
for (const field of def.fields) {
|
|
527
|
+
if (field.value === enumValue) {
|
|
528
|
+
return field.name;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return `UNKNOWN_ENUM_${enumValue}`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (def.kind === 'STRUCT') {
|
|
535
|
+
for (const field of def.fields) {
|
|
536
|
+
try {
|
|
537
|
+
result[field.name] = decodeValue(bb, field.type as string, field.isArray, defMap);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
console.warn(`[figma-kiwi-decoder] Error decoding struct field ${field.name}:`, e);
|
|
540
|
+
result[field.name] = null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// MESSAGE type - fields are optional with field IDs
|
|
547
|
+
if (def.kind === 'MESSAGE') {
|
|
548
|
+
const fieldMap = new Map<number, Field>();
|
|
549
|
+
for (const field of def.fields) {
|
|
550
|
+
fieldMap.set(field.value, field);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
while (bb.remaining > 0) {
|
|
554
|
+
const fieldId = bb.readVarUint();
|
|
555
|
+
if (fieldId === 0) break;
|
|
556
|
+
|
|
557
|
+
const field = fieldMap.get(fieldId);
|
|
558
|
+
if (field) {
|
|
559
|
+
try {
|
|
560
|
+
result[field.name] = decodeValue(bb, field.type as string, field.isArray, defMap);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
console.warn(`[figma-kiwi-decoder] Error decoding message field ${field.name}:`, e);
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
// Unknown field - we can't skip it properly without knowing the type
|
|
567
|
+
console.warn(`[figma-kiwi-decoder] Unknown field ID: ${fieldId} in ${def.name}`);
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Convert Figma message to simplified node structure
|
|
579
|
+
*/
|
|
580
|
+
export interface FigmaNode {
|
|
581
|
+
guid?: { sessionID: number; localID: number };
|
|
582
|
+
parentIndex?: { guid?: { sessionID: number; localID: number }; position?: string };
|
|
583
|
+
type?: string;
|
|
584
|
+
name?: string;
|
|
585
|
+
visible?: boolean;
|
|
586
|
+
locked?: boolean;
|
|
587
|
+
opacity?: number;
|
|
588
|
+
blendMode?: string;
|
|
589
|
+
size?: { x: number; y: number };
|
|
590
|
+
transform?: { m00: number; m01: number; m02: number; m10: number; m11: number; m12: number };
|
|
591
|
+
|
|
592
|
+
// Fill & Stroke
|
|
593
|
+
fillPaints?: unknown[];
|
|
594
|
+
strokePaints?: unknown[];
|
|
595
|
+
strokeWeight?: number;
|
|
596
|
+
strokeAlign?: string;
|
|
597
|
+
strokeCap?: string;
|
|
598
|
+
strokeJoin?: string;
|
|
599
|
+
dashPattern?: number[];
|
|
600
|
+
|
|
601
|
+
// Individual border weights
|
|
602
|
+
borderTopWeight?: number;
|
|
603
|
+
borderRightWeight?: number;
|
|
604
|
+
borderBottomWeight?: number;
|
|
605
|
+
borderLeftWeight?: number;
|
|
606
|
+
borderStrokeWeightsIndependent?: boolean;
|
|
607
|
+
|
|
608
|
+
// Corner radius
|
|
609
|
+
cornerRadius?: number;
|
|
610
|
+
rectangleCornerRadii?: number[];
|
|
611
|
+
rectangleTopLeftCornerRadius?: number;
|
|
612
|
+
rectangleTopRightCornerRadius?: number;
|
|
613
|
+
rectangleBottomLeftCornerRadius?: number;
|
|
614
|
+
rectangleBottomRightCornerRadius?: number;
|
|
615
|
+
|
|
616
|
+
// Effects
|
|
617
|
+
effects?: unknown[];
|
|
618
|
+
|
|
619
|
+
// Text properties
|
|
620
|
+
fontSize?: number;
|
|
621
|
+
fontName?: { family: string; style: string; postscript: string };
|
|
622
|
+
textData?: { characters: string };
|
|
623
|
+
textAlignHorizontal?: string;
|
|
624
|
+
textAlignVertical?: string;
|
|
625
|
+
lineHeight?: { value: number; units: string };
|
|
626
|
+
letterSpacing?: { value: number; units: string };
|
|
627
|
+
textCase?: string;
|
|
628
|
+
textDecoration?: string;
|
|
629
|
+
paragraphIndent?: number;
|
|
630
|
+
paragraphSpacing?: number;
|
|
631
|
+
textTruncation?: string;
|
|
632
|
+
maxLines?: number;
|
|
633
|
+
|
|
634
|
+
// Auto-layout properties
|
|
635
|
+
stackMode?: string;
|
|
636
|
+
stackSpacing?: number;
|
|
637
|
+
stackPadding?: number;
|
|
638
|
+
stackHorizontalPadding?: number;
|
|
639
|
+
stackVerticalPadding?: number;
|
|
640
|
+
stackPaddingRight?: number;
|
|
641
|
+
stackPaddingBottom?: number;
|
|
642
|
+
stackPrimaryAlignItems?: string;
|
|
643
|
+
stackCounterAlignItems?: string;
|
|
644
|
+
stackPrimarySizing?: string;
|
|
645
|
+
stackCounterSizing?: string;
|
|
646
|
+
stackChildPrimaryGrow?: number;
|
|
647
|
+
stackChildAlignSelf?: string;
|
|
648
|
+
stackPositioning?: string;
|
|
649
|
+
stackReverseZIndex?: boolean;
|
|
650
|
+
stackWrap?: string;
|
|
651
|
+
|
|
652
|
+
// Constraints
|
|
653
|
+
horizontalConstraint?: string;
|
|
654
|
+
verticalConstraint?: string;
|
|
655
|
+
|
|
656
|
+
// Clipping
|
|
657
|
+
clipsContent?: boolean;
|
|
658
|
+
frameMaskDisabled?: boolean;
|
|
659
|
+
|
|
660
|
+
// Other
|
|
661
|
+
count?: number;
|
|
662
|
+
[key: string]: unknown;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export function extractNodes(message: Record<string, unknown>): FigmaNode[] {
|
|
666
|
+
const nodes: FigmaNode[] = [];
|
|
667
|
+
|
|
668
|
+
// Try to extract node changes from message
|
|
669
|
+
const nodeChanges = message.nodeChanges as unknown[];
|
|
670
|
+
if (Array.isArray(nodeChanges)) {
|
|
671
|
+
console.log('[figma-kiwi-decoder] === NODE CHANGES DEBUG ===');
|
|
672
|
+
for (let i = 0; i < nodeChanges.length; i++) {
|
|
673
|
+
const change = nodeChanges[i];
|
|
674
|
+
if (change && typeof change === 'object') {
|
|
675
|
+
const nodeObj = change as Record<string, unknown>;
|
|
676
|
+
// Log all keys to see what fields are available
|
|
677
|
+
console.log(`[figma-kiwi-decoder] Node ${i}: ${nodeObj.type} "${nodeObj.name}"`);
|
|
678
|
+
console.log(`[figma-kiwi-decoder] Keys: ${Object.keys(nodeObj).join(', ')}`);
|
|
679
|
+
// Look for parent-related fields - parentIndex has nested guid structure
|
|
680
|
+
if ('parentIndex' in nodeObj && nodeObj.parentIndex) {
|
|
681
|
+
const pi = nodeObj.parentIndex as { guid?: { sessionID?: number; localID?: number } };
|
|
682
|
+
if (pi.guid) {
|
|
683
|
+
console.log(`[figma-kiwi-decoder] parentIndex.guid: ${pi.guid.sessionID}:${pi.guid.localID}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if ('guid' in nodeObj && nodeObj.guid) {
|
|
687
|
+
const g = nodeObj.guid as { sessionID?: number; localID?: number };
|
|
688
|
+
console.log(`[figma-kiwi-decoder] guid: ${g.sessionID}:${g.localID}`);
|
|
689
|
+
}
|
|
690
|
+
// Log auto-layout properties if present
|
|
691
|
+
if ('stackMode' in nodeObj) {
|
|
692
|
+
console.log(`[figma-kiwi-decoder] auto-layout: mode=${nodeObj.stackMode} spacing=${nodeObj.stackSpacing} padding=${nodeObj.stackPadding}`);
|
|
693
|
+
}
|
|
694
|
+
// Log text properties if present
|
|
695
|
+
if ('textAlignHorizontal' in nodeObj || 'lineHeight' in nodeObj) {
|
|
696
|
+
const lh = nodeObj.lineHeight as { value?: number; units?: string } | undefined;
|
|
697
|
+
const ls = nodeObj.letterSpacing as { value?: number; units?: string } | undefined;
|
|
698
|
+
console.log(`[figma-kiwi-decoder] text: align=${nodeObj.textAlignHorizontal} lineHeight=${lh?.value}${lh?.units} letterSpacing=${ls?.value}${ls?.units}`);
|
|
699
|
+
}
|
|
700
|
+
// Log fill paints details for debugging image detection
|
|
701
|
+
if ('fillPaints' in nodeObj && Array.isArray(nodeObj.fillPaints)) {
|
|
702
|
+
for (let j = 0; j < nodeObj.fillPaints.length; j++) {
|
|
703
|
+
const paint = nodeObj.fillPaints[j] as Record<string, unknown>;
|
|
704
|
+
const paintKeys = Object.keys(paint);
|
|
705
|
+
console.log(`[figma-kiwi-decoder] fillPaint[${j}]: type=${paint.type} keys=[${paintKeys.join(', ')}]`);
|
|
706
|
+
// Log all values for IMAGE type or unknown types
|
|
707
|
+
if (paint.type === 'IMAGE' || paintKeys.includes('imageHash') || paintKeys.includes('imageRef')) {
|
|
708
|
+
console.log(`[figma-kiwi-decoder] IMAGE paint detail:`, JSON.stringify(paint, (_, v) => {
|
|
709
|
+
if (v instanceof Uint8Array) return `<Uint8Array(${v.length})>`;
|
|
710
|
+
return v;
|
|
711
|
+
}, 2).substring(0, 500));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
nodes.push(nodeObj as FigmaNode);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
console.log('[figma-kiwi-decoder] === END NODE CHANGES DEBUG ===');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return nodes;
|
|
722
|
+
}
|