@thatopen/services 0.0.1 → 0.0.2
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/AGENTS.md +76 -0
- package/CONTEXT.md +4 -4
- package/README.md +4 -4
- package/dist/cli.js +7 -5
- package/dist/core/client.d.ts +1 -1
- package/docs/access-backend-data.md +19 -0
- package/docs/app-architecture.md +94 -0
- package/docs/app-layout.md +139 -0
- package/docs/app-wiring.md +123 -0
- package/docs/bim-components/coding-conventions.md +128 -0
- package/docs/bim-components/element-collections.md +69 -0
- package/docs/bim-components/expose-events.md +84 -0
- package/docs/bim-components/library-examples.md +28 -0
- package/docs/bim-components/observable-collections.md +83 -0
- package/docs/bim-components/overview.md +160 -0
- package/docs/bim-components/per-frame-updates.md +20 -0
- package/docs/bim-components/save-and-restore-state.md +21 -0
- package/docs/bim-components/setup-and-cleanup.md +64 -0
- package/docs/bim-components/type-conventions.md +49 -0
- package/docs/bim-components/user-driven-object-creation.md +56 -0
- package/docs/cli-setup.md +54 -0
- package/docs/cloud-components.md +74 -0
- package/docs/connect-logic-to-ui.md +113 -0
- package/docs/previewing.md +45 -0
- package/docs/publishing.md +35 -0
- package/docs/scaffolding.md +54 -0
- package/docs/ui-components/async-actions.md +18 -0
- package/docs/ui-components/confirmation-dialog.md +39 -0
- package/docs/ui-components/data-table.md +185 -0
- package/docs/ui-components/display-text.md +39 -0
- package/docs/ui-components/inline-form.md +44 -0
- package/docs/ui-components/library-examples.md +24 -0
- package/docs/ui-components/overview.md +194 -0
- package/docs/ui-components/rendering-patterns.md +129 -0
- package/docs/ui-components/sections-layout.md +123 -0
- package/docs/update-grid-elements.md +46 -0
- package/docs/using-colors.md +32 -0
- package/docs/using-icons.md +37 -0
- package/package.json +3 -1
- package/src/cli/templates/bim/CONTEXT.md +3 -3
- package/src/cli/templates/bim/package.json +1 -1
- package/src/cli/templates/bim/src/app.ts +1 -1
- package/src/cli/templates/bim/src/bim-components/CloudRunner/index.ts +1 -1
- package/src/cli/templates/bim/src/main.ts +1 -1
- package/src/cli/templates/bim/src/setups/ui-manager.ts +1 -1
- package/src/cli/templates/bim/src/setups/viewports-manager.ts +1 -1
- package/src/cli/templates/cloud/CONTEXT.md +4 -4
- package/src/cli/templates/cloud/package.json +1 -1
- package/src/cli/templates/cloud/src/main.ts +1 -1
- package/src/cli/templates/cloud-test/package.json +1 -1
- package/src/cli/templates/cloud-test/src/main.ts +1 -1
- package/src/cli/templates/default/CONTEXT.md +4 -4
- package/src/cli/templates/default/package.json +3 -0
- package/src/cli/templates/default/src/main.ts +3 -3
- package/src/cli/templates/shared/AGENTS.md +23 -0
- package/src/cli/templates/shared/CLAUDE.md +1 -0
- package/src/cli/templates/shared/cloud/vite.config.js +3 -3
- package/src/cli/templates/test/package.json +1 -1
- package/src/cli/templates/test/src/main.ts +2 -2
package/AGENTS.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Building on the That Open Platform — Agent Guide
|
|
2
|
+
|
|
3
|
+
You are an AI assistant helping a user build a **BIM app** or **cloud component** on the
|
|
4
|
+
That Open Platform using **`@thatopen/services`** (the `thatopen` CLI + client library).
|
|
5
|
+
|
|
6
|
+
**Read this file first.** It states the rules you must follow, then routes you to the one
|
|
7
|
+
detailed doc for whatever you're doing. Open only the doc you need — don't load everything.
|
|
8
|
+
|
|
9
|
+
> This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All docs it
|
|
10
|
+
> references ship inside the `@thatopen/services` package under `docs/`.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## How to work
|
|
15
|
+
|
|
16
|
+
- **New app or component?** The **first step is always the CLI** (`thatopen create`) — never
|
|
17
|
+
hand-write the scaffold. See [docs/cli-setup.md](./docs/cli-setup.md) then
|
|
18
|
+
[docs/scaffolding.md](./docs/scaffolding.md).
|
|
19
|
+
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
|
|
20
|
+
- Prefer existing engine functionality over custom code (see rule 2).
|
|
21
|
+
|
|
22
|
+
## Hard rules (always apply)
|
|
23
|
+
|
|
24
|
+
1. **All business logic lives in a BIM component** (`src/bim-components/`).
|
|
25
|
+
`setups/` only wire; `ui-components/` only render; `main.ts` only boots. Logic that doesn't
|
|
26
|
+
fit an existing component → create a new one. Never put logic in a setup, template, or `main.ts`.
|
|
27
|
+
2. **Check engine components first** — `@thatopen/components` (`OBC`) and
|
|
28
|
+
`@thatopen/components-front` (`OBF`) — before building anything custom.
|
|
29
|
+
3. **Platform built-ins** (`AppManager`, `ViewportsManager`, `UIManager`, …) come from
|
|
30
|
+
`@thatopen/services` and are available after `client.setup()`. Don't reinvent them.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What are you doing? → open the right doc
|
|
35
|
+
|
|
36
|
+
### Set up & ship
|
|
37
|
+
| Goal | Doc |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Install the CLI + authenticate | [docs/cli-setup.md](./docs/cli-setup.md) |
|
|
40
|
+
| Scaffold a new app / component | [docs/scaffolding.md](./docs/scaffolding.md) |
|
|
41
|
+
| Preview the app inside the platform | [docs/previewing.md](./docs/previewing.md) |
|
|
42
|
+
| Publish an app or component | [docs/publishing.md](./docs/publishing.md) |
|
|
43
|
+
|
|
44
|
+
### Structure & wire an app
|
|
45
|
+
| Goal | Doc |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Project structure, architecture rules, component tiers | [docs/app-architecture.md](./docs/app-architecture.md) |
|
|
48
|
+
| Boot / `app.ts` / `main.ts` / `client.setup()` | [docs/app-wiring.md](./docs/app-wiring.md) |
|
|
49
|
+
| Configure layout, add/reorganize grid sections | [docs/app-layout.md](./docs/app-layout.md) |
|
|
50
|
+
| Connect component logic to the UI | [docs/connect-logic-to-ui.md](./docs/connect-logic-to-ui.md) |
|
|
51
|
+
| Update a grid element's state at runtime | [docs/update-grid-elements.md](./docs/update-grid-elements.md) |
|
|
52
|
+
| Access the backend client / project data | [docs/access-backend-data.md](./docs/access-backend-data.md) |
|
|
53
|
+
| Register and use icons | [docs/using-icons.md](./docs/using-icons.md) |
|
|
54
|
+
| Declare and use colors | [docs/using-colors.md](./docs/using-colors.md) |
|
|
55
|
+
|
|
56
|
+
### Build a custom BIM component
|
|
57
|
+
Start at [docs/bim-components/overview.md](./docs/bim-components/overview.md) — conventions,
|
|
58
|
+
lifecycle (setup/cleanup), exposing events, observable/element collections, per-frame updates,
|
|
59
|
+
saving/restoring state, user-driven object creation.
|
|
60
|
+
|
|
61
|
+
### Build a UI component
|
|
62
|
+
Start at [docs/ui-components/overview.md](./docs/ui-components/overview.md) — rendering patterns,
|
|
63
|
+
section layout, data tables, inline forms, confirmation dialogs, display text, async actions.
|
|
64
|
+
|
|
65
|
+
### Cloud components & automations
|
|
66
|
+
[docs/cloud-components.md](./docs/cloud-components.md) — build / run locally / publish a cloud
|
|
67
|
+
component, the execution globals, and event-triggered automations.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Reference (also shipped in this package)
|
|
72
|
+
|
|
73
|
+
- **Library API** — `PlatformClient` vs `EngineServicesClient`, the permissions contract, and the
|
|
74
|
+
full method surface → [CONTEXT.md](./CONTEXT.md)
|
|
75
|
+
- **Built-in components API** — config interfaces, method signatures, `@example` blocks →
|
|
76
|
+
`src/built-in/index.ts` (in the installed package, `node_modules/@thatopen/services/`)
|
package/CONTEXT.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# thatopen
|
|
1
|
+
# @thatopen/services
|
|
2
2
|
|
|
3
3
|
Client library and CLI for the That Open Platform — a cloud platform for building BIM (Building Information Modeling) software.
|
|
4
4
|
|
|
@@ -81,7 +81,7 @@ every request, so Auth0's `getAccessTokenSilently()` and similar
|
|
|
81
81
|
refreshing sources Just Work:
|
|
82
82
|
|
|
83
83
|
```ts
|
|
84
|
-
import { PlatformClient } from 'thatopen
|
|
84
|
+
import { PlatformClient } from '@thatopen/services';
|
|
85
85
|
const client = new PlatformClient(
|
|
86
86
|
() => auth0.getAccessTokenSilently(),
|
|
87
87
|
'https://api.thatopen.com',
|
|
@@ -142,7 +142,7 @@ yarn create-version # Build → changeset → version → publish
|
|
|
142
142
|
| **Item type** | `APP` | `TOOL` |
|
|
143
143
|
| **Entry point** | Side effects in `main.ts` (renders UI) | `export async function main()` |
|
|
144
144
|
| **Context** | `window.__THATOPEN_CONTEXT__` provides `{ appId, projectId, accessToken, apiUrl }` | Globals: `thatOpenServices`, `executionParams`, `executionContext` (`{ projectId?, executionId, toolId, toolVersion }`), `executionReporter` (`message/error/progress`). `OBC`, `THREE`, `web-ifc`, `fs` are NOT injected — import them and let the bundler include them. |
|
|
145
|
-
| **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only
|
|
145
|
+
| **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `@thatopen/services` externalized) |
|
|
146
146
|
| **Template** | `bim`, `default`, or `test` | `cloud` or `cloud-test` |
|
|
147
147
|
|
|
148
148
|
### Authentication
|
|
@@ -156,7 +156,7 @@ Two modes, controlled by `useBearer` in the constructor:
|
|
|
156
156
|
Built-in components are platform-hosted UI modules fetched at runtime. Usage pattern:
|
|
157
157
|
|
|
158
158
|
```ts
|
|
159
|
-
import { AppManager, ViewportManager } from "thatopen
|
|
159
|
+
import { AppManager, ViewportManager } from "@thatopen/services";
|
|
160
160
|
|
|
161
161
|
// Register all library globals once
|
|
162
162
|
client.setBuiltInGlobals({ OBC, OBF, BUI, CUI, THREE, FRAGS });
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# thatopen
|
|
1
|
+
# @thatopen/services
|
|
2
2
|
|
|
3
3
|
Client library and CLI for building BIM apps and cloud components on the [That Open Platform](https://platform.thatopen.com).
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ Client library and CLI for building BIM apps and cloud components on the [That O
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
# Install the services package globally
|
|
11
|
-
npm i thatopen
|
|
11
|
+
npm i @thatopen/services -g
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Then, create a brand new app repository:
|
|
@@ -58,7 +58,7 @@ Use `npx thatopen create .` to scaffold in the current directory instead of crea
|
|
|
58
58
|
## Library usage
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
|
-
import { EngineServicesClient } from 'thatopen
|
|
61
|
+
import { EngineServicesClient } from '@thatopen/services';
|
|
62
62
|
|
|
63
63
|
const client = new EngineServicesClient(accessToken, apiUrl);
|
|
64
64
|
|
|
@@ -152,7 +152,7 @@ Cloud components export an `async function main()` that runs on the server. The
|
|
|
152
152
|
Platform-hosted UI components loaded at runtime:
|
|
153
153
|
|
|
154
154
|
```typescript
|
|
155
|
-
import { AppManager, ViewportManager } from "thatopen
|
|
155
|
+
import { AppManager, ViewportManager } from "@thatopen/services";
|
|
156
156
|
|
|
157
157
|
// Register all library globals once
|
|
158
158
|
client.setBuiltInGlobals({ OBC, OBF, BUI, CUI, THREE, FRAGS });
|
package/dist/cli.js
CHANGED
|
@@ -8013,6 +8013,8 @@ const createCommand = new Command2("create").argument("<project-name>", 'Name of
|
|
|
8013
8013
|
const sharedDir = require$$2.join(templatesDir, "shared");
|
|
8014
8014
|
const sharedVariantDir = require$$2.join(sharedDir, isCloud ? "cloud" : "app");
|
|
8015
8015
|
require$$3.copyFileSync(require$$2.join(sharedDir, "_gitignore"), require$$2.join(targetDir, ".gitignore"));
|
|
8016
|
+
require$$3.copyFileSync(require$$2.join(sharedDir, "AGENTS.md"), require$$2.join(targetDir, "AGENTS.md"));
|
|
8017
|
+
require$$3.copyFileSync(require$$2.join(sharedDir, "CLAUDE.md"), require$$2.join(targetDir, "CLAUDE.md"));
|
|
8016
8018
|
require$$3.copyFileSync(require$$2.join(sharedVariantDir, "tsconfig.json"), require$$2.join(targetDir, "tsconfig.json"));
|
|
8017
8019
|
require$$3.copyFileSync(require$$2.join(sharedVariantDir, "vite.config.js"), require$$2.join(targetDir, "vite.config.js"));
|
|
8018
8020
|
if (!isCloud) {
|
|
@@ -8024,7 +8026,7 @@ const createCommand = new Command2("create").argument("<project-name>", 'Name of
|
|
|
8024
8026
|
require$$3.renameSync(require$$2.join(targetDir, "_thatopen"), require$$2.join(targetDir, ".thatopen"));
|
|
8025
8027
|
}
|
|
8026
8028
|
const pkgPath = require$$2.join(targetDir, "package.json");
|
|
8027
|
-
const pkg2 = require$$3.readFileSync(pkgPath, "utf-8").replace(/\{\{PROJECT_NAME\}\}/g, packageName).replace(/\{\{VERSION\}\}/g, libVersion).replace(/"thatopen
|
|
8029
|
+
const pkg2 = require$$3.readFileSync(pkgPath, "utf-8").replace(/\{\{PROJECT_NAME\}\}/g, packageName).replace(/\{\{VERSION\}\}/g, libVersion).replace(/"@thatopen\/services": "file:[^"]*"/, `"@thatopen/services": "^${libVersion}"`);
|
|
8028
8030
|
require$$3.writeFileSync(pkgPath, pkg2);
|
|
8029
8031
|
console.log("");
|
|
8030
8032
|
console.log("Installing dependencies...");
|
|
@@ -10854,7 +10856,7 @@ function detectGlobalName(cwd) {
|
|
|
10854
10856
|
}
|
|
10855
10857
|
function buildEngineScript(bundleCode, accessToken, apiUrl, executionParams) {
|
|
10856
10858
|
return `/* eslint-disable */
|
|
10857
|
-
const { EngineServicesClient } = require('thatopen
|
|
10859
|
+
const { EngineServicesClient } = require('@thatopen/services');
|
|
10858
10860
|
|
|
10859
10861
|
const OBC = require('@thatopen/components');
|
|
10860
10862
|
const THREE = require('three');
|
|
@@ -11280,7 +11282,7 @@ const localServerCommand = new Command2("local-server").description(
|
|
|
11280
11282
|
".webp": "dataurl"
|
|
11281
11283
|
},
|
|
11282
11284
|
external: [
|
|
11283
|
-
"thatopen
|
|
11285
|
+
"@thatopen/services",
|
|
11284
11286
|
"@thatopen/components",
|
|
11285
11287
|
"three",
|
|
11286
11288
|
"web-ifc",
|
|
@@ -11539,13 +11541,13 @@ const pkg = JSON.parse(
|
|
|
11539
11541
|
require$$3.readFileSync(require$$2.join(__dirname, "..", "package.json"), "utf-8")
|
|
11540
11542
|
);
|
|
11541
11543
|
let updateMessage;
|
|
11542
|
-
fetch("https://registry.npmjs.org/
|
|
11544
|
+
fetch("https://registry.npmjs.org/@thatopen/services/latest", {
|
|
11543
11545
|
signal: AbortSignal.timeout(3e3)
|
|
11544
11546
|
}).then((res) => res.ok && res.json()).then((data) => {
|
|
11545
11547
|
if (data?.version && data.version !== pkg.version) {
|
|
11546
11548
|
updateMessage = `
|
|
11547
11549
|
⚠ Update available: ${pkg.version} → ${data.version}
|
|
11548
|
-
Run "npm install -g thatopen
|
|
11550
|
+
Run "npm install -g @thatopen/services@latest" to update.
|
|
11549
11551
|
`;
|
|
11550
11552
|
}
|
|
11551
11553
|
}).catch(() => {
|
package/dist/core/client.d.ts
CHANGED
|
@@ -113,7 +113,7 @@ export type EngineServicesClientProps = {
|
|
|
113
113
|
*
|
|
114
114
|
* @example
|
|
115
115
|
* ```ts
|
|
116
|
-
* import { EngineServicesClient } from 'thatopen
|
|
116
|
+
* import { EngineServicesClient } from '@thatopen/services';
|
|
117
117
|
*
|
|
118
118
|
* const client = new EngineServicesClient('my-access-token', 'https://api.thatopen.com');
|
|
119
119
|
* const files = await client.listFiles();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Access Backend Data
|
|
2
|
+
|
|
3
|
+
## Accessing backend data via `AppManager`
|
|
4
|
+
|
|
5
|
+
After `init()`, `AppManager` exposes:
|
|
6
|
+
|
|
7
|
+
- **`app.client`** — `EngineServicesClient`: backend client for API calls
|
|
8
|
+
- **`app.projectData`** — `ProjectData`: current user info, role, project metadata
|
|
9
|
+
|
|
10
|
+
Both are resolved before any setup runs. Always access them through `getAppManager(components)`:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const app = getAppManager(components)
|
|
14
|
+
if (app.projectData.currentUser?.role.name === "Project Admin") {
|
|
15
|
+
// render admin-only controls
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const files = await app.client.getProjectFiles(app.projectData.id)
|
|
19
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# App Architecture and Composition
|
|
2
|
+
|
|
3
|
+
## Architecture rule: logic lives only in BIM components
|
|
4
|
+
|
|
5
|
+
**All business logic in a platform app must live inside a BIM component** (`src/bim-components/`). This is not a convention — it is the architectural constraint of the platform.
|
|
6
|
+
|
|
7
|
+
| Layer | What belongs here | What does NOT belong here |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `bim-components/` | State, events, async operations, data access, coordination logic | — |
|
|
10
|
+
| `setups/` | Event subscriptions, calling component methods, UI sync | Business logic, data processing, state |
|
|
11
|
+
| `ui-components/` | Rendering, layout, reading component state | Logic, API calls, mutations |
|
|
12
|
+
| `main.ts` | Boot sequence only | Any logic whatsoever |
|
|
13
|
+
|
|
14
|
+
If logic doesn't fit inside an existing BIM component, the answer is always to create a new one or extend an existing one — never to write it in a setup file, a template, or `main.ts`.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// ✗ Forbidden — logic in a setup file
|
|
18
|
+
export const mySetup = (components: OBC.Components) => {
|
|
19
|
+
const fragments = components.get(OBC.FragmentsManager)
|
|
20
|
+
fragments.list.onItemSet.add(async (model) => {
|
|
21
|
+
const data = await fetchSomething(model.uuid) // ← logic here is wrong
|
|
22
|
+
processData(data)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ✓ Correct — setup only wires; logic lives in the component
|
|
27
|
+
export const mySetup = (components: OBC.Components) => {
|
|
28
|
+
const uis = getUIManager(components)
|
|
29
|
+
const manager = components.get(MyManager)
|
|
30
|
+
manager.onDataReady.add(() => uis.custom.get("myPanel").updateInstances())
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Component tiers in a platform app
|
|
37
|
+
|
|
38
|
+
Every platform app works with three tiers of components, all accessible via `components.get()`:
|
|
39
|
+
|
|
40
|
+
- **Engine components** — public, from `@thatopen/components` (OBC) and `@thatopen/components-front` (OBF). Always check these first before building custom logic.
|
|
41
|
+
- **Platform built-in components** — private components from `@thatopen/services` (e.g. `AppManager`, `UIManager`, `ViewportsManager`). Available after `client.setup()`, already wired up.
|
|
42
|
+
- **Custom components** — see the BIM-component and UI-component guides in `docs/`. Live in `src/bim-components/`, self-register in their constructor via `components.add()`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Project Structure
|
|
47
|
+
|
|
48
|
+
Every app has this shape under `src/`. To configure the app layout or add and reorganize sections in the grid, see [./app-layout.md](./app-layout.md).
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
src/
|
|
52
|
+
├── bim-components/ → custom OBC domain components
|
|
53
|
+
├── ui-components/ → BUI templates, barrel only
|
|
54
|
+
├── setups/ → initialization and wiring
|
|
55
|
+
├── app.ts → typed app identity
|
|
56
|
+
├── globals.ts → global constants (icons, tooltips, colors, etc.)
|
|
57
|
+
└── main.ts → entry point
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `bim-components/`
|
|
61
|
+
|
|
62
|
+
Each custom OBC component lives in its own folder, built following the BIM-component guide in `docs/` (see [./bim-components/overview.md](./bim-components/overview.md)). The `index.ts` barrel re-exports every component class, its types, and its static `uuid` — this is the single import point for all custom domain logic throughout the app.
|
|
63
|
+
|
|
64
|
+
### `ui-components/`
|
|
65
|
+
|
|
66
|
+
Templates created following the UI-component guide in `docs/` (see [./ui-components/overview.md](./ui-components/overview.md)) are re-exported from the barrel. The `index.ts` is a **pure barrel** — only re-exports, nothing else. Registration of templates in the UIManager happens in `setups/ui-manager.ts`.
|
|
67
|
+
|
|
68
|
+
### `setups/`
|
|
69
|
+
|
|
70
|
+
One file per component being initialized. `ui-manager.ts` is always present as fixed boilerplate; all other files are custom wiring. See [./connect-logic-to-ui.md](./connect-logic-to-ui.md) for the full implementation reference. To update the state of a grid element at runtime from a setup or event handler, see [./update-grid-elements.md](./update-grid-elements.md).
|
|
71
|
+
|
|
72
|
+
### `app.ts` and `main.ts`
|
|
73
|
+
|
|
74
|
+
`app.ts` defines the app's typed shape and the `getAppManager` accessor. `main.ts` boots the platform — no business logic belongs here. See [./app-wiring.md](./app-wiring.md) for structure, `client.setup()`, and `componentSetups`. To access the backend client or project data (`app.client`, `app.projectData`), see [./access-backend-data.md](./access-backend-data.md).
|
|
75
|
+
|
|
76
|
+
### `globals.ts`
|
|
77
|
+
|
|
78
|
+
Contains the icon registry, color registry, and any other global constants. All icons must be declared here and accessed through `AppManager`. Colors are declared here and imported directly where needed. See [./using-icons.md](./using-icons.md) and [./using-colors.md](./using-colors.md) for details.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## See also
|
|
83
|
+
|
|
84
|
+
- [Connect logic to the UI](./connect-logic-to-ui.md) — register templates, sync views, etc.
|
|
85
|
+
- [Configure the app layout](./app-layout.md) — add or reorganize sections in the grid.
|
|
86
|
+
- [App wiring](./app-wiring.md) — boot the app, configure `app.ts`, `main.ts`, `client.setup()`, `componentSetups`.
|
|
87
|
+
- [Access backend data](./access-backend-data.md) — the backend client and project data (`app.client`, `app.projectData`).
|
|
88
|
+
- [Using icons](./using-icons.md) — register or use icons in the app.
|
|
89
|
+
- [Using colors](./using-colors.md) — declare or use global colors (highlighter, palette, etc.).
|
|
90
|
+
- [Update grid elements](./update-grid-elements.md) — update the state of a grid element at runtime.
|
|
91
|
+
- [Scaffolding a new app](./scaffolding.md) — scaffold a new app from scratch using the CLI.
|
|
92
|
+
- [Publishing an app](./publishing.md) — publish an app to the platform (login + publish).
|
|
93
|
+
- [CLI setup](./cli-setup.md) — install the CLI and authenticate with a platform token.
|
|
94
|
+
- [Previewing apps](./previewing.md) — preview the app during development inside the platform.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Configure App Layout
|
|
2
|
+
|
|
3
|
+
## Grid element constraint
|
|
4
|
+
|
|
5
|
+
Every entry in `grid.elements` must be a single `bim-panel-section`. This is a hard constraint of the platform — the grid only knows how to host panel sections, not arbitrary HTML containers.
|
|
6
|
+
|
|
7
|
+
**Correct:** one `grid.elements` entry = one `bim-panel-section`
|
|
8
|
+
**Wrong:** a UI component that returns a `<div>` wrapping multiple `<bui-panel-section>` elements used as a grid entry
|
|
9
|
+
|
|
10
|
+
If you need multiple sections in one area, register each section separately in `grid.elements` and group them using `panel:` or `tabs:` in the grid template — never wrap them in a container component.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// ✗ Wrong — MyDashboard returns <div><bui-panel-section>...</bui-panel-section><bui-panel-section>...</bui-panel-section></div>
|
|
14
|
+
grid.elements = {
|
|
15
|
+
dashboard: { template: uis.custom.get("myDashboard").template, initialState: { components } },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ✓ Correct — each section is its own grid element; grouping happens in the template
|
|
19
|
+
grid.elements = {
|
|
20
|
+
stats: { template: uis.custom.get("statsSection").template, initialState: { components } },
|
|
21
|
+
charts: { template: uis.custom.get("chartsSection").template, initialState: { components } },
|
|
22
|
+
}
|
|
23
|
+
// Then in the layout template:
|
|
24
|
+
// "panel:right(stats,charts) viewer" 1fr / 22rem 1fr
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Grid sync rules
|
|
30
|
+
|
|
31
|
+
Any change to the grid requires updates in multiple places. Never update one without the others.
|
|
32
|
+
|
|
33
|
+
**Adding or removing a layout:**
|
|
34
|
+
1. `app.ts` → first argument of `BUI.Grid<[layouts], ...>`
|
|
35
|
+
2. `main.ts` → `grid.layouts` (add/remove the definition) and `grid.layout` if it was the initial layout
|
|
36
|
+
|
|
37
|
+
**Adding or removing an element:**
|
|
38
|
+
1. `app.ts` → second argument of `BUI.Grid<..., [elements]>`
|
|
39
|
+
2. `main.ts` → `grid.elements`, `grid.areaGroups` (if applicable), and any layout templates that reference it
|
|
40
|
+
|
|
41
|
+
**Renaming an element** — the name originates in the component, so the change cascades outward:
|
|
42
|
+
1. Rename the folder in `ui-components/` (kebab-case of the new name)
|
|
43
|
+
2. Update all internal identifiers: types (`{ComponentName}State`, `{ComponentName}Component`, `{ComponentName}GridElement`), functions (`{camelCase}Template`, `on{PascalCase}Created`)
|
|
44
|
+
3. Update the `ui-components/index.ts` barrel to point to the renamed folder
|
|
45
|
+
4. `app.ts` → second argument of `BUI.Grid` reflects the new `{ComponentName}GridElement`
|
|
46
|
+
5. `main.ts` → `grid.elements` and all layout templates that use that area name
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Defining layouts
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
grid.layouts = {
|
|
54
|
+
Viewer: {
|
|
55
|
+
template: `"viewer" 1fr / 1fr`,
|
|
56
|
+
},
|
|
57
|
+
Quantities: {
|
|
58
|
+
icon: app.icons.QUANTITY,
|
|
59
|
+
template: `
|
|
60
|
+
"qtos viewer" 1fr
|
|
61
|
+
/40rem 1fr
|
|
62
|
+
`,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
grid.layout = "Collider"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Special area tokens
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
{groupType}:{areaName}(elementA, elementB)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
There are two group types: `tabs` and `panel`.
|
|
77
|
+
|
|
78
|
+
### `tabs:` — one element at a time
|
|
79
|
+
|
|
80
|
+
Shows one element at a time with tab switchers. The user picks which section is visible. Supports nested sub-groups:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
tabs:{areaName}({groupName}[elementA, elementB], elementC)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
Collider: {
|
|
88
|
+
icon: appIcons.COLLISION,
|
|
89
|
+
template: `
|
|
90
|
+
"tabs:left(group[collider,qtos],collider) viewer" 1fr
|
|
91
|
+
/30rem 1fr
|
|
92
|
+
`,
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`areaGroups` options for `tabs:`: `switchersCompact` or `switchersFull`.
|
|
97
|
+
|
|
98
|
+
### `panel:` — all elements stacked vertically
|
|
99
|
+
|
|
100
|
+
Stacks all elements and keeps them all visible simultaneously, scrollable as a single panel. Use when sections are related and should all be visible at once (e.g. an inspector with multiple info sections):
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
app: {
|
|
104
|
+
template: `
|
|
105
|
+
"tabs:left viewport panel:right" 1fr
|
|
106
|
+
/ 22rem 1fr 20rem
|
|
107
|
+
`,
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`areaGroups` options for `panel:`: `label` to give the panel a title.
|
|
112
|
+
|
|
113
|
+
### Choosing between `tabs:` and `panel:`
|
|
114
|
+
|
|
115
|
+
- Use `tabs:` when sections are **alternatives** — the user focuses on one at a time
|
|
116
|
+
- Use `panel:` when sections are **complementary** — the user needs all of them visible together
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## `grid.elements` and `grid.areaGroups`
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
grid.elements = {
|
|
124
|
+
viewer: viewport,
|
|
125
|
+
qtos: {
|
|
126
|
+
template: uis.custom.get("qtosSection").template,
|
|
127
|
+
initialState: { components },
|
|
128
|
+
label: "Quantities",
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
grid.areaGroups = {
|
|
133
|
+
left: { switchersFull: true },
|
|
134
|
+
right: { label: "Inspector" },
|
|
135
|
+
group: { label: "Configuration", icon: appIcons.APPLY },
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> **Grouping sections always happens at the grid level.** To group, combine, or stack panel sections together, the answer is always `tabs:` or `panel:` in the grid template — never nesting one section inside another in the template code.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# App Wiring
|
|
2
|
+
|
|
3
|
+
## `app.ts` — App Identity
|
|
4
|
+
|
|
5
|
+
Defines the app's typed shape and the typed accessor for `AppManager`. The `App` type uses `GridElement` types exported from `ui-components/`:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import * as OBC from "@thatopen/components"
|
|
9
|
+
import * as BUI from "@thatopen/ui"
|
|
10
|
+
import { AppManager } from "@thatopen/services"
|
|
11
|
+
import { icons } from "./globals"
|
|
12
|
+
import { FilesSectionGridElement, QtosSectionGridElement } from "./ui-components"
|
|
13
|
+
|
|
14
|
+
export type App = {
|
|
15
|
+
icons: (keyof typeof icons)[]
|
|
16
|
+
grid: BUI.Grid<
|
|
17
|
+
["Viewer", "Quantities", "Collider"],
|
|
18
|
+
[
|
|
19
|
+
"viewer",
|
|
20
|
+
FilesSectionGridElement,
|
|
21
|
+
QtosSectionGridElement,
|
|
22
|
+
]
|
|
23
|
+
>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const getAppManager = (components: OBC.Components) =>
|
|
27
|
+
components.get(AppManager<App>)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`getAppManager` is the **only** valid way to access `AppManager` in an app. Never use `components.get(AppManager)` directly — it drops the `App` type parameter and loses all type information about the app's icons, grid layout, and grid elements.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// ✗ Avoid — loses the App type
|
|
34
|
+
const app = components.get(AppManager)
|
|
35
|
+
|
|
36
|
+
// ✓ Correct — fully typed
|
|
37
|
+
const app = getAppManager(components)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Grid element types
|
|
41
|
+
|
|
42
|
+
- Raw elements → typed as a string literal: `"viewer"`
|
|
43
|
+
- Template-based sections → typed as `{ComponentName}GridElement`, defined in the component's `src/types.ts` and exported through `ui-components/index.ts`
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// ui-components/files-section/src/types.ts
|
|
47
|
+
export type FilesSectionGridElement = {
|
|
48
|
+
name: "filesSection"
|
|
49
|
+
state: FilesSectionState
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## `main.ts` — Entry Point
|
|
56
|
+
|
|
57
|
+
`main.ts` boots the platform and calls `app.init()`. No business logic belongs here.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
async function main() {
|
|
61
|
+
const client = EngineServicesClient.fromPlatformContext()
|
|
62
|
+
|
|
63
|
+
const { components } = await client.setup(
|
|
64
|
+
{ OBC, OBF, BUI, CUI, THREE, FRAGS },
|
|
65
|
+
{ uuid: ViewportsManager.uuid },
|
|
66
|
+
{ uuid: AppManager.uuid },
|
|
67
|
+
{ uuid: UIManager.uuid },
|
|
68
|
+
) as { components: OBC.Components }
|
|
69
|
+
|
|
70
|
+
await viewportsManager(components)
|
|
71
|
+
const viewports = components.get(ViewportsManager)
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
const { element: viewport } = [...viewports._instances.values()][0]
|
|
74
|
+
|
|
75
|
+
const app = getAppManager(components)
|
|
76
|
+
await app.init({
|
|
77
|
+
client,
|
|
78
|
+
icons: appIcons,
|
|
79
|
+
componentSetups: { ... },
|
|
80
|
+
grid: (grid) => { ... },
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch(console.error)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `client.setup()` — Platform initialization
|
|
88
|
+
|
|
89
|
+
- **First argument** — libraries object (`OBC`, `OBF`, `BUI`, `CUI`, `THREE`, `FRAGS`, etc.)
|
|
90
|
+
- **Subsequent arguments** — `{ uuid }` for each platform built-in component needed. Always present: `ViewportsManager`, `AppManager`, `UIManager`
|
|
91
|
+
|
|
92
|
+
Custom components (from `bim-components/`) do not need to be listed here — they self-register via `components.add()` in their constructor.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## `componentSetups` in `app.init()`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
componentSetups: {
|
|
100
|
+
core: [uiManager, fragmentsManager, highlighter, ifcLoader],
|
|
101
|
+
lazy: [
|
|
102
|
+
{ uuid: MyComponent.uuid, fn: myComponentSetup },
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**`core`** — runs before the grid mounts, in parallel. `uiManager` always goes here. Other engine component setups (fragments, highlighter, IFC loader) also typically go here.
|
|
108
|
+
|
|
109
|
+
**`lazy`** — runs the first time the corresponding component is instantiated. The `uuid` is imported from the `bim-components/` barrel. Use for custom components that can be loaded on demand.
|
|
110
|
+
|
|
111
|
+
The `uuid` of each custom component — a static property defined on the class — is imported from the `bim-components/` barrel when referencing it in `lazy`:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { MyComponent } from "../bim-components"
|
|
115
|
+
|
|
116
|
+
componentSetups: {
|
|
117
|
+
lazy: [
|
|
118
|
+
{ uuid: MyComponent.uuid, fn: myComponentSetup },
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
When it's not obvious whether a custom component should be `core` or `lazy`, **decide based on whether the component is needed at boot or only on demand.**
|