@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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Type Conventions
|
|
2
|
+
|
|
3
|
+
All types for a component live in `src/types.ts` — domain interfaces, event payloads, serialized equivalents — so there is a single place to look.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Runtime vs serialized
|
|
8
|
+
|
|
9
|
+
Components that implement `OBC.Serializable` need two versions of their data types: a runtime version that uses rich TypeScript types, and a serialized version that is JSON-safe.
|
|
10
|
+
|
|
11
|
+
The serialized type is named `Serialized{TypeName}`:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// Runtime — uses Map, Set, Date, THREE.Color
|
|
15
|
+
export interface Activity {
|
|
16
|
+
index: number;
|
|
17
|
+
name: string;
|
|
18
|
+
color: THREE.Color;
|
|
19
|
+
items: OBC.ModelIdMap;
|
|
20
|
+
dates: Set<string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Serialized — JSON-safe equivalent
|
|
24
|
+
export interface SerializedActivity {
|
|
25
|
+
index: number;
|
|
26
|
+
name: string;
|
|
27
|
+
color: string; // THREE.Color → hex string
|
|
28
|
+
items: [string, number[]][]; // ModelIdMap → tuple array
|
|
29
|
+
dates: string[]; // Set → array
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Conversion happens inside `export()` and `import()` (see [`./save-and-restore-state.md`](./save-and-restore-state.md)).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Generics that require `type`, not `interface`
|
|
38
|
+
|
|
39
|
+
Some OBC and FRAGS generics enforce that their type parameter is a `type` alias. Passing an `interface` causes a TypeScript error. When in doubt, prefer `type` for types used as generic parameters:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// ✗ Fails — interface not assignable to the generic constraint
|
|
43
|
+
export interface ActivityData { name: string; value: number }
|
|
44
|
+
const list = new FRAGS.DataMap<string, ActivityData>(); // error
|
|
45
|
+
|
|
46
|
+
// ✓ Correct
|
|
47
|
+
export type ActivityData = { name: string; value: number }
|
|
48
|
+
const list = new FRAGS.DataMap<string, ActivityData>();
|
|
49
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# User-Driven Object Creation
|
|
2
|
+
|
|
3
|
+
## `OBC.Createable`
|
|
4
|
+
|
|
5
|
+
Implement when the component lets the user create objects in the scene through a multi-step interaction — click to start, click to confirm, Escape to cancel, click existing to delete.
|
|
6
|
+
|
|
7
|
+
The key characteristic is **statefulness between clicks**: `create()` is called on every user click, but the first click initiates the workflow and the second confirms it by calling `endCreation()` internally.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export class ActivityTracker extends OBC.Component implements OBC.Createable {
|
|
11
|
+
private _temp: { isDragging: boolean; preview?: SomeObject } = {
|
|
12
|
+
isDragging: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
create = () => {
|
|
16
|
+
if (!this.enabled) return;
|
|
17
|
+
if (!this._temp.isDragging) {
|
|
18
|
+
this._temp.isDragging = true;
|
|
19
|
+
this._temp.preview = new SomeObject();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.endCreation();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
endCreation = () => {
|
|
26
|
+
if (!this._temp.preview) return;
|
|
27
|
+
this.list.add(this._temp.preview);
|
|
28
|
+
this._temp.isDragging = false;
|
|
29
|
+
this._temp.preview = undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
cancelCreation = () => {
|
|
33
|
+
this._temp.isDragging = false;
|
|
34
|
+
this._temp.preview?.dispose();
|
|
35
|
+
this._temp.preview = undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
delete = () => {
|
|
39
|
+
if (!this.world) return;
|
|
40
|
+
const casters = this.components.get(OBC.Raycasters);
|
|
41
|
+
const caster = casters.get(this.world);
|
|
42
|
+
const intersect = caster.castRayToObjects([...this._boundingBoxes]);
|
|
43
|
+
if (!intersect) return;
|
|
44
|
+
// identify and remove the hit object from list
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Wire up the Escape key in the component's `enabled` setter so `cancelCreation()` fires automatically when the component is disabled mid-flow:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
set enabled(value: boolean) {
|
|
53
|
+
this._enabled = value;
|
|
54
|
+
if (!value) this.cancelCreation();
|
|
55
|
+
}
|
|
56
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# CLI Setup and Authentication
|
|
2
|
+
|
|
3
|
+
Before you can scaffold or publish a That Open Platform app, two things must be in place: the `@thatopen/services` CLI installed globally, and a valid platform token for authentication.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Step 1: Install or update the CLI
|
|
8
|
+
|
|
9
|
+
Run the following command in the terminal. This installs the CLI if it isn't present, or updates it to the latest version if it already is:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i @thatopen/services@latest -g
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Once done, verify it works:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
thatopen --version
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If this returns a version number, the CLI is ready. If `npm` is not found, install Node.js first — download the LTS version from [https://nodejs.org](https://nodejs.org), then retry.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Step 2: Get a platform token
|
|
26
|
+
|
|
27
|
+
Generate an access token from the platform:
|
|
28
|
+
|
|
29
|
+
1. Go to [https://dev.platform.thatopen.com](https://dev.platform.thatopen.com)
|
|
30
|
+
2. In the header, click **Data**
|
|
31
|
+
3. Find the **Tokens** card and create a new token
|
|
32
|
+
4. Enable the permissions: **Apps**, **Components** and **Storage**
|
|
33
|
+
5. Copy the token
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 3: Authenticate
|
|
38
|
+
|
|
39
|
+
Once you have the token, run:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run login -- --token <TOKEN>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
where `<TOKEN>` is the token you copied. This stores credentials globally at `~/.thatopen/config.json` and persists across sessions — you only need to do this once per machine.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## When to use this guide
|
|
50
|
+
|
|
51
|
+
Read this guide when:
|
|
52
|
+
- You are starting a new app and haven't confirmed the CLI is installed
|
|
53
|
+
- A `thatopen` command fails with a "command not found" or "not authenticated" error
|
|
54
|
+
- You have never built a platform app before on this machine
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Cloud components & automations
|
|
2
|
+
|
|
3
|
+
A **cloud component** is a piece of logic that runs **server-side** (Node.js) on the platform,
|
|
4
|
+
on the shared project context, via the API. Use them to automate real operations — clash checks,
|
|
5
|
+
validations, document generation — and to let apps offload heavy work.
|
|
6
|
+
|
|
7
|
+
## Apps vs cloud components
|
|
8
|
+
|
|
9
|
+
| | Apps | Cloud components |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| **Runs in** | Browser (iframe on the platform) | Server (Node.js child process) |
|
|
12
|
+
| **Item type** | `APP` | `TOOL` |
|
|
13
|
+
| **Entry point** | Side effects in `main.ts` (renders UI) | `export async function main()` |
|
|
14
|
+
| **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `@thatopen/services` externalized) |
|
|
15
|
+
| **Template** | `bim`, `default`, `test` | `cloud`, `cloud-test` |
|
|
16
|
+
|
|
17
|
+
## Scaffold
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
thatopen create my-component --template cloud
|
|
21
|
+
cd my-component
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The component is an `export async function main()` that runs on the server. The execution engine
|
|
25
|
+
injects these globals (do **not** import them; for `OBC`/`THREE`/`web-ifc` import them so the
|
|
26
|
+
bundler includes them):
|
|
27
|
+
|
|
28
|
+
| Global | Purpose |
|
|
29
|
+
|--------|---------|
|
|
30
|
+
| `thatOpenServices` | Authenticated `EngineServicesClient` |
|
|
31
|
+
| `executionParams` | Parameters passed by the caller |
|
|
32
|
+
| `executionContext` | `{ projectId?, executionId, toolId, toolVersion }` |
|
|
33
|
+
| `executionReporter` | `{ message(msg), error(msg), progress(pct) }` for live feedback |
|
|
34
|
+
| `OBC` | `@thatopen/components` — BIM engine (import it) |
|
|
35
|
+
| `THREE` | `three` — 3D math/geometry (import it) |
|
|
36
|
+
| `fs` | Node.js filesystem (import it) |
|
|
37
|
+
|
|
38
|
+
## Run locally
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run run # build + test locally
|
|
42
|
+
npx thatopen run --params '{"inputFile":"model.ifc"}' # pass parameters
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`thatopen local-server` starts an API-compatible local execution server (default `:4001`) so an
|
|
46
|
+
app can call the component before it's deployed (set `client.localServerUrl`).
|
|
47
|
+
|
|
48
|
+
## Authenticate & publish
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run login -- --token <your-token>
|
|
52
|
+
npm run publish
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Calling a component from an app
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const { executionId } = await client.executeComponent(componentId, { param: "value" }, versionTag?);
|
|
59
|
+
client.onExecutionProgress(executionId, (data) => {
|
|
60
|
+
// data.progressUpdate — percentage
|
|
61
|
+
// data.messageUpdate — status messages
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Include `projectId` in the execution params to scope the run to a project (the backend validates
|
|
66
|
+
the component is linked to that project). See [CONTEXT.md](../CONTEXT.md) for the permissions contract.
|
|
67
|
+
|
|
68
|
+
## Automations (event-triggered components)
|
|
69
|
+
|
|
70
|
+
An **automation** is a cloud component that runs automatically in response to a platform **event**
|
|
71
|
+
(rather than being invoked by hand) — e.g. "run the clash check whenever a model is updated."
|
|
72
|
+
The component is the same; what differs is the trigger. Add it to a project, choose the event(s)
|
|
73
|
+
that fire it, and the platform executes it on the shared project context, reporting progress and
|
|
74
|
+
logs through `executionReporter`. Build and publish it exactly like any other cloud component above.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Connect Logic to UI
|
|
2
|
+
|
|
3
|
+
Setup files are the bridge between custom BIM components (logic) and the platform UI. This is where event subscriptions, UI sync, and component wiring live — not in the component itself.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
setups/
|
|
9
|
+
├── index.ts → barrel (always present)
|
|
10
|
+
├── ui-manager.ts → fixed boilerplate (always present)
|
|
11
|
+
└── {custom}.ts → one per custom BIM component
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
One file per component being initialized. Each file exports a single function that receives `components: OBC.Components`.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## `setups/ui-manager.ts` — Fixed boilerplate
|
|
19
|
+
|
|
20
|
+
This is where all UI templates get registered in the platform (see the UI-component guide in `docs/`: [./ui-components/overview.md](./ui-components/overview.md)). The file always has this shape — the content changes (which templates are registered), but the structure never does.
|
|
21
|
+
|
|
22
|
+
### `CustomUIs` type
|
|
23
|
+
|
|
24
|
+
Maps every UI template to its element type and state. Add one entry per registered template:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
export type CustomUIs = {
|
|
28
|
+
filesSection: { type: BUI.PanelSection; state: FilesSectionState }
|
|
29
|
+
collisionsTable: { type: BUI.Table<CollisionsTableData>; state: CollisionsTableState }
|
|
30
|
+
// one entry per registered template
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `getUIManager` — typed accessor
|
|
35
|
+
|
|
36
|
+
The typed accessor for `UIManager`. Defined here because `CustomUIs` lives here. Used everywhere else in `setups/` to interact with the UI registry — never access `UIManager` directly:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
export const getUIManager = (components: OBC.Components) =>
|
|
40
|
+
components.get(UIManager<CustomUIs>)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`getUIManager` is the **only** valid way to access `UIManager` in an app. Never use `components.get(UIManager)` directly — it drops the `CustomUIs` type parameter and loses all type information about the registered templates.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// ✗ Avoid — loses the CustomUIs type
|
|
47
|
+
const uis = components.get(UIManager)
|
|
48
|
+
|
|
49
|
+
// ✓ Correct — fully typed
|
|
50
|
+
const uis = getUIManager(components)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `uiManager` setup function
|
|
54
|
+
|
|
55
|
+
Registers each template and its optional `onInstanceCreated` callback, exported from the UI component:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
export const uiManager = (components: OBC.Components) => {
|
|
59
|
+
const uis = getUIManager(components)
|
|
60
|
+
uis.registerTemplate("filesSection", {
|
|
61
|
+
template: filesSectionTemplate,
|
|
62
|
+
onInstanceCreated: onFilesSectionCreated
|
|
63
|
+
})
|
|
64
|
+
uis.registerTemplate("collisionsTable", {
|
|
65
|
+
template: collisionsTableTemplate
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Composing UI components
|
|
71
|
+
|
|
72
|
+
Templates can embed other registered components by creating instances via `uis.custom.get()`:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
export const filesSectionTemplate: FilesSectionComponent = (state) => {
|
|
76
|
+
const uis = state.components.get(UIManager)
|
|
77
|
+
const [modelsList] = uis.custom.get("modelsList").create(state)
|
|
78
|
+
return BUI.html`
|
|
79
|
+
<bim-panel-section label="Files">
|
|
80
|
+
${modelsList}
|
|
81
|
+
</bim-panel-section>
|
|
82
|
+
`
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Keeping UIs in sync with `updateInstances()`
|
|
87
|
+
|
|
88
|
+
To push a state update to all existing instances of a template, call `updateInstances()` from within a setup file — typically in response to engine events:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const updateUIs = () => uis.custom.get("modelsList").updateInstances()
|
|
92
|
+
fragments.list.onItemSet.add(updateUIs)
|
|
93
|
+
fragments.list.onItemDeleted.add(updateUIs)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Custom setup files
|
|
99
|
+
|
|
100
|
+
Each setup file is the bridge between a custom BIM component and the platform (see the BIM-component guide in `docs/`: [./bim-components/overview.md](./bim-components/overview.md)). This is where wiring lands: event listeners, UI updates, and configuration go here — not in the component's constructor.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// setups/qto-manager.ts
|
|
104
|
+
export const qtoManager = (components: OBC.Components) => {
|
|
105
|
+
const uis = getUIManager(components)
|
|
106
|
+
const qtoManager = components.get(QtoManager)
|
|
107
|
+
|
|
108
|
+
qtoManager.onStateChanged.add((states) => {
|
|
109
|
+
if (!states.includes("value")) return
|
|
110
|
+
uis.custom.get("qtosTable").updateInstances()
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Previewing Apps During Development
|
|
2
|
+
|
|
3
|
+
That Open Platform apps can only be previewed correctly within the platform context — opening `localhost:4000` directly in the browser will just bring the app bundle. The app must be loaded from inside a project on the platform as it gives context.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Step 1: Start the dev server
|
|
8
|
+
|
|
9
|
+
In the project root, run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This invokes `thatopen serve` under the hood — the same CLI installed during setup. It performs a special build and serves the bundle on **port 4000**. Keep this process running throughout your development session.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Step 2: Open the app in the platform
|
|
20
|
+
|
|
21
|
+
Once the dev server is running:
|
|
22
|
+
|
|
23
|
+
1. Go to [https://dev.platform.thatopen.com](https://dev.platform.thatopen.com) and enter a project. If no project exists yet, create one first.
|
|
24
|
+
2. In the project sidebar, click **Local App**.
|
|
25
|
+
3. Click **Get Started**.
|
|
26
|
+
|
|
27
|
+
The platform will load the locally-running app from port 4000 and render it inside the project context, with full access to its models, data, and users.
|
|
28
|
+
|
|
29
|
+
The resulting URL follows this pattern:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
https://dev.platform.thatopen.com/dashboard/projects/{projectId}/apps/local-app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
where `{projectId}` is the ID of the project. Bookmark this URL to return quickly during development.
|
|
36
|
+
|
|
37
|
+
> The dev server must be running before opening this URL. If the bundle is not being served on port 4000, the platform will fail to load the app.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## When to read this guide
|
|
42
|
+
|
|
43
|
+
- When you want to see, run, or preview the app
|
|
44
|
+
- When you have run `npm run dev` and don't know what to do next
|
|
45
|
+
- When the app is not showing up in the platform
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Publishing an App
|
|
2
|
+
|
|
3
|
+
Publishing to the That Open Platform is a two-step process: authenticate first, then publish.
|
|
4
|
+
|
|
5
|
+
## Step 1: Authenticate
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
thatopen login
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This stores credentials globally at `~/.thatopen/config.json`. Authentication persists across sessions — you only need to do this once per machine.
|
|
12
|
+
|
|
13
|
+
**Options:**
|
|
14
|
+
|
|
15
|
+
| Flag | Purpose |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `--token <token>` | Non-interactive login (CI, scripts) |
|
|
18
|
+
| `--local` | Store credentials in the project directory (`.thatopen`) instead of globally |
|
|
19
|
+
|
|
20
|
+
## Step 2: Publish
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
thatopen publish
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This builds the app and uploads it to the platform. The command handles the build step automatically unless told otherwise.
|
|
27
|
+
|
|
28
|
+
**Common options:**
|
|
29
|
+
|
|
30
|
+
| Flag | Purpose |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `--name <name>` | Override the published app name |
|
|
33
|
+
| `--version-tag <tag>` | Tag the release (e.g. `v1.2.0`) |
|
|
34
|
+
| `--skip-build` | Skip the build step if the bundle is already built |
|
|
35
|
+
| `--app-id <id>` | Publish to a specific existing app |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Scaffolding a New App
|
|
2
|
+
|
|
3
|
+
When starting a new platform app from scratch, **always use the CLI to scaffold the project — never create the files manually.** The CLI produces the exact project structure these docs describe, correctly configured and with dependencies installed. Writing the scaffold by hand defeats the purpose of the tool and risks inconsistencies.
|
|
4
|
+
|
|
5
|
+
## Command
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
thatopen create <app-name>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use `.` as the name to scaffold in the current directory:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
thatopen create .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This copies the template files into the target directory and runs `npm install` automatically.
|
|
18
|
+
|
|
19
|
+
## Templates
|
|
20
|
+
|
|
21
|
+
Pass `--template <name>` to choose a template. The default is `bim`.
|
|
22
|
+
|
|
23
|
+
| Template | When to use |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `bim` (default) | Standard BIM viewer app — Three.js viewport, BIM viewer, platform UI components. This is the right starting point for almost every app. |
|
|
26
|
+
| `default` | Minimal shell — just shows platform context. Use only when you explicitly want to start from scratch without any viewer. |
|
|
27
|
+
|
|
28
|
+
If no template is specified, use `bim`.
|
|
29
|
+
|
|
30
|
+
## What the scaffold produces
|
|
31
|
+
|
|
32
|
+
The `bim` template generates the full structure described in the **Project Structure** section of [./app-architecture.md](./app-architecture.md):
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
src/
|
|
36
|
+
├── bim-components/
|
|
37
|
+
├── ui-components/
|
|
38
|
+
├── setups/
|
|
39
|
+
├── app.ts
|
|
40
|
+
├── globals.ts
|
|
41
|
+
└── main.ts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
All platform built-ins (`AppManager`, `UIManager`, `ViewportsManager`) are already wired up in the generated `main.ts`. There is nothing to manually wire at the entry point — just extend from there.
|
|
45
|
+
|
|
46
|
+
## Starting the dev server
|
|
47
|
+
|
|
48
|
+
After scaffolding, start the local dev server:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
thatopen serve
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This launches an esbuild watch process that rebundles on every file change and serves the app with live reload. No configuration needed.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Async Actions
|
|
2
|
+
|
|
3
|
+
## Async button handler
|
|
4
|
+
|
|
5
|
+
When a button triggers an async operation, set `target.loading = true` at the start and `false` when done — including in error paths:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const onClick = async ({ target }: { target: BUI.Button }) => {
|
|
9
|
+
target.loading = true
|
|
10
|
+
try {
|
|
11
|
+
await doSomething()
|
|
12
|
+
} finally {
|
|
13
|
+
target.loading = false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Always restore `target.loading = false` in error paths. If an exception is thrown and loading is not reset, the button stays stuck in the loading state.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Confirmation Dialog
|
|
2
|
+
|
|
3
|
+
The standard way to ask the user to confirm a destructive or irreversible action is a `contextMenuTemplate` on a `bim-button`. The menu opens lazily when clicked and contains Confirm / Cancel buttons.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
`contextMenuTemplate` must always be assigned via `BUI.ref` — never inline in `BUI.html`:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const onBtnCreated = (e?: Element) => {
|
|
11
|
+
if (!e) return
|
|
12
|
+
const btn = e as BUI.Button
|
|
13
|
+
btn.contextMenuTemplate = () => {
|
|
14
|
+
const onConfirm = async ({ target }: { target: BUI.Button }) => {
|
|
15
|
+
target.loading = true
|
|
16
|
+
await doSomething()
|
|
17
|
+
target.loading = false
|
|
18
|
+
BUI.ContextMenu.removeMenus()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const onCancel = () => {
|
|
22
|
+
BUI.ContextMenu.removeMenus()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return BUI.html`
|
|
26
|
+
<bim-context-menu>
|
|
27
|
+
<bim-button @click=${onConfirm} label="Confirm"></bim-button>
|
|
28
|
+
<bim-button @click=${onCancel} label="Cancel"></bim-button>
|
|
29
|
+
</bim-context-menu>
|
|
30
|
+
`
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return BUI.html`<bim-button ${BUI.ref(onBtnCreated)} icon="delete"></bim-button>`
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Closing the menu
|
|
38
|
+
|
|
39
|
+
Call `BUI.ContextMenu.removeMenus()` after the action completes (or is cancelled) to dismiss the popup. Without it, the menu stays open after the user clicks.
|