From 807cebad4d05108a9b75b48d5ce3c7538b7d61bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Gonz=C3=A1lez=20Viegas?= Date: Sat, 6 Jun 2026 20:59:38 +0200 Subject: [PATCH] feat: add bim-beta template (beta engine libs) + drop ui-obc; v0.0.3 - New `bim-beta` template: @thatopen-platform/components-beta / components-front-beta / fragments-beta + public @thatopen/ui, no ui-obc (matches the platform's library line) - Remove @thatopen/ui-obc from the `bim` template (deprecated) - Register `bim-beta` in `thatopen create` - AGENTS.md / docs/scaffolding.md / shared AGENTS: ask about beta access, use `-t bim-beta`; note the scaffold is already a working viewer - Fix `build:cli` (drop the missing copy-templates.mjs step) Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 10 + docs/scaffolding.md | 19 +- package.json | 4 +- src/cli/commands/create.ts | 12 +- src/cli/templates/bim-beta/CONTEXT.md | 241 ++++++++++++++++++ src/cli/templates/bim-beta/package.json | 25 ++ src/cli/templates/bim-beta/src/app.ts | 16 ++ .../src/bim-components/CloudRunner/index.ts | 91 +++++++ .../bim-components/CloudRunner/src/index.ts | 1 + .../bim-components/CloudRunner/src/types.ts | 5 + .../bim-beta/src/bim-components/index.ts | 1 + src/cli/templates/bim-beta/src/globals.ts | 1 + src/cli/templates/bim-beta/src/main.ts | 89 +++++++ .../bim-beta/src/setups/cloud-runner.ts | 13 + .../templates/bim-beta/src/setups/index.ts | 3 + .../bim-beta/src/setups/ui-manager.ts | 27 ++ .../bim-beta/src/setups/viewports-manager.ts | 22 ++ .../ui-components/app-info-section/index.ts | 26 ++ .../app-info-section/src/index.ts | 1 + .../app-info-section/src/types.ts | 15 ++ .../cloud-runner-section/index.ts | 37 +++ .../cloud-runner-section/src/index.ts | 1 + .../cloud-runner-section/src/types.ts | 14 + .../bim-beta/src/ui-components/index.ts | 2 + src/cli/templates/bim/CONTEXT.md | 5 +- src/cli/templates/bim/package.json | 1 - src/cli/templates/bim/src/main.ts | 3 +- src/cli/templates/shared/AGENTS.md | 7 + 28 files changed, 681 insertions(+), 11 deletions(-) create mode 100644 src/cli/templates/bim-beta/CONTEXT.md create mode 100644 src/cli/templates/bim-beta/package.json create mode 100644 src/cli/templates/bim-beta/src/app.ts create mode 100644 src/cli/templates/bim-beta/src/bim-components/CloudRunner/index.ts create mode 100644 src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/index.ts create mode 100644 src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/types.ts create mode 100644 src/cli/templates/bim-beta/src/bim-components/index.ts create mode 100644 src/cli/templates/bim-beta/src/globals.ts create mode 100644 src/cli/templates/bim-beta/src/main.ts create mode 100644 src/cli/templates/bim-beta/src/setups/cloud-runner.ts create mode 100644 src/cli/templates/bim-beta/src/setups/index.ts create mode 100644 src/cli/templates/bim-beta/src/setups/ui-manager.ts create mode 100644 src/cli/templates/bim-beta/src/setups/viewports-manager.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/app-info-section/index.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/app-info-section/src/index.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/app-info-section/src/types.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/index.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/index.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/types.ts create mode 100644 src/cli/templates/bim-beta/src/ui-components/index.ts diff --git a/AGENTS.md b/AGENTS.md index bae7132..56bd117 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,9 +13,19 @@ detailed doc for whatever you're doing. Open only the doc you need — don't loa ## How to work +- 🚩 **BEFORE creating anything, ask the user about beta access — this is important.** Ask: + *"Do you have That Open beta access? It gives you the latest libraries, features and progress — + and it's what the platform currently runs. Do you want to use it?"* **If yes, scaffold with the + `bim-beta` template** (`thatopen create -t bim-beta`). Founding members have **permanent** beta + access, so for them the answer is almost always yes. Using the matching library line avoids + runtime errors against the platform. (Beta packages are private — the user needs their beta + access token configured in npm, or `npm install` will fail.) See + [docs/scaffolding.md](./docs/scaffolding.md). - **New app or component?** The **first step is always the CLI** (`thatopen create`) — never hand-write the scaffold. See [docs/cli-setup.md](./docs/cli-setup.md) then [docs/scaffolding.md](./docs/scaffolding.md). +- **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties). + **Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch. - **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask. - Prefer existing engine functionality over custom code (see rule 2). diff --git a/docs/scaffolding.md b/docs/scaffolding.md index 59383f0..0e29ea0 100644 --- a/docs/scaffolding.md +++ b/docs/scaffolding.md @@ -22,11 +22,28 @@ Pass `--template ` to choose a template. The default is `bim`. | Template | When to use | |---|---| -| `bim` (default) | Standard BIM viewer app — Three.js viewport, BIM viewer, platform UI components. This is the right starting point for almost every app. | +| `bim` (default) | Standard BIM viewer app on the public engine libraries. | +| `bim-beta` | Same as `bim` but on the private **beta** engine libraries (`@thatopen-platform/*-beta`) — the line the platform currently runs. Use this for founding members (see below). | | `default` | Minimal shell — just shows platform context. Use only when you explicitly want to start from scratch without any viewer. | If no template is specified, use `bim`. +## Beta libraries (founding members) — use the `bim-beta` template + +**Before scaffolding, ask the user whether they have beta access** and want the latest engine +libraries. If so, scaffold with the `bim-beta` template: + +```bash +thatopen create -t bim-beta +``` + +`bim-beta` is the `bim` viewer wired directly to the private `@thatopen-platform/*-beta` packages — +the **same library line the platform currently runs**. Prefer it whenever the user has beta access: +the public `bim` template uses older libraries that can error at runtime against a beta platform. + +Founding members have **permanent** beta access. The beta packages are private, so the user must +have their beta access token configured in npm, or `npm install` will fail with a 401/403. + ## What the scaffold produces The `bim` template generates the full structure described in the **Project Structure** section of [./app-architecture.md](./app-architecture.md): diff --git a/package.json b/package.json index 99d0368..ffa1467 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "access": "public" }, "private": false, - "version": "0.0.2", + "version": "0.0.3", "main": "dist/index.cjs.js", "module": "dist/index.es.js", "types": "dist/index.d.ts", @@ -37,7 +37,7 @@ "build:lib": "tsc && vite build", "test": "vitest run", "test:watch": "vitest", - "build:cli": "vite build --config vite.config.cli.mts && node scripts/copy-templates.mjs", + "build:cli": "vite build --config vite.config.cli.mts", "preview": "vite preview", "test:ui": "vite --config test/vite.config.mts", "test:cli-build-app": "npm run build:cli && node test/setup-test-app.mjs", diff --git a/src/cli/commands/create.ts b/src/cli/commands/create.ts index fa1904a..ddd2a9c 100644 --- a/src/cli/commands/create.ts +++ b/src/cli/commands/create.ts @@ -3,7 +3,7 @@ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, renam import { basename, join, resolve } from 'node:path'; import { execSync } from 'node:child_process'; -const TEMPLATES = ['default', 'bim', 'cloud', 'test', 'cloud-test'] as const; +const TEMPLATES = ['default', 'bim', 'bim-beta', 'cloud', 'test', 'cloud-test'] as const; type Template = (typeof TEMPLATES)[number]; /** Read the library version from package.json so templates stay in sync. */ @@ -83,6 +83,13 @@ export const createCommand = new Command('create') .replace(/"@thatopen\/services": "file:[^"]*"/, `"@thatopen/services": "^${libVersion}"`); writeFileSync(pkgPath, pkg); + const isBeta = template === 'bim-beta'; + if (isBeta) { + console.log(''); + console.log('This template uses the private BETA engine libraries (@thatopen-platform/*-beta).'); + console.log('If install fails with a 401/403, configure your beta access token in npm first.'); + } + // Install dependencies automatically console.log(''); console.log('Installing dependencies...'); @@ -90,6 +97,9 @@ export const createCommand = new Command('create') execSync('npm install', { cwd: targetDir, stdio: 'inherit' }); } catch { console.error('Failed to install dependencies. Run `npm install` manually.'); + if (isBeta) { + console.error('Beta packages are private — if this is an auth error, your beta token may not be configured.'); + } } console.log(''); diff --git a/src/cli/templates/bim-beta/CONTEXT.md b/src/cli/templates/bim-beta/CONTEXT.md new file mode 100644 index 0000000..7bb8290 --- /dev/null +++ b/src/cli/templates/bim-beta/CONTEXT.md @@ -0,0 +1,241 @@ +# ThatOpen BIM App + +This is a BIM (Building Information Modeling) app built for the That Open Platform. +It runs in the browser inside the platform's iframe and has access to a 3D viewer, +UI components, and the platform API. + +## How this app works + +- **Entry point**: `src/main.ts` — runs as an IIFE when the platform loads the app. +- **Build output**: `dist/bundle.js` — a single IIFE file built by Vite. +- **Platform context**: The platform injects `window.__THATOPEN_CONTEXT__` with: + - `appId` — this app's unique ID + - `projectId` — the project this app belongs to + - `accessToken` — Auth0 JWT for API calls + - `apiUrl` — base URL for the That Open API + +## Commands + +```bash +npm run dev # Start dev server (esbuild watch + serve on :4000) +npm run build # Build dist/bundle.js (Vite/Rollup production build) +npm run login # Authenticate with the platform (saves token locally) +npm run publish # Publish to the platform +``` + +### Local development + +Apps run inside the That Open Platform (platform.thatopen.com) within a project — +not as standalone websites. To develop locally: + +1. Run `npm run dev` — this watches source files with esbuild and serves the bundle on port 4000. +2. Open your project on the platform and click the debug button. +3. Live reload is enabled — save a file to rebuild automatically. + +The dev server (`thatopen serve`) uses esbuild for near-instant incremental rebuilds. +**Important**: Do NOT run `vite`, `vite build --watch`, or `npx vite` directly for development. +Always use `npm run dev` which runs `thatopen serve` under the hood. + +## Key libraries + +| Package | Import | Purpose | +|---------|--------|---------| +| `@thatopen-platform/components-beta` | `OBC` | BIM engine — components, fragments, worlds | +| `@thatopen-platform/components-front-beta` | `OBF` | Front-end BIM components (Highlighter, measurements, etc.) | +| `@thatopen-platform/fragments-beta` | `FRAGS` | Fragment geometry format | +| `@thatopen/ui` | `BUI` | UI web components (``, ``, etc.) | +| `three` | `THREE` | 3D rendering engine | +| `@thatopen/services` | `EngineServicesClient` | Platform API client + built-in components | + +## Architecture pattern + +``` +1. Create EngineServicesClient from platform context +2. Call client.setup(globals, ...builtIns) — creates OBC.Components, + inits BUI, loads built-in components, calls components.init() +3. Create viewport(s) and UI elements +4. Configure AppManager with elements + layouts +5. Call app.init() +``` + +## Built-in components + +Built-in components are platform-hosted UI modules loaded at runtime via the API client. +They are fetched, evaluated, and registered with the OBC component system. + +| Component | Purpose | +|-----------|---------| +| **AppManager** | App shell — CSS grid layout system with sidebar for switching layouts | +| **ViewportsManager** | Factory for 3D viewports with pre-configured world (scene, camera, renderer) | +| **LoadModelButton** | Button + dropdown for loading IFC and Fragments files | +| **ViewerToolbar** | Toolbar with Show/Hide/Focus/Isolate actions and color palette | +| **ModelsPanel** | Panel listing loaded models with search bar and load button | +| **ModelsDropdown** | Dropdown selector listing loaded models | +| **ClassificationsList** | Hierarchical table of IFC classification data | +| **ClashesList** | Interactive clash detection results with click-to-highlight | +| **ClippingsList** | Panel listing clipping planes with enable/delete controls | +| **LengthMeasuringsList** | Panel listing length measurements with cumulative total | +| **AreaMeasuringsList** | Panel listing area measurements with area/perimeter totals | +| **ColorsPalette** | Color picker grid with custom input and Highlighter styles | +| **HighlightersList** | Panel listing Highlighter styles with manage/apply actions | +| **QtoComparisonList** | Side-by-side quantity comparison for two selected elements | +| **QueriesHierarchy** | Recursive multi-level query browser for IFC data | +| **CustomViewLegend** | Color legend overlay with colored circles and labels | +| **ScreenshotAnnotator** | Modal for annotating screenshots (arrows, text, freehand) via MarkerJS | + +**Full API reference**: Each component has detailed JSDoc with `@example` blocks in the +`@thatopen/services` package source (`src/built-in/index.ts`). Read that file for config +interfaces, method signatures, and code examples. + +### Loading pattern + +Use `setup` to create the component system and load built-in components in one call: + +```ts +import { PlatformClient, AppManager, ViewportsManager } from "@thatopen/services"; + +const client = PlatformClient.fromPlatformContext(); + +// Creates OBC.Components, inits BUI, loads built-ins, calls components.init() +const { components } = await client.setup( + { OBC, OBF, BUI, THREE, FRAGS }, + AppManager, ViewportsManager, +); + +const app = components.get(AppManager); +const viewports = components.get(ViewportsManager); +``` + +You can also load components individually if needed: + +```ts +// Batch load (parallel) +await client.initBuiltInComponents(components, AppManager, ViewportsManager); + +// Or one at a time +await client.initBuiltInComponent(AppManager, components); +``` + +### Required globals per component + +| Component | Globals to pass | Extra npm packages needed | +|-----------|----------------|--------------------------| +| AppManager | `{ OBC, BUI }` | — | +| ViewportsManager | `{ OBC, BUI, THREE, FRAGS }` | — | +| LoadModelButton | `{ OBC, BUI }` | — | +| ModelsDropdown | `{ OBC, BUI }` | — | +| ViewerToolbar | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | +| ColorsPalette | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | +| ClashesList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | +| ClassificationsList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | +| ClippingsList | `{ OBC, BUI }` | — | +| HighlightersList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | +| LengthMeasuringsList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | +| AreaMeasuringsList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | +| QtoComparisonList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | +| QueriesHierarchy | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | +| CustomViewLegend | `{ OBC, BUI }` | — | +| ScreenshotAnnotator | `{ OBC, BUI, MARKERJS }` | `@markerjs/markerjs3` | + +### Global abbreviations + +```ts +import * as OBC from "@thatopen-platform/components-beta"; +import * as OBF from "@thatopen-platform/components-front-beta"; // needed for Toolbar, Highlighters, Clashes, etc. +import * as BUI from "@thatopen/ui"; +import * as THREE from "three"; +import * as FRAGS from "@thatopen-platform/fragments-beta"; +import * as MARKERJS from "@markerjs/markerjs3"; // needed for ScreenshotAnnotator +``` + +### AppManager — app shell with CSS grid layouts + +Creates a grid-based layout system. Define named element slots and named layouts. +A sidebar for switching layouts appears automatically when multiple layouts exist. + +```ts +const Layouts = ["Viewer", "Split"]; +const Elements = ["viewer", "panel"]; + +await app.init({ + client, + icons: undefined, // pass Record when using typed App interface with icon keys + grid: (grid: BUI.Grid) => { + grid.elements = { + viewer: viewportElement, + panel: panelFunction, // Can be HTMLElement, () => BUI.TemplateResult, or { template, initialState } + }; + grid.layouts = { + Viewer: { template: `"viewer" 1fr / 1fr` }, + Split: { + template: `"panel viewer" 1fr / 20rem 1fr`, + icon: "solar:settings-bold", + }, + }; + }, +}); +``` + +The `template` string uses CSS `grid-template` shorthand: `"areas" rows / columns`. + +### ViewportsManager — 3D viewport factory + +Creates viewports with pre-configured world (scene, camera, renderer) and auto-initialized fragments. + +```ts +const viewports = components.get(ViewportsManager); +const { element, world } = await viewports.create(); +// element is an HTMLElement to place in a layout slot +// world has world.scene, world.camera, world.renderer +``` + +## Loading a BIM model + +```ts +const fragments = components.get(OBC.FragmentsManager); + +// From URL +const response = await fetch("https://example.com/model.frag"); +const buffer = await response.arrayBuffer(); +await fragments.core.load(buffer, { modelId: "my-model" }); + +// From platform storage +const fileResponse = await client.downloadFile(fileId); +const fileBuffer = await fileResponse.arrayBuffer(); +await fragments.core.load(fileBuffer, { modelId: "my-model" }); +``` + +## EngineServicesClient API (commonly used in apps) + +```ts +// Recommended: auto-reads window.__THATOPEN_CONTEXT__ and sets useBearer: true +const client = PlatformClient.fromPlatformContext(); +console.log(client.context.projectId); // access the platform context + +// Files +const files = await client.listFiles(); +const file = await client.getFile(fileId); +const response = await client.downloadFile(fileId); +await client.createFile({ file: blob, name: "model.ifc", versionTag: "v1" }); + +// Folders +const folders = await client.listFolders(); +await client.createFolder("My Folder"); + +// Execute cloud components +const { executionId } = await client.executeComponent(componentId, { param: "value" }); +client.onExecutionProgress(executionId, (data) => { + // data.progressUpdate — progress percentage + // data.messageUpdate — status messages +}); + +// Test against a local cloud component (requires thatopen local-server running in the component project) +client.localServerUrl = "http://localhost:4001"; +const local = await client.executeComponent("any-id", { param: "value" }); +client.localServerUrl = null; // reset to use the cloud API +``` + +## Configuration + +- `.thatopen` — local config (gitignored). Created by `npm run login`. Contains `accessToken`, `apiUrl`, and `appId` after first publish. +- `vite.config.js` — builds to IIFE format as `dist/bundle.js`. All dependencies are bundled. diff --git a/src/cli/templates/bim-beta/package.json b/src/cli/templates/bim-beta/package.json new file mode 100644 index 0000000..49d6d6f --- /dev/null +++ b/src/cli/templates/bim-beta/package.json @@ -0,0 +1,25 @@ +{ + "name": "{{PROJECT_NAME}}", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "thatopen serve", + "build": "vite build", + "login": "thatopen login --local", + "publish": "thatopen publish" + }, + "dependencies": { + "@markerjs/markerjs3": "^3.9.0", + "@thatopen-platform/components-beta": "latest", + "@thatopen-platform/components-front-beta": "latest", + "@thatopen-platform/fragments-beta": "latest", + "@thatopen/ui": "~3.4.0", + "@thatopen/services": "file:../../../..", + "three": "^0.182.0" + }, + "devDependencies": { + "@types/three": "^0.182.0", + "typescript": "^5.2.0", + "vite": "^5.2.0" + } +} diff --git a/src/cli/templates/bim-beta/src/app.ts b/src/cli/templates/bim-beta/src/app.ts new file mode 100644 index 0000000..0784743 --- /dev/null +++ b/src/cli/templates/bim-beta/src/app.ts @@ -0,0 +1,16 @@ +import * as OBC from "@thatopen-platform/components-beta"; +import * as BUI from "@thatopen/ui"; +import { AppManager } from "@thatopen/services"; +import { icons } from "./globals"; +import { AppInfoSectionGridElement, CloudRunnerSectionGridElement } from "./ui-components"; + +export type App = { + icons: (keyof typeof icons)[]; + grid: BUI.Grid< + ["Viewer", "Split"], + ["viewer", AppInfoSectionGridElement, CloudRunnerSectionGridElement] + >; +}; + +export const getAppManager = (components: OBC.Components) => + components.get(AppManager); diff --git a/src/cli/templates/bim-beta/src/bim-components/CloudRunner/index.ts b/src/cli/templates/bim-beta/src/bim-components/CloudRunner/index.ts new file mode 100644 index 0000000..aff728f --- /dev/null +++ b/src/cli/templates/bim-beta/src/bim-components/CloudRunner/index.ts @@ -0,0 +1,91 @@ +import * as OBC from "@thatopen-platform/components-beta"; +import { AppManager } from "@thatopen/services"; +import { CloudRunnerStatus } from "./src"; + +export class CloudRunner extends OBC.Component { + static readonly uuid = "7c4e5d3b-2a1f-4e8d-9b6c-0a3f7e2c5d1b" as const; + + enabled = true; + + readonly onExecutionUpdated = new OBC.Event(); + + // TODO: Replace with your actual component ID after publishing. + componentId = "your-component-id"; + localServerUrl = "http://localhost:4001"; + + // Reactive state — read by the UI template on each render. + status = "Idle"; + progress = 0; + messages: string[] = []; + + constructor(components: OBC.Components) { + super(components); + components.add(CloudRunner.uuid, this); + } + + async run(useLocal: boolean) { + // Resolve client at call time via AppManager — never store it as a field. + const app = this.components.get(AppManager); + const client = app.client; + + client.localServerUrl = useLocal ? this.localServerUrl : null; + + this.status = useLocal ? "Starting (local)..." : "Starting (deployed)..."; + this.progress = 0; + this.messages = []; + this._trigger(); + + try { + const { executionId } = await client.executeComponent(this.componentId, { + greeting: "Hello from the BIM app!", + }); + + this.status = `Running (${executionId.slice(0, 8)}...)`; + this._trigger(); + + // Subscribe to real-time progress updates via WebSocket. + client.onExecutionProgress(executionId, (data) => { + if (data.progressUpdate) { + this.progress = data.progressUpdate.progress; + if (data.progressUpdate.result) { + this.status = `${data.progressUpdate.result}: ${data.progressUpdate.resultMessage ?? "Done"}`; + } + } + if (data.messageUpdate) { + this.messages.push(data.messageUpdate.content); + } + this._trigger(); + }); + + // Poll once after a short delay to catch fast executions that complete + // before the WebSocket subscription is established. + setTimeout(async () => { + try { + const exec = await client.getExecution(executionId); + if (exec.result) { + this.progress = exec.progress; + this.status = `${exec.result}: ${exec.resultMessage ?? "Done"}`; + this._trigger(); + } + } catch { + /* WebSocket handles it */ + } + }, 2000); + } catch (err) { + this.status = `Error: ${err}`; + this._trigger(); + } finally { + client.localServerUrl = null; + } + } + + private _trigger() { + this.onExecutionUpdated.trigger({ + status: this.status, + progress: this.progress, + messages: [...this.messages], + }); + } +} + +export * from "./src"; diff --git a/src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/index.ts b/src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/types.ts b/src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/types.ts new file mode 100644 index 0000000..888f006 --- /dev/null +++ b/src/cli/templates/bim-beta/src/bim-components/CloudRunner/src/types.ts @@ -0,0 +1,5 @@ +export interface CloudRunnerStatus { + status: string; + progress: number; + messages: string[]; +} diff --git a/src/cli/templates/bim-beta/src/bim-components/index.ts b/src/cli/templates/bim-beta/src/bim-components/index.ts new file mode 100644 index 0000000..dee39fe --- /dev/null +++ b/src/cli/templates/bim-beta/src/bim-components/index.ts @@ -0,0 +1 @@ +export * from "./CloudRunner"; diff --git a/src/cli/templates/bim-beta/src/globals.ts b/src/cli/templates/bim-beta/src/globals.ts new file mode 100644 index 0000000..bd5e5c3 --- /dev/null +++ b/src/cli/templates/bim-beta/src/globals.ts @@ -0,0 +1 @@ +export const icons = {}; diff --git a/src/cli/templates/bim-beta/src/main.ts b/src/cli/templates/bim-beta/src/main.ts new file mode 100644 index 0000000..eade424 --- /dev/null +++ b/src/cli/templates/bim-beta/src/main.ts @@ -0,0 +1,89 @@ +import * as THREE from "three"; +import * as OBC from "@thatopen-platform/components-beta"; +import * as OBF from "@thatopen-platform/components-front-beta"; +import * as FRAGS from "@thatopen-platform/fragments-beta"; +import * as BUI from "@thatopen/ui"; +import * as MARKERJS from "@markerjs/markerjs3" +import { + PlatformClient, + AppManager, + ViewportsManager, + UIManager, +} from "@thatopen/services"; + +import { getAppManager } from "./app"; +import { uiManager, cloudRunner, viewportsManager, getUIManager } from "./setups"; + +// Wrap in async function since IIFE format does not support top-level await. +async function main() { + // ─── Platform Client ────────────────────────────────────────── + // Reads auth context from window.__THATOPEN_CONTEXT__ (injected by the + // platform) and creates a client with Bearer auth. + const client = PlatformClient.fromPlatformContext(); + + // ─── Built-in Components ────────────────────────────────────── + // First argument: library globals used by the engine. + // Subsequent arguments: platform built-in components to initialise. + const { components } = await client.setup( + { OBC, OBF, BUI, THREE, FRAGS, MARKERJS }, + { uuid: ViewportsManager.uuid }, + { uuid: AppManager.uuid }, + { uuid: UIManager.uuid }, + ); + + // ─── Viewport ───────────────────────────────────────────────── + // Must run before app.init() so the element is ready for the grid. + const viewerElement = await viewportsManager(components); + + // ─── App Init ───────────────────────────────────────────────── + const app = getAppManager(components); + + await app.init({ + client, + icons: [], + componentSetups: { + // core: runs in parallel before the grid mounts. + core: [uiManager, cloudRunner], + }, + grid: (grid) => { + const uis = getUIManager(components); + + grid.elements = { + viewer: viewerElement, + appInfoSection: { + template: uis.custom.get("appInfoSection").template, + initialState: { components }, + label: "App Info", + icon: "solar:info-circle-bold", + }, + cloudRunnerSection: { + template: uis.custom.get("cloudRunnerSection").template, + initialState: { components }, + label: "Cloud Component", + icon: "solar:code-bold", + }, + }; + + grid.layouts = { + Viewer: { + template: `"viewer" 1fr / 1fr`, + }, + Split: { + template: ` + "tabs:left(appInfoSection, cloudRunnerSection) viewer" 1fr + / 24rem 1fr + `, + icon: "solar:info-circle-bold", + }, + }; + + grid.areaGroups = { + left: { switchersFull: true }, + }; + + grid.layout = "Split"; + }, + }); +} + +main().catch(console.error); diff --git a/src/cli/templates/bim-beta/src/setups/cloud-runner.ts b/src/cli/templates/bim-beta/src/setups/cloud-runner.ts new file mode 100644 index 0000000..13f7ce8 --- /dev/null +++ b/src/cli/templates/bim-beta/src/setups/cloud-runner.ts @@ -0,0 +1,13 @@ +import * as OBC from "@thatopen-platform/components-beta"; +import { CloudRunner } from "../bim-components"; +import { getUIManager } from "./ui-manager"; + +export const cloudRunner = (components: OBC.Components) => { + const runner = components.get(CloudRunner); + const uis = getUIManager(components); + + // Re-render all appInfoSection instances whenever execution state changes. + runner.onExecutionUpdated.add(() => { + uis.custom.get("cloudRunnerSection").updateInstances(); + }); +}; diff --git a/src/cli/templates/bim-beta/src/setups/index.ts b/src/cli/templates/bim-beta/src/setups/index.ts new file mode 100644 index 0000000..84421c2 --- /dev/null +++ b/src/cli/templates/bim-beta/src/setups/index.ts @@ -0,0 +1,3 @@ +export * from "./ui-manager"; +export * from "./viewports-manager"; +export * from "./cloud-runner"; diff --git a/src/cli/templates/bim-beta/src/setups/ui-manager.ts b/src/cli/templates/bim-beta/src/setups/ui-manager.ts new file mode 100644 index 0000000..916ac1a --- /dev/null +++ b/src/cli/templates/bim-beta/src/setups/ui-manager.ts @@ -0,0 +1,27 @@ +import * as OBC from "@thatopen-platform/components-beta"; +import * as BUI from "@thatopen/ui"; +import { UIManager } from "@thatopen/services"; +import { + appInfoSectionTemplate, + AppInfoSectionState, + cloudRunnerSectionTemplate, + CloudRunnerSectionState, +} from "../ui-components"; + +export type CustomUIs = { + appInfoSection: { type: BUI.PanelSection; state: AppInfoSectionState }; + cloudRunnerSection: { type: BUI.PanelSection; state: CloudRunnerSectionState }; +}; + +export const getUIManager = (components: OBC.Components) => + components.get(UIManager); + +export const uiManager = (components: OBC.Components) => { + const uis = getUIManager(components); + uis.registerTemplate("appInfoSection", { + template: appInfoSectionTemplate, + }); + uis.registerTemplate("cloudRunnerSection", { + template: cloudRunnerSectionTemplate, + }); +}; diff --git a/src/cli/templates/bim-beta/src/setups/viewports-manager.ts b/src/cli/templates/bim-beta/src/setups/viewports-manager.ts new file mode 100644 index 0000000..3dcc2d2 --- /dev/null +++ b/src/cli/templates/bim-beta/src/setups/viewports-manager.ts @@ -0,0 +1,22 @@ +import * as OBC from "@thatopen-platform/components-beta"; +import { ViewportsManager } from "@thatopen/services"; + +export const viewportsManager = async (components: OBC.Components) => { + const viewports = components.get(ViewportsManager); + const { element, world } = await viewports.create(); + + // Load a sample model. Replace with client.downloadFile(fileId) to load + // a model from the platform: + // const response = await client.downloadFile(fileId); + // const buffer = await response.arrayBuffer(); + const fragments = components.get(OBC.FragmentsManager); + const file = await fetch( + "https://thatopen.github.io/engine_components/resources/frags/school_arq.frag", + ); + const buffer = await file.arrayBuffer(); + await fragments.core.load(buffer, { modelId: "school_arq" }); + await world.camera.controls.setLookAt(68, 23, -8.5, 21.5, -5.5, 23); + await fragments.core.update(true); + + return element; +}; diff --git a/src/cli/templates/bim-beta/src/ui-components/app-info-section/index.ts b/src/cli/templates/bim-beta/src/ui-components/app-info-section/index.ts new file mode 100644 index 0000000..f6f5e52 --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/app-info-section/index.ts @@ -0,0 +1,26 @@ +import * as BUI from "@thatopen/ui"; +import { AppInfoSectionComponent } from "./src"; +import { getAppManager } from "../../app"; + +export const appInfoSectionTemplate: AppInfoSectionComponent = ({ + components, +}) => { + const app = getAppManager(components); + + const fileItems = + app.projectData?.files.map( + (f: { name: string }) => BUI.html`${f.name}`, + ) ?? []; + + return BUI.html` + + App ID: ${app.client?.context.appId} + Project ID: ${app.client?.context.projectId} + API URL: ${app.client?.context.apiUrl} + Files: ${fileItems.length} + ${fileItems} + + `; +}; + +export * from "./src"; diff --git a/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/index.ts b/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/types.ts b/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/types.ts new file mode 100644 index 0000000..d7046d4 --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/types.ts @@ -0,0 +1,15 @@ +import * as BUI from "@thatopen/ui"; +import * as OBC from "@thatopen-platform/components-beta"; + +export interface AppInfoSectionState { + components: OBC.Components; +} + +export type AppInfoSectionComponent = + BUI.StatefullComponent; + +// Used in App type (app.ts) to type this element slot in the grid. +export type AppInfoSectionGridElement = { + name: "appInfoSection"; + state: AppInfoSectionState; +}; diff --git a/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/index.ts b/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/index.ts new file mode 100644 index 0000000..21f2101 --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/index.ts @@ -0,0 +1,37 @@ +import * as BUI from "@thatopen/ui"; +import { CloudRunner } from "../../bim-components"; +import { CloudRunnerSectionComponent } from "./src"; + +export const cloudRunnerSectionTemplate: CloudRunnerSectionComponent = ({ + components, +}) => { + const runner = components.get(CloudRunner); + + const messageItems = runner.messages.map( + (m) => BUI.html`${m}`, + ); + + return BUI.html` + + Component ID: ${runner.componentId} + Local server: ${runner.localServerUrl} +
+ runner.run(true)} + > + runner.run(false)} + > +
+ ${runner.status} + Progress: ${runner.progress}% + ${messageItems} +
+ `; +}; + +export * from "./src"; diff --git a/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/index.ts b/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/types.ts b/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/types.ts new file mode 100644 index 0000000..ff33079 --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/types.ts @@ -0,0 +1,14 @@ +import * as OBC from "@thatopen-platform/components-beta"; +import * as BUI from "@thatopen/ui"; + +export interface CloudRunnerSectionState { + components: OBC.Components; +} + +export type CloudRunnerSectionComponent = + BUI.StatefullComponent; + +export type CloudRunnerSectionGridElement = { + name: "cloudRunnerSection"; + state: CloudRunnerSectionState; +}; diff --git a/src/cli/templates/bim-beta/src/ui-components/index.ts b/src/cli/templates/bim-beta/src/ui-components/index.ts new file mode 100644 index 0000000..a76d9d1 --- /dev/null +++ b/src/cli/templates/bim-beta/src/ui-components/index.ts @@ -0,0 +1,2 @@ +export * from "./app-info-section"; +export * from "./cloud-runner-section"; diff --git a/src/cli/templates/bim/CONTEXT.md b/src/cli/templates/bim/CONTEXT.md index 2f2c254..43b0de2 100644 --- a/src/cli/templates/bim/CONTEXT.md +++ b/src/cli/templates/bim/CONTEXT.md @@ -44,7 +44,6 @@ Always use `npm run dev` which runs `thatopen serve` under the hood. | `@thatopen/components-front` | `OBF` | Front-end BIM components (Highlighter, measurements, etc.) | | `@thatopen/fragments` | `FRAGS` | Fragment geometry format | | `@thatopen/ui` | `BUI` | UI web components (``, ``, etc.) | -| `@thatopen/ui-obc` | `CUI` | Pre-built OBC UI tables (used by ModelsPanel) | | `three` | `THREE` | 3D rendering engine | | `@thatopen/services` | `EngineServicesClient` | Platform API client + built-in components | @@ -99,7 +98,7 @@ const client = PlatformClient.fromPlatformContext(); // Creates OBC.Components, inits BUI, loads built-ins, calls components.init() const { components } = await client.setup( - { OBC, OBF, BUI, CUI, THREE, FRAGS }, + { OBC, OBF, BUI, THREE, FRAGS }, AppManager, ViewportsManager, ); @@ -125,7 +124,6 @@ await client.initBuiltInComponent(AppManager, components); | ViewportsManager | `{ OBC, BUI, THREE, FRAGS }` | — | | LoadModelButton | `{ OBC, BUI }` | — | | ModelsDropdown | `{ OBC, BUI }` | — | -| ModelsPanel | `{ OBC, BUI, CUI }` | `@thatopen/ui-obc` | | ViewerToolbar | `{ OBC, OBF, BUI, THREE }` | `@thatopen/components-front` | | ColorsPalette | `{ OBC, OBF, BUI }` | `@thatopen/components-front` | | ClashesList | `{ OBC, OBF, BUI, THREE }` | `@thatopen/components-front` | @@ -145,7 +143,6 @@ await client.initBuiltInComponent(AppManager, components); import * as OBC from "@thatopen/components"; import * as OBF from "@thatopen/components-front"; // needed for Toolbar, Highlighters, Clashes, etc. import * as BUI from "@thatopen/ui"; -import * as CUI from "@thatopen/ui-obc"; // needed for ModelsPanel import * as THREE from "three"; import * as FRAGS from "@thatopen/fragments"; import * as MARKERJS from "@markerjs/markerjs3"; // needed for ScreenshotAnnotator diff --git a/src/cli/templates/bim/package.json b/src/cli/templates/bim/package.json index 206dab8..dc48d09 100644 --- a/src/cli/templates/bim/package.json +++ b/src/cli/templates/bim/package.json @@ -14,7 +14,6 @@ "@thatopen/components-front": "~3.4.0", "@thatopen/fragments": "~3.4.0", "@thatopen/ui": "~3.4.0", - "@thatopen/ui-obc": "~3.4.0", "@thatopen/services": "file:../../../..", "three": "^0.182.0" }, diff --git a/src/cli/templates/bim/src/main.ts b/src/cli/templates/bim/src/main.ts index 3b819ac..86efcfe 100644 --- a/src/cli/templates/bim/src/main.ts +++ b/src/cli/templates/bim/src/main.ts @@ -3,7 +3,6 @@ import * as OBC from "@thatopen/components"; import * as OBF from "@thatopen/components-front"; import * as FRAGS from "@thatopen/fragments"; import * as BUI from "@thatopen/ui"; -import * as CUI from "@thatopen/ui-obc"; import * as MARKERJS from "@markerjs/markerjs3" import { PlatformClient, @@ -26,7 +25,7 @@ async function main() { // First argument: library globals used by the engine. // Subsequent arguments: platform built-in components to initialise. const { components } = await client.setup( - { OBC, OBF, BUI, CUI, THREE, FRAGS, MARKERJS }, + { OBC, OBF, BUI, THREE, FRAGS, MARKERJS }, { uuid: ViewportsManager.uuid }, { uuid: AppManager.uuid }, { uuid: UIManager.uuid }, diff --git a/src/cli/templates/shared/AGENTS.md b/src/cli/templates/shared/AGENTS.md index 7f30495..c4c6dd4 100644 --- a/src/cli/templates/shared/AGENTS.md +++ b/src/cli/templates/shared/AGENTS.md @@ -18,6 +18,13 @@ The complete, up-to-date guides ship with the library. Read them from the instal - `node_modules/@thatopen/services/CONTEXT.md` — library API reference (`PlatformClient` vs `EngineServicesClient`, permissions) +## Beta libraries +If you scaffold **another** app, first ask the user whether they have **beta access** and want the +latest features/progress. If yes, use `thatopen create -t bim-beta` — it scaffolds on the +private `@thatopen-platform/*-beta` packages (the line the platform runs). Founding members have +permanent beta access; the beta packages are private, so the user's beta token must be configured +for `npm install`. + ## This project - `CONTEXT.md` (this folder) — the context for *this specific* project. - `package.json` scripts — `npm run dev` (preview), `npm run login`, `npm run publish`.