@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,524 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
import { Button } from "../../components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuSeparator,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from "../../components/ui/dropdown-menu";
|
|
12
|
+
import {
|
|
13
|
+
ZoomIn,
|
|
14
|
+
ZoomOut,
|
|
15
|
+
Maximize,
|
|
16
|
+
RotateCcw,
|
|
17
|
+
Import,
|
|
18
|
+
Code,
|
|
19
|
+
Braces,
|
|
20
|
+
Settings,
|
|
21
|
+
FileText,
|
|
22
|
+
FolderArchive,
|
|
23
|
+
ChevronDown,
|
|
24
|
+
MoreHorizontal,
|
|
25
|
+
MessageSquare,
|
|
26
|
+
} from "lucide-react";
|
|
27
|
+
import { useEditorContext } from "../EditorContext";
|
|
28
|
+
import { EditorTopBar } from "./shell/EditorTopBar";
|
|
29
|
+
import { MIN_ZOOM, MAX_ZOOM, editorZoomApiRef } from "../hooks/useCanvasControls";
|
|
30
|
+
import { generateEditableHtml } from "../utils/html-utils";
|
|
31
|
+
import { useDeck } from "../../components/viewer/useDeck";
|
|
32
|
+
import { unresolvedCount } from "./ppt/PptComments";
|
|
33
|
+
import {
|
|
34
|
+
convertToAbsolutePositioning,
|
|
35
|
+
buildDomTree,
|
|
36
|
+
getArtboardContent,
|
|
37
|
+
} from "../utils/dom-utils";
|
|
38
|
+
import { can } from "../../io";
|
|
39
|
+
|
|
40
|
+
interface EditorHeaderProps {
|
|
41
|
+
/** コメントパネルの開閉(Figma風にもPowerPoint同等のコメントを出す) */
|
|
42
|
+
comments?: { open: boolean; toggle: () => void; page: number };
|
|
43
|
+
/** PowerPoint風UIへの切り替え(任意) */
|
|
44
|
+
onSwitchUi?: () => void;
|
|
45
|
+
onSave: (html: string) => Promise<void>;
|
|
46
|
+
onSaveSettings?: () => Promise<void>;
|
|
47
|
+
onClose: () => void;
|
|
48
|
+
/**
|
|
49
|
+
* 自動保存の状態。エディタ本体(FrontendVisualEditor)が算出したものを表示するだけ。
|
|
50
|
+
* dirty=このあと自動保存される / saving=保存中 / saved=保存済み / error=自動保存に失敗
|
|
51
|
+
*/
|
|
52
|
+
saveStatus?: 'saved' | 'dirty' | 'saving' | 'error';
|
|
53
|
+
/** キャンバス編集モード: trueの場合、閉じるボタンを「キャンバスに戻る」に変更 */
|
|
54
|
+
isCanvasEditing?: boolean;
|
|
55
|
+
onImport?: () => void;
|
|
56
|
+
onCssEdit?: () => void;
|
|
57
|
+
hasCss?: boolean;
|
|
58
|
+
onJsEdit?: () => void;
|
|
59
|
+
hasJs?: boolean;
|
|
60
|
+
onPageSettings?: () => void;
|
|
61
|
+
hasPageSettings?: boolean;
|
|
62
|
+
onExport?: (format: 'html' | 'zip') => void;
|
|
63
|
+
/** [移植時の追加] 編集中スライドの番号(1始まり)。ビューアと同じ文脈表示に使う */
|
|
64
|
+
contextNumber?: number;
|
|
65
|
+
/** [移植時の追加] 編集中スライドのタイトル */
|
|
66
|
+
contextTitle?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function EditorHeader({
|
|
70
|
+
onSwitchUi,
|
|
71
|
+
onSave,
|
|
72
|
+
onSaveSettings,
|
|
73
|
+
onClose,
|
|
74
|
+
saveStatus = 'saved',
|
|
75
|
+
isCanvasEditing,
|
|
76
|
+
onImport,
|
|
77
|
+
onCssEdit,
|
|
78
|
+
hasCss,
|
|
79
|
+
onJsEdit,
|
|
80
|
+
hasJs,
|
|
81
|
+
onPageSettings,
|
|
82
|
+
hasPageSettings,
|
|
83
|
+
onExport,
|
|
84
|
+
contextNumber,
|
|
85
|
+
comments,
|
|
86
|
+
contextTitle,
|
|
87
|
+
}: EditorHeaderProps) {
|
|
88
|
+
const {
|
|
89
|
+
iframeRef,
|
|
90
|
+
originalHtml,
|
|
91
|
+
zoom,
|
|
92
|
+
setZoom,
|
|
93
|
+
fitZoom,
|
|
94
|
+
setSelectedElement,
|
|
95
|
+
setSelectedElementIds,
|
|
96
|
+
setHtml,
|
|
97
|
+
getIframeDoc,
|
|
98
|
+
layoutMode,
|
|
99
|
+
setLayoutMode,
|
|
100
|
+
notifyIframeChange,
|
|
101
|
+
setDomTree,
|
|
102
|
+
setExpandedNodes,
|
|
103
|
+
autoLayoutHtml,
|
|
104
|
+
setAutoLayoutHtml,
|
|
105
|
+
editorMode,
|
|
106
|
+
} = useEditorContext();
|
|
107
|
+
|
|
108
|
+
// 保存・共有・プレビュー・閉じるは共通トップバー(EditorTopBar)が持つ。
|
|
109
|
+
// ここに実装を残すと、PowerPoint風UIと2本の保存経路ができてしまう
|
|
110
|
+
|
|
111
|
+
// リセット処理
|
|
112
|
+
const handleReset = () => {
|
|
113
|
+
const iframe = iframeRef.current;
|
|
114
|
+
if (iframe) {
|
|
115
|
+
const blob = new Blob([generateEditableHtml(originalHtml, editorMode)], {
|
|
116
|
+
type: "text/html",
|
|
117
|
+
});
|
|
118
|
+
iframe.src = URL.createObjectURL(blob);
|
|
119
|
+
}
|
|
120
|
+
setHtml(originalHtml);
|
|
121
|
+
setSelectedElement(null);
|
|
122
|
+
setSelectedElementIds([]);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// レイアウトモード切替
|
|
126
|
+
const handleLayoutModeChange = (mode: "absolute" | "auto") => {
|
|
127
|
+
// Webページを絶対配置へ倒すと流し込みレイアウトが固定ピクセルに固まるため受け付けない
|
|
128
|
+
if (editorMode === "webpage" && mode === "absolute") return;
|
|
129
|
+
// キャンバスモードでは状態が実際の表示と不整合の場合があるため、
|
|
130
|
+
// 同じモードでもクリック時に強制変換する(isCanvasEditing時)
|
|
131
|
+
if (mode === layoutMode && !isCanvasEditing) return;
|
|
132
|
+
|
|
133
|
+
const iframeDoc = getIframeDoc();
|
|
134
|
+
if (!iframeDoc) return;
|
|
135
|
+
|
|
136
|
+
if (mode === "absolute") {
|
|
137
|
+
// オートレイアウト → 絶対配置: 変換前のHTMLを保存(リセット用)
|
|
138
|
+
setAutoLayoutHtml(getArtboardContent(iframeDoc));
|
|
139
|
+
|
|
140
|
+
// 全要素を絶対配置に変換
|
|
141
|
+
const count = convertToAbsolutePositioning(iframeDoc);
|
|
142
|
+
console.log(
|
|
143
|
+
"[Header] Converted",
|
|
144
|
+
count,
|
|
145
|
+
"elements to absolute positioning",
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// DOMツリーを再構築
|
|
149
|
+
const tree = buildDomTree(iframeDoc);
|
|
150
|
+
setDomTree(tree);
|
|
151
|
+
setExpandedNodes(new Set(tree.map((n) => n.id)));
|
|
152
|
+
|
|
153
|
+
// 変更を通知
|
|
154
|
+
notifyIframeChange();
|
|
155
|
+
} else if (mode === "auto") {
|
|
156
|
+
// [移植時の修正] 絶対配置 → オートレイアウト
|
|
157
|
+
// 元実装はインラインstyleを削るだけで、classベースのレイアウト
|
|
158
|
+
// (Tailwind等)が絶対配置変換で崩れたケースを復元できなかった。
|
|
159
|
+
// 変換前に保存した autoLayoutHtml があればそれを復元する。
|
|
160
|
+
if (autoLayoutHtml) {
|
|
161
|
+
const ok = window.confirm(
|
|
162
|
+
"オートレイアウトに戻します。\n絶対配置モードで加えた変更は破棄され、切り替え前の状態に復元されます。よろしいですか?",
|
|
163
|
+
);
|
|
164
|
+
if (!ok) return;
|
|
165
|
+
const artboard = iframeDoc.getElementById("artboard");
|
|
166
|
+
if (artboard) {
|
|
167
|
+
artboard.innerHTML = autoLayoutHtml;
|
|
168
|
+
setAutoLayoutHtml(null);
|
|
169
|
+
const restoredTree = buildDomTree(iframeDoc);
|
|
170
|
+
setDomTree(restoredTree);
|
|
171
|
+
setExpandedNodes(new Set(restoredTree.map((n) => n.id)));
|
|
172
|
+
setSelectedElement(null);
|
|
173
|
+
setSelectedElementIds([]);
|
|
174
|
+
notifyIframeChange();
|
|
175
|
+
setLayoutMode(mode);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// フォールバック: 変換前HTMLが無い場合は従来どおりインラインstyleを削除
|
|
181
|
+
let editableElements = iframeDoc.querySelectorAll(
|
|
182
|
+
'[data-editable="true"]',
|
|
183
|
+
);
|
|
184
|
+
// フォールバック: data-editableが無い場合は#artboard直下のdata-element-id要素
|
|
185
|
+
if (editableElements.length === 0) {
|
|
186
|
+
editableElements = iframeDoc.querySelectorAll(
|
|
187
|
+
'#artboard > [data-element-id]',
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
editableElements.forEach((el) => {
|
|
191
|
+
const element = el as HTMLElement;
|
|
192
|
+
// 絶対配置関連のスタイルを削除
|
|
193
|
+
element.style.removeProperty("position");
|
|
194
|
+
element.style.removeProperty("left");
|
|
195
|
+
element.style.removeProperty("top");
|
|
196
|
+
// width/heightは削除(autoに戻す)
|
|
197
|
+
element.style.removeProperty("width");
|
|
198
|
+
element.style.removeProperty("height");
|
|
199
|
+
element.style.removeProperty("margin");
|
|
200
|
+
element.style.removeProperty("flex");
|
|
201
|
+
element.style.removeProperty("flex-grow");
|
|
202
|
+
element.style.removeProperty("flex-shrink");
|
|
203
|
+
});
|
|
204
|
+
console.log(
|
|
205
|
+
"[Header] Removed absolute positioning from",
|
|
206
|
+
editableElements.length,
|
|
207
|
+
"elements",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// 保存したHTMLをクリア
|
|
211
|
+
setAutoLayoutHtml(null);
|
|
212
|
+
|
|
213
|
+
// DOMツリーを再構築
|
|
214
|
+
const tree = buildDomTree(iframeDoc);
|
|
215
|
+
setDomTree(tree);
|
|
216
|
+
setExpandedNodes(new Set(tree.map((n) => n.id)));
|
|
217
|
+
|
|
218
|
+
// 選択をクリア
|
|
219
|
+
setSelectedElement(null);
|
|
220
|
+
setSelectedElementIds([]);
|
|
221
|
+
|
|
222
|
+
// 変更を通知
|
|
223
|
+
notifyIframeChange();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setLayoutMode(mode);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// ズーム操作
|
|
230
|
+
//
|
|
231
|
+
// [移植時の修正] 刻みは従来どおり 10% のままにして、頭打ちの値だけ直す。
|
|
232
|
+
// 以前は上限200%/下限25%で止めていたが、キャンバス側(useCanvasControls)の
|
|
233
|
+
// 実際の可動域は 10〜400%。ボタンだけ手前で止まるため、ホイールで250%まで
|
|
234
|
+
// 拡大したあとに「+を押しても何も起きない」状態が生まれていた。
|
|
235
|
+
const clampZoom = (v: number) => Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, v));
|
|
236
|
+
const canZoomIn = zoom < MAX_ZOOM - 0.01;
|
|
237
|
+
const canZoomOut = zoom > MIN_ZOOM + 0.01;
|
|
238
|
+
|
|
239
|
+
const handleZoomIn = () => setZoom(clampZoom(Math.floor(zoom / 10) * 10 + 10));
|
|
240
|
+
const handleZoomOut = () => setZoom(clampZoom(Math.ceil(zoom / 10) * 10 - 10));
|
|
241
|
+
// 全体表示/100% はキャンバス側(useCanvasControls)の実装に委ねる。
|
|
242
|
+
// Cmd+0 / Cmd+1 と同じ関数を呼ぶことで、「ヘッダーから押したときだけ
|
|
243
|
+
// 固定点の扱いが違う」というズレを作らない。未登録時は従来どおり値を入れる。
|
|
244
|
+
const handleFitZoom = () => {
|
|
245
|
+
const api = editorZoomApiRef.current;
|
|
246
|
+
if (api) api.fit();
|
|
247
|
+
else setZoom(fitZoom);
|
|
248
|
+
};
|
|
249
|
+
const handleActualZoom = () => {
|
|
250
|
+
const api = editorZoomApiRef.current;
|
|
251
|
+
if (api) api.actual();
|
|
252
|
+
else setZoom(100);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// 倍率表示のメニュー
|
|
256
|
+
//
|
|
257
|
+
// [移植時の修正] ここは以前ただの <span> で、ダブルクリックしたときだけ
|
|
258
|
+
// 100% に戻るという隠し機能があるだけだった。倍率は「見るもの」ではなく
|
|
259
|
+
// 「決めるもの」なので、押せることが分かるボタンにして
|
|
260
|
+
// 全体表示 / 100% / 50% / 200% と直接入力をまとめて置く。
|
|
261
|
+
// 検証・自動操作から掴みやすいよう data-zoom-* を目印に付けている。
|
|
262
|
+
const [zoomMenuOpen, setZoomMenuOpen] = useState(false);
|
|
263
|
+
const [zoomInput, setZoomInput] = useState("");
|
|
264
|
+
const zoomInputRef = useRef<HTMLInputElement>(null);
|
|
265
|
+
|
|
266
|
+
const applyZoomInput = () => {
|
|
267
|
+
const parsed = parseFloat(zoomInput);
|
|
268
|
+
if (!Number.isFinite(parsed)) return;
|
|
269
|
+
setZoom(clampZoom(parsed));
|
|
270
|
+
setZoomMenuOpen(false);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const openZoomMenu = (open: boolean) => {
|
|
274
|
+
setZoomMenuOpen(open);
|
|
275
|
+
if (!open) return;
|
|
276
|
+
// 開いたときは現在値を初期値にする(そのまま Enter しても倍率が変わらない)
|
|
277
|
+
setZoomInput(String(Math.round(zoom)));
|
|
278
|
+
// Radix のメニューは開くと先頭項目にフォーカスを移すので、
|
|
279
|
+
// その後(2フレーム後)に数値入力へ移し替える。開いてすぐ数字を打てる状態にする。
|
|
280
|
+
requestAnimationFrame(() => {
|
|
281
|
+
requestAnimationFrame(() => {
|
|
282
|
+
zoomInputRef.current?.focus();
|
|
283
|
+
zoomInputRef.current?.select();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<EditorTopBar
|
|
290
|
+
variant="figma"
|
|
291
|
+
/* 一覧・PPT風UIはスライドのデッキが前提。Webページでは出さない */
|
|
292
|
+
showBackToList={editorMode !== "webpage"}
|
|
293
|
+
title={contextTitle}
|
|
294
|
+
pageNumber={contextNumber}
|
|
295
|
+
saveStatus={saveStatus}
|
|
296
|
+
onSave={onSave}
|
|
297
|
+
onSaveSettings={onSaveSettings}
|
|
298
|
+
onClose={onClose}
|
|
299
|
+
isCanvasEditing={isCanvasEditing}
|
|
300
|
+
onSwitchUi={editorMode === "webpage" ? undefined : onSwitchUi}
|
|
301
|
+
/* Figma風の固有機能: ズームコントロール */
|
|
302
|
+
rightExtra={
|
|
303
|
+
<div className="flex items-center gap-1">
|
|
304
|
+
{comments && can("commentAction") && (
|
|
305
|
+
<CommentToggleButton open={comments.open} page={comments.page} onToggle={comments.toggle} />
|
|
306
|
+
)}
|
|
307
|
+
{/* [移植時の修正] 押しても何も起きないボタンを作らないため、
|
|
308
|
+
可動域の端では disabled にして「これ以上は無い」ことを見た目で示す。
|
|
309
|
+
title にショートカットを併記して、キー操作の存在も分かるようにした。 */}
|
|
310
|
+
<Button
|
|
311
|
+
variant="ghost"
|
|
312
|
+
size="icon"
|
|
313
|
+
data-zoom-out
|
|
314
|
+
onClick={handleZoomOut}
|
|
315
|
+
disabled={!canZoomOut}
|
|
316
|
+
title={canZoomOut ? "縮小" : `これ以上縮小できません(下限 ${MIN_ZOOM}%)`}
|
|
317
|
+
className="h-7 w-7 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
|
|
318
|
+
>
|
|
319
|
+
<ZoomOut className="h-4 w-4" />
|
|
320
|
+
</Button>
|
|
321
|
+
{/* 倍率表示(押せる) */}
|
|
322
|
+
<DropdownMenu open={zoomMenuOpen} onOpenChange={openZoomMenu}>
|
|
323
|
+
<DropdownMenuTrigger asChild>
|
|
324
|
+
<button
|
|
325
|
+
data-zoom-menu
|
|
326
|
+
title="表示倍率(クリックでメニュー・⌘0 全体表示 / ⌘1 100% / ⌘2 選択範囲)"
|
|
327
|
+
className="flex h-7 min-w-[3.5rem] items-center justify-center gap-0.5 rounded-md text-xs tabular-nums text-gray-300 transition-colors hover:bg-[#444444] hover:text-white"
|
|
328
|
+
>
|
|
329
|
+
{Math.round(zoom)}%
|
|
330
|
+
<ChevronDown className="h-3 w-3 opacity-60" />
|
|
331
|
+
</button>
|
|
332
|
+
</DropdownMenuTrigger>
|
|
333
|
+
<DropdownMenuContent align="center" className="w-48 bg-[#2c2c2c] border-[#444444]">
|
|
334
|
+
{/* 数値入力。メニューのキー操作(先頭文字ジャンプ等)に数字を食われないよう
|
|
335
|
+
この階層で keydown を止める */}
|
|
336
|
+
<div
|
|
337
|
+
className="flex items-center gap-1 px-2 py-1.5"
|
|
338
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
339
|
+
>
|
|
340
|
+
<input
|
|
341
|
+
ref={zoomInputRef}
|
|
342
|
+
data-zoom-input
|
|
343
|
+
type="number"
|
|
344
|
+
min={MIN_ZOOM}
|
|
345
|
+
max={MAX_ZOOM}
|
|
346
|
+
value={zoomInput}
|
|
347
|
+
onChange={(e) => setZoomInput(e.target.value)}
|
|
348
|
+
onKeyDown={(e) => {
|
|
349
|
+
if (e.key === "Enter") {
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
applyZoomInput();
|
|
352
|
+
}
|
|
353
|
+
if (e.key === "Escape") setZoomMenuOpen(false);
|
|
354
|
+
}}
|
|
355
|
+
className="h-7 w-full rounded border border-[#4a4a4a] bg-[#1e1e1e] px-2 text-xs tabular-nums text-white outline-none focus:border-[#0d99ff]"
|
|
356
|
+
aria-label={`表示倍率(%) ${MIN_ZOOM}〜${MAX_ZOOM}`}
|
|
357
|
+
/>
|
|
358
|
+
<span className="text-xs text-gray-500">%</span>
|
|
359
|
+
<Button
|
|
360
|
+
variant="ghost"
|
|
361
|
+
size="sm"
|
|
362
|
+
data-zoom-apply
|
|
363
|
+
onClick={applyZoomInput}
|
|
364
|
+
className="h-7 px-2 text-xs text-gray-300 hover:bg-[#444444] hover:text-white"
|
|
365
|
+
>
|
|
366
|
+
適用
|
|
367
|
+
</Button>
|
|
368
|
+
</div>
|
|
369
|
+
<DropdownMenuSeparator className="bg-[#444444]" />
|
|
370
|
+
<DropdownMenuItem
|
|
371
|
+
data-zoom-preset="fit"
|
|
372
|
+
onClick={handleFitZoom}
|
|
373
|
+
className="cursor-pointer justify-between text-gray-300 hover:bg-[#444444] hover:text-white"
|
|
374
|
+
>
|
|
375
|
+
全体表示
|
|
376
|
+
<span className="text-[10px] text-gray-500">⌘0</span>
|
|
377
|
+
</DropdownMenuItem>
|
|
378
|
+
<DropdownMenuItem
|
|
379
|
+
data-zoom-preset="100"
|
|
380
|
+
onClick={handleActualZoom}
|
|
381
|
+
className="cursor-pointer justify-between text-gray-300 hover:bg-[#444444] hover:text-white"
|
|
382
|
+
>
|
|
383
|
+
100%
|
|
384
|
+
<span className="text-[10px] text-gray-500">⌘1</span>
|
|
385
|
+
</DropdownMenuItem>
|
|
386
|
+
<DropdownMenuItem
|
|
387
|
+
data-zoom-preset="50"
|
|
388
|
+
onClick={() => setZoom(50)}
|
|
389
|
+
className="cursor-pointer text-gray-300 hover:bg-[#444444] hover:text-white"
|
|
390
|
+
>
|
|
391
|
+
50%
|
|
392
|
+
</DropdownMenuItem>
|
|
393
|
+
<DropdownMenuItem
|
|
394
|
+
data-zoom-preset="200"
|
|
395
|
+
onClick={() => setZoom(200)}
|
|
396
|
+
className="cursor-pointer text-gray-300 hover:bg-[#444444] hover:text-white"
|
|
397
|
+
>
|
|
398
|
+
200%
|
|
399
|
+
</DropdownMenuItem>
|
|
400
|
+
</DropdownMenuContent>
|
|
401
|
+
</DropdownMenu>
|
|
402
|
+
<Button
|
|
403
|
+
variant="ghost"
|
|
404
|
+
size="icon"
|
|
405
|
+
data-zoom-in
|
|
406
|
+
onClick={handleZoomIn}
|
|
407
|
+
disabled={!canZoomIn}
|
|
408
|
+
title={canZoomIn ? "拡大" : `これ以上拡大できません(上限 ${MAX_ZOOM}%)`}
|
|
409
|
+
className="h-7 w-7 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
|
|
410
|
+
>
|
|
411
|
+
<ZoomIn className="h-4 w-4" />
|
|
412
|
+
</Button>
|
|
413
|
+
<Button
|
|
414
|
+
variant="ghost"
|
|
415
|
+
size="icon"
|
|
416
|
+
data-zoom-fit
|
|
417
|
+
onClick={handleFitZoom}
|
|
418
|
+
className="h-7 w-7 text-gray-400 hover:text-white hover:bg-[#444444]"
|
|
419
|
+
title="全体を表示(⌘0)"
|
|
420
|
+
>
|
|
421
|
+
<Maximize className="h-4 w-4" />
|
|
422
|
+
</Button>
|
|
423
|
+
<div className="mx-1 h-4 w-px bg-[#444444]" />
|
|
424
|
+
</div>
|
|
425
|
+
}
|
|
426
|
+
/*
|
|
427
|
+
[移植時の修正] リセット/インポート/CSS/JS/設定/エクスポートが常時並んでいて
|
|
428
|
+
ヘッダーが横に伸び、編集中スライド名の居場所が無くなっていた。
|
|
429
|
+
主要導線(保存・共有・閉じる)は共通トップバーが持ち、残りは「…」メニューへ畳む。
|
|
430
|
+
*/
|
|
431
|
+
menuExtra={
|
|
432
|
+
<DropdownMenu>
|
|
433
|
+
<DropdownMenuTrigger asChild>
|
|
434
|
+
<Button
|
|
435
|
+
variant="ghost"
|
|
436
|
+
size="icon"
|
|
437
|
+
data-header-more
|
|
438
|
+
className="h-7 w-7 text-gray-400 hover:bg-[#444444] hover:text-white"
|
|
439
|
+
title="その他の操作"
|
|
440
|
+
>
|
|
441
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
442
|
+
</Button>
|
|
443
|
+
</DropdownMenuTrigger>
|
|
444
|
+
<DropdownMenuContent align="end" className="w-52 bg-[#2c2c2c] border-[#444444]">
|
|
445
|
+
<DropdownMenuItem onClick={handleReset} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
|
|
446
|
+
<RotateCcw className="h-4 w-4" />
|
|
447
|
+
変更をリセット
|
|
448
|
+
</DropdownMenuItem>
|
|
449
|
+
{onImport && (
|
|
450
|
+
<DropdownMenuItem onClick={onImport} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
|
|
451
|
+
<Import className="h-4 w-4" />
|
|
452
|
+
インポート
|
|
453
|
+
</DropdownMenuItem>
|
|
454
|
+
)}
|
|
455
|
+
{(onCssEdit || onJsEdit || onPageSettings) && <DropdownMenuSeparator className="bg-[#444444]" />}
|
|
456
|
+
{onCssEdit && (
|
|
457
|
+
<DropdownMenuItem onClick={onCssEdit} className={`cursor-pointer gap-2 hover:bg-[#444444] hover:text-white ${hasCss ? 'text-[#4fb8ff]' : 'text-gray-300'}`}>
|
|
458
|
+
<Code className="h-4 w-4" />
|
|
459
|
+
{hasCss ? 'CSSを編集' : 'CSSを追加'}
|
|
460
|
+
</DropdownMenuItem>
|
|
461
|
+
)}
|
|
462
|
+
{onJsEdit && (
|
|
463
|
+
<DropdownMenuItem onClick={onJsEdit} className={`cursor-pointer gap-2 hover:bg-[#444444] hover:text-white ${hasJs ? 'text-yellow-400' : 'text-gray-300'}`}>
|
|
464
|
+
<Braces className="h-4 w-4" />
|
|
465
|
+
{hasJs ? 'JSを編集' : 'JSを追加'}
|
|
466
|
+
</DropdownMenuItem>
|
|
467
|
+
)}
|
|
468
|
+
{onPageSettings && (
|
|
469
|
+
<DropdownMenuItem onClick={onPageSettings} className={`cursor-pointer gap-2 hover:bg-[#444444] hover:text-white ${hasPageSettings ? 'text-green-400' : 'text-gray-300'}`}>
|
|
470
|
+
<Settings className="h-4 w-4" />
|
|
471
|
+
ページ設定
|
|
472
|
+
</DropdownMenuItem>
|
|
473
|
+
)}
|
|
474
|
+
{onExport && (
|
|
475
|
+
<>
|
|
476
|
+
<DropdownMenuSeparator className="bg-[#444444]" />
|
|
477
|
+
<DropdownMenuItem onClick={() => onExport('html')} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
|
|
478
|
+
<FileText className="h-4 w-4" />
|
|
479
|
+
HTMLで書き出し
|
|
480
|
+
</DropdownMenuItem>
|
|
481
|
+
<DropdownMenuItem onClick={() => onExport('zip')} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
|
|
482
|
+
<FolderArchive className="h-4 w-4" />
|
|
483
|
+
ZIPで書き出し(画像含む)
|
|
484
|
+
</DropdownMenuItem>
|
|
485
|
+
</>
|
|
486
|
+
)}
|
|
487
|
+
</DropdownMenuContent>
|
|
488
|
+
</DropdownMenu>
|
|
489
|
+
}
|
|
490
|
+
/>
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Figma風トップバーのコメントトグル。実機Figmaの吹き出しアイコンに相当し、
|
|
497
|
+
* 未解決コメント数をバッジで示す(PowerPoint風のコメントピルと同じ情報)。
|
|
498
|
+
*/
|
|
499
|
+
function CommentToggleButton({
|
|
500
|
+
open, page, onToggle,
|
|
501
|
+
}: {
|
|
502
|
+
open: boolean;
|
|
503
|
+
page: number;
|
|
504
|
+
onToggle: () => void;
|
|
505
|
+
}) {
|
|
506
|
+
const deck = useDeck();
|
|
507
|
+
const count = unresolvedCount(deck.slides[page - 1]?.comments);
|
|
508
|
+
return (
|
|
509
|
+
<Button
|
|
510
|
+
variant="ghost"
|
|
511
|
+
size="icon"
|
|
512
|
+
onClick={onToggle}
|
|
513
|
+
title="コメントパネルの表示/非表示"
|
|
514
|
+
className={`relative h-8 w-8 hover:bg-[#444444] ${open ? "text-[#4fb8ff]" : "text-gray-400 hover:text-white"}`}
|
|
515
|
+
>
|
|
516
|
+
<MessageSquare className="h-4 w-4" />
|
|
517
|
+
{count > 0 && (
|
|
518
|
+
<span className="absolute -right-0.5 -top-0.5 rounded-full bg-[#0d99ff] px-1 text-[9px] font-bold leading-[14px] text-white">
|
|
519
|
+
{count}
|
|
520
|
+
</span>
|
|
521
|
+
)}
|
|
522
|
+
</Button>
|
|
523
|
+
);
|
|
524
|
+
}
|