|
7 | 7 | import type * as OBC from '@thatopen/components'; |
8 | 8 | import type * as BUI from '@thatopen/ui'; |
9 | 9 | import type * as THREE from 'three'; |
10 | | -type DataSet<_A = any, _B = any, _C = any, _D = any> = any; |
11 | | -import type { EngineServicesClient, ProjectData } from '../'; |
| 10 | +type DataMap<_A = any, _B = any, _C = any, _D = any> = any; |
| 11 | +import type { EngineServicesClient, Item, ProjectData } from '../'; |
12 | 12 |
|
13 | 13 | export type ComponentSetup = (components: OBC.Components) => Promise<void> | void; |
14 | 14 | export type ComponentSetups = { |
@@ -118,6 +118,115 @@ export type AppManager = InstanceType<typeof _AppManager>; |
118 | 118 | */ |
119 | 119 | export const AppManager = { uuid: '2e32d873-02c9-421c-8743-d8a5ca6ad38a' } as typeof _AppManager & { uuid: '2e32d873-02c9-421c-8743-d8a5ca6ad38a' }; |
120 | 120 |
|
| 121 | +/** |
| 122 | + * Which action buttons to expose per file row. All default to `true`. |
| 123 | + */ |
| 124 | +export interface FileListActions { |
| 125 | + download?: boolean; |
| 126 | + rename?: boolean; |
| 127 | + delete?: boolean; |
| 128 | +} |
| 129 | +/** |
| 130 | + * Config passed to {@link FileList.create}. |
| 131 | + */ |
| 132 | +export interface FileListConfig { |
| 133 | + /** Services client used to list and mutate files. */ |
| 134 | + client: EngineServicesClient; |
| 135 | + /** Optional folder ID to scope the list. If omitted, lists root files. */ |
| 136 | + folderId?: string; |
| 137 | + /** Which action buttons to show per row. Defaults to all enabled. */ |
| 138 | + actions?: FileListActions; |
| 139 | + /** Called when a file row is clicked (not an action button). */ |
| 140 | + onFileClick?: (file: Item) => void; |
| 141 | +} |
| 142 | +/** |
| 143 | + * The result returned by {@link FileList.create}. |
| 144 | + */ |
| 145 | +export interface FileListInstance { |
| 146 | + /** The DOM element to mount somewhere in your layout. */ |
| 147 | + element: HTMLElement; |
| 148 | + /** Reloads the list from the services client. */ |
| 149 | + refresh: () => Promise<void>; |
| 150 | + /** Changes the folder scope and reloads. Pass `undefined` for the root. */ |
| 151 | + setFolder: (folderId?: string) => Promise<void>; |
| 152 | + /** Removes the element and forgets the instance. */ |
| 153 | + dispose: () => void; |
| 154 | +} |
| 155 | + |
| 156 | +/** |
| 157 | + * Built-in component that renders a file list backed by the services client, |
| 158 | + * with built-in download / rename / delete actions. |
| 159 | + * |
| 160 | + * The backing data comes from {@link EngineServicesClient.listFiles}, so the |
| 161 | + * list stays in sync with the storage service. Each call to {@link create} |
| 162 | + * returns an independent instance with its own DOM element. |
| 163 | + * |
| 164 | + * @example |
| 165 | + * ```ts |
| 166 | + * const fileList = components.get(FileList); |
| 167 | + * const { element, refresh } = fileList.create({ |
| 168 | + * client: app.client, |
| 169 | + * folderId: someFolderId, |
| 170 | + * }); |
| 171 | + * container.appendChild(element); |
| 172 | + * ``` |
| 173 | + */ |
| 174 | +declare class _FileList extends OBC.Component { |
| 175 | + static readonly uuid: "b0b5e2a2-0b3a-4a6b-8b1c-0b1c4a6b8b1c"; |
| 176 | + enabled: boolean; |
| 177 | + readonly name = "FileList"; |
| 178 | + private _instances; |
| 179 | + /** |
| 180 | + * Creates a new file-list instance. Returns the DOM element to mount plus a |
| 181 | + * `refresh` handle to reload the list, a `setFolder` to change scope, and a |
| 182 | + * `dispose` to remove it. |
| 183 | + */ |
| 184 | + create(config: FileListConfig): FileListInstance; |
| 185 | + /** |
| 186 | + * Removes every instance this manager has created. |
| 187 | + */ |
| 188 | + dispose(): void; |
| 189 | +} |
| 190 | + |
| 191 | +/** |
| 192 | + * Built-in component that renders a file list backed by the services client, |
| 193 | + * with built-in download / rename / delete actions. |
| 194 | + * |
| 195 | + * The backing data comes from {@link EngineServicesClient.listFiles}, so the |
| 196 | + * list stays in sync with the storage service. Each call to {@link create} |
| 197 | + * returns an independent instance with its own DOM element. |
| 198 | + * |
| 199 | + * @example |
| 200 | + * ```ts |
| 201 | + * const fileList = components.get(FileList); |
| 202 | + * const { element, refresh } = fileList.create({ |
| 203 | + * client: app.client, |
| 204 | + * folderId: someFolderId, |
| 205 | + * }); |
| 206 | + * container.appendChild(element); |
| 207 | + * ``` |
| 208 | + */ |
| 209 | +export type FileList = InstanceType<typeof _FileList>; |
| 210 | +/** |
| 211 | + * Built-in component that renders a file list backed by the services client, |
| 212 | + * with built-in download / rename / delete actions. |
| 213 | + * |
| 214 | + * The backing data comes from {@link EngineServicesClient.listFiles}, so the |
| 215 | + * list stays in sync with the storage service. Each call to {@link create} |
| 216 | + * returns an independent instance with its own DOM element. |
| 217 | + * |
| 218 | + * @example |
| 219 | + * ```ts |
| 220 | + * const fileList = components.get(FileList); |
| 221 | + * const { element, refresh } = fileList.create({ |
| 222 | + * client: app.client, |
| 223 | + * folderId: someFolderId, |
| 224 | + * }); |
| 225 | + * container.appendChild(element); |
| 226 | + * ``` |
| 227 | + */ |
| 228 | +export const FileList = { uuid: 'b0b5e2a2-0b3a-4a6b-8b1c-0b1c4a6b8b1c' } as typeof _FileList & { uuid: 'b0b5e2a2-0b3a-4a6b-8b1c-0b1c4a6b8b1c' }; |
| 229 | + |
121 | 230 | /** |
122 | 231 | * A simple test component to validate the built-in component pipeline. |
123 | 232 | * Replace this with real components once the infrastructure is verified. |
@@ -150,6 +259,108 @@ export type HelloWorld = InstanceType<typeof _HelloWorld>; |
150 | 259 | */ |
151 | 260 | export const HelloWorld = { uuid: '2c4ae432-fc24-43e9-9783-0c960c674e96' } as typeof _HelloWorld & { uuid: '2c4ae432-fc24-43e9-9783-0c960c674e96' }; |
152 | 261 |
|
| 262 | +/** |
| 263 | + * Config passed to {@link TabbedNavigation.create}. All fields are optional. |
| 264 | + */ |
| 265 | +export interface TabbedNavigationConfig { |
| 266 | + /** Hide the text labels and show only icons. Defaults to `false`. */ |
| 267 | + iconsOnly?: boolean; |
| 268 | + /** |
| 269 | + * Fallback icon used when a layout doesn't declare one in its definition. |
| 270 | + * Defaults to `"mdi:view-dashboard"`. |
| 271 | + */ |
| 272 | + fallbackIcon?: string; |
| 273 | + /** Called after the user switches to a different layout via the tabs. */ |
| 274 | + onLayoutChange?: (layoutName: string) => void; |
| 275 | +} |
| 276 | +/** |
| 277 | + * The result returned by {@link TabbedNavigation.create}. |
| 278 | + */ |
| 279 | +export interface TabbedNavigationInstance { |
| 280 | + /** The tab-bar DOM element. Mount it inside an AppManager grid slot. */ |
| 281 | + element: HTMLElement; |
| 282 | + /** Re-reads the grid's layouts and re-renders the tabs. Call after mutating `grid.layouts`. */ |
| 283 | + refresh: () => void; |
| 284 | + /** Tears down listeners and removes the element. */ |
| 285 | + dispose: () => void; |
| 286 | +} |
| 287 | + |
| 288 | +/** |
| 289 | + * Built-in tabbed navigation for a platform app. Renders one tab per layout |
| 290 | + * declared on the `AppManager`'s grid, with the current layout highlighted. |
| 291 | + * Clicking a tab switches `grid.layout`, which swaps the visible area set. |
| 292 | + * |
| 293 | + * This is the default top-of-app navigation pattern the platform proposes — |
| 294 | + * Claude Code should use it by default when scaffolding apps with multiple |
| 295 | + * layouts, instead of hand-rolling a custom tab bar. |
| 296 | + * |
| 297 | + * Must be called after `AppManager.init()` has mounted the grid. |
| 298 | + * |
| 299 | + * @example |
| 300 | + * ```ts |
| 301 | + * const app = components.get(AppManager<MyApp>); |
| 302 | + * await app.init({ ... }); |
| 303 | + * const tabs = components.get(TabbedNavigation); |
| 304 | + * const { element } = tabs.create(); |
| 305 | + * // Mount `element` in a grid area that sits above the main content. |
| 306 | + * ``` |
| 307 | + */ |
| 308 | +declare class _TabbedNavigation extends OBC.Component { |
| 309 | + static readonly uuid: "3c2a9f34-8b6c-4d1b-8f7a-1e4a5b2c9f08"; |
| 310 | + enabled: boolean; |
| 311 | + readonly name = "TabbedNavigation"; |
| 312 | + private _instances; |
| 313 | + /** |
| 314 | + * Creates a new tab-bar instance bound to the current `AppManager` grid. |
| 315 | + */ |
| 316 | + create(config?: TabbedNavigationConfig): TabbedNavigationInstance; |
| 317 | + /** Disposes every instance this manager has created. */ |
| 318 | + dispose(): void; |
| 319 | +} |
| 320 | + |
| 321 | +/** |
| 322 | + * Built-in tabbed navigation for a platform app. Renders one tab per layout |
| 323 | + * declared on the `AppManager`'s grid, with the current layout highlighted. |
| 324 | + * Clicking a tab switches `grid.layout`, which swaps the visible area set. |
| 325 | + * |
| 326 | + * This is the default top-of-app navigation pattern the platform proposes — |
| 327 | + * Claude Code should use it by default when scaffolding apps with multiple |
| 328 | + * layouts, instead of hand-rolling a custom tab bar. |
| 329 | + * |
| 330 | + * Must be called after `AppManager.init()` has mounted the grid. |
| 331 | + * |
| 332 | + * @example |
| 333 | + * ```ts |
| 334 | + * const app = components.get(AppManager<MyApp>); |
| 335 | + * await app.init({ ... }); |
| 336 | + * const tabs = components.get(TabbedNavigation); |
| 337 | + * const { element } = tabs.create(); |
| 338 | + * // Mount `element` in a grid area that sits above the main content. |
| 339 | + * ``` |
| 340 | + */ |
| 341 | +export type TabbedNavigation = InstanceType<typeof _TabbedNavigation>; |
| 342 | +/** |
| 343 | + * Built-in tabbed navigation for a platform app. Renders one tab per layout |
| 344 | + * declared on the `AppManager`'s grid, with the current layout highlighted. |
| 345 | + * Clicking a tab switches `grid.layout`, which swaps the visible area set. |
| 346 | + * |
| 347 | + * This is the default top-of-app navigation pattern the platform proposes — |
| 348 | + * Claude Code should use it by default when scaffolding apps with multiple |
| 349 | + * layouts, instead of hand-rolling a custom tab bar. |
| 350 | + * |
| 351 | + * Must be called after `AppManager.init()` has mounted the grid. |
| 352 | + * |
| 353 | + * @example |
| 354 | + * ```ts |
| 355 | + * const app = components.get(AppManager<MyApp>); |
| 356 | + * await app.init({ ... }); |
| 357 | + * const tabs = components.get(TabbedNavigation); |
| 358 | + * const { element } = tabs.create(); |
| 359 | + * // Mount `element` in a grid area that sits above the main content. |
| 360 | + * ``` |
| 361 | + */ |
| 362 | +export const TabbedNavigation = { uuid: '3c2a9f34-8b6c-4d1b-8f7a-1e4a5b2c9f08' } as typeof _TabbedNavigation & { uuid: '3c2a9f34-8b6c-4d1b-8f7a-1e4a5b2c9f08' }; |
| 363 | + |
153 | 364 | export interface ColorsPaletteState { |
154 | 365 | components: OBC.Components; |
155 | 366 | onApply: (color: string, name?: string) => void | Promise<void>; |
@@ -367,13 +578,16 @@ export declare class UIDataMap<T extends UICustomShape> { |
367 | 578 | get<K extends keyof T & string>(key: K): UIFactoryMap<T>[K]; |
368 | 579 | get(key: keyof T & string): UIFactoryMap<T>[keyof T]; |
369 | 580 | } |
| 581 | +export type UIFactoryInstance<TElement extends HTMLElement, TState extends Record<string, any>> = [TElement, BUI.UpdateFunction<TState>, BUI.ComponentUtils<TState>]; |
370 | 582 | export declare abstract class UIFactory<TElement extends HTMLElement, TState extends Record<string, any>> { |
371 | | - readonly instances: DataSet<[TElement, BUI.UpdateFunction<TState>, BUI.ComponentUtils<TState>]>; |
| 583 | + readonly instances: DataMap<string, UIFactoryInstance<TElement, TState>>; |
372 | 584 | abstract readonly template: BUI.StatefullComponent<TState>; |
373 | | - create(state: TState): [element: TElement, update: BUI.UpdateFunction<TState>, utils: BUI.ComponentUtils<TState>]; |
| 585 | + create(state: TState, options?: { |
| 586 | + id?: string; |
| 587 | + }): UIFactoryInstance<TElement, TState>; |
374 | 588 | updateInstances(state?: Partial<TState>): void; |
375 | 589 | } |
376 | | -export declare function createFactory<TEl extends HTMLElement, TState extends Record<string, any>>(template: BUI.StatefullComponent<TState>, onInstanceCreated?: (item: [TEl, BUI.UpdateFunction<TState>, BUI.ComponentUtils<TState>]) => void): UIFactory<TEl, TState>; |
| 590 | +export declare function createFactory<TEl extends HTMLElement, TState extends Record<string, any>>(template: BUI.StatefullComponent<TState>, onInstanceCreated?: (item: UIFactoryInstance<TEl, TState>) => void): UIFactory<TEl, TState>; |
377 | 591 |
|
378 | 592 | declare class _UIManager<TCustom extends UICustomShape = UICustomShape> extends OBC.Component { |
379 | 593 | static readonly uuid: "234f1416-528d-452a-9cc9-a16c5239b2eb"; |
@@ -489,6 +703,12 @@ declare class _ViewportsManager extends OBC.Component { |
489 | 703 | * model loading for the given world. |
490 | 704 | */ |
491 | 705 | private _initFragments; |
| 706 | + /** |
| 707 | + * Grows the camera's far plane so it always encompasses every loaded |
| 708 | + * model. Without this, large models clip against the default far plane |
| 709 | + * and disappear from view. |
| 710 | + */ |
| 711 | + private _expandCameraFar; |
492 | 712 | } |
493 | 713 |
|
494 | 714 | /** |
|
0 commit comments