@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,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ComponentSearch - Search input with filter for components
|
|
5
|
+
*
|
|
6
|
+
* Provides a search input field with a search icon and clear button.
|
|
7
|
+
* Matches the dark theme of the editor.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useRef, useCallback } from 'react';
|
|
11
|
+
import { Search, X } from 'lucide-react';
|
|
12
|
+
import { Input } from '../../../components/ui/input';
|
|
13
|
+
import { Button } from '../../../components/ui/button';
|
|
14
|
+
|
|
15
|
+
interface ComponentSearchProps {
|
|
16
|
+
/** Current search value */
|
|
17
|
+
value: string;
|
|
18
|
+
/** Callback when search value changes */
|
|
19
|
+
onChange: (value: string) => void;
|
|
20
|
+
/** Placeholder text */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Auto-focus the input on mount */
|
|
23
|
+
autoFocus?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Search input component for filtering components.
|
|
28
|
+
* Supports keyboard navigation (Escape to clear).
|
|
29
|
+
*/
|
|
30
|
+
export function ComponentSearch({
|
|
31
|
+
value,
|
|
32
|
+
onChange,
|
|
33
|
+
placeholder = 'コンポーネントを検索...',
|
|
34
|
+
autoFocus = false,
|
|
35
|
+
}: ComponentSearchProps) {
|
|
36
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
37
|
+
|
|
38
|
+
const handleClear = useCallback(() => {
|
|
39
|
+
onChange('');
|
|
40
|
+
inputRef.current?.focus();
|
|
41
|
+
}, [onChange]);
|
|
42
|
+
|
|
43
|
+
const handleKeyDown = useCallback(
|
|
44
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
45
|
+
if (e.key === 'Escape' && value) {
|
|
46
|
+
e.stopPropagation();
|
|
47
|
+
handleClear();
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
[value, handleClear]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="relative">
|
|
55
|
+
<Search className="w-3.5 h-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none" />
|
|
56
|
+
<Input
|
|
57
|
+
ref={inputRef}
|
|
58
|
+
type="text"
|
|
59
|
+
value={value}
|
|
60
|
+
onChange={(e) => onChange(e.target.value)}
|
|
61
|
+
onKeyDown={handleKeyDown}
|
|
62
|
+
placeholder={placeholder}
|
|
63
|
+
autoFocus={autoFocus}
|
|
64
|
+
className="h-7 text-xs pl-7 pr-7 bg-[#383838] border-[#444444] text-white placeholder:text-gray-500 focus-visible:ring-1 focus-visible:ring-[#0d99ff]"
|
|
65
|
+
aria-label="コンポーネントを検索"
|
|
66
|
+
/>
|
|
67
|
+
{value && (
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="icon"
|
|
71
|
+
className="absolute right-1 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-500 hover:text-white hover:bg-transparent"
|
|
72
|
+
onClick={handleClear}
|
|
73
|
+
aria-label="検索をクリア"
|
|
74
|
+
>
|
|
75
|
+
<X className="w-3 h-3" />
|
|
76
|
+
</Button>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default ComponentSearch;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VariantList - Display variants of a component
|
|
5
|
+
*
|
|
6
|
+
* Shows all variants of a component with thumbnails.
|
|
7
|
+
* Each variant can be dragged to the canvas separately.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useCallback, useRef, useEffect, useState } from 'react';
|
|
11
|
+
import { Layers } from 'lucide-react';
|
|
12
|
+
import type { MasterComponent, VariantDefinition } from '../../../types/editor-components';
|
|
13
|
+
import { createDOMElement } from '../../utils/component-renderer';
|
|
14
|
+
|
|
15
|
+
interface VariantListProps {
|
|
16
|
+
/** The component containing the variants */
|
|
17
|
+
component: MasterComponent;
|
|
18
|
+
/** Whether the variant list is expanded */
|
|
19
|
+
isExpanded: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Calculate the scale factor to fit the element within the container
|
|
24
|
+
*/
|
|
25
|
+
function calculateScale(
|
|
26
|
+
elementWidth: number,
|
|
27
|
+
elementHeight: number,
|
|
28
|
+
containerWidth: number,
|
|
29
|
+
containerHeight: number,
|
|
30
|
+
padding: number = 4
|
|
31
|
+
): number {
|
|
32
|
+
const availableWidth = containerWidth - padding * 2;
|
|
33
|
+
const availableHeight = containerHeight - padding * 2;
|
|
34
|
+
|
|
35
|
+
const scaleX = availableWidth / elementWidth;
|
|
36
|
+
const scaleY = availableHeight / elementHeight;
|
|
37
|
+
|
|
38
|
+
return Math.min(scaleX, scaleY, 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* VariantThumbnail - Renders a preview of a specific variant
|
|
43
|
+
*/
|
|
44
|
+
function VariantThumbnail({
|
|
45
|
+
variant,
|
|
46
|
+
componentId,
|
|
47
|
+
}: {
|
|
48
|
+
variant: VariantDefinition;
|
|
49
|
+
componentId: string;
|
|
50
|
+
}) {
|
|
51
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
const [isReady, setIsReady] = useState(false);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!containerRef.current || !variant.rootElement) return;
|
|
56
|
+
|
|
57
|
+
setIsReady(false);
|
|
58
|
+
const container = containerRef.current;
|
|
59
|
+
container.innerHTML = '';
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const domElement = createDOMElement(variant.rootElement);
|
|
63
|
+
|
|
64
|
+
const wrapper = document.createElement('div');
|
|
65
|
+
wrapper.style.cssText = `
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
width: 100%;
|
|
70
|
+
height: 100%;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
pointer-events: none;
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const scaleContainer = document.createElement('div');
|
|
76
|
+
scaleContainer.style.cssText = `
|
|
77
|
+
transform-origin: center center;
|
|
78
|
+
transition: transform 0.15s ease;
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
domElement.style.position = 'relative';
|
|
82
|
+
domElement.style.left = 'auto';
|
|
83
|
+
domElement.style.top = 'auto';
|
|
84
|
+
domElement.style.transform = 'none';
|
|
85
|
+
domElement.style.margin = '0';
|
|
86
|
+
|
|
87
|
+
scaleContainer.appendChild(domElement);
|
|
88
|
+
wrapper.appendChild(scaleContainer);
|
|
89
|
+
container.appendChild(wrapper);
|
|
90
|
+
|
|
91
|
+
requestAnimationFrame(() => {
|
|
92
|
+
const containerRect = container.getBoundingClientRect();
|
|
93
|
+
const elementRect = domElement.getBoundingClientRect();
|
|
94
|
+
|
|
95
|
+
if (elementRect.width > 0 && elementRect.height > 0) {
|
|
96
|
+
const scale = calculateScale(
|
|
97
|
+
elementRect.width,
|
|
98
|
+
elementRect.height,
|
|
99
|
+
containerRect.width,
|
|
100
|
+
containerRect.height,
|
|
101
|
+
2
|
|
102
|
+
);
|
|
103
|
+
scaleContainer.style.transform = `scale(${scale})`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setIsReady(true);
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('[VariantThumbnail] Error:', error);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
if (container) container.innerHTML = '';
|
|
114
|
+
};
|
|
115
|
+
}, [variant, componentId]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
ref={containerRef}
|
|
120
|
+
className={`w-full h-full bg-white transition-opacity duration-200 ${
|
|
121
|
+
isReady ? 'opacity-100' : 'opacity-0'
|
|
122
|
+
}`}
|
|
123
|
+
style={{
|
|
124
|
+
background: 'linear-gradient(45deg, #f8f8f8 25%, transparent 25%), linear-gradient(-45deg, #f8f8f8 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f8f8f8 75%), linear-gradient(-45deg, transparent 75%, #f8f8f8 75%)',
|
|
125
|
+
backgroundSize: '6px 6px',
|
|
126
|
+
backgroundPosition: '0 0, 0 3px, 3px -3px, -3px 0px',
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* VariantItem - Single variant item with drag support
|
|
134
|
+
*/
|
|
135
|
+
function VariantItem({
|
|
136
|
+
component,
|
|
137
|
+
variant,
|
|
138
|
+
isDefault,
|
|
139
|
+
}: {
|
|
140
|
+
component: MasterComponent;
|
|
141
|
+
variant: VariantDefinition;
|
|
142
|
+
isDefault: boolean;
|
|
143
|
+
}) {
|
|
144
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
145
|
+
|
|
146
|
+
const handleDragStart = useCallback(
|
|
147
|
+
(e: React.DragEvent<HTMLDivElement>) => {
|
|
148
|
+
setIsDragging(true);
|
|
149
|
+
|
|
150
|
+
e.dataTransfer.setData(
|
|
151
|
+
'application/x-editor-component',
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
type: 'component',
|
|
154
|
+
componentId: component.id,
|
|
155
|
+
componentName: component.name,
|
|
156
|
+
variantId: variant.id,
|
|
157
|
+
variantName: variant.name,
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
161
|
+
|
|
162
|
+
// Custom drag image
|
|
163
|
+
const dragImage = document.createElement('div');
|
|
164
|
+
dragImage.className =
|
|
165
|
+
'bg-[#2c2c2c] border border-purple-500 rounded px-2 py-1 text-xs text-white shadow-lg';
|
|
166
|
+
dragImage.textContent = `${component.name} - ${variant.name}`;
|
|
167
|
+
dragImage.style.position = 'absolute';
|
|
168
|
+
dragImage.style.top = '-1000px';
|
|
169
|
+
document.body.appendChild(dragImage);
|
|
170
|
+
e.dataTransfer.setDragImage(dragImage, 0, 0);
|
|
171
|
+
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
document.body.removeChild(dragImage);
|
|
174
|
+
}, 0);
|
|
175
|
+
},
|
|
176
|
+
[component, variant]
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const handleDragEnd = useCallback(() => {
|
|
180
|
+
setIsDragging(false);
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
// Get variant property values display
|
|
184
|
+
const propertyDisplay = variant.propertyValues
|
|
185
|
+
? Object.entries(variant.propertyValues)
|
|
186
|
+
.map(([key, value]) => `${value}`)
|
|
187
|
+
.join(', ')
|
|
188
|
+
: null;
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div
|
|
192
|
+
className={`
|
|
193
|
+
flex items-center gap-2 p-1.5 rounded border cursor-grab
|
|
194
|
+
transition-all duration-150
|
|
195
|
+
${isDragging
|
|
196
|
+
? 'opacity-50 border-purple-500 bg-purple-500/10'
|
|
197
|
+
: 'border-[#444444] bg-[#2a2a2a] hover:border-gray-500 hover:bg-[#333]'
|
|
198
|
+
}
|
|
199
|
+
`}
|
|
200
|
+
draggable
|
|
201
|
+
onDragStart={handleDragStart}
|
|
202
|
+
onDragEnd={handleDragEnd}
|
|
203
|
+
title={`${variant.name}${variant.description ? `: ${variant.description}` : ''}`}
|
|
204
|
+
>
|
|
205
|
+
{/* Thumbnail */}
|
|
206
|
+
<div className="w-10 h-8 rounded overflow-hidden flex-shrink-0 border border-[#444444]">
|
|
207
|
+
<VariantThumbnail variant={variant} componentId={component.id} />
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Info */}
|
|
211
|
+
<div className="flex-1 min-w-0">
|
|
212
|
+
<div className="flex items-center gap-1">
|
|
213
|
+
<span className="text-[9px] text-gray-300 truncate">
|
|
214
|
+
{variant.name}
|
|
215
|
+
</span>
|
|
216
|
+
{isDefault && (
|
|
217
|
+
<span className="text-[7px] text-[#4fb8ff] bg-[#0d99ff]/20 px-1 rounded">
|
|
218
|
+
デフォルト
|
|
219
|
+
</span>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
{propertyDisplay && (
|
|
223
|
+
<p className="text-[8px] text-gray-500 truncate">
|
|
224
|
+
{propertyDisplay}
|
|
225
|
+
</p>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* VariantList displays all variants of a component
|
|
234
|
+
*/
|
|
235
|
+
export function VariantList({ component, isExpanded }: VariantListProps) {
|
|
236
|
+
if (!isExpanded || component.variants.length <= 1) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div className="mt-1 space-y-1 animate-in fade-in slide-in-from-top-1 duration-200">
|
|
242
|
+
<div className="flex items-center gap-1 px-1 text-[9px] text-gray-500">
|
|
243
|
+
<Layers className="w-3 h-3" />
|
|
244
|
+
<span>バリアント</span>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="space-y-1">
|
|
247
|
+
{component.variants.map((variant) => (
|
|
248
|
+
<VariantItem
|
|
249
|
+
key={variant.id}
|
|
250
|
+
component={component}
|
|
251
|
+
variant={variant}
|
|
252
|
+
isDefault={variant.id === component.defaultVariantId}
|
|
253
|
+
/>
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export default VariantList;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Panel exports
|
|
3
|
+
*
|
|
4
|
+
* Provides a Figma-like component panel for the frontend editor.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { ComponentPanel, default } from './ComponentPanel';
|
|
8
|
+
export { ComponentSearch } from './ComponentSearch';
|
|
9
|
+
export { ComponentGrid } from './ComponentGrid';
|
|
10
|
+
export { ComponentItem } from './ComponentItem';
|
|
11
|
+
export { CategoryAccordion } from './CategoryAccordion';
|
|
12
|
+
export { VariantList } from './VariantList';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* エディタコンポーネントのエクスポート
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { EditorHeader } from './EditorHeader';
|
|
6
|
+
export { EditorCanvas } from './EditorCanvas';
|
|
7
|
+
export { EditorLayerPanel } from './EditorLayerPanel';
|
|
8
|
+
export { EditorPropertyPanel } from './EditorPropertyPanel';
|
|
9
|
+
export { EditorFooter } from './EditorFooter';
|
|
10
|
+
export { BreakpointSelector } from './BreakpointSelector';
|
|
11
|
+
export { BreakpointGuides } from './BreakpointGuides';
|
|
12
|
+
export { FigmaColorPicker, fillConfigToCss, parseCssToFillConfig } from './FigmaColorPicker';
|
|
13
|
+
export type { FillConfig, FillType, GradientStop } from './FigmaColorPicker';
|
|
14
|
+
export { EditorContextMenu } from './EditorContextMenu';
|
|
15
|
+
export type { ContextMenuPosition, ContextMenuProps } from './EditorContextMenu';
|
|
16
|
+
export { AiPromptPopover } from './AiPromptPopover';
|
|
17
|
+
export type { AiPromptPopoverProps } from './AiPromptPopover';
|
|
18
|
+
export { HtmlEditorDialog } from './HtmlEditorDialog';
|
|
19
|
+
export { HtmlImportDialog } from './HtmlImportDialog';
|
|
20
|
+
export type { HtmlImportDialogProps, HtmlImportResult } from './HtmlImportDialog';
|
|
21
|
+
export { CssEditorDialog } from './CssEditorDialog';
|
|
22
|
+
export type { CssEditorDialogProps } from './CssEditorDialog';
|
|
23
|
+
export { JsEditorDialog } from './JsEditorDialog';
|
|
24
|
+
export type { JsEditorDialogProps } from './JsEditorDialog';
|
|
25
|
+
export { PageSettingsDialog } from './PageSettingsDialog';
|
|
26
|
+
export type { PageSettingsDialogProps } from './PageSettingsDialog';
|
|
27
|
+
export { MediaLibraryDialog } from './MediaLibraryDialog';
|
|
28
|
+
export type { MediaLibraryDialogProps, MediaLibraryMode } from './MediaLibraryDialog';
|
|
29
|
+
export { VariablesDialog } from './VariablesDialog';
|
|
30
|
+
export { VariablePicker, VariableLinkButton } from './VariablePicker';
|
|
31
|
+
export { VariablesPanel } from './variables-panel';
|
|
32
|
+
export { ComponentPanel } from './component-panel';
|
|
33
|
+
export { MasterComponentEditor } from './MasterComponentEditor';
|
|
34
|
+
// 旧サイドバー(互換性のため残す)
|
|
35
|
+
export { EditorSidebar } from './EditorSidebar';
|
|
36
|
+
|
|
37
|
+
/** @deprecated Use EditorHeader instead */
|
|
38
|
+
export { EditorHeader as SlideEditorHeader } from './EditorHeader';
|
|
39
|
+
/** @deprecated Use EditorCanvas instead */
|
|
40
|
+
export { EditorCanvas as SlideEditorCanvas } from './EditorCanvas';
|
|
41
|
+
/** @deprecated Use EditorLayerPanel instead */
|
|
42
|
+
export { EditorLayerPanel as SlideEditorLayerPanel } from './EditorLayerPanel';
|
|
43
|
+
/** @deprecated Use EditorPropertyPanel instead */
|
|
44
|
+
export { EditorPropertyPanel as SlideEditorPropertyPanel } from './EditorPropertyPanel';
|
|
45
|
+
/** @deprecated Use EditorFooter instead */
|
|
46
|
+
export { EditorFooter as SlideEditorFooter } from './EditorFooter';
|
|
47
|
+
/** @deprecated Use EditorSidebar instead */
|
|
48
|
+
export { EditorSidebar as SlideEditorSidebar } from './EditorSidebar';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo, useCallback, useRef } from 'react';
|
|
4
|
+
import { Minus, Plus, Maximize } from 'lucide-react';
|
|
5
|
+
import { Button } from '../../../components/ui/button';
|
|
6
|
+
import { useMultiPageCanvas } from '../../contexts/MultiPageCanvasContext';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ズームコントロール(右下固定)
|
|
10
|
+
* - ズームイン/アウトボタン
|
|
11
|
+
* - ズーム値表示
|
|
12
|
+
* - フィットボタン (全ページを表示)
|
|
13
|
+
*/
|
|
14
|
+
export const CanvasZoomControls = memo(function CanvasZoomControls() {
|
|
15
|
+
const { viewState, zoomIn, zoomOut, zoomToFit } = useMultiPageCanvas();
|
|
16
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
17
|
+
|
|
18
|
+
const handleFitToView = useCallback(() => {
|
|
19
|
+
// 親のInfiniteCanvasコンテナサイズを取得
|
|
20
|
+
const canvas = containerRef.current?.closest('[data-infinite-canvas]');
|
|
21
|
+
if (canvas) {
|
|
22
|
+
const rect = canvas.getBoundingClientRect();
|
|
23
|
+
zoomToFit(rect.width, rect.height);
|
|
24
|
+
}
|
|
25
|
+
}, [zoomToFit]);
|
|
26
|
+
|
|
27
|
+
const zoomPercent = Math.round(viewState.canvasZoom * 100);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
ref={containerRef}
|
|
32
|
+
className="absolute bottom-4 right-4 z-10 flex items-center gap-1 bg-[#2a2a2a] border border-[#404040] rounded-lg px-1 py-1 shadow-lg"
|
|
33
|
+
>
|
|
34
|
+
<Button
|
|
35
|
+
variant="ghost"
|
|
36
|
+
size="sm"
|
|
37
|
+
className="h-7 w-7 p-0 text-gray-400 hover:text-white hover:bg-[#404040]"
|
|
38
|
+
onClick={zoomOut}
|
|
39
|
+
title="ズームアウト"
|
|
40
|
+
>
|
|
41
|
+
<Minus className="w-3.5 h-3.5" />
|
|
42
|
+
</Button>
|
|
43
|
+
|
|
44
|
+
<span className="text-xs text-gray-300 min-w-[40px] text-center tabular-nums select-none">
|
|
45
|
+
{zoomPercent}%
|
|
46
|
+
</span>
|
|
47
|
+
|
|
48
|
+
<Button
|
|
49
|
+
variant="ghost"
|
|
50
|
+
size="sm"
|
|
51
|
+
className="h-7 w-7 p-0 text-gray-400 hover:text-white hover:bg-[#404040]"
|
|
52
|
+
onClick={zoomIn}
|
|
53
|
+
title="ズームイン"
|
|
54
|
+
>
|
|
55
|
+
<Plus className="w-3.5 h-3.5" />
|
|
56
|
+
</Button>
|
|
57
|
+
|
|
58
|
+
<div className="w-px h-4 bg-[#404040] mx-0.5" />
|
|
59
|
+
|
|
60
|
+
<Button
|
|
61
|
+
variant="ghost"
|
|
62
|
+
size="sm"
|
|
63
|
+
className="h-7 w-7 p-0 text-gray-400 hover:text-white hover:bg-[#404040]"
|
|
64
|
+
onClick={handleFitToView}
|
|
65
|
+
title="全体にフィット (Ctrl+0)"
|
|
66
|
+
>
|
|
67
|
+
<Maximize className="w-3.5 h-3.5" />
|
|
68
|
+
</Button>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { useMultiPageCanvas } from '../../contexts/MultiPageCanvasContext';
|
|
5
|
+
import { useInfiniteCanvas } from '../../hooks/useInfiniteCanvas';
|
|
6
|
+
import { PageFrameOverlay } from './PageFrameOverlay';
|
|
7
|
+
import { CanvasZoomControls } from './CanvasZoomControls';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* InfiniteCanvas: 無限キャンバスコンテナ(レガシー)
|
|
11
|
+
*
|
|
12
|
+
* 全ページをフレームとして表示し、CSS transformでズーム/パンを実現。
|
|
13
|
+
* Note: マルチページ統合ビューはMultiPageCanvasViewを使用。
|
|
14
|
+
* このコンポーネントはサムネイル表示のみ(編集iframe含まず)。
|
|
15
|
+
*/
|
|
16
|
+
export const InfiniteCanvas = memo(function InfiniteCanvas() {
|
|
17
|
+
const {
|
|
18
|
+
viewState,
|
|
19
|
+
pages,
|
|
20
|
+
switchPage,
|
|
21
|
+
zoomToFit,
|
|
22
|
+
} = useMultiPageCanvas();
|
|
23
|
+
const { canvasOffset, canvasZoom, activePageId } = viewState;
|
|
24
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
|
|
26
|
+
// 無限キャンバスのズーム/パン操作
|
|
27
|
+
useInfiniteCanvas(containerRef);
|
|
28
|
+
|
|
29
|
+
// 初回表示時にフィットズーム
|
|
30
|
+
const hasInitialFitRef = useRef(false);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (hasInitialFitRef.current || pages.length === 0) return;
|
|
33
|
+
const container = containerRef.current;
|
|
34
|
+
if (!container) return;
|
|
35
|
+
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
const rect = container.getBoundingClientRect();
|
|
38
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
39
|
+
zoomToFit(rect.width, rect.height);
|
|
40
|
+
hasInitialFitRef.current = true;
|
|
41
|
+
}
|
|
42
|
+
}, 100);
|
|
43
|
+
return () => clearTimeout(timer);
|
|
44
|
+
}, [pages.length, zoomToFit]);
|
|
45
|
+
|
|
46
|
+
const handlePageClick = useCallback((pageId: string) => {
|
|
47
|
+
switchPage(pageId);
|
|
48
|
+
}, [switchPage]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
ref={containerRef}
|
|
53
|
+
data-infinite-canvas="true"
|
|
54
|
+
className="absolute inset-0 overflow-hidden select-none"
|
|
55
|
+
style={{ backgroundColor: '#1a1a1a' }}
|
|
56
|
+
>
|
|
57
|
+
{/* ドットグリッド背景 */}
|
|
58
|
+
<div
|
|
59
|
+
className="absolute inset-0 pointer-events-none"
|
|
60
|
+
style={{
|
|
61
|
+
backgroundImage: `radial-gradient(circle, rgba(255,255,255,0.05) 1px, transparent 1px)`,
|
|
62
|
+
backgroundSize: `${24 * canvasZoom}px ${24 * canvasZoom}px`,
|
|
63
|
+
backgroundPosition: `${canvasOffset.x % (24 * canvasZoom)}px ${canvasOffset.y % (24 * canvasZoom)}px`,
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
{/* CSS transform による無限キャンバス */}
|
|
68
|
+
<div
|
|
69
|
+
className="canvas-transform-layer"
|
|
70
|
+
style={{
|
|
71
|
+
transform: `translate(${canvasOffset.x}px, ${canvasOffset.y}px) scale(${canvasZoom})`,
|
|
72
|
+
transformOrigin: '0 0',
|
|
73
|
+
willChange: 'transform',
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{pages.map(page => (
|
|
77
|
+
<PageFrameOverlay
|
|
78
|
+
key={page.id}
|
|
79
|
+
page={page}
|
|
80
|
+
isActive={page.id === activePageId}
|
|
81
|
+
onClick={handlePageClick}
|
|
82
|
+
/>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* ズームコントロール */}
|
|
87
|
+
<CanvasZoomControls />
|
|
88
|
+
|
|
89
|
+
{/* ページ数表示 */}
|
|
90
|
+
<div className="absolute top-4 left-4 z-10 text-xs text-gray-500 select-none">
|
|
91
|
+
{pages.length} pages
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
});
|