@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,446 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* エディタツールバー
|
|
5
|
+
*
|
|
6
|
+
* Phase 2: パフォーマンス最適化
|
|
7
|
+
* - React.memoでラップして不要な再レンダリングを防止
|
|
8
|
+
* - 子コンポーネントもメモ化
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState, useRef, useEffect, memo } from 'react';
|
|
12
|
+
import { Button } from '../components/ui/button';
|
|
13
|
+
import { Separator } from '../components/ui/separator';
|
|
14
|
+
import {
|
|
15
|
+
Tooltip,
|
|
16
|
+
TooltipContent,
|
|
17
|
+
TooltipProvider,
|
|
18
|
+
TooltipTrigger,
|
|
19
|
+
} from '../components/ui/tooltip';
|
|
20
|
+
import {
|
|
21
|
+
MousePointer2,
|
|
22
|
+
Hand,
|
|
23
|
+
Square,
|
|
24
|
+
Circle,
|
|
25
|
+
Minus,
|
|
26
|
+
ArrowRight,
|
|
27
|
+
Pen,
|
|
28
|
+
Pencil,
|
|
29
|
+
Eraser,
|
|
30
|
+
Highlighter,
|
|
31
|
+
Shapes,
|
|
32
|
+
Type,
|
|
33
|
+
Frame,
|
|
34
|
+
Undo2,
|
|
35
|
+
Redo2,
|
|
36
|
+
Group,
|
|
37
|
+
Ungroup,
|
|
38
|
+
Trash2,
|
|
39
|
+
Copy,
|
|
40
|
+
ArrowUp,
|
|
41
|
+
ArrowDown,
|
|
42
|
+
Image,
|
|
43
|
+
Sparkles,
|
|
44
|
+
Scaling,
|
|
45
|
+
ChevronDown,
|
|
46
|
+
LucideIcon,
|
|
47
|
+
Paintbrush,
|
|
48
|
+
Component,
|
|
49
|
+
Images,
|
|
50
|
+
Plus,
|
|
51
|
+
MoreHorizontal,
|
|
52
|
+
} from 'lucide-react';
|
|
53
|
+
import { PEN_PRESETS, inkStyle, setInkPreset } from './utils/ink-style';
|
|
54
|
+
import {
|
|
55
|
+
DropdownMenu,
|
|
56
|
+
DropdownMenuContent,
|
|
57
|
+
DropdownMenuItem,
|
|
58
|
+
DropdownMenuSeparator,
|
|
59
|
+
DropdownMenuTrigger,
|
|
60
|
+
} from '../components/ui/dropdown-menu';
|
|
61
|
+
import { EditorTool, EditorToolbarProps, TOOL_CONFIGS } from '../types/editor';
|
|
62
|
+
import { cn } from '../lib/utils';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* [メディアライブラリ追加時の拡張]
|
|
66
|
+
* EditorToolbarProps 本体は `src/types/editor.ts`(共有型)にあるため、
|
|
67
|
+
* エディタ固有のプロパティはここでローカルに拡張する。
|
|
68
|
+
*/
|
|
69
|
+
export interface EditorToolbarExtendedProps extends EditorToolbarProps {
|
|
70
|
+
/** メディアライブラリを開くコールバック */
|
|
71
|
+
onOpenMediaLibrary?: () => void;
|
|
72
|
+
/** <img> 選択中は「差し替え」表記にする */
|
|
73
|
+
isMediaReplaceMode?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Icon mapping
|
|
77
|
+
const TOOL_ICONS: Record<EditorTool, LucideIcon> = {
|
|
78
|
+
select: MousePointer2,
|
|
79
|
+
scale: Scaling,
|
|
80
|
+
move: Hand,
|
|
81
|
+
frame: Frame,
|
|
82
|
+
rectangle: Square,
|
|
83
|
+
ellipse: Circle,
|
|
84
|
+
line: Minus,
|
|
85
|
+
arrow: ArrowRight,
|
|
86
|
+
pen: Pen,
|
|
87
|
+
pencil: Pencil,
|
|
88
|
+
eraser: Eraser,
|
|
89
|
+
shape: Shapes,
|
|
90
|
+
text: Type,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
interface ToolButtonProps {
|
|
94
|
+
tool: EditorTool;
|
|
95
|
+
isActive: boolean;
|
|
96
|
+
onClick: () => void;
|
|
97
|
+
shortcut: string;
|
|
98
|
+
label: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* ツールボタン(メモ化)
|
|
103
|
+
* isActiveが変更されない限り再レンダリングしない
|
|
104
|
+
*/
|
|
105
|
+
const ToolButton = memo(function ToolButton({ tool, isActive, onClick, shortcut, label }: ToolButtonProps) {
|
|
106
|
+
const Icon = TOOL_ICONS[tool];
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Tooltip>
|
|
110
|
+
<TooltipTrigger asChild>
|
|
111
|
+
<Button
|
|
112
|
+
variant={isActive ? 'default' : 'ghost'}
|
|
113
|
+
size="icon"
|
|
114
|
+
className={cn(
|
|
115
|
+
'h-9 w-9',
|
|
116
|
+
isActive && 'bg-[#0d99ff] hover:bg-[#0c8ce9] text-white'
|
|
117
|
+
)}
|
|
118
|
+
onClick={onClick}
|
|
119
|
+
aria-label={label}
|
|
120
|
+
aria-pressed={isActive}
|
|
121
|
+
>
|
|
122
|
+
<Icon className="h-4 w-4" />
|
|
123
|
+
</Button>
|
|
124
|
+
</TooltipTrigger>
|
|
125
|
+
<TooltipContent side="top" className="flex items-center gap-2">
|
|
126
|
+
<span>{label}</span>
|
|
127
|
+
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
|
|
128
|
+
{shortcut}
|
|
129
|
+
</kbd>
|
|
130
|
+
</TooltipContent>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// [移植時の修正] ツール群ごとの分割ドロップダウンは、ツールバー整理により
|
|
136
|
+
// 単一の「追加」メニューへ統合したため削除した。
|
|
137
|
+
|
|
138
|
+
interface ActionButtonProps {
|
|
139
|
+
icon: LucideIcon;
|
|
140
|
+
onClick: () => void;
|
|
141
|
+
disabled?: boolean;
|
|
142
|
+
tooltip: string;
|
|
143
|
+
shortcut?: string;
|
|
144
|
+
className?: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* アクションボタン(メモ化)
|
|
149
|
+
* disabledが変更されない限り再レンダリングしない
|
|
150
|
+
*/
|
|
151
|
+
const ActionButton = memo(function ActionButton({ icon: Icon, onClick, disabled, tooltip, shortcut, className }: ActionButtonProps) {
|
|
152
|
+
return (
|
|
153
|
+
<Tooltip>
|
|
154
|
+
<TooltipTrigger asChild>
|
|
155
|
+
<Button
|
|
156
|
+
variant="ghost"
|
|
157
|
+
size="icon"
|
|
158
|
+
className={cn('h-9 w-9', className)}
|
|
159
|
+
onClick={onClick}
|
|
160
|
+
disabled={disabled}
|
|
161
|
+
// アイコンのみのボタンなので、支援技術向けにツールチップ文言を名前として与える
|
|
162
|
+
aria-label={tooltip}
|
|
163
|
+
>
|
|
164
|
+
<Icon className={cn('h-4 w-4', className)} />
|
|
165
|
+
</Button>
|
|
166
|
+
</TooltipTrigger>
|
|
167
|
+
<TooltipContent side="top" className="flex items-center gap-2">
|
|
168
|
+
<span>{tooltip}</span>
|
|
169
|
+
{shortcut && (
|
|
170
|
+
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
|
|
171
|
+
{shortcut}
|
|
172
|
+
</kbd>
|
|
173
|
+
)}
|
|
174
|
+
</TooltipContent>
|
|
175
|
+
</Tooltip>
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* エディタツールバー(メモ化)
|
|
181
|
+
*
|
|
182
|
+
* propsが変更されない限り再レンダリングしない
|
|
183
|
+
* - activeTool, selectedCount, canUndo, canRedoの変更時のみ再レンダリング
|
|
184
|
+
*/
|
|
185
|
+
export const EditorToolbar = memo(function EditorToolbar({
|
|
186
|
+
activeTool,
|
|
187
|
+
onToolChange,
|
|
188
|
+
onUndo,
|
|
189
|
+
onRedo,
|
|
190
|
+
canUndo,
|
|
191
|
+
canRedo,
|
|
192
|
+
selectedCount,
|
|
193
|
+
onGroup,
|
|
194
|
+
onUngroup,
|
|
195
|
+
onDelete,
|
|
196
|
+
onDuplicate,
|
|
197
|
+
onBringForward,
|
|
198
|
+
onSendBackward,
|
|
199
|
+
onBringToFront,
|
|
200
|
+
onSendToBack,
|
|
201
|
+
onImageUpload,
|
|
202
|
+
onAiRegenerate,
|
|
203
|
+
onOpenVariables,
|
|
204
|
+
hasVariables,
|
|
205
|
+
onOpenComponents,
|
|
206
|
+
hasComponents,
|
|
207
|
+
onOpenMediaLibrary,
|
|
208
|
+
isMediaReplaceMode,
|
|
209
|
+
}: EditorToolbarExtendedProps) {
|
|
210
|
+
const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
211
|
+
const cmdKey = isMac ? '⌘' : 'Ctrl+';
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
// 各グループで最後に選択したツールを記憶
|
|
215
|
+
// 「追加」メニューで最後に選んだ図形/描画ツールを覚えておく
|
|
216
|
+
const [, setLastShapeTool] = useState<EditorTool>('rectangle');
|
|
217
|
+
const [, setLastDrawingTool] = useState<EditorTool>('pen');
|
|
218
|
+
|
|
219
|
+
// Group tools by category
|
|
220
|
+
const selectionTools = TOOL_CONFIGS.filter(t => t.group === 'selection');
|
|
221
|
+
const shapeTools = TOOL_CONFIGS.filter(t => t.group === 'shapes');
|
|
222
|
+
const drawingTools = TOOL_CONFIGS.filter(t => t.group === 'drawing');
|
|
223
|
+
const textTools = TOOL_CONFIGS.filter(t => t.group === 'text');
|
|
224
|
+
const frameTools = TOOL_CONFIGS.filter(t => t.group === 'frame');
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<TooltipProvider delayDuration={300}>
|
|
228
|
+
{/*
|
|
229
|
+
[移植時の修正] ツールバーの整理。
|
|
230
|
+
元は14個以上のアイコンと7本の区切りが常時1列に並んでいて煩雑だった。
|
|
231
|
+
「常に使うもの(取り消し/選択/表示)」だけを出し、追加系は+メニュー、
|
|
232
|
+
選択中の副次操作(重ね順・グループ)は…メニューへ畳む。配色もビューアに合わせる。
|
|
233
|
+
*/}
|
|
234
|
+
<div className="absolute bottom-5 left-1/2 z-50 max-w-[calc(100%-5rem)] -translate-x-1/2 px-0">
|
|
235
|
+
<div className="no-scrollbar flex items-center gap-0.5 overflow-x-auto rounded-xl border border-[#444444] bg-[#2c2c2c]/95 px-1.5 py-1.5 text-gray-200 shadow-2xl shadow-black/60 backdrop-blur"
|
|
236
|
+
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
|
237
|
+
{/* 取り消し/やり直し */}
|
|
238
|
+
<ActionButton icon={Undo2} onClick={onUndo} disabled={!canUndo} tooltip="元に戻す" shortcut={`${cmdKey}Z`} />
|
|
239
|
+
<ActionButton icon={Redo2} onClick={onRedo} disabled={!canRedo} tooltip="やり直し" shortcut={`${cmdKey}⇧Z`} />
|
|
240
|
+
|
|
241
|
+
<Separator orientation="vertical" className="mx-1 h-6 bg-white/15" />
|
|
242
|
+
|
|
243
|
+
{/* 選択・移動系(常時) */}
|
|
244
|
+
{selectionTools.map((tool) => (
|
|
245
|
+
<ToolButton
|
|
246
|
+
key={tool.id}
|
|
247
|
+
tool={tool.id}
|
|
248
|
+
isActive={activeTool === tool.id}
|
|
249
|
+
onClick={() => onToolChange(tool.id)}
|
|
250
|
+
shortcut={tool.shortcut}
|
|
251
|
+
label={tool.label}
|
|
252
|
+
/>
|
|
253
|
+
))}
|
|
254
|
+
|
|
255
|
+
<Separator orientation="vertical" className="mx-1 h-6 bg-white/15" />
|
|
256
|
+
|
|
257
|
+
{/* 追加(フレーム・図形・描画・テキスト・画像をまとめる) */}
|
|
258
|
+
<DropdownMenu>
|
|
259
|
+
<Tooltip>
|
|
260
|
+
<TooltipTrigger asChild>
|
|
261
|
+
<DropdownMenuTrigger asChild>
|
|
262
|
+
<Button variant="ghost" size="sm" className="h-9 gap-1 px-2.5 text-xs font-medium" aria-label="追加">
|
|
263
|
+
<Plus className="h-4 w-4" />
|
|
264
|
+
追加
|
|
265
|
+
<ChevronDown className="h-3 w-3 opacity-60" />
|
|
266
|
+
</Button>
|
|
267
|
+
</DropdownMenuTrigger>
|
|
268
|
+
</TooltipTrigger>
|
|
269
|
+
<TooltipContent side="top">要素を追加</TooltipContent>
|
|
270
|
+
</Tooltip>
|
|
271
|
+
<DropdownMenuContent side="top" align="center" className="w-52">
|
|
272
|
+
{[...frameTools, ...textTools, ...shapeTools, ...drawingTools].map((tool) => {
|
|
273
|
+
const Icon = TOOL_ICONS[tool.id];
|
|
274
|
+
return (
|
|
275
|
+
<DropdownMenuItem
|
|
276
|
+
key={tool.id}
|
|
277
|
+
onSelect={() => {
|
|
278
|
+
onToolChange(tool.id);
|
|
279
|
+
if (shapeTools.some((t) => t.id === tool.id)) setLastShapeTool(tool.id);
|
|
280
|
+
if (drawingTools.some((t) => t.id === tool.id)) setLastDrawingTool(tool.id);
|
|
281
|
+
}}
|
|
282
|
+
className="gap-2 text-xs"
|
|
283
|
+
>
|
|
284
|
+
<Icon className="h-3.5 w-3.5" />
|
|
285
|
+
<span className="flex-1">{tool.label}</span>
|
|
286
|
+
<kbd className="font-mono text-[10px] text-muted-foreground">{tool.shortcut}</kbd>
|
|
287
|
+
</DropdownMenuItem>
|
|
288
|
+
);
|
|
289
|
+
})}
|
|
290
|
+
<DropdownMenuSeparator />
|
|
291
|
+
{/* ペンギャラリー(PowerPointの描画タブ相当)。選ぶとそのインクで描画開始 */}
|
|
292
|
+
{PEN_PRESETS.map((p) => (
|
|
293
|
+
<DropdownMenuItem
|
|
294
|
+
key={p.id}
|
|
295
|
+
onSelect={() => { setInkPreset(p); onToolChange(p.tool); setLastDrawingTool(p.tool); }}
|
|
296
|
+
className="gap-2 text-xs"
|
|
297
|
+
>
|
|
298
|
+
{p.cap === 'butt'
|
|
299
|
+
? <Highlighter className="h-3.5 w-3.5" style={{ color: p.color }} />
|
|
300
|
+
: <Pencil className="h-3.5 w-3.5" style={{ color: p.color }} />}
|
|
301
|
+
<span className="flex-1">{p.label}</span>
|
|
302
|
+
<span className="h-[3px] w-5 rounded-full" style={{ backgroundColor: p.color, opacity: p.opacity }} />
|
|
303
|
+
</DropdownMenuItem>
|
|
304
|
+
))}
|
|
305
|
+
<DropdownMenuItem onSelect={() => onToolChange('eraser')} className="gap-2 text-xs">
|
|
306
|
+
<Eraser className="h-3.5 w-3.5" />
|
|
307
|
+
<span className="flex-1">消しゴム(ストローク削除)</span>
|
|
308
|
+
<kbd className="font-mono text-[10px] text-muted-foreground">E</kbd>
|
|
309
|
+
</DropdownMenuItem>
|
|
310
|
+
{(onImageUpload || onOpenMediaLibrary) && <DropdownMenuSeparator />}
|
|
311
|
+
{onOpenMediaLibrary && (
|
|
312
|
+
<DropdownMenuItem onSelect={onOpenMediaLibrary} className="gap-2 text-xs">
|
|
313
|
+
<Images className="h-3.5 w-3.5" />
|
|
314
|
+
{isMediaReplaceMode ? 'メディアライブラリから差し替え' : 'メディアライブラリから挿入'}
|
|
315
|
+
</DropdownMenuItem>
|
|
316
|
+
)}
|
|
317
|
+
{onImageUpload && (
|
|
318
|
+
<DropdownMenuItem onSelect={onImageUpload} className="gap-2 text-xs">
|
|
319
|
+
<Image className="h-3.5 w-3.5" />
|
|
320
|
+
ファイルから画像を追加
|
|
321
|
+
</DropdownMenuItem>
|
|
322
|
+
)}
|
|
323
|
+
</DropdownMenuContent>
|
|
324
|
+
</DropdownMenu>
|
|
325
|
+
|
|
326
|
+
{/* 描画中はペンの持ち替えチップを出す(Figmaのサブツールバー相当) */}
|
|
327
|
+
{(activeTool === 'pen' || activeTool === 'pencil' || activeTool === 'eraser') && (
|
|
328
|
+
<>
|
|
329
|
+
<Separator orientation="vertical" className="mx-1 h-6 bg-white/15" />
|
|
330
|
+
{PEN_PRESETS.map((p) => {
|
|
331
|
+
const active = activeTool !== 'eraser' && inkStyle.presetId === p.id;
|
|
332
|
+
return (
|
|
333
|
+
<Tooltip key={p.id}>
|
|
334
|
+
<TooltipTrigger asChild>
|
|
335
|
+
<button
|
|
336
|
+
onClick={() => { setInkPreset(p); onToolChange(p.tool); }}
|
|
337
|
+
className={`flex h-9 w-8 items-center justify-center rounded-md ${active ? 'bg-white/20' : 'hover:bg-white/10'}`}
|
|
338
|
+
>
|
|
339
|
+
{p.cap === 'butt'
|
|
340
|
+
? <Highlighter className="h-4 w-4" style={{ color: p.color }} />
|
|
341
|
+
: <Pencil className="h-4 w-4" style={{ color: p.color }} />}
|
|
342
|
+
</button>
|
|
343
|
+
</TooltipTrigger>
|
|
344
|
+
<TooltipContent side="top"><span>{p.label}</span></TooltipContent>
|
|
345
|
+
</Tooltip>
|
|
346
|
+
);
|
|
347
|
+
})}
|
|
348
|
+
<Tooltip>
|
|
349
|
+
<TooltipTrigger asChild>
|
|
350
|
+
<button
|
|
351
|
+
onClick={() => onToolChange('eraser')}
|
|
352
|
+
className={`flex h-9 w-8 items-center justify-center rounded-md ${activeTool === 'eraser' ? 'bg-white/20' : 'hover:bg-white/10'}`}
|
|
353
|
+
>
|
|
354
|
+
<Eraser className="h-4 w-4" />
|
|
355
|
+
</button>
|
|
356
|
+
</TooltipTrigger>
|
|
357
|
+
<TooltipContent side="top"><span>消しゴム (E)</span></TooltipContent>
|
|
358
|
+
</Tooltip>
|
|
359
|
+
</>
|
|
360
|
+
)}
|
|
361
|
+
|
|
362
|
+
{/* パネル(CSS変数・コンポーネント) */}
|
|
363
|
+
{(onOpenVariables || onOpenComponents) && (
|
|
364
|
+
<Separator orientation="vertical" className="mx-1 h-6 bg-white/15" />
|
|
365
|
+
)}
|
|
366
|
+
{onOpenVariables && (
|
|
367
|
+
<ActionButton
|
|
368
|
+
icon={Paintbrush}
|
|
369
|
+
onClick={onOpenVariables}
|
|
370
|
+
tooltip="CSS変数"
|
|
371
|
+
className={hasVariables ? 'text-sky-400' : undefined}
|
|
372
|
+
/>
|
|
373
|
+
)}
|
|
374
|
+
{onOpenComponents && (
|
|
375
|
+
<ActionButton
|
|
376
|
+
icon={Component}
|
|
377
|
+
onClick={onOpenComponents}
|
|
378
|
+
tooltip="コンポーネント"
|
|
379
|
+
className={hasComponents ? 'text-sky-400' : undefined}
|
|
380
|
+
/>
|
|
381
|
+
)}
|
|
382
|
+
|
|
383
|
+
{/* 選択中のみ: よく使う3つを出し、残りは…メニュー */}
|
|
384
|
+
{selectedCount > 0 && (
|
|
385
|
+
<>
|
|
386
|
+
<Separator orientation="vertical" className="mx-1 h-6 bg-white/15" />
|
|
387
|
+
{onDuplicate && (
|
|
388
|
+
<ActionButton icon={Copy} onClick={onDuplicate} tooltip="複製" shortcut={`${cmdKey}D`} />
|
|
389
|
+
)}
|
|
390
|
+
{onDelete && (
|
|
391
|
+
<ActionButton icon={Trash2} onClick={onDelete} tooltip="削除" shortcut="⌫" />
|
|
392
|
+
)}
|
|
393
|
+
{selectedCount === 1 && onAiRegenerate && (
|
|
394
|
+
<ActionButton
|
|
395
|
+
icon={Sparkles}
|
|
396
|
+
onClick={onAiRegenerate}
|
|
397
|
+
tooltip="AIで再生成"
|
|
398
|
+
className="text-sky-400"
|
|
399
|
+
/>
|
|
400
|
+
)}
|
|
401
|
+
<DropdownMenu>
|
|
402
|
+
<DropdownMenuTrigger asChild>
|
|
403
|
+
<Button variant="ghost" size="icon" className="h-9 w-9" aria-label="その他の操作">
|
|
404
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
405
|
+
</Button>
|
|
406
|
+
</DropdownMenuTrigger>
|
|
407
|
+
<DropdownMenuContent side="top" align="end" className="w-48">
|
|
408
|
+
{onBringForward && (
|
|
409
|
+
<DropdownMenuItem onSelect={onBringForward} className="gap-2 text-xs">
|
|
410
|
+
<ArrowUp className="h-3.5 w-3.5" />
|
|
411
|
+
<span className="flex-1">前面へ</span>
|
|
412
|
+
<kbd className="font-mono text-[10px] text-muted-foreground">{cmdKey}]</kbd>
|
|
413
|
+
</DropdownMenuItem>
|
|
414
|
+
)}
|
|
415
|
+
{onSendBackward && (
|
|
416
|
+
<DropdownMenuItem onSelect={onSendBackward} className="gap-2 text-xs">
|
|
417
|
+
<ArrowDown className="h-3.5 w-3.5" />
|
|
418
|
+
<span className="flex-1">背面へ</span>
|
|
419
|
+
<kbd className="font-mono text-[10px] text-muted-foreground">{cmdKey}[</kbd>
|
|
420
|
+
</DropdownMenuItem>
|
|
421
|
+
)}
|
|
422
|
+
{selectedCount > 1 && onGroup && (
|
|
423
|
+
<DropdownMenuItem onSelect={onGroup} className="gap-2 text-xs">
|
|
424
|
+
<Group className="h-3.5 w-3.5" />
|
|
425
|
+
<span className="flex-1">グループ化</span>
|
|
426
|
+
<kbd className="font-mono text-[10px] text-muted-foreground">{cmdKey}G</kbd>
|
|
427
|
+
</DropdownMenuItem>
|
|
428
|
+
)}
|
|
429
|
+
{onUngroup && (
|
|
430
|
+
<DropdownMenuItem onSelect={onUngroup} className="gap-2 text-xs">
|
|
431
|
+
<Ungroup className="h-3.5 w-3.5" />
|
|
432
|
+
<span className="flex-1">グループ解除</span>
|
|
433
|
+
</DropdownMenuItem>
|
|
434
|
+
)}
|
|
435
|
+
</DropdownMenuContent>
|
|
436
|
+
</DropdownMenu>
|
|
437
|
+
</>
|
|
438
|
+
)}
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</TooltipProvider>
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
export default EditorToolbar;
|
|
446
|
+
|