|
6 | 6 | * |
7 | 7 | * Endpoints are per-app, so every I/O path is a callback — no product route is |
8 | 8 | * hardcoded: |
9 | | - * - `onUploadImage(file) => Promise<url>` — the host stores the file and returns |
10 | | - * the src to insert. The url MUST be http(s) or a rooted `/api/` path |
11 | | - * (enforced by `assertSceneMediaSrc` before insertion); a `data:` url is |
12 | | - * rejected, matching the scene model's media boundary. |
| 9 | + * - `onUploadImage(file) => Promise<url>` — optional. When provided, the host |
| 10 | + * stores the file and returns the src to insert. The url MUST be http(s) or |
| 11 | + * a rooted `/api/` path (enforced by `assertSceneMediaSrc` before insertion); |
| 12 | + * a `data:` url is rejected, matching the scene model's media boundary. |
13 | 13 | * - `loadGenerations?()` — optional provider for "already generated in this |
14 | 14 | * workspace" images; omit to hide the tab. |
15 | 15 | * - `templates?` — optional template set; defaults to {@link DEFAULT_INSERT_TEMPLATES}. |
@@ -50,7 +50,7 @@ export interface CanvasInsertPanelProps { |
50 | 50 | /** Submit operations through the host's apply pipeline. */ |
51 | 51 | onInsert(operations: SceneOperation[]): Promise<unknown> |
52 | 52 | /** Store an uploaded file and return its src (http(s) or rooted `/api/`). */ |
53 | | - onUploadImage(file: File): Promise<string> |
| 53 | + onUploadImage?(file: File): Promise<string> |
54 | 54 | /** Optional provider for the Generations tab; omit to hide it. */ |
55 | 55 | loadGenerations?(): Promise<InsertGeneration[]> |
56 | 56 | /** Drop-in templates; defaults to the built-in starter set. */ |
@@ -181,14 +181,26 @@ export function CanvasInsertPanel({ |
181 | 181 | accept = DEFAULT_ACCEPT, |
182 | 182 | className, |
183 | 183 | }: CanvasInsertPanelProps) { |
184 | | - const [tab, setTab] = useState<Tab>('uploads') |
| 184 | + const [tab, setTab] = useState<Tab>(() => (templates.length > 0 ? 'templates' : 'uploads')) |
185 | 185 | const [busy, setBusy] = useState(false) |
186 | 186 | const [error, setError] = useState('') |
187 | 187 | const [dragOver, setDragOver] = useState(false) |
188 | 188 | const [generations, setGenerations] = useState<InsertGeneration[]>([]) |
189 | 189 | const [generationsLoaded, setGenerationsLoaded] = useState(false) |
190 | 190 | const fileInputRef = useRef<HTMLInputElement | null>(null) |
191 | 191 |
|
| 192 | + useEffect(() => { |
| 193 | + if (tab === 'uploads' && !onUploadImage) { |
| 194 | + setTab(templates.length > 0 ? 'templates' : loadGenerations ? 'generations' : 'uploads') |
| 195 | + } |
| 196 | + if (tab === 'templates' && templates.length === 0) { |
| 197 | + setTab(onUploadImage ? 'uploads' : loadGenerations ? 'generations' : 'uploads') |
| 198 | + } |
| 199 | + if (tab === 'generations' && !loadGenerations) { |
| 200 | + setTab(templates.length > 0 ? 'templates' : onUploadImage ? 'uploads' : 'templates') |
| 201 | + } |
| 202 | + }, [tab, templates.length, onUploadImage, loadGenerations]) |
| 203 | + |
192 | 204 | useEffect(() => { |
193 | 205 | if (tab !== 'generations' || generationsLoaded || !loadGenerations) return |
194 | 206 | let cancelled = false |
@@ -217,6 +229,7 @@ export function CanvasInsertPanel({ |
217 | 229 |
|
218 | 230 | async function handleFiles(files: FileList | File[]) { |
219 | 231 | if (!canWrite || busy) return |
| 232 | + if (!onUploadImage) return |
220 | 233 | const list = Array.from(files).filter((f) => f.type.startsWith('image/')) |
221 | 234 | if (list.length === 0) { |
222 | 235 | setError('Only image files can be added to the canvas') |
@@ -250,7 +263,7 @@ export function CanvasInsertPanel({ |
250 | 263 | } |
251 | 264 |
|
252 | 265 | const tabs: Array<{ id: Tab; label: string; icon: (p: { className?: string }) => ReactElement; show: boolean }> = [ |
253 | | - { id: 'uploads', label: 'Uploads', icon: ImageGlyph, show: true }, |
| 266 | + { id: 'uploads', label: 'Uploads', icon: ImageGlyph, show: !!onUploadImage }, |
254 | 267 | { id: 'templates', label: 'Templates', icon: ShapesGlyph, show: templates.length > 0 }, |
255 | 268 | { id: 'generations', label: 'Generations', icon: SparkleGlyph, show: !!loadGenerations }, |
256 | 269 | ] |
|
0 commit comments