@growgroup/visual-editor 0.3.1 → 0.4.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/README.md +6 -1
- package/dist/editor.css +1 -1
- package/package.json +1 -1
- package/src/editor/EditorContext.tsx +3 -2
- package/src/editor/EditorToolbar.tsx +8 -2
- package/src/editor/FrontendVisualEditor.tsx +114 -44
- package/src/editor/components/EditorCanvas.tsx +73 -15
- package/src/editor/components/EditorFooter.tsx +2 -0
- package/src/editor/components/EditorHeader.tsx +3 -2
- package/src/editor/components/FigmaColorPicker.tsx +2 -1
- package/src/editor/components/JsEditorDialog.tsx +1 -0
- package/src/editor/components/PageSettingsDialog.tsx +1 -0
- package/src/editor/components/comments/CommentBoard.tsx +9 -6
- package/src/editor/components/multi-page/CanvasFrameDecor.tsx +172 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +173 -77
- package/src/editor/components/multi-page/PageFramePreview.tsx +96 -20
- package/src/editor/components/multi-page/index.ts +1 -0
- package/src/editor/components/ppt/PptChrome.tsx +6 -2
- package/src/editor/components/ppt/PptComments.tsx +411 -71
- package/src/editor/constants.ts +16 -1
- package/src/editor/contexts/EditorArtboardContext.tsx +8 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +31 -27
- package/src/editor/contexts/EditorUIStateContext.tsx +4 -3
- package/src/editor/contexts/EditorVariablesContext.tsx +5 -4
- package/src/editor/contexts/MultiPageCanvasContext.tsx +26 -2
- package/src/editor/hooks/useAiReplace.ts +4 -3
- package/src/editor/hooks/useBrowserZoomPrevention.ts +2 -1
- package/src/editor/hooks/useComponentEditMode.ts +27 -26
- package/src/editor/hooks/useEditorMessages.ts +3 -2
- package/src/editor/hooks/useElementActions.ts +27 -26
- package/src/editor/hooks/useElementSelection.ts +19 -16
- package/src/editor/hooks/useFocusManagement.ts +7 -6
- package/src/editor/hooks/useGoogleFonts.ts +3 -2
- package/src/editor/hooks/useIframeInitializer.ts +6 -3
- package/src/editor/hooks/useIframeSetup.ts +4 -3
- package/src/editor/hooks/useImageUpload.ts +4 -3
- package/src/editor/hooks/useMarqueeSelection.ts +2 -1
- package/src/editor/hooks/usePageSettingsManager.ts +24 -23
- package/src/editor/hooks/useRichPaste.ts +11 -10
- package/src/editor/parts.ts +7 -25
- package/src/editor/utils/component-renderer.ts +8 -7
- package/src/editor/utils/component-sync.ts +16 -15
- package/src/editor/utils/debug.ts +52 -0
- package/src/editor/utils/dom-utils.ts +11 -10
- package/src/editor/utils/drop-target.ts +135 -0
- package/src/editor/utils/eyedropper.ts +3 -1
- package/src/editor/utils/figma-export.ts +6 -5
- package/src/editor/utils/figma-kiwi-decoder.ts +28 -27
- package/src/editor/utils/figma-kiwi-encoder.ts +9 -8
- package/src/editor/utils/figma-paste.ts +57 -56
- package/src/editor/utils/flex-utils.ts +12 -10
- package/src/editor/utils/override-apply.ts +3 -2
- package/src/editor/utils/override-detection.ts +11 -10
- package/src/editor/utils/tailwind-utils.ts +4 -3
- package/src/io.ts +22 -1
- package/src/lib/firebase/css-variables.ts +4 -3
- package/src/lib/firebase/storage.ts +3 -2
- package/src/styles/skin.css +9 -4
- package/src/types/editor.ts +6 -2
- package/src/version.ts +1 -1
|
@@ -9,6 +9,7 @@ import { readHTMLMessage } from 'fig-kiwi';
|
|
|
9
9
|
import { generateElementId } from './dom-utils';
|
|
10
10
|
import { parseFigmaClipboardToJson, extractNodes, type FigmaNode } from './figma-kiwi-decoder';
|
|
11
11
|
import { cacheFigKiwiSchema } from './figma-export';
|
|
12
|
+
import { debugLog } from './debug';
|
|
12
13
|
|
|
13
14
|
// Type definitions for Figma data structures
|
|
14
15
|
// Based on fig-kiwi schema-defs
|
|
@@ -254,7 +255,7 @@ export function parseFigmaClipboard(html: string): FigmaPasteResult {
|
|
|
254
255
|
// Cache the schema for later use in export
|
|
255
256
|
if (parsed?.schema) {
|
|
256
257
|
cacheFigKiwiSchema(parsed.schema);
|
|
257
|
-
|
|
258
|
+
debugLog('[figma-paste] Cached fig-kiwi schema for export');
|
|
258
259
|
}
|
|
259
260
|
} catch (decodeError) {
|
|
260
261
|
console.warn('[figma-paste] fig-kiwi decode failed, trying custom decoder:', decodeError);
|
|
@@ -267,12 +268,12 @@ export function parseFigmaClipboard(html: string): FigmaPasteResult {
|
|
|
267
268
|
// Use custom decoder with extended type support
|
|
268
269
|
try {
|
|
269
270
|
const customResult = parseFigmaClipboardToJson(html);
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
debugLog('[figma-paste] Custom decoder result:', customResult.message);
|
|
272
|
+
debugLog('[figma-paste] Schema has', customResult.schema.definitions.length, 'definitions');
|
|
272
273
|
|
|
273
274
|
// Extract nodes from custom decoder result
|
|
274
275
|
const customNodes = extractNodes(customResult.message);
|
|
275
|
-
|
|
276
|
+
debugLog('[figma-paste] Extracted', customNodes.length, 'nodes from custom decoder');
|
|
276
277
|
|
|
277
278
|
// Convert FigmaNode to NodeChange format
|
|
278
279
|
nodeChanges = convertFigmaNodes(customNodes);
|
|
@@ -294,7 +295,7 @@ export function parseFigmaClipboard(html: string): FigmaPasteResult {
|
|
|
294
295
|
nodeChanges = message.nodeChanges || [];
|
|
295
296
|
}
|
|
296
297
|
|
|
297
|
-
|
|
298
|
+
debugLog('[figma-paste] Parsed nodes:', nodeChanges.length);
|
|
298
299
|
|
|
299
300
|
// Convert to SVG
|
|
300
301
|
const svg = convertNodesToSvg(nodeChanges);
|
|
@@ -586,7 +587,7 @@ function nodeToSvgElement(node: NodeChange, defs: string[]): string {
|
|
|
586
587
|
|
|
587
588
|
default:
|
|
588
589
|
// Fallback to rectangle for unknown types
|
|
589
|
-
|
|
590
|
+
debugLog(`[figma-paste] Unknown node type: ${type}`);
|
|
590
591
|
return wrapWithTransform(
|
|
591
592
|
`<rect x="0" y="0" width="${width}" height="${height}" ${styleStr} data-name="${escapeXml(name)}" data-type="${type}"/>`
|
|
592
593
|
);
|
|
@@ -850,7 +851,7 @@ function buildNodeHierarchy(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
850
851
|
|
|
851
852
|
// Check if nodes have parentIndex with valid guid
|
|
852
853
|
const hasParentIndex = nodes.some(n => n.parentIndex?.guid !== undefined);
|
|
853
|
-
|
|
854
|
+
debugLog('[figma-paste] Has parentIndex.guid field:', hasParentIndex);
|
|
854
855
|
|
|
855
856
|
if (hasParentIndex) {
|
|
856
857
|
return buildHierarchyFromParentIndex(nodes);
|
|
@@ -863,7 +864,7 @@ function buildNodeHierarchy(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
863
864
|
* Build hierarchy using Figma's parentIndex field (accurate method)
|
|
864
865
|
*/
|
|
865
866
|
function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
|
|
866
|
-
|
|
867
|
+
debugLog('[figma-paste] Building hierarchy from parentIndex for', nodes.length, 'nodes');
|
|
867
868
|
|
|
868
869
|
// Create hierarchy nodes and map by GUID
|
|
869
870
|
const nodeMap = new Map<string, HierarchyNodeWithBounds>();
|
|
@@ -882,7 +883,7 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
882
883
|
const guidKey = guid ? `${guid.sessionID}:${guid.localID}` : 'no-guid';
|
|
883
884
|
const parentKey = parentGuid ? `${parentGuid.sessionID}:${parentGuid.localID}` : 'no-parent';
|
|
884
885
|
|
|
885
|
-
|
|
886
|
+
debugLog(`[figma-paste] Node: ${node.type} "${node.name}" guid=${guidKey} parent=${parentKey} pos=(${x.toFixed(0)},${y.toFixed(0)}) size=${width.toFixed(0)}x${height.toFixed(0)}`);
|
|
886
887
|
|
|
887
888
|
const hNode: HierarchyNodeWithBounds = {
|
|
888
889
|
node,
|
|
@@ -902,7 +903,7 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
902
903
|
}
|
|
903
904
|
}
|
|
904
905
|
|
|
905
|
-
|
|
906
|
+
debugLog('[figma-paste] Node map has', nodeMap.size, 'entries');
|
|
906
907
|
|
|
907
908
|
// Second pass: build parent-child relationships
|
|
908
909
|
const roots: HierarchyNodeWithBounds[] = [];
|
|
@@ -916,26 +917,26 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
916
917
|
const parent = nodeMap.get(parentKey);
|
|
917
918
|
|
|
918
919
|
if (parent) {
|
|
919
|
-
|
|
920
|
+
debugLog(`[figma-paste] ${nodeName} → child of ${parent.node.type} "${parent.node.name}"`);
|
|
920
921
|
parent.children.push(hNode);
|
|
921
922
|
childCount++;
|
|
922
923
|
} else {
|
|
923
924
|
// Parent not found in copied nodes - this is a root
|
|
924
|
-
|
|
925
|
+
debugLog(`[figma-paste] ${nodeName} → ROOT (parent ${parentKey} not in selection)`);
|
|
925
926
|
hNode.relativeX = 0;
|
|
926
927
|
hNode.relativeY = 0;
|
|
927
928
|
roots.push(hNode);
|
|
928
929
|
}
|
|
929
930
|
} else {
|
|
930
931
|
// No parent - this is a root
|
|
931
|
-
|
|
932
|
+
debugLog(`[figma-paste] ${nodeName} → ROOT (no parentIndex)`);
|
|
932
933
|
hNode.relativeX = 0;
|
|
933
934
|
hNode.relativeY = 0;
|
|
934
935
|
roots.push(hNode);
|
|
935
936
|
}
|
|
936
937
|
}
|
|
937
938
|
|
|
938
|
-
|
|
939
|
+
debugLog(`[figma-paste] Result: ${roots.length} roots, ${childCount} children linked`);
|
|
939
940
|
|
|
940
941
|
// Debug: Print tree structure
|
|
941
942
|
printHierarchyTree(roots);
|
|
@@ -947,7 +948,7 @@ function buildHierarchyFromParentIndex(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
947
948
|
* Build hierarchy using containment-based detection (fallback method)
|
|
948
949
|
*/
|
|
949
950
|
function buildHierarchyFromContainment(nodes: NodeChange[]): HierarchyNode[] {
|
|
950
|
-
|
|
951
|
+
debugLog('[figma-paste] Building hierarchy from containment (fallback)');
|
|
951
952
|
|
|
952
953
|
// Sort by area descending - larger containers processed first
|
|
953
954
|
const sorted = [...nodes].sort((a, b) => {
|
|
@@ -1014,9 +1015,9 @@ function buildHierarchyFromContainment(nodes: NodeChange[]): HierarchyNode[] {
|
|
|
1014
1015
|
* Debug helper: Print hierarchy tree
|
|
1015
1016
|
*/
|
|
1016
1017
|
function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1018
|
+
debugLog('[figma-paste] ========================================');
|
|
1019
|
+
debugLog('[figma-paste] HIERARCHY STRUCTURE:', roots.length, 'root(s)');
|
|
1020
|
+
debugLog('[figma-paste] ========================================');
|
|
1020
1021
|
|
|
1021
1022
|
function countNodes(node: HierarchyNodeWithBounds): number {
|
|
1022
1023
|
return 1 + node.children.reduce((sum, c) => sum + countNodes(c as HierarchyNodeWithBounds), 0);
|
|
@@ -1028,7 +1029,7 @@ function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
|
|
|
1028
1029
|
const sizeStr = node.width > 0 && node.height > 0
|
|
1029
1030
|
? `${node.width.toFixed(0)}x${node.height.toFixed(0)}`
|
|
1030
1031
|
: 'no-size';
|
|
1031
|
-
|
|
1032
|
+
debugLog(`[figma-paste] ${indent}├─ ${node.node.type} "${node.node.name}"${imageMarker} [${sizeStr}] (${node.relativeX.toFixed(0)},${node.relativeY.toFixed(0)})`);
|
|
1032
1033
|
for (const child of node.children) {
|
|
1033
1034
|
printTree(child as HierarchyNodeWithBounds, indent + '│ ');
|
|
1034
1035
|
}
|
|
@@ -1040,7 +1041,7 @@ function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
|
|
|
1040
1041
|
for (const root of roots) {
|
|
1041
1042
|
totalNodes += countNodes(root);
|
|
1042
1043
|
printTree(root);
|
|
1043
|
-
|
|
1044
|
+
debugLog('[figma-paste] ----------------------------------------');
|
|
1044
1045
|
}
|
|
1045
1046
|
|
|
1046
1047
|
// Count nodes with image fills
|
|
@@ -1053,11 +1054,11 @@ function printHierarchyTree(roots: HierarchyNodeWithBounds[]) {
|
|
|
1053
1054
|
nodesWithImages += countImagesInTree(root);
|
|
1054
1055
|
}
|
|
1055
1056
|
|
|
1056
|
-
|
|
1057
|
+
debugLog(`[figma-paste] Summary: ${totalNodes} nodes, ${nodesWithImages} with image fills, ${roots.length} root(s)`);
|
|
1057
1058
|
if (roots.length > 1) {
|
|
1058
1059
|
console.warn('[figma-paste] ⚠️ Multiple roots detected - elements will stack vertically');
|
|
1059
1060
|
}
|
|
1060
|
-
|
|
1061
|
+
debugLog('[figma-paste] ========================================');
|
|
1061
1062
|
}
|
|
1062
1063
|
|
|
1063
1064
|
/**
|
|
@@ -1075,7 +1076,7 @@ function hierarchyNodeToElement(
|
|
|
1075
1076
|
|
|
1076
1077
|
// Skip invisible nodes - but still process children in case they're visible
|
|
1077
1078
|
if (node.visible === false) {
|
|
1078
|
-
|
|
1079
|
+
debugLog(`[figma-paste] Skip invisible node: ${node.type} "${node.name}"`);
|
|
1079
1080
|
// Still process children - they might be visible even if parent is marked invisible
|
|
1080
1081
|
const visibleChildren: HTMLElement[] = [];
|
|
1081
1082
|
for (const child of hNode.children) {
|
|
@@ -1092,7 +1093,7 @@ function hierarchyNodeToElement(
|
|
|
1092
1093
|
// Skip nodes without valid size (but not containers which may have zero size themselves)
|
|
1093
1094
|
const isContainer = ['FRAME', 'GROUP', 'COMPONENT', 'INSTANCE', 'SECTION'].includes(node.type || '');
|
|
1094
1095
|
if (!isContainer && (!node.size || node.size.x <= 0 || node.size.y <= 0)) {
|
|
1095
|
-
|
|
1096
|
+
debugLog(`[figma-paste] Skip zero-size node: ${node.type} "${node.name}"`);
|
|
1096
1097
|
return null;
|
|
1097
1098
|
}
|
|
1098
1099
|
|
|
@@ -1340,7 +1341,7 @@ function hierarchyNodeToElement(
|
|
|
1340
1341
|
}
|
|
1341
1342
|
}
|
|
1342
1343
|
} else {
|
|
1343
|
-
|
|
1344
|
+
debugLog(`[figma-paste] Skipping children for ${node.type} "${node.name}" (will render as instance)`);
|
|
1344
1345
|
}
|
|
1345
1346
|
|
|
1346
1347
|
return el;
|
|
@@ -1420,7 +1421,7 @@ function getBackgroundCss(paints: Paint[] | undefined): BackgroundCssResult {
|
|
|
1420
1421
|
backgroundPosition = 'center';
|
|
1421
1422
|
}
|
|
1422
1423
|
|
|
1423
|
-
|
|
1424
|
+
debugLog(`[figma-paste] IMAGE fill detected: hash=${detectedHash?.substring(0, 16) || 'none'} scaleMode=${scaleMode}`);
|
|
1424
1425
|
}
|
|
1425
1426
|
}
|
|
1426
1427
|
|
|
@@ -1827,7 +1828,7 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
|
|
|
1827
1828
|
// If decode failed (schema version mismatch), signal to fallback to image
|
|
1828
1829
|
if (!result.success) {
|
|
1829
1830
|
if (result.error === 'DECODE_FAILED') {
|
|
1830
|
-
|
|
1831
|
+
debugLog('[figma-paste] Decode failed, should fallback to image paste');
|
|
1831
1832
|
return { elements: [], shouldFallbackToImage: true };
|
|
1832
1833
|
}
|
|
1833
1834
|
console.warn('[figma-paste] Failed to process Figma paste:', result.error);
|
|
@@ -1842,22 +1843,22 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
|
|
|
1842
1843
|
// Collect image hashes and node IDs from all nodes
|
|
1843
1844
|
const imageInfo = collectImageInfo(result.nodes);
|
|
1844
1845
|
if (imageInfo.hashes.length > 0) {
|
|
1845
|
-
|
|
1846
|
+
debugLog(`[figma-paste] Found ${imageInfo.hashes.length} image hashes:`, imageInfo.hashes);
|
|
1846
1847
|
}
|
|
1847
1848
|
if (imageInfo.nodeIds.length > 0) {
|
|
1848
|
-
|
|
1849
|
+
debugLog(`[figma-paste] Found ${imageInfo.nodeIds.length} image node IDs:`, imageInfo.nodeIds);
|
|
1849
1850
|
}
|
|
1850
1851
|
|
|
1851
1852
|
// Filter out only document structure nodes - keep everything else for hierarchy building
|
|
1852
1853
|
// IMPORTANT: We must NOT filter nodes before building hierarchy, or children become orphaned
|
|
1853
1854
|
const SKIP_NODE_TYPES = ['DOCUMENT', 'CANVAS', 'PAGE'];
|
|
1854
|
-
|
|
1855
|
+
debugLog('[figma-paste] Total nodes from clipboard:', result.nodes.length);
|
|
1855
1856
|
|
|
1856
1857
|
// Only remove document structure nodes - keep all content nodes including containers
|
|
1857
1858
|
// The hierarchy builder needs parent nodes to properly nest children
|
|
1858
1859
|
const allContentNodes = result.nodes.filter(n => {
|
|
1859
1860
|
if (n.type && SKIP_NODE_TYPES.includes(n.type)) {
|
|
1860
|
-
|
|
1861
|
+
debugLog(`[figma-paste] SKIP: ${n.type} "${n.name}" (structure node)`);
|
|
1861
1862
|
return false;
|
|
1862
1863
|
}
|
|
1863
1864
|
return true;
|
|
@@ -1868,7 +1869,7 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
|
|
|
1868
1869
|
return { elements: [], shouldFallbackToImage: true };
|
|
1869
1870
|
}
|
|
1870
1871
|
|
|
1871
|
-
|
|
1872
|
+
debugLog('[figma-paste] Content nodes for hierarchy:', allContentNodes.length);
|
|
1872
1873
|
|
|
1873
1874
|
// Convert ALL nodes to DOM elements - hierarchy builder handles parent-child nesting
|
|
1874
1875
|
// Filtering of invisible/zero-size nodes happens during element conversion, not before
|
|
@@ -1881,7 +1882,7 @@ export function processFigmaPaste(html: string, doc: Document): FigmaPasteProces
|
|
|
1881
1882
|
|
|
1882
1883
|
// Log generated elements
|
|
1883
1884
|
for (const el of elements) {
|
|
1884
|
-
|
|
1885
|
+
debugLog('[figma-paste] Generated element:',
|
|
1885
1886
|
el.style.width, 'x', el.style.height,
|
|
1886
1887
|
'with', el.children.length, 'nested children');
|
|
1887
1888
|
}
|
|
@@ -1924,7 +1925,7 @@ function deepExtractHash(value: unknown, maxDepth: number): string | null {
|
|
|
1924
1925
|
if (obj[key] !== undefined) {
|
|
1925
1926
|
const result = deepExtractHash(obj[key], maxDepth - 1);
|
|
1926
1927
|
if (result) {
|
|
1927
|
-
|
|
1928
|
+
debugLog(`[figma-paste] Found hash via key: ${key}`);
|
|
1928
1929
|
return result;
|
|
1929
1930
|
}
|
|
1930
1931
|
}
|
|
@@ -1934,7 +1935,7 @@ function deepExtractHash(value: unknown, maxDepth: number): string | null {
|
|
|
1934
1935
|
if (['hash', 'value', 'ref', 'data', 'bytes', 'imageHash', 'imageRef'].includes(k)) continue;
|
|
1935
1936
|
const result = deepExtractHash(v, maxDepth - 1);
|
|
1936
1937
|
if (result) {
|
|
1937
|
-
|
|
1938
|
+
debugLog(`[figma-paste] Found hash via deep search at key: ${k}`);
|
|
1938
1939
|
return result;
|
|
1939
1940
|
}
|
|
1940
1941
|
}
|
|
@@ -1956,7 +1957,7 @@ function extractImageHashFromPaint(paint: Record<string, unknown>): string | nul
|
|
|
1956
1957
|
|
|
1957
1958
|
const hash = deepExtractHash(field, 4); // search up to 4 levels deep
|
|
1958
1959
|
if (hash) {
|
|
1959
|
-
|
|
1960
|
+
debugLog(`[figma-paste] Extracted hash from paint.${key}: ${hash.substring(0, 20)}...`);
|
|
1960
1961
|
return hash;
|
|
1961
1962
|
}
|
|
1962
1963
|
}
|
|
@@ -1993,7 +1994,7 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
|
|
|
1993
1994
|
const nodeId = `${node.guid.sessionID}:${node.guid.localID}`;
|
|
1994
1995
|
instanceNodeIds.add(nodeId);
|
|
1995
1996
|
nodeIds.add(nodeId);
|
|
1996
|
-
|
|
1997
|
+
debugLog(`[figma-paste] INSTANCE/COMPONENT "${node.name}" will be rendered as single image: ${nodeId}`);
|
|
1997
1998
|
}
|
|
1998
1999
|
}
|
|
1999
2000
|
}
|
|
@@ -2018,7 +2019,7 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
|
|
|
2018
2019
|
|
|
2019
2020
|
// Skip children of INSTANCE/COMPONENT nodes (they're rendered as part of the parent)
|
|
2020
2021
|
if (childrenOfInstances.has(nodeId)) {
|
|
2021
|
-
|
|
2022
|
+
debugLog(`[figma-paste] Skipping child of instance: "${node.name}"`);
|
|
2022
2023
|
continue;
|
|
2023
2024
|
}
|
|
2024
2025
|
|
|
@@ -2034,15 +2035,15 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
|
|
|
2034
2035
|
if (paint.type === 'IMAGE') {
|
|
2035
2036
|
// Deep-inspect the `image` field
|
|
2036
2037
|
const imageField = anyPaint.image;
|
|
2037
|
-
|
|
2038
|
+
debugLog(`[figma-paste] IMAGE paint on "${node.name}": image field type=${typeof imageField}, isUint8Array=${imageField instanceof Uint8Array}`);
|
|
2038
2039
|
|
|
2039
2040
|
if (imageField && typeof imageField === 'object') {
|
|
2040
2041
|
if (imageField instanceof Uint8Array) {
|
|
2041
|
-
|
|
2042
|
+
debugLog(`[figma-paste] image = <Uint8Array(${imageField.length})> hex=${bytesToHex(imageField).substring(0, 40)}...`);
|
|
2042
2043
|
} else {
|
|
2043
2044
|
// Log nested object structure for debugging
|
|
2044
2045
|
const imgObj = imageField as Record<string, unknown>;
|
|
2045
|
-
|
|
2046
|
+
debugLog(`[figma-paste] image object keys: [${Object.keys(imgObj).join(', ')}]`);
|
|
2046
2047
|
logObjectDeep(imgObj, 'image', 2);
|
|
2047
2048
|
}
|
|
2048
2049
|
}
|
|
@@ -2050,23 +2051,23 @@ function collectImageInfo(nodes: NodeChange[]): ImageCollectionResult {
|
|
|
2050
2051
|
// Extract hash using recursive deep search
|
|
2051
2052
|
const hash = extractImageHashFromPaint(anyPaint);
|
|
2052
2053
|
if (hash) {
|
|
2053
|
-
|
|
2054
|
+
debugLog(`[figma-paste] => extracted hash: "${hash.substring(0, 40)}${hash.length > 40 ? '...' : ''}"`);
|
|
2054
2055
|
hashes.add(hash);
|
|
2055
2056
|
} else {
|
|
2056
|
-
|
|
2057
|
+
debugLog(`[figma-paste] => NO hash extracted from paint`);
|
|
2057
2058
|
}
|
|
2058
2059
|
|
|
2059
2060
|
// Collect node GUID for fallback rendering
|
|
2060
2061
|
if (node.guid) {
|
|
2061
2062
|
nodeIds.add(nodeId);
|
|
2062
|
-
|
|
2063
|
+
debugLog(`[figma-paste] => node ID for rendering: ${nodeId}`);
|
|
2063
2064
|
}
|
|
2064
2065
|
}
|
|
2065
2066
|
}
|
|
2066
2067
|
}
|
|
2067
2068
|
}
|
|
2068
2069
|
|
|
2069
|
-
|
|
2070
|
+
debugLog(`[figma-paste] collectImageInfo: ${hashes.size} hashes, ${nodeIds.size} node IDs (${instanceNodeIds.size} instances)`);
|
|
2070
2071
|
return {
|
|
2071
2072
|
hashes: Array.from(hashes),
|
|
2072
2073
|
nodeIds: Array.from(nodeIds),
|
|
@@ -2080,13 +2081,13 @@ function logObjectDeep(obj: Record<string, unknown>, prefix: string, maxDepth: n
|
|
|
2080
2081
|
if (maxDepth <= 0) return;
|
|
2081
2082
|
for (const [k, v] of Object.entries(obj)) {
|
|
2082
2083
|
if (v instanceof Uint8Array) {
|
|
2083
|
-
|
|
2084
|
+
debugLog(`[figma-paste] ${prefix}.${k} = <Uint8Array(${v.length})> hex=${bytesToHex(v).substring(0, 40)}...`);
|
|
2084
2085
|
} else if (typeof v === 'object' && v !== null) {
|
|
2085
2086
|
const nested = v as Record<string, unknown>;
|
|
2086
|
-
|
|
2087
|
+
debugLog(`[figma-paste] ${prefix}.${k} = {${Object.keys(nested).join(', ')}}`);
|
|
2087
2088
|
logObjectDeep(nested, `${prefix}.${k}`, maxDepth - 1);
|
|
2088
2089
|
} else {
|
|
2089
|
-
|
|
2090
|
+
debugLog(`[figma-paste] ${prefix}.${k} = ${JSON.stringify(v)}`.substring(0, 120));
|
|
2090
2091
|
}
|
|
2091
2092
|
}
|
|
2092
2093
|
}
|
|
@@ -2126,7 +2127,7 @@ export async function fetchAndApplyFigmaImages(
|
|
|
2126
2127
|
|
|
2127
2128
|
// Strategy 1: Try image fills API with hash matching
|
|
2128
2129
|
if (hasHashes) {
|
|
2129
|
-
|
|
2130
|
+
debugLog(`[figma-paste] Strategy 1: Fetching ${imageHashes.length} images by hash...`);
|
|
2130
2131
|
try {
|
|
2131
2132
|
const response = await fetch('/api/figma/images', {
|
|
2132
2133
|
method: 'POST',
|
|
@@ -2138,11 +2139,11 @@ export async function fetchAndApplyFigmaImages(
|
|
|
2138
2139
|
const data = await response.json();
|
|
2139
2140
|
if (data.success && data.images) {
|
|
2140
2141
|
const imageMap = data.images as Record<string, string>;
|
|
2141
|
-
|
|
2142
|
+
debugLog(`[figma-paste] Strategy 1: Got ${Object.keys(imageMap).length} image URLs`);
|
|
2142
2143
|
|
|
2143
2144
|
// Also log all available image refs for debugging
|
|
2144
2145
|
if (data.allImageRefs) {
|
|
2145
|
-
|
|
2146
|
+
debugLog(`[figma-paste] All available image refs in file:`, data.allImageRefs);
|
|
2146
2147
|
}
|
|
2147
2148
|
|
|
2148
2149
|
appliedCount += applyImagesByHash(rootElement, imageMap);
|
|
@@ -2159,7 +2160,7 @@ export async function fetchAndApplyFigmaImages(
|
|
|
2159
2160
|
// Strategy 2: Fall back to node rendering if hash matching didn't cover all elements
|
|
2160
2161
|
const remainingElements = Array.from(rootElement.querySelectorAll('[data-needs-image]'));
|
|
2161
2162
|
if (remainingElements.length > 0 && hasNodeIds) {
|
|
2162
|
-
|
|
2163
|
+
debugLog(`[figma-paste] Strategy 2: Rendering ${remainingElements.length} nodes via API...`);
|
|
2163
2164
|
try {
|
|
2164
2165
|
// Collect node IDs from remaining elements
|
|
2165
2166
|
const nodeIdsToRender: string[] = [];
|
|
@@ -2181,7 +2182,7 @@ export async function fetchAndApplyFigmaImages(
|
|
|
2181
2182
|
const data = await response.json();
|
|
2182
2183
|
if (data.success && data.images) {
|
|
2183
2184
|
const nodeImageMap = data.images as Record<string, string>;
|
|
2184
|
-
|
|
2185
|
+
debugLog(`[figma-paste] Strategy 2: Got ${Object.keys(nodeImageMap).length} rendered images`);
|
|
2185
2186
|
appliedCount += applyImagesByNodeId(rootElement, nodeImageMap);
|
|
2186
2187
|
}
|
|
2187
2188
|
} else {
|
|
@@ -2194,7 +2195,7 @@ export async function fetchAndApplyFigmaImages(
|
|
|
2194
2195
|
}
|
|
2195
2196
|
}
|
|
2196
2197
|
|
|
2197
|
-
|
|
2198
|
+
debugLog(`[figma-paste] Total applied: ${appliedCount} images`);
|
|
2198
2199
|
return { applied: appliedCount, errors };
|
|
2199
2200
|
}
|
|
2200
2201
|
|
|
@@ -2210,7 +2211,7 @@ function applyImagesByHash(rootElement: HTMLElement | Document, imageMap: Record
|
|
|
2210
2211
|
if (hash && imageMap[hash]) {
|
|
2211
2212
|
applyImageToElement(el as HTMLElement, imageMap[hash]);
|
|
2212
2213
|
applied++;
|
|
2213
|
-
|
|
2214
|
+
debugLog(`[figma-paste] Applied image by hash: ${hash.substring(0, 16)}... → ${el.getAttribute('data-name') || 'unnamed'}`);
|
|
2214
2215
|
}
|
|
2215
2216
|
}
|
|
2216
2217
|
|
|
@@ -2229,7 +2230,7 @@ function applyImagesByNodeId(rootElement: HTMLElement | Document, nodeImageMap:
|
|
|
2229
2230
|
if (nodeId && nodeImageMap[nodeId]) {
|
|
2230
2231
|
applyImageToElement(el as HTMLElement, nodeImageMap[nodeId]);
|
|
2231
2232
|
applied++;
|
|
2232
|
-
|
|
2233
|
+
debugLog(`[figma-paste] Applied image by node render: ${nodeId} → ${el.getAttribute('data-name') || 'unnamed'}`);
|
|
2233
2234
|
}
|
|
2234
2235
|
}
|
|
2235
2236
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Concrete5-style element reordering in auto-layout containers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { debugLog } from './debug';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Information about a flex container
|
|
8
10
|
*/
|
|
@@ -479,7 +481,7 @@ export function endAutoLayoutDrag(
|
|
|
479
481
|
const { element, ghost, originalParent, originalIndex, scale } = state;
|
|
480
482
|
|
|
481
483
|
// Debug logging
|
|
482
|
-
|
|
484
|
+
debugLog('[endAutoLayoutDrag] Starting:', {
|
|
483
485
|
elementTag: element?.tagName,
|
|
484
486
|
elementId: element?.id,
|
|
485
487
|
elementClass: element?.className,
|
|
@@ -519,7 +521,7 @@ export function endAutoLayoutDrag(
|
|
|
519
521
|
// Get the current parent of the element (should match originalParent in most cases)
|
|
520
522
|
const currentParent = element.parentElement;
|
|
521
523
|
|
|
522
|
-
|
|
524
|
+
debugLog('[endAutoLayoutDrag] Before DOM operation:', {
|
|
523
525
|
elementInDOM: element.isConnected,
|
|
524
526
|
currentParentTag: currentParent?.tagName,
|
|
525
527
|
currentParentId: currentParent?.id,
|
|
@@ -545,7 +547,7 @@ export function endAutoLayoutDrag(
|
|
|
545
547
|
dropInfo.container.id === 'artboard'
|
|
546
548
|
);
|
|
547
549
|
|
|
548
|
-
|
|
550
|
+
debugLog('[endAutoLayoutDrag] isSameParent:', isSameParent, 'isValidDropLocation:', isValidDropLocation);
|
|
549
551
|
|
|
550
552
|
if (!isValidDropLocation && dropInfo) {
|
|
551
553
|
// Drop location is outside artboard - keep element in original position
|
|
@@ -554,16 +556,16 @@ export function endAutoLayoutDrag(
|
|
|
554
556
|
} else if (dropInfo && isSameParent && originalParent) {
|
|
555
557
|
// Same parent - reorder within the same container
|
|
556
558
|
const targetIndex = dropInfo.index;
|
|
557
|
-
|
|
559
|
+
debugLog('[endAutoLayoutDrag] Same parent reorder:', { targetIndex, originalIndex });
|
|
558
560
|
if (targetIndex !== originalIndex) {
|
|
559
561
|
moved = reorderFlexChild(originalParent, element, targetIndex);
|
|
560
|
-
|
|
562
|
+
debugLog('[endAutoLayoutDrag] reorderFlexChild result:', moved);
|
|
561
563
|
}
|
|
562
564
|
// If targetIndex === originalIndex, no move needed (element stays in place)
|
|
563
565
|
} else if (dropInfo && dropInfo.container && isValidDropLocation) {
|
|
564
566
|
// Different parent but valid location - move to new container
|
|
565
567
|
// Safety check: don't move into self or descendants
|
|
566
|
-
|
|
568
|
+
debugLog('[endAutoLayoutDrag] Different parent move:', {
|
|
567
569
|
containerTag: dropInfo.container.tagName,
|
|
568
570
|
containerId: dropInfo.container.id,
|
|
569
571
|
isElementSelf: dropInfo.container === element,
|
|
@@ -571,15 +573,15 @@ export function endAutoLayoutDrag(
|
|
|
571
573
|
});
|
|
572
574
|
if (dropInfo.container !== element && !element.contains(dropInfo.container)) {
|
|
573
575
|
moved = moveElementToContainer(element, dropInfo);
|
|
574
|
-
|
|
576
|
+
debugLog('[endAutoLayoutDrag] moveElementToContainer result:', moved);
|
|
575
577
|
} else {
|
|
576
578
|
console.warn('[flex-utils] Prevented moving element into itself or descendant');
|
|
577
579
|
}
|
|
578
580
|
} else {
|
|
579
|
-
|
|
581
|
+
debugLog('[endAutoLayoutDrag] No dropInfo or container, element stays in place');
|
|
580
582
|
}
|
|
581
583
|
|
|
582
|
-
|
|
584
|
+
debugLog('[endAutoLayoutDrag] After DOM operation:', {
|
|
583
585
|
elementInDOM: element.isConnected,
|
|
584
586
|
hasParent: !!element.parentElement,
|
|
585
587
|
parentTag: element.parentElement?.tagName,
|
|
@@ -1142,7 +1144,7 @@ function moveElementToContainer(
|
|
|
1142
1144
|
): boolean {
|
|
1143
1145
|
const { container, position, index, referenceElement } = dropInfo;
|
|
1144
1146
|
|
|
1145
|
-
|
|
1147
|
+
debugLog('[moveElementToContainer] Called:', {
|
|
1146
1148
|
elementTag: element.tagName,
|
|
1147
1149
|
elementId: element.id,
|
|
1148
1150
|
containerTag: container?.tagName,
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
OverrideInstanceSwapValue,
|
|
16
16
|
} from '../../types/editor-components';
|
|
17
17
|
import { findElementById, findElementByPath } from './override-detection';
|
|
18
|
+
import { debugLog } from './debug';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Deep clone a ComponentElement tree
|
|
@@ -121,7 +122,7 @@ export function applyOverride(
|
|
|
121
122
|
|
|
122
123
|
case 'instanceSwap':
|
|
123
124
|
// Instance swap is handled at a higher level (requires loading the swapped component)
|
|
124
|
-
|
|
125
|
+
debugLog('[applyOverride] Instance swap override detected:', override.value);
|
|
125
126
|
break;
|
|
126
127
|
|
|
127
128
|
default:
|
|
@@ -245,7 +246,7 @@ export function applyOverrideToDOM(
|
|
|
245
246
|
break;
|
|
246
247
|
|
|
247
248
|
case 'instanceSwap':
|
|
248
|
-
|
|
249
|
+
debugLog('[applyOverrideToDOM] Instance swap requires higher-level handling');
|
|
249
250
|
break;
|
|
250
251
|
|
|
251
252
|
default:
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ComponentInstance,
|
|
15
15
|
OverridableProperties,
|
|
16
16
|
} from '../../types/editor-components';
|
|
17
|
+
import { debugLog } from './debug';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Find an element within the ComponentElement tree by ID
|
|
@@ -148,7 +149,7 @@ function detectSinglePropertyOverride(
|
|
|
148
149
|
switch (type) {
|
|
149
150
|
case 'text': {
|
|
150
151
|
// Debug: Log text comparison attempt
|
|
151
|
-
|
|
152
|
+
debugLog('[detectOverrides] Text comparison for element:', {
|
|
152
153
|
elementId,
|
|
153
154
|
masterHasInnerHTML: masterElement.innerHTML !== undefined,
|
|
154
155
|
masterChildrenCount: masterElement.children.length,
|
|
@@ -160,7 +161,7 @@ function detectSinglePropertyOverride(
|
|
|
160
161
|
// Skip text comparison for elements that use innerHTML (mixed content)
|
|
161
162
|
// because the text is embedded in the HTML structure
|
|
162
163
|
if (masterElement.innerHTML !== undefined) {
|
|
163
|
-
|
|
164
|
+
debugLog('[detectOverrides] Skipping: element uses innerHTML');
|
|
164
165
|
return null;
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -171,7 +172,7 @@ function detectSinglePropertyOverride(
|
|
|
171
172
|
if (masterElement.children.length === 0) {
|
|
172
173
|
const masterText = masterElement.textContent || '';
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
debugLog('[detectOverrides] Comparing text (leaf element):', {
|
|
175
176
|
elementId,
|
|
176
177
|
instanceText,
|
|
177
178
|
masterText,
|
|
@@ -179,7 +180,7 @@ function detectSinglePropertyOverride(
|
|
|
179
180
|
});
|
|
180
181
|
|
|
181
182
|
if (instanceText !== masterText) {
|
|
182
|
-
|
|
183
|
+
debugLog('[detectOverrides] Text override detected:', {
|
|
183
184
|
elementId,
|
|
184
185
|
instanceText: instanceText.substring(0, 50),
|
|
185
186
|
masterText: masterText.substring(0, 50),
|
|
@@ -196,7 +197,7 @@ function detectSinglePropertyOverride(
|
|
|
196
197
|
// Text overrides should only be created at leaf elements (no children)
|
|
197
198
|
// Creating a parent-level text override would destroy the child structure
|
|
198
199
|
// when the override is applied (textContent replaces all children)
|
|
199
|
-
|
|
200
|
+
debugLog('[detectOverrides] Skipping text override for element with children:', {
|
|
200
201
|
elementId,
|
|
201
202
|
childrenCount: masterElement.children.length,
|
|
202
203
|
reason: 'Text overrides at parent level would destroy child structure',
|
|
@@ -351,7 +352,7 @@ export function detectOverrides(
|
|
|
351
352
|
|
|
352
353
|
// Debug: Log child matching
|
|
353
354
|
if (componentElement.children.length > 0 || domChildren.length > 0) {
|
|
354
|
-
|
|
355
|
+
debugLog('[detectOverrides] Child matching:', {
|
|
355
356
|
parentId: componentElement.id,
|
|
356
357
|
domChildrenCount: domChildren.length,
|
|
357
358
|
domChildrenIds: domChildren.map(dc => dc.getAttribute('data-element-id')),
|
|
@@ -375,7 +376,7 @@ export function detectOverrides(
|
|
|
375
376
|
if (matchingDomChild) {
|
|
376
377
|
processElement(matchingDomChild, componentChild);
|
|
377
378
|
} else {
|
|
378
|
-
|
|
379
|
+
debugLog('[detectOverrides] No matching DOM child for component child:', {
|
|
379
380
|
componentChildId: componentChild.id,
|
|
380
381
|
componentChildTagName: componentChild.tagName,
|
|
381
382
|
availableDomIds: domChildren.map(dc => dc.getAttribute('data-element-id')),
|
|
@@ -387,10 +388,10 @@ export function detectOverrides(
|
|
|
387
388
|
|
|
388
389
|
processElement(instanceElement, masterElement);
|
|
389
390
|
|
|
390
|
-
|
|
391
|
-
|
|
391
|
+
debugLog('[detectOverrides] ========== FINAL RESULT ==========');
|
|
392
|
+
debugLog('[detectOverrides] Total overrides detected:', overrides.length);
|
|
392
393
|
if (overrides.length > 0) {
|
|
393
|
-
|
|
394
|
+
debugLog('[detectOverrides] Detected overrides:', overrides.map(o => ({
|
|
394
395
|
type: o.type,
|
|
395
396
|
targetElementId: o.targetElementId,
|
|
396
397
|
value: typeof o.value === 'string' ? o.value.substring(0, 50) : o.value,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
import { debugLog } from './debug';
|
|
8
9
|
import {
|
|
9
10
|
TAILWIND_CONFLICT_GROUPS,
|
|
10
11
|
ENUM_MAPPINGS,
|
|
@@ -63,7 +64,7 @@ export function cssToTailwindClass(
|
|
|
63
64
|
// CSS変数参照はstyle属性に直接設定
|
|
64
65
|
if (value.startsWith('var(')) {
|
|
65
66
|
// DEBUG: var() を検出した時のログ
|
|
66
|
-
|
|
67
|
+
debugLog('[DEBUG tailwind-utils] cssToTailwindClass detected var():', {
|
|
67
68
|
property,
|
|
68
69
|
value,
|
|
69
70
|
result: { className: null, styleValue: value },
|
|
@@ -421,7 +422,7 @@ export function applyTailwindStyles(
|
|
|
421
422
|
styles: Record<string, string>
|
|
422
423
|
): void {
|
|
423
424
|
// DEBUG: applyTailwindStyles 開始時のログ
|
|
424
|
-
|
|
425
|
+
debugLog('[DEBUG tailwind-utils] applyTailwindStyles called:', {
|
|
425
426
|
elementId: element.id,
|
|
426
427
|
elementTagName: element.tagName,
|
|
427
428
|
inputStyles: styles,
|
|
@@ -492,7 +493,7 @@ export function applyTailwindStyles(
|
|
|
492
493
|
});
|
|
493
494
|
|
|
494
495
|
// DEBUG: applyTailwindStyles 最終適用結果のログ
|
|
495
|
-
|
|
496
|
+
debugLog('[DEBUG tailwind-utils] applyTailwindStyles completed:', {
|
|
496
497
|
elementId: element.id,
|
|
497
498
|
classesToAdd,
|
|
498
499
|
stylesToApply,
|