@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.
Files changed (219) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +52 -0
  3. package/README.md +143 -0
  4. package/dist/editor.css +2 -0
  5. package/package.json +94 -0
  6. package/src/components/DeckSlideRender.tsx +21 -0
  7. package/src/components/SaveNote.tsx +21 -0
  8. package/src/components/auth/AuthProvider.tsx +44 -0
  9. package/src/components/ui/accordion.tsx +57 -0
  10. package/src/components/ui/alert-dialog.tsx +141 -0
  11. package/src/components/ui/badge.tsx +36 -0
  12. package/src/components/ui/button.tsx +56 -0
  13. package/src/components/ui/card.tsx +76 -0
  14. package/src/components/ui/checkbox.tsx +30 -0
  15. package/src/components/ui/collapsible.tsx +11 -0
  16. package/src/components/ui/context-menu.tsx +200 -0
  17. package/src/components/ui/dialog.tsx +122 -0
  18. package/src/components/ui/dropdown-menu.tsx +201 -0
  19. package/src/components/ui/input.tsx +22 -0
  20. package/src/components/ui/label.tsx +26 -0
  21. package/src/components/ui/popover.tsx +33 -0
  22. package/src/components/ui/scroll-area.tsx +48 -0
  23. package/src/components/ui/scrubbable-label.tsx +95 -0
  24. package/src/components/ui/select.tsx +159 -0
  25. package/src/components/ui/separator.tsx +31 -0
  26. package/src/components/ui/slider.tsx +28 -0
  27. package/src/components/ui/switch.tsx +29 -0
  28. package/src/components/ui/tabs.tsx +55 -0
  29. package/src/components/ui/textarea.tsx +22 -0
  30. package/src/components/ui/tooltip.tsx +32 -0
  31. package/src/components/viewer/useDeck.ts +58 -0
  32. package/src/editor/EditorContext.tsx +632 -0
  33. package/src/editor/EditorToolbar.tsx +446 -0
  34. package/src/editor/FrontendVisualEditor.tsx +2595 -0
  35. package/src/editor/autosave.ts +34 -0
  36. package/src/editor/components/AiPromptPopover.tsx +378 -0
  37. package/src/editor/components/BreakpointGuides.tsx +323 -0
  38. package/src/editor/components/BreakpointSelector.tsx +180 -0
  39. package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
  40. package/src/editor/components/CssEditorDialog.tsx +168 -0
  41. package/src/editor/components/EditorCanvas.tsx +987 -0
  42. package/src/editor/components/EditorContextMenu.tsx +515 -0
  43. package/src/editor/components/EditorFooter.tsx +81 -0
  44. package/src/editor/components/EditorHeader.tsx +524 -0
  45. package/src/editor/components/EditorLayerPanel.tsx +1197 -0
  46. package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
  47. package/src/editor/components/EditorSidebar.tsx +226 -0
  48. package/src/editor/components/FigmaColorPicker.tsx +1577 -0
  49. package/src/editor/components/HtmlEditorDialog.tsx +71 -0
  50. package/src/editor/components/HtmlImportDialog.tsx +346 -0
  51. package/src/editor/components/JsEditorDialog.tsx +175 -0
  52. package/src/editor/components/LayerTreeItem.tsx +494 -0
  53. package/src/editor/components/MasterComponentEditor.tsx +846 -0
  54. package/src/editor/components/MediaLibraryDialog.tsx +447 -0
  55. package/src/editor/components/PageSettingsDialog.tsx +776 -0
  56. package/src/editor/components/VariablePicker.tsx +355 -0
  57. package/src/editor/components/VariablesDialog.tsx +564 -0
  58. package/src/editor/components/VariantEditor.tsx +687 -0
  59. package/src/editor/components/VariantMatrix.tsx +507 -0
  60. package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
  61. package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
  62. package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
  63. package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
  64. package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
  65. package/src/editor/components/component-panel/VariantList.tsx +260 -0
  66. package/src/editor/components/component-panel/index.ts +12 -0
  67. package/src/editor/components/index.ts +48 -0
  68. package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
  69. package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
  70. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
  71. package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
  72. package/src/editor/components/multi-page/PageLabel.tsx +34 -0
  73. package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
  74. package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
  75. package/src/editor/components/multi-page/index.ts +7 -0
  76. package/src/editor/components/ppt/PptChrome.tsx +2508 -0
  77. package/src/editor/components/ppt/PptComments.tsx +589 -0
  78. package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
  79. package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
  80. package/src/editor/components/ppt/PptNotes.tsx +246 -0
  81. package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
  82. package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
  83. package/src/editor/components/property-panel/BorderSection.tsx +104 -0
  84. package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
  85. package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
  86. package/src/editor/components/property-panel/EffectSection.tsx +157 -0
  87. package/src/editor/components/property-panel/FillSection.tsx +69 -0
  88. package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
  89. package/src/editor/components/property-panel/ImageSection.tsx +206 -0
  90. package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
  91. package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
  92. package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
  93. package/src/editor/components/property-panel/LinkSection.tsx +103 -0
  94. package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
  95. package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
  96. package/src/editor/components/property-panel/TypographySection.tsx +230 -0
  97. package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
  98. package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
  99. package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
  100. package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
  101. package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
  102. package/src/editor/components/property-panel/index.ts +24 -0
  103. package/src/editor/components/property-panel/unit-utils.ts +261 -0
  104. package/src/editor/components/shell/EditorTopBar.tsx +392 -0
  105. package/src/editor/components/shell/LeftPanel.tsx +47 -0
  106. package/src/editor/components/shell/PagesPanel.tsx +118 -0
  107. package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
  108. package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
  109. package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
  110. package/src/editor/components/variables-panel/index.ts +7 -0
  111. package/src/editor/constants.ts +468 -0
  112. package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
  113. package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
  114. package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
  115. package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
  116. package/src/editor/contexts/EditorRefsContext.tsx +204 -0
  117. package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
  118. package/src/editor/contexts/EditorToolContext.tsx +58 -0
  119. package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
  120. package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
  121. package/src/editor/contexts/EditorViewContext.tsx +101 -0
  122. package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
  123. package/src/editor/contexts/index.ts +17 -0
  124. package/src/editor/editor-skin.css +122 -0
  125. package/src/editor/hooks/index.ts +38 -0
  126. package/src/editor/hooks/useAiReplace.ts +397 -0
  127. package/src/editor/hooks/useAltMeasure.ts +67 -0
  128. package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
  129. package/src/editor/hooks/useCanvasControls.ts +1222 -0
  130. package/src/editor/hooks/useComponentEditMode.ts +658 -0
  131. package/src/editor/hooks/useComponentInstances.ts +515 -0
  132. package/src/editor/hooks/useContextMenuHandler.ts +157 -0
  133. package/src/editor/hooks/useCoordinateTransform.ts +207 -0
  134. package/src/editor/hooks/useDragResize.ts +1317 -0
  135. package/src/editor/hooks/useDrawingMode.ts +472 -0
  136. package/src/editor/hooks/useEditorColors.ts +144 -0
  137. package/src/editor/hooks/useEditorMessages.ts +108 -0
  138. package/src/editor/hooks/useElementActions.ts +1754 -0
  139. package/src/editor/hooks/useElementSelection.ts +1573 -0
  140. package/src/editor/hooks/useFocusManagement.ts +249 -0
  141. package/src/editor/hooks/useGoogleFonts.ts +305 -0
  142. package/src/editor/hooks/useIframeInitializer.ts +584 -0
  143. package/src/editor/hooks/useIframeSetup.ts +360 -0
  144. package/src/editor/hooks/useImageUpload.ts +270 -0
  145. package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
  146. package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
  147. package/src/editor/hooks/useMarqueeSelection.ts +388 -0
  148. package/src/editor/hooks/useMediaLibrary.ts +129 -0
  149. package/src/editor/hooks/usePageSettingsManager.ts +739 -0
  150. package/src/editor/hooks/useResizablePanel.ts +163 -0
  151. package/src/editor/hooks/useRichPaste.ts +255 -0
  152. package/src/editor/hooks/useTouchGestures.ts +197 -0
  153. package/src/editor/index.ts +27 -0
  154. package/src/editor/types/page-settings.ts +513 -0
  155. package/src/editor/types.ts +382 -0
  156. package/src/editor/utils/align-elements.ts +169 -0
  157. package/src/editor/utils/api-json.ts +21 -0
  158. package/src/editor/utils/aspect-lock.ts +50 -0
  159. package/src/editor/utils/component-renderer.ts +702 -0
  160. package/src/editor/utils/component-sync.ts +845 -0
  161. package/src/editor/utils/crop-mode.ts +265 -0
  162. package/src/editor/utils/dom-utils.ts +2238 -0
  163. package/src/editor/utils/element-effects.ts +218 -0
  164. package/src/editor/utils/eyedropper.ts +35 -0
  165. package/src/editor/utils/figma-export.ts +560 -0
  166. package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
  167. package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
  168. package/src/editor/utils/figma-paste.ts +2262 -0
  169. package/src/editor/utils/flex-reorder.ts +231 -0
  170. package/src/editor/utils/flex-utils.ts +1230 -0
  171. package/src/editor/utils/geometry.ts +232 -0
  172. package/src/editor/utils/grid-layout.ts +210 -0
  173. package/src/editor/utils/html-utils.ts +470 -0
  174. package/src/editor/utils/index.ts +11 -0
  175. package/src/editor/utils/ink-style.ts +57 -0
  176. package/src/editor/utils/inline-format.ts +236 -0
  177. package/src/editor/utils/measure-distance.ts +120 -0
  178. package/src/editor/utils/override-apply.ts +526 -0
  179. package/src/editor/utils/override-detection.ts +613 -0
  180. package/src/editor/utils/paste-processors.ts +609 -0
  181. package/src/editor/utils/paste-sanitizer.ts +244 -0
  182. package/src/editor/utils/restore-flow.ts +188 -0
  183. package/src/editor/utils/shape-library.ts +171 -0
  184. package/src/editor/utils/slide-root.ts +29 -0
  185. package/src/editor/utils/smart-guides.ts +218 -0
  186. package/src/editor/utils/style-utils.ts +685 -0
  187. package/src/editor/utils/table-edit.ts +151 -0
  188. package/src/editor/utils/tailwind-mappings.ts +778 -0
  189. package/src/editor/utils/tailwind-utils.ts +804 -0
  190. package/src/editor/utils/text-highlight.ts +216 -0
  191. package/src/editor/utils/theme-to-variables.ts +341 -0
  192. package/src/editor/utils/variant-resolver.ts +456 -0
  193. package/src/editor/utils/viewport-utils.ts +295 -0
  194. package/src/hooks/useEditorHistory.ts +195 -0
  195. package/src/hooks/useEditorShortcuts.ts +260 -0
  196. package/src/index.ts +40 -0
  197. package/src/io.ts +134 -0
  198. package/src/lib/agent/slide-agent/types.ts +547 -0
  199. package/src/lib/agent/website-agent/types.ts +805 -0
  200. package/src/lib/api/auth-fetch.ts +58 -0
  201. package/src/lib/deck.ts +69 -0
  202. package/src/lib/export.ts +47 -0
  203. package/src/lib/firebase/config.ts +29 -0
  204. package/src/lib/firebase/css-variables.ts +378 -0
  205. package/src/lib/firebase/editor-components.ts +677 -0
  206. package/src/lib/firebase/storage.ts +469 -0
  207. package/src/lib/utils.ts +6 -0
  208. package/src/styles/editor.css +52 -0
  209. package/src/styles/skin.css +122 -0
  210. package/src/types/css-variables.ts +645 -0
  211. package/src/types/editor-components.ts +944 -0
  212. package/src/types/editor.ts +420 -0
  213. package/src/types/page-master.ts +197 -0
  214. package/src/types/page.ts +419 -0
  215. package/src/types/slide.ts +402 -0
  216. package/src/types/website-theme.ts +788 -0
  217. package/src/types/website.ts +374 -0
  218. package/src/vendor/firebase-firestore.ts +13 -0
  219. package/src/vendor/firebase-functions.ts +17 -0
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogFooter,
10
+ } from '../../components/ui/dialog';
11
+
12
+ interface HtmlEditorDialogProps {
13
+ isOpen: boolean;
14
+ onClose: () => void;
15
+ onSave: (html: string) => void;
16
+ initialHtml: string;
17
+ }
18
+
19
+ export function HtmlEditorDialog({
20
+ isOpen,
21
+ onClose,
22
+ onSave,
23
+ initialHtml,
24
+ }: HtmlEditorDialogProps) {
25
+ const [html, setHtml] = useState(initialHtml);
26
+
27
+ useEffect(() => {
28
+ if (isOpen) {
29
+ setHtml(initialHtml);
30
+ }
31
+ }, [isOpen, initialHtml]);
32
+
33
+ const handleSave = () => {
34
+ onSave(html);
35
+ onClose();
36
+ };
37
+
38
+ return (
39
+ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
40
+ <DialogContent className="max-w-3xl z-[100]">
41
+ <DialogHeader>
42
+ <DialogTitle>HTMLを編集</DialogTitle>
43
+ </DialogHeader>
44
+
45
+ <div className="py-2">
46
+ <textarea
47
+ className="w-full h-96 p-4 font-mono text-sm bg-gray-900 text-gray-100 rounded-md border border-gray-700 focus:outline-none focus:ring-2 focus:ring-[#0d99ff] resize-none"
48
+ value={html}
49
+ onChange={(e) => setHtml(e.target.value)}
50
+ spellCheck={false}
51
+ />
52
+ </div>
53
+
54
+ <DialogFooter>
55
+ <button
56
+ onClick={onClose}
57
+ className="px-4 py-2 text-sm text-gray-300 hover:text-white transition-colors"
58
+ >
59
+ キャンセル
60
+ </button>
61
+ <button
62
+ onClick={handleSave}
63
+ className="px-4 py-2 text-sm bg-[#0d99ff] hover:bg-[#0c8ce9] text-white rounded-md transition-colors"
64
+ >
65
+ 保存
66
+ </button>
67
+ </DialogFooter>
68
+ </DialogContent>
69
+ </Dialog>
70
+ );
71
+ }
@@ -0,0 +1,346 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * HTMLインポートダイアログ
5
+ * URLまたはテキスト入力からHTMLをインポート
6
+ * URLインポートはFirebase Functionsを使用(CSS・画像をStorageに保存)
7
+ */
8
+
9
+ import { useState, useCallback } from 'react';
10
+ import { httpsCallable } from '../../vendor/firebase-functions';
11
+ import { functions } from '../../lib/firebase/config';
12
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../components/ui/dialog';
13
+ import { Button } from '../../components/ui/button';
14
+ import { Input } from '../../components/ui/input';
15
+ import { Textarea } from '../../components/ui/textarea';
16
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../components/ui/tabs';
17
+ import { Label } from '../../components/ui/label';
18
+ import { Loader2, Globe, Code, AlertCircle, CheckCircle2, Image, FileText } from 'lucide-react';
19
+
20
+ // インポートリソースの型定義
21
+ interface ImportedResource {
22
+ id: string;
23
+ type: 'image' | 'stylesheet' | 'font' | 'script';
24
+ originalUrl: string;
25
+ storagePath: string;
26
+ storageUrl: string;
27
+ contentType: string;
28
+ size: number;
29
+ hash: string;
30
+ }
31
+
32
+ // Firebase Function の型定義
33
+ interface HtmlImportResponse {
34
+ success: boolean;
35
+ html: string;
36
+ css: string;
37
+ js: string;
38
+ title?: string;
39
+ resources: {
40
+ images: ImportedResource[];
41
+ stylesheets: ImportedResource[];
42
+ scripts: ImportedResource[];
43
+ };
44
+ error?: string;
45
+ }
46
+
47
+ // インポート結果の型
48
+ export interface HtmlImportResult {
49
+ html: string;
50
+ css: string;
51
+ js?: string;
52
+ resources?: {
53
+ images: ImportedResource[];
54
+ stylesheets: ImportedResource[];
55
+ scripts?: ImportedResource[];
56
+ };
57
+ }
58
+
59
+ export interface HtmlImportDialogProps {
60
+ isOpen: boolean;
61
+ onClose: () => void;
62
+ onImport: (result: HtmlImportResult) => void;
63
+ presentationId?: string;
64
+ slideId?: string;
65
+ }
66
+
67
+ export function HtmlImportDialog({
68
+ isOpen,
69
+ onClose,
70
+ onImport,
71
+ presentationId,
72
+ slideId,
73
+ }: HtmlImportDialogProps) {
74
+ const [activeTab, setActiveTab] = useState<'url' | 'code'>('url');
75
+ const [url, setUrl] = useState('');
76
+ const [htmlCode, setHtmlCode] = useState('');
77
+ const [cssCode, setCssCode] = useState('');
78
+ const [isLoading, setIsLoading] = useState(false);
79
+ const [error, setError] = useState<string | null>(null);
80
+ const [loadingStatus, setLoadingStatus] = useState<string>('');
81
+ const [importStats, setImportStats] = useState<{
82
+ images: number;
83
+ cssSize: number;
84
+ jsSize: number;
85
+ } | null>(null);
86
+
87
+ // URLからインポート(Firebase Functions使用)
88
+ const handleUrlImport = useCallback(async () => {
89
+ if (!url.trim()) {
90
+ setError('URLを入力してください');
91
+ return;
92
+ }
93
+
94
+ // URL形式チェック
95
+ try {
96
+ new URL(url);
97
+ } catch {
98
+ setError('有効なURLを入力してください');
99
+ return;
100
+ }
101
+
102
+ setIsLoading(true);
103
+ setError(null);
104
+ setImportStats(null);
105
+ setLoadingStatus('ページを読み込み中...');
106
+
107
+ try {
108
+ // Firebase Function を呼び出し
109
+ const importHtmlFromUrl = httpsCallable<
110
+ { url: string; presentationId?: string; slideId?: string },
111
+ HtmlImportResponse
112
+ >(functions, 'importHtmlFromUrl');
113
+
114
+ setLoadingStatus('HTMLとCSSを抽出中...');
115
+
116
+ const result = await importHtmlFromUrl({
117
+ url,
118
+ presentationId,
119
+ slideId,
120
+ });
121
+
122
+ if (result.data.success && result.data.html) {
123
+ setLoadingStatus('完了!');
124
+
125
+ // 統計情報を設定
126
+ setImportStats({
127
+ images: result.data.resources?.images?.length || 0,
128
+ cssSize: result.data.css?.length || 0,
129
+ jsSize: result.data.js?.length || 0,
130
+ });
131
+
132
+ // 少し待ってから結果を渡す(統計表示のため)
133
+ await new Promise(resolve => setTimeout(resolve, 500));
134
+
135
+ onImport({
136
+ html: result.data.html,
137
+ css: result.data.css || '',
138
+ js: result.data.js || '',
139
+ resources: result.data.resources,
140
+ });
141
+ handleClose();
142
+ } else {
143
+ throw new Error(result.data.error || 'インポートに失敗しました');
144
+ }
145
+ } catch (err) {
146
+ console.error('[HtmlImportDialog] Import error:', err);
147
+ // Firebase Functions のエラーメッセージを抽出
148
+ const errorMessage = err instanceof Error
149
+ ? err.message.replace(/^.*?:\s*/, '') // "FirebaseError: " などのプレフィックスを除去
150
+ : 'インポートに失敗しました';
151
+ setError(errorMessage);
152
+ } finally {
153
+ setIsLoading(false);
154
+ setLoadingStatus('');
155
+ }
156
+ }, [url, onImport, presentationId, slideId]);
157
+
158
+ // テキストからインポート
159
+ const handleCodeImport = useCallback(() => {
160
+ if (!htmlCode.trim()) {
161
+ setError('HTMLを入力してください');
162
+ return;
163
+ }
164
+
165
+ setError(null);
166
+
167
+ onImport({
168
+ html: htmlCode.trim(),
169
+ css: cssCode.trim(),
170
+ });
171
+ handleClose();
172
+ }, [htmlCode, cssCode, onImport]);
173
+
174
+ // ダイアログを閉じる
175
+ const handleClose = useCallback(() => {
176
+ setUrl('');
177
+ setHtmlCode('');
178
+ setCssCode('');
179
+ setError(null);
180
+ setIsLoading(false);
181
+ setLoadingStatus('');
182
+ setImportStats(null);
183
+ onClose();
184
+ }, [onClose]);
185
+
186
+ // ファイルサイズをフォーマット
187
+ const formatSize = (bytes: number): string => {
188
+ if (bytes < 1024) return `${bytes} B`;
189
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
190
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
191
+ };
192
+
193
+ return (
194
+ <Dialog open={isOpen} onOpenChange={(open) => !open && handleClose()}>
195
+ <DialogContent className="sm:max-w-[600px] bg-[#2c2c2c] border-[#444444] text-white">
196
+ <DialogHeader>
197
+ <DialogTitle className="text-white flex items-center gap-2">
198
+ <Code className="w-5 h-5" />
199
+ HTMLインポート
200
+ </DialogTitle>
201
+ </DialogHeader>
202
+
203
+ <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as 'url' | 'code')}>
204
+ <TabsList className="grid w-full grid-cols-2 bg-[#1e1e1e]">
205
+ <TabsTrigger
206
+ value="url"
207
+ className="data-[state=active]:bg-[#444444] data-[state=active]:text-white"
208
+ >
209
+ <Globe className="w-4 h-4 mr-2" />
210
+ URLから取得
211
+ </TabsTrigger>
212
+ <TabsTrigger
213
+ value="code"
214
+ className="data-[state=active]:bg-[#444444] data-[state=active]:text-white"
215
+ >
216
+ <Code className="w-4 h-4 mr-2" />
217
+ コード入力
218
+ </TabsTrigger>
219
+ </TabsList>
220
+
221
+ {/* URLタブ */}
222
+ <TabsContent value="url" className="space-y-4 mt-4">
223
+ <div className="space-y-2">
224
+ <Label htmlFor="import-url" className="text-gray-300">
225
+ URL
226
+ </Label>
227
+ <Input
228
+ id="import-url"
229
+ type="url"
230
+ placeholder="https://example.com"
231
+ value={url}
232
+ onChange={(e) => setUrl(e.target.value)}
233
+ className="bg-[#1e1e1e] border-[#444444] text-white placeholder:text-gray-500"
234
+ disabled={isLoading}
235
+ />
236
+ <p className="text-xs text-gray-500">
237
+ ページのHTML・CSS・画像を取得し、Firebase Storageに保存します。
238
+ </p>
239
+ </div>
240
+
241
+ {/* ローディング状態 */}
242
+ {isLoading && (
243
+ <div className="flex items-center gap-3 p-3 bg-[#0d99ff]/10 border border-[#0d99ff]/30 rounded-md">
244
+ <Loader2 className="w-5 h-5 animate-spin text-[#4fb8ff]" />
245
+ <div className="flex-1">
246
+ <p className="text-sm text-[#7cc4ff]">{loadingStatus}</p>
247
+ <p className="text-xs text-gray-500 mt-1">
248
+ ページの内容によっては1〜2分かかることがあります
249
+ </p>
250
+ </div>
251
+ </div>
252
+ )}
253
+
254
+ {/* インポート統計 */}
255
+ {importStats && !isLoading && (
256
+ <div className="flex items-center gap-3 p-3 bg-green-500/10 border border-green-500/30 rounded-md">
257
+ <CheckCircle2 className="w-5 h-5 text-green-400" />
258
+ <div className="flex-1 flex items-center gap-4 text-sm flex-wrap">
259
+ <span className="flex items-center gap-1 text-gray-300">
260
+ <Image className="w-4 h-4" />
261
+ {importStats.images} 画像
262
+ </span>
263
+ <span className="flex items-center gap-1 text-gray-300">
264
+ <FileText className="w-4 h-4" />
265
+ CSS {formatSize(importStats.cssSize)}
266
+ </span>
267
+ {importStats.jsSize > 0 && (
268
+ <span className="flex items-center gap-1 text-gray-300">
269
+ <Code className="w-4 h-4" />
270
+ JS {formatSize(importStats.jsSize)}
271
+ </span>
272
+ )}
273
+ </div>
274
+ </div>
275
+ )}
276
+
277
+ <Button
278
+ onClick={handleUrlImport}
279
+ disabled={isLoading || !url.trim()}
280
+ className="w-full bg-[#0d99ff] hover:bg-[#0c8ce9]"
281
+ >
282
+ {isLoading ? (
283
+ <>
284
+ <Loader2 className="w-4 h-4 mr-2 animate-spin" />
285
+ インポート中...
286
+ </>
287
+ ) : (
288
+ 'インポート'
289
+ )}
290
+ </Button>
291
+ </TabsContent>
292
+
293
+ {/* コード入力タブ */}
294
+ <TabsContent value="code" className="space-y-4 mt-4">
295
+ <div className="space-y-2">
296
+ <Label htmlFor="import-html" className="text-gray-300">
297
+ HTML
298
+ </Label>
299
+ <Textarea
300
+ id="import-html"
301
+ placeholder="<div>...</div>"
302
+ value={htmlCode}
303
+ onChange={(e) => setHtmlCode(e.target.value)}
304
+ className="bg-[#1e1e1e] border-[#444444] text-white placeholder:text-gray-500 font-mono text-sm min-h-[150px]"
305
+ />
306
+ </div>
307
+
308
+ <div className="space-y-2">
309
+ <Label htmlFor="import-css" className="text-gray-300">
310
+ CSS(オプション)
311
+ </Label>
312
+ <Textarea
313
+ id="import-css"
314
+ placeholder=".class { ... }"
315
+ value={cssCode}
316
+ onChange={(e) => setCssCode(e.target.value)}
317
+ className="bg-[#1e1e1e] border-[#444444] text-white placeholder:text-gray-500 font-mono text-sm min-h-[100px]"
318
+ />
319
+ <p className="text-xs text-gray-500">
320
+ CSSを入力すると、エディタ内でスタイルが適用されます。
321
+ </p>
322
+ </div>
323
+
324
+ <Button
325
+ onClick={handleCodeImport}
326
+ disabled={!htmlCode.trim()}
327
+ className="w-full bg-[#0d99ff] hover:bg-[#0c8ce9]"
328
+ >
329
+ インポート
330
+ </Button>
331
+ </TabsContent>
332
+ </Tabs>
333
+
334
+ {/* エラー表示 */}
335
+ {error && (
336
+ <div className="flex items-center gap-2 p-3 bg-red-500/20 border border-red-500/50 rounded-md text-red-300 text-sm">
337
+ <AlertCircle className="w-4 h-4 flex-shrink-0" />
338
+ <span>{error}</span>
339
+ </div>
340
+ )}
341
+ </DialogContent>
342
+ </Dialog>
343
+ );
344
+ }
345
+
346
+ export default HtmlImportDialog;
@@ -0,0 +1,175 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * JSエディタダイアログ
5
+ * インポートされたJavaScriptを編集するためのダイアログ
6
+ */
7
+
8
+ import { useState, useCallback, useEffect } from 'react';
9
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../../components/ui/dialog';
10
+ import { Button } from '../../components/ui/button';
11
+ import { Textarea } from '../../components/ui/textarea';
12
+ import { Braces, Trash2, RefreshCw, AlertTriangle } from 'lucide-react';
13
+
14
+ export interface JsEditorDialogProps {
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ initialJs: string;
18
+ onSave: (js: string) => void;
19
+ onClear?: () => void;
20
+ }
21
+
22
+ export function JsEditorDialog({
23
+ isOpen,
24
+ onClose,
25
+ initialJs,
26
+ onSave,
27
+ onClear,
28
+ }: JsEditorDialogProps) {
29
+ const [js, setJs] = useState(initialJs);
30
+ const [hasChanges, setHasChanges] = useState(false);
31
+
32
+ // ダイアログが開かれたときに初期値をセット
33
+ useEffect(() => {
34
+ if (isOpen) {
35
+ setJs(initialJs);
36
+ setHasChanges(false);
37
+ }
38
+ }, [isOpen, initialJs]);
39
+
40
+ // JS変更ハンドラ
41
+ const handleJsChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
42
+ setJs(e.target.value);
43
+ setHasChanges(e.target.value !== initialJs);
44
+ }, [initialJs]);
45
+
46
+ // 保存ハンドラ
47
+ const handleSave = useCallback(() => {
48
+ onSave(js);
49
+ onClose();
50
+ }, [js, onSave, onClose]);
51
+
52
+ // クリアハンドラ
53
+ const handleClear = useCallback(() => {
54
+ if (onClear) {
55
+ onClear();
56
+ onClose();
57
+ }
58
+ }, [onClear, onClose]);
59
+
60
+ // リセットハンドラ
61
+ const handleReset = useCallback(() => {
62
+ setJs(initialJs);
63
+ setHasChanges(false);
64
+ }, [initialJs]);
65
+
66
+ // JSの行数を計算
67
+ const lineCount = js.split('\n').length;
68
+
69
+ return (
70
+ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
71
+ <DialogContent className="sm:max-w-[900px] w-[90vw] h-[90vh] max-h-[90vh] bg-[#2c2c2c] border-[#444444] text-white flex flex-col">
72
+ <DialogHeader className="flex-shrink-0">
73
+ <DialogTitle className="text-white flex items-center gap-2">
74
+ <Braces className="w-5 h-5" />
75
+ インポートJavaScript編集
76
+ </DialogTitle>
77
+ </DialogHeader>
78
+
79
+ <div className="flex-1 min-h-0 flex flex-col gap-3 overflow-hidden">
80
+ {/* 警告 */}
81
+ <div className="text-xs text-yellow-400 flex items-center gap-2 p-2 bg-yellow-500/10 rounded border border-yellow-500/30 flex-shrink-0">
82
+ <AlertTriangle className="w-4 h-4 flex-shrink-0" />
83
+ <span>
84
+ JavaScriptはプレビュー時に実行されます。信頼できるソースからのみインポートしてください。
85
+ </span>
86
+ </div>
87
+
88
+ {/* 情報 */}
89
+ <div className="text-xs text-gray-400 flex items-center justify-between flex-shrink-0">
90
+ <span>
91
+ インポート時に抽出されたJavaScriptを編集できます。
92
+ 変更はリアルタイムでプレビューに反映されます。
93
+ </span>
94
+ <span className="text-gray-500">
95
+ {lineCount}行 / {js.length}文字
96
+ </span>
97
+ </div>
98
+
99
+ {/* JSエディタ */}
100
+ <div className="flex-1 min-h-0 overflow-hidden">
101
+ <Textarea
102
+ value={js}
103
+ onChange={handleJsChange}
104
+ className="w-full h-full bg-[#1e1e1e] border-[#444444] text-white font-mono text-sm resize-none"
105
+ placeholder={`// 変数や関数の定義
106
+ const greeting = "Hello, World!";
107
+
108
+ function handleClick(event) {
109
+ console.log("Clicked:", event.target);
110
+ }
111
+
112
+ // DOM操作
113
+ document.addEventListener('DOMContentLoaded', () => {
114
+ const buttons = document.querySelectorAll('.btn');
115
+ buttons.forEach(btn => {
116
+ btn.addEventListener('click', handleClick);
117
+ });
118
+ });`}
119
+ />
120
+ </div>
121
+
122
+ {/* JSがない場合のヒント */}
123
+ {!initialJs && !js && (
124
+ <div className="text-xs text-gray-500 p-3 bg-[#1e1e1e] rounded border border-[#444444] flex-shrink-0">
125
+ URLからHTMLをインポートすると、ページのJavaScriptも自動的に抽出されます。
126
+ 手動でJavaScriptを追加することもできます。
127
+ </div>
128
+ )}
129
+ </div>
130
+
131
+ <DialogFooter className="flex items-center justify-between sm:justify-between gap-2 flex-shrink-0">
132
+ <div className="flex items-center gap-2">
133
+ {onClear && initialJs && (
134
+ <Button
135
+ variant="ghost"
136
+ onClick={handleClear}
137
+ className="text-red-400 hover:text-red-300 hover:bg-red-500/20"
138
+ >
139
+ <Trash2 className="w-4 h-4 mr-2" />
140
+ JSを削除
141
+ </Button>
142
+ )}
143
+ </div>
144
+ <div className="flex items-center gap-2">
145
+ {hasChanges && (
146
+ <Button
147
+ variant="ghost"
148
+ onClick={handleReset}
149
+ className="text-gray-400 hover:text-white"
150
+ >
151
+ <RefreshCw className="w-4 h-4 mr-2" />
152
+ リセット
153
+ </Button>
154
+ )}
155
+ <Button
156
+ variant="ghost"
157
+ onClick={onClose}
158
+ className="text-gray-400 hover:text-white"
159
+ >
160
+ キャンセル
161
+ </Button>
162
+ <Button
163
+ onClick={handleSave}
164
+ className="bg-[#0d99ff] hover:bg-[#0c8ce9]"
165
+ >
166
+ 適用
167
+ </Button>
168
+ </div>
169
+ </DialogFooter>
170
+ </DialogContent>
171
+ </Dialog>
172
+ );
173
+ }
174
+
175
+ export default JsEditorDialog;