@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,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;
|