@growgroup/visual-editor 0.1.6 → 0.2.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 +59 -2
- package/dist/editor.css +1 -1
- package/package.json +7 -3
- package/src/components/ui/alert-dialog.tsx +18 -4
- package/src/components/ui/context-menu.tsx +18 -4
- package/src/components/ui/dialog.tsx +19 -5
- package/src/components/ui/dropdown-menu.tsx +18 -4
- package/src/components/ui/popover.tsx +10 -2
- package/src/components/ui/select.tsx +10 -2
- package/src/components/ui/tooltip.tsx +10 -2
- package/src/editor/EditorToolbar.tsx +10 -20
- package/src/editor/FrontendVisualEditor.tsx +157 -43
- package/src/editor/components/EditorCanvas.tsx +1 -1
- package/src/editor/components/EditorContextMenu.tsx +53 -71
- package/src/editor/components/EditorFooter.tsx +36 -70
- package/src/editor/components/EditorHeader.tsx +37 -20
- package/src/editor/components/EditorLayerPanel.tsx +7 -9
- package/src/editor/components/EditorPropertyPanel.tsx +84 -8
- package/src/editor/components/LayerTreeItem.tsx +28 -5
- package/src/editor/components/component-panel/ComponentPanel.tsx +4 -3
- package/src/editor/components/ppt/PptChrome.tsx +156 -344
- package/src/editor/components/ppt/PptComments.tsx +135 -87
- package/src/editor/components/ppt/PptDesignProposals.tsx +21 -26
- package/src/editor/components/ppt/PptNotes.tsx +15 -15
- package/src/editor/components/shell/CanvasAppearance.tsx +19 -0
- package/src/editor/components/shell/ConfirmDialog.tsx +15 -0
- package/src/editor/components/shell/EditorTopBar.tsx +29 -68
- package/src/editor/components/shell/LeftPanel.tsx +20 -94
- package/src/editor/components/shell/PagesPanel.tsx +4 -2
- package/src/editor/contexts/EditorAppearanceContext.tsx +44 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +278 -16
- package/src/editor/editor-skin.css +2 -122
- package/src/editor/hooks/useComponentEditMode.ts +95 -1
- package/src/editor/hooks/useIframeSetup.ts +10 -0
- package/src/editor/hooks/useMediaLibrary.ts +4 -5
- package/src/editor/hooks/useResizablePanel.ts +40 -11
- package/src/editor/parts.ts +321 -0
- package/src/editor/utils/dom-utils.ts +5 -4
- package/src/index.ts +25 -0
- package/src/io.ts +54 -0
- package/src/styles/skin.css +159 -0
- package/src/types/editor-components.ts +23 -1
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* 右クリックで要素操作メニューを表示
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../components/ui/dropdown-menu';
|
|
9
|
+
import { useEditorContext } from '../EditorContext';
|
|
9
10
|
import {
|
|
10
11
|
Copy,
|
|
11
12
|
Scissors,
|
|
@@ -56,6 +57,12 @@ export interface ContextMenuProps {
|
|
|
56
57
|
// コンポーネント状態
|
|
57
58
|
isComponentInstance?: boolean;
|
|
58
59
|
hasOverrides?: boolean;
|
|
60
|
+
// 部品(HTML template)状態
|
|
61
|
+
/** io.loadParts が渡されている(「コンポーネントを作成」が「部品として保存」になる) */
|
|
62
|
+
partsMode?: boolean;
|
|
63
|
+
isPartInstance?: boolean;
|
|
64
|
+
/** 「名前 vN」 */
|
|
65
|
+
partLabel?: string;
|
|
59
66
|
// アクション
|
|
60
67
|
/** 画像の変更(選択が<img>のときのみ渡される) */
|
|
61
68
|
onReplaceImageFromFile?: () => void;
|
|
@@ -93,6 +100,9 @@ export interface ContextMenuProps {
|
|
|
93
100
|
onDetachInstance?: () => void;
|
|
94
101
|
onResetOverrides?: () => void;
|
|
95
102
|
onPushOverridesToMain?: () => void;
|
|
103
|
+
// 部品操作
|
|
104
|
+
onDetachPart?: () => void;
|
|
105
|
+
onUpdatePart?: () => void;
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
interface MenuItemProps {
|
|
@@ -106,7 +116,7 @@ interface MenuItemProps {
|
|
|
106
116
|
|
|
107
117
|
function MenuItem({ icon, label, shortcut, onClick, disabled, danger }: MenuItemProps) {
|
|
108
118
|
return (
|
|
109
|
-
<
|
|
119
|
+
<DropdownMenuItem
|
|
110
120
|
className={`
|
|
111
121
|
w-full flex items-center gap-2 px-3 py-1.5 text-xs
|
|
112
122
|
${disabled
|
|
@@ -117,7 +127,7 @@ function MenuItem({ icon, label, shortcut, onClick, disabled, danger }: MenuItem
|
|
|
117
127
|
}
|
|
118
128
|
transition-colors
|
|
119
129
|
`}
|
|
120
|
-
|
|
130
|
+
onSelect={(e) => {
|
|
121
131
|
e.stopPropagation();
|
|
122
132
|
if (!disabled && onClick) onClick();
|
|
123
133
|
}}
|
|
@@ -128,7 +138,7 @@ function MenuItem({ icon, label, shortcut, onClick, disabled, danger }: MenuItem
|
|
|
128
138
|
{shortcut && (
|
|
129
139
|
<span className="text-[10px] text-gray-500">{shortcut}</span>
|
|
130
140
|
)}
|
|
131
|
-
</
|
|
141
|
+
</DropdownMenuItem>
|
|
132
142
|
);
|
|
133
143
|
}
|
|
134
144
|
|
|
@@ -145,6 +155,11 @@ export function EditorContextMenu({
|
|
|
145
155
|
hasStyleInClipboard,
|
|
146
156
|
isComponentInstance,
|
|
147
157
|
hasOverrides,
|
|
158
|
+
partsMode,
|
|
159
|
+
isPartInstance,
|
|
160
|
+
partLabel,
|
|
161
|
+
onDetachPart,
|
|
162
|
+
onUpdatePart,
|
|
148
163
|
onCopy,
|
|
149
164
|
onCut,
|
|
150
165
|
onPaste,
|
|
@@ -176,63 +191,7 @@ export function EditorContextMenu({
|
|
|
176
191
|
onResetOverrides,
|
|
177
192
|
onPushOverridesToMain,
|
|
178
193
|
}: ContextMenuProps) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
// メニュー外クリックで閉じる
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
if (!position) return;
|
|
184
|
-
|
|
185
|
-
const handleClickOutside = (e: MouseEvent) => {
|
|
186
|
-
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
187
|
-
onClose();
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
192
|
-
if (e.key === 'Escape') {
|
|
193
|
-
onClose();
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// 次のフレームでリスナーを追加(右クリックイベントの完了を待つ)
|
|
198
|
-
requestAnimationFrame(() => {
|
|
199
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
200
|
-
document.addEventListener('keydown', handleEscape);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
return () => {
|
|
204
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
205
|
-
document.removeEventListener('keydown', handleEscape);
|
|
206
|
-
};
|
|
207
|
-
}, [position, onClose]);
|
|
208
|
-
|
|
209
|
-
// メニュー位置の調整(画面外にはみ出さないように)
|
|
210
|
-
const getMenuStyle = useCallback(() => {
|
|
211
|
-
if (!position) return {};
|
|
212
|
-
|
|
213
|
-
const menuWidth = 200;
|
|
214
|
-
const menuHeight = 320;
|
|
215
|
-
const padding = 8;
|
|
216
|
-
|
|
217
|
-
let x = position.x;
|
|
218
|
-
let y = position.y;
|
|
219
|
-
|
|
220
|
-
// 右端チェック
|
|
221
|
-
if (x + menuWidth > window.innerWidth - padding) {
|
|
222
|
-
x = window.innerWidth - menuWidth - padding;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 下端チェック
|
|
226
|
-
if (y + menuHeight > window.innerHeight - padding) {
|
|
227
|
-
y = window.innerHeight - menuHeight - padding;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
left: `${x}px`,
|
|
232
|
-
top: `${y}px`,
|
|
233
|
-
};
|
|
234
|
-
}, [position]);
|
|
235
|
-
|
|
194
|
+
const { restoreFocus } = useEditorContext();
|
|
236
195
|
if (!position) return null;
|
|
237
196
|
|
|
238
197
|
const handleAction = (action?: () => void) => {
|
|
@@ -243,12 +202,12 @@ export function EditorContextMenu({
|
|
|
243
202
|
};
|
|
244
203
|
|
|
245
204
|
return (
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
205
|
+
<DropdownMenu open onOpenChange={(open) => { if (!open) onClose(); }}>
|
|
206
|
+
<DropdownMenuTrigger asChild>
|
|
207
|
+
<button tabIndex={-1} aria-label="要素の操作" style={{ position: 'fixed', left: position.x, top: position.y, width: 1, height: 1, opacity: 0, pointerEvents: 'none' }} />
|
|
208
|
+
</DropdownMenuTrigger>
|
|
209
|
+
<DropdownMenuContent align="start" sideOffset={0} collisionPadding={8} className="w-60"
|
|
210
|
+
onCloseAutoFocus={(e) => { e.preventDefault(); restoreFocus(); }}>
|
|
252
211
|
{/* クリップボード操作 */}
|
|
253
212
|
<MenuItem
|
|
254
213
|
icon={<Copy className="w-3.5 h-3.5" />}
|
|
@@ -471,11 +430,33 @@ export function EditorContextMenu({
|
|
|
471
430
|
{hasSelection && selectionCount === 1 && (
|
|
472
431
|
<>
|
|
473
432
|
<MenuDivider />
|
|
474
|
-
{/*
|
|
475
|
-
{
|
|
433
|
+
{/* 部品(data-part)のインスタンス: 更新・切り離し */}
|
|
434
|
+
{isPartInstance && (
|
|
435
|
+
<>
|
|
436
|
+
<MenuItem
|
|
437
|
+
icon={<Component className="w-3.5 h-3.5 text-purple-400" />}
|
|
438
|
+
label={`部品: ${partLabel ?? ''}`}
|
|
439
|
+
disabled
|
|
440
|
+
/>
|
|
441
|
+
{onUpdatePart && (
|
|
442
|
+
<MenuItem
|
|
443
|
+
icon={<ArrowUpFromLine className="w-3.5 h-3.5" />}
|
|
444
|
+
label="この姿で部品を更新"
|
|
445
|
+
onClick={() => handleAction(onUpdatePart)}
|
|
446
|
+
/>
|
|
447
|
+
)}
|
|
448
|
+
<MenuItem
|
|
449
|
+
icon={<Unlink className="w-3.5 h-3.5" />}
|
|
450
|
+
label="部品から切り離す"
|
|
451
|
+
onClick={() => handleAction(onDetachPart)}
|
|
452
|
+
/>
|
|
453
|
+
</>
|
|
454
|
+
)}
|
|
455
|
+
{/* 通常要素の場合: コンポーネント作成(部品モードでは「部品として保存」) */}
|
|
456
|
+
{!isComponentInstance && !isPartInstance && (
|
|
476
457
|
<MenuItem
|
|
477
458
|
icon={<Component className="w-3.5 h-3.5 text-purple-400" />}
|
|
478
|
-
label=
|
|
459
|
+
label={partsMode ? '部品として保存' : 'コンポーネントを作成'}
|
|
479
460
|
onClick={() => handleAction(onCreateComponent)}
|
|
480
461
|
/>
|
|
481
462
|
)}
|
|
@@ -510,6 +491,7 @@ export function EditorContextMenu({
|
|
|
510
491
|
)}
|
|
511
492
|
</>
|
|
512
493
|
)}
|
|
513
|
-
|
|
494
|
+
</DropdownMenuContent>
|
|
495
|
+
</DropdownMenu>
|
|
514
496
|
);
|
|
515
497
|
}
|
|
@@ -1,80 +1,46 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { Keyboard } from 'lucide-react';
|
|
3
4
|
import { useEditorContext } from '../EditorContext';
|
|
4
5
|
import { BreakpointSelector } from './BreakpointSelector';
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
|
5
7
|
|
|
6
|
-
/**
|
|
7
|
-
* エディタのフッター(ショートカットヘルプ表示 + ブレイクポイントセレクター)
|
|
8
|
-
*/
|
|
9
8
|
export function EditorFooter() {
|
|
10
|
-
const { activeTool, editorMode } = useEditorContext();
|
|
11
|
-
|
|
9
|
+
const { activeTool, editorMode, selectedElement, selectedElementIds } = useEditorContext();
|
|
10
|
+
const cmd = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform) ? '⌘' : 'Ctrl+';
|
|
11
|
+
const hint = activeTool === 'move' ? 'ドラッグで紙面を移動 · V で選択に戻る'
|
|
12
|
+
: activeTool === 'scale' ? 'ドラッグで拡大・縮小 · V で選択に戻る'
|
|
13
|
+
: activeTool !== 'select' ? 'ドラッグで追加 · Esc でキャンセル'
|
|
14
|
+
: selectedElement || selectedElementIds.length ? 'ダブルクリックで文字を編集 · Esc で選択を解除'
|
|
15
|
+
: 'クリックで選択 · Space+ドラッグで紙面を移動';
|
|
16
|
+
const shortcuts = [
|
|
17
|
+
['選択 / 移動 / 拡大・縮小', 'V / H / K'],
|
|
18
|
+
['元に戻す / やり直し', `${cmd}Z / ${cmd}⇧Z`],
|
|
19
|
+
['保存', `${cmd}S`], ['複製', `${cmd}D`],
|
|
20
|
+
['全体表示 / 100% / 選択範囲', `${cmd}0 / ${cmd}1 / ${cmd}2`],
|
|
21
|
+
['複数選択', 'Shift+クリック'], ['最下層の要素を選択', `${cmd}クリック`],
|
|
22
|
+
['選択・入力を終了', 'Esc'],
|
|
23
|
+
];
|
|
12
24
|
return (
|
|
13
|
-
<div className="
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
|
|
33
|
-
Esc
|
|
34
|
-
</kbd>{' '}
|
|
35
|
-
キャンセル
|
|
36
|
-
</span>
|
|
37
|
-
</>
|
|
38
|
-
) : (
|
|
39
|
-
<>
|
|
40
|
-
<span>
|
|
41
|
-
<kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
|
|
42
|
-
ドラッグ
|
|
43
|
-
</kbd>{' '}
|
|
44
|
-
描画
|
|
45
|
-
</span>
|
|
46
|
-
<span>
|
|
47
|
-
<kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
|
|
48
|
-
V
|
|
49
|
-
</kbd>{' '}
|
|
50
|
-
選択ツールに戻る
|
|
51
|
-
</span>
|
|
52
|
-
</>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
{/* 中央: ブレイクポイントセレクター(webpageモードのみ) */}
|
|
57
|
-
{editorMode === 'webpage' && (
|
|
58
|
-
<div className="flex items-center">
|
|
59
|
-
<BreakpointSelector />
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
|
-
|
|
63
|
-
{/* 右側: Undo/Redoショートカット */}
|
|
64
|
-
<div className="flex items-center gap-4">
|
|
65
|
-
<span>
|
|
66
|
-
<kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
|
|
67
|
-
⌘Z
|
|
68
|
-
</kbd>{' '}
|
|
69
|
-
元に戻す
|
|
70
|
-
</span>
|
|
71
|
-
<span>
|
|
72
|
-
<kbd className="px-1 py-0.5 bg-[#444444] rounded text-gray-400">
|
|
73
|
-
⌘⇧Z
|
|
74
|
-
</kbd>{' '}
|
|
75
|
-
やり直し
|
|
76
|
-
</span>
|
|
77
|
-
</div>
|
|
25
|
+
<div className="ed-footer">
|
|
26
|
+
<span className="ed-footer-hint">{hint}</span>
|
|
27
|
+
<div className="flex shrink-0 items-center gap-4">
|
|
28
|
+
{editorMode === 'webpage' && <BreakpointSelector />}
|
|
29
|
+
<Popover>
|
|
30
|
+
<PopoverTrigger asChild>
|
|
31
|
+
<button className="flex items-center gap-2 rounded px-2 py-1" aria-label="キーボードショートカット">
|
|
32
|
+
<Keyboard className="h-4 w-4" />ショートカット
|
|
33
|
+
</button>
|
|
34
|
+
</PopoverTrigger>
|
|
35
|
+
<PopoverContent side="top" align="end" className="w-[400px]">
|
|
36
|
+
<h2 className="mb-4 text-sm font-semibold">キーボードショートカット</h2>
|
|
37
|
+
<dl className="space-y-3">
|
|
38
|
+
{shortcuts.map(([label, key]) => <div key={label} className="flex items-center justify-between gap-4 text-xs">
|
|
39
|
+
<dt>{label}</dt><dd className="m-0"><kbd className="rounded bg-muted px-2 py-1">{key}</kbd></dd>
|
|
40
|
+
</div>)}
|
|
41
|
+
</dl>
|
|
42
|
+
</PopoverContent>
|
|
43
|
+
</Popover>
|
|
78
44
|
</div>
|
|
79
45
|
</div>
|
|
80
46
|
);
|
|
@@ -22,9 +22,10 @@ import {
|
|
|
22
22
|
FolderArchive,
|
|
23
23
|
ChevronDown,
|
|
24
24
|
MoreHorizontal,
|
|
25
|
-
MessageSquare,
|
|
25
|
+
MessageSquare, PanelLeft, Sun, Moon, Ruler,
|
|
26
26
|
} from "lucide-react";
|
|
27
27
|
import { useEditorContext } from "../EditorContext";
|
|
28
|
+
import { ConfirmDialog } from "./shell/ConfirmDialog";
|
|
28
29
|
import { EditorTopBar } from "./shell/EditorTopBar";
|
|
29
30
|
import { MIN_ZOOM, MAX_ZOOM, editorZoomApiRef } from "../hooks/useCanvasControls";
|
|
30
31
|
import { generateEditableHtml } from "../utils/html-utils";
|
|
@@ -38,6 +39,12 @@ import {
|
|
|
38
39
|
import { can } from "../../io";
|
|
39
40
|
|
|
40
41
|
interface EditorHeaderProps {
|
|
42
|
+
theme?: "light" | "dark";
|
|
43
|
+
onToggleTheme?: () => void;
|
|
44
|
+
layersOpen?: boolean;
|
|
45
|
+
onToggleLayers?: () => void;
|
|
46
|
+
guidesOpen?: boolean;
|
|
47
|
+
onToggleGuides?: () => void;
|
|
41
48
|
/** コメントパネルの開閉(Figma風にもPowerPoint同等のコメントを出す) */
|
|
42
49
|
comments?: { open: boolean; toggle: () => void; page: number };
|
|
43
50
|
/** PowerPoint風UIへの切り替え(任意) */
|
|
@@ -69,6 +76,7 @@ interface EditorHeaderProps {
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
export function EditorHeader({
|
|
79
|
+
theme, onToggleTheme, layersOpen, onToggleLayers, guidesOpen, onToggleGuides,
|
|
72
80
|
onSwitchUi,
|
|
73
81
|
onSave,
|
|
74
82
|
onSaveSettings,
|
|
@@ -108,6 +116,8 @@ export function EditorHeader({
|
|
|
108
116
|
editorMode,
|
|
109
117
|
} = useEditorContext();
|
|
110
118
|
|
|
119
|
+
const [resetOpen, setResetOpen] = useState(false);
|
|
120
|
+
|
|
111
121
|
// 保存・共有・プレビュー・閉じるは共通トップバー(EditorTopBar)が持つ。
|
|
112
122
|
// ここに実装を残すと、PowerPoint風UIと2本の保存経路ができてしまう
|
|
113
123
|
|
|
@@ -289,6 +299,7 @@ export function EditorHeader({
|
|
|
289
299
|
};
|
|
290
300
|
|
|
291
301
|
return (
|
|
302
|
+
<>
|
|
292
303
|
<EditorTopBar
|
|
293
304
|
variant="figma"
|
|
294
305
|
/* 一覧・PPT風UIはスライドのデッキが前提。Webページでは出さない */
|
|
@@ -302,7 +313,11 @@ export function EditorHeader({
|
|
|
302
313
|
isCanvasEditing={isCanvasEditing}
|
|
303
314
|
onSwitchUi={editorMode === "webpage" ? undefined : onSwitchUi}
|
|
304
315
|
/* 利用側の導線。一覧へ戻る/UI切替が出ない使い方では左端が空くので、そこに並ぶ */
|
|
305
|
-
leftExtra={headerExtra}
|
|
316
|
+
leftExtra={<>{headerExtra}{onToggleLayers && (
|
|
317
|
+
<button className="ed-button" onClick={onToggleLayers} aria-pressed={layersOpen} title="左パネルの表示を切り替え">
|
|
318
|
+
<PanelLeft className="h-4 w-4" />レイヤー
|
|
319
|
+
</button>
|
|
320
|
+
)}</>}
|
|
306
321
|
/* Figma風の固有機能: ズームコントロール */
|
|
307
322
|
rightExtra={
|
|
308
323
|
<div className="flex items-center gap-1">
|
|
@@ -319,7 +334,7 @@ export function EditorHeader({
|
|
|
319
334
|
onClick={handleZoomOut}
|
|
320
335
|
disabled={!canZoomOut}
|
|
321
336
|
title={canZoomOut ? "縮小" : `これ以上縮小できません(下限 ${MIN_ZOOM}%)`}
|
|
322
|
-
className="h-
|
|
337
|
+
className="h-8 w-8 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
|
|
323
338
|
>
|
|
324
339
|
<ZoomOut className="h-4 w-4" />
|
|
325
340
|
</Button>
|
|
@@ -411,7 +426,7 @@ export function EditorHeader({
|
|
|
411
426
|
onClick={handleZoomIn}
|
|
412
427
|
disabled={!canZoomIn}
|
|
413
428
|
title={canZoomIn ? "拡大" : `これ以上拡大できません(上限 ${MAX_ZOOM}%)`}
|
|
414
|
-
className="h-
|
|
429
|
+
className="h-8 w-8 text-gray-400 hover:text-white hover:bg-[#444444] disabled:opacity-30"
|
|
415
430
|
>
|
|
416
431
|
<ZoomIn className="h-4 w-4" />
|
|
417
432
|
</Button>
|
|
@@ -420,7 +435,7 @@ export function EditorHeader({
|
|
|
420
435
|
size="icon"
|
|
421
436
|
data-zoom-fit
|
|
422
437
|
onClick={handleFitZoom}
|
|
423
|
-
className="h-
|
|
438
|
+
className="h-8 w-8 text-gray-400 hover:text-white hover:bg-[#444444]"
|
|
424
439
|
title="全体を表示(⌘0)"
|
|
425
440
|
>
|
|
426
441
|
<Maximize className="h-4 w-4" />
|
|
@@ -440,14 +455,22 @@ export function EditorHeader({
|
|
|
440
455
|
variant="ghost"
|
|
441
456
|
size="icon"
|
|
442
457
|
data-header-more
|
|
443
|
-
className="h-
|
|
458
|
+
className="h-8 w-8 text-gray-400 hover:bg-[#444444] hover:text-white"
|
|
444
459
|
title="その他の操作"
|
|
445
460
|
>
|
|
446
461
|
<MoreHorizontal className="h-4 w-4" />
|
|
447
462
|
</Button>
|
|
448
463
|
</DropdownMenuTrigger>
|
|
449
464
|
<DropdownMenuContent align="end" className="w-52 bg-[#2c2c2c] border-[#444444]">
|
|
450
|
-
<DropdownMenuItem
|
|
465
|
+
{onToggleTheme && <DropdownMenuItem onSelect={onToggleTheme} className="gap-2">
|
|
466
|
+
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
|
467
|
+
{theme === 'dark' ? 'ライト表示にする' : 'ダーク表示にする'}
|
|
468
|
+
</DropdownMenuItem>}
|
|
469
|
+
{editorMode === 'webpage' && onToggleGuides && <DropdownMenuItem onSelect={onToggleGuides} className="gap-2">
|
|
470
|
+
<Ruler className="h-4 w-4" />{guidesOpen ? '幅のガイドを隠す' : '幅のガイドを表示'}
|
|
471
|
+
</DropdownMenuItem>}
|
|
472
|
+
<DropdownMenuSeparator />
|
|
473
|
+
<DropdownMenuItem onSelect={() => setResetOpen(true)} className="cursor-pointer gap-2 text-gray-300 hover:bg-[#444444] hover:text-white">
|
|
451
474
|
<RotateCcw className="h-4 w-4" />
|
|
452
475
|
変更をリセット
|
|
453
476
|
</DropdownMenuItem>
|
|
@@ -493,6 +516,8 @@ export function EditorHeader({
|
|
|
493
516
|
</DropdownMenu>
|
|
494
517
|
}
|
|
495
518
|
/>
|
|
519
|
+
<ConfirmDialog open={resetOpen} onOpenChange={setResetOpen} title="変更をリセットしますか?" description="最後に保存した内容に戻ります。未保存の変更は失われます。" confirmLabel="リセットする" onConfirm={handleReset} />
|
|
520
|
+
</>
|
|
496
521
|
);
|
|
497
522
|
}
|
|
498
523
|
|
|
@@ -511,19 +536,11 @@ function CommentToggleButton({
|
|
|
511
536
|
const deck = useDeck();
|
|
512
537
|
const count = unresolvedCount(deck.slides[page - 1]?.comments);
|
|
513
538
|
return (
|
|
514
|
-
<
|
|
515
|
-
|
|
516
|
-
size="icon"
|
|
517
|
-
onClick={onToggle}
|
|
518
|
-
title="コメントパネルの表示/非表示"
|
|
519
|
-
className={`relative h-8 w-8 hover:bg-[#444444] ${open ? "text-[#4fb8ff]" : "text-gray-400 hover:text-white"}`}
|
|
520
|
-
>
|
|
539
|
+
<button className="ed-button" data-comment-toggle onClick={onToggle} aria-pressed={open}
|
|
540
|
+
aria-expanded={open} title="このページのコメントを確認・追加">
|
|
521
541
|
<MessageSquare className="h-4 w-4" />
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
</span>
|
|
526
|
-
)}
|
|
527
|
-
</Button>
|
|
542
|
+
コメント
|
|
543
|
+
{count > 0 && <span className="tabular-nums" aria-label={`未解決${count}件`}>{count}</span>}
|
|
544
|
+
</button>
|
|
528
545
|
);
|
|
529
546
|
}
|
|
@@ -610,7 +610,7 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
|
|
|
610
610
|
}, [getIframeDoc]);
|
|
611
611
|
|
|
612
612
|
// ノードクリックハンドラ
|
|
613
|
-
const handleNodeClick = useCallback((e: React.MouseEvent, nodeId: string) => {
|
|
613
|
+
const handleNodeClick = useCallback((e: React.MouseEvent | React.KeyboardEvent, nodeId: string) => {
|
|
614
614
|
e.stopPropagation(); // バブリング防止
|
|
615
615
|
|
|
616
616
|
let newSelectedIds: string[] = [];
|
|
@@ -1096,7 +1096,7 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
|
|
|
1096
1096
|
|
|
1097
1097
|
return (
|
|
1098
1098
|
<div
|
|
1099
|
-
className="
|
|
1099
|
+
className="ed-layer-panel min-h-0 flex-1 bg-[#2c2c2c] flex flex-col relative"
|
|
1100
1100
|
// 2段構成(ページ+レイヤー)に埋め込まれているときは幅を親(LeftPanel)が持つ。
|
|
1101
1101
|
// ここで固定幅を持つと上下の段で幅が食い違い、リサイズも二重になる
|
|
1102
1102
|
style={hideSlideList ? { width: '100%' } : { width: `${width}px` }}
|
|
@@ -1119,8 +1119,9 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
|
|
|
1119
1119
|
<Input
|
|
1120
1120
|
value={searchQuery}
|
|
1121
1121
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
1122
|
-
placeholder="
|
|
1123
|
-
|
|
1122
|
+
placeholder="文字や要素名で検索"
|
|
1123
|
+
aria-label="レイヤーを検索"
|
|
1124
|
+
className="h-8 text-xs pl-7 bg-[#383838] border-[#444444] text-white placeholder:text-gray-500"
|
|
1124
1125
|
/>
|
|
1125
1126
|
</div>
|
|
1126
1127
|
</div>
|
|
@@ -1194,15 +1195,12 @@ export function EditorLayerPanel({ hideSlideList = false }: { hideSlideList?: bo
|
|
|
1194
1195
|
) : (
|
|
1195
1196
|
// スライドがない場合は従来のレイヤー表示
|
|
1196
1197
|
<>
|
|
1197
|
-
<
|
|
1198
|
-
<Layers className="w-3.5 h-3.5" />
|
|
1199
|
-
レイヤー
|
|
1200
|
-
</div>
|
|
1198
|
+
<p className="px-3 py-2 text-xs text-gray-400">クリックで選択・ドラッグで並べ替え</p>
|
|
1201
1199
|
{filteredDomTree.length > 0 ? (
|
|
1202
1200
|
filteredDomTree.map((node) => renderTreeNode(node))
|
|
1203
1201
|
) : (
|
|
1204
1202
|
<div className="text-xs text-gray-500 text-center py-4">
|
|
1205
|
-
{searchQuery ? '
|
|
1203
|
+
{searchQuery ? '一致する要素がありません。別の言葉でお試しください。' : 'このページには編集できる要素がありません。'}
|
|
1206
1204
|
</div>
|
|
1207
1205
|
)}
|
|
1208
1206
|
</>
|