@thatopen/services 0.3.15 → 0.4.0
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/README.md +62 -62
- package/dist/built-in/index.d.ts +165 -2
- package/dist/cli.js +27 -13
- package/dist/core/client.d.ts +1 -0
- package/dist/core/platform-client.d.ts +7 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.es.js +396 -374
- package/dist/vite-env.d.ts +1 -1
- package/docs/builtin/paths.json +94 -86
- package/docs/cli/create.md +29 -29
- package/docs/cli/local-server.md +38 -38
- package/docs/cli/login.md +32 -32
- package/docs/cli/publish.md +31 -31
- package/docs/cli/run.md +26 -26
- package/docs/cli/serve.md +43 -43
- package/docs/cli/swap.md +30 -30
- package/package.json +4 -4
- package/resources/AGENTS.md +40 -40
- package/src/built-in/CDEManager/example.ts +214 -214
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
- package/src/built-in/ClashesManager/example.ts +283 -283
- package/src/built-in/UIManager/src/app/example.ts +97 -97
- package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
- package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
- package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
- package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
- package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
- package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
- package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
- package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -0
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -160
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -0
- package/src/built-in/index.ts +1048 -881
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
- package/src/cli/templates/app/src/bim-components/index.ts +1 -1
- package/src/cli/templates/app/src/globals.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
- package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
- package/src/cli/templates/app/src/ui-components/index.ts +2 -2
- package/src/cli/templates/app/tsconfig.json +16 -16
- package/src/cli/templates/app/vite.config.js +57 -57
- package/src/cli/templates/cloud-component/README.md +205 -205
- package/src/cli/templates/cloud-component/_thatopen +5 -5
- package/src/cli/templates/cloud-component/declarations.json +4 -4
- package/src/cli/templates/cloud-component/package.json +21 -21
- package/src/cli/templates/cloud-component/src/main.ts +70 -70
- package/src/cli/templates/cloud-component/tsconfig.json +16 -16
- package/src/cli/templates/cloud-component/vite.config.js +46 -46
- package/src/cli/templates/shared/AGENTS.md +8 -8
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/src/cli/templates/shared/_gitignore +5 -5
package/docs/cli/swap.md
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "Toggle or explicitly set the engine library mode between stable public packages (@thatopen/*) and private beta packages (@thatopen-platform/*-beta). Updates package.json and .thatopen, then runs npm install. Source code never changes — aliases handle resolution."
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
## thatopen swap
|
|
6
|
-
|
|
7
|
-
Switches the project between stable and beta engine libraries. Without flags, it toggles from the current state. Updates `package.json` dependencies and sets `beta` in `.thatopen` so `thatopen serve`, `thatopen local-server`, and `vite build` all pick up the change automatically.
|
|
8
|
-
|
|
9
|
-
Source code stays unchanged — `@thatopen/components` imports resolve to the correct package via esbuild/vite aliases based on the `beta` flag in `.thatopen`.
|
|
10
|
-
|
|
11
|
-
**Usage:**
|
|
12
|
-
```bash
|
|
13
|
-
thatopen swap # toggle: stable → beta or beta → stable
|
|
14
|
-
thatopen swap --beta # force beta
|
|
15
|
-
thatopen swap --stable # force stable
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Flags:**
|
|
19
|
-
|
|
20
|
-
- `--beta` — Switch to beta libraries (`@thatopen-platform/*-beta`).
|
|
21
|
-
- `--stable` — Switch back to stable public libraries (`@thatopen/*`).
|
|
22
|
-
|
|
23
|
-
**Examples:**
|
|
24
|
-
```bash
|
|
25
|
-
thatopen swap
|
|
26
|
-
thatopen swap --beta
|
|
27
|
-
thatopen swap --stable
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
> Beta packages are private and the access must be explicitly granted by That Open Company.
|
|
1
|
+
---
|
|
2
|
+
description: "Toggle or explicitly set the engine library mode between stable public packages (@thatopen/*) and private beta packages (@thatopen-platform/*-beta). Updates package.json and .thatopen, then runs npm install. Source code never changes — aliases handle resolution."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## thatopen swap
|
|
6
|
+
|
|
7
|
+
Switches the project between stable and beta engine libraries. Without flags, it toggles from the current state. Updates `package.json` dependencies and sets `beta` in `.thatopen` so `thatopen serve`, `thatopen local-server`, and `vite build` all pick up the change automatically.
|
|
8
|
+
|
|
9
|
+
Source code stays unchanged — `@thatopen/components` imports resolve to the correct package via esbuild/vite aliases based on the `beta` flag in `.thatopen`.
|
|
10
|
+
|
|
11
|
+
**Usage:**
|
|
12
|
+
```bash
|
|
13
|
+
thatopen swap # toggle: stable → beta or beta → stable
|
|
14
|
+
thatopen swap --beta # force beta
|
|
15
|
+
thatopen swap --stable # force stable
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Flags:**
|
|
19
|
+
|
|
20
|
+
- `--beta` — Switch to beta libraries (`@thatopen-platform/*-beta`).
|
|
21
|
+
- `--stable` — Switch back to stable public libraries (`@thatopen/*`).
|
|
22
|
+
|
|
23
|
+
**Examples:**
|
|
24
|
+
```bash
|
|
25
|
+
thatopen swap
|
|
26
|
+
thatopen swap --beta
|
|
27
|
+
thatopen swap --stable
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> Beta packages are private and the access must be explicitly granted by That Open Company.
|
package/package.json
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
"description": "Client library and CLI for building BIM apps and cloud components on the That Open Platform",
|
|
4
4
|
"author": "That Open Company",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"homepage": "https://github.com/ThatOpen/
|
|
6
|
+
"homepage": "https://github.com/ThatOpen/platform_services#readme",
|
|
7
7
|
"bugs": {
|
|
8
|
-
"url": "https://github.com/ThatOpen/
|
|
8
|
+
"url": "https://github.com/ThatOpen/platform_services/issues"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/ThatOpen/
|
|
12
|
+
"url": "git+https://github.com/ThatOpen/platform_services.git"
|
|
13
13
|
},
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"private": false,
|
|
18
|
-
"version": "0.
|
|
18
|
+
"version": "0.4.0",
|
|
19
19
|
"main": "dist/index.cjs.js",
|
|
20
20
|
"module": "dist/index.es.js",
|
|
21
21
|
"types": "dist/index.d.ts",
|
package/resources/AGENTS.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
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, then immediately load all indexes listed below before doing anything else.**
|
|
7
|
-
|
|
8
|
-
> This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All local docs it
|
|
9
|
-
> references ship inside the `@thatopen/services` package under `docs/`.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Session start — load all indexes in parallel
|
|
14
|
-
|
|
15
|
-
Do this before answering any question or writing any code. These are compact description indexes:
|
|
16
|
-
|
|
17
|
-
| What | Where |
|
|
18
|
-
|---|---|
|
|
19
|
-
| Platform built-ins | `node_modules/@thatopen/services/docs/builtin/paths.json` |
|
|
20
|
-
| Platform client API | `node_modules/@thatopen/services/docs/client/paths.json` |
|
|
21
|
-
| CLI commands | `node_modules/@thatopen/services/docs/cli/paths.json` |
|
|
22
|
-
| Engine components (`OBC`, `OBF`) | `https://raw.githubusercontent.com/ThatOpen/engine_components/refs/heads/main/examples/paths.json` |
|
|
23
|
-
| Fragments (`FRAGS`) | `https://raw.githubusercontent.com/ThatOpen/engine_fragment/refs/heads/main/examples/paths.json` |
|
|
24
|
-
| UI components (`BUI`) | `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json` — **skip** entries whose path contains `packages/obc` or `bim-grid` |
|
|
25
|
-
|
|
26
|
-
Once you have these, you know everything available on the platform. Only then fetch a specific example file when you need implementation details.
|
|
27
|
-
|
|
28
|
-
> **Path resolution:** all `path` entries inside the local indexes (`docs/`, `src/`) are relative to the `@thatopen/services` package root — prepend `node_modules/@thatopen/services/` when reading any of those files.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## How to work
|
|
33
|
-
|
|
34
|
-
- **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
|
|
35
|
-
**Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
|
|
36
|
-
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
|
|
37
|
-
- If something already exists in the indexes, use it — don't reimplement it.
|
|
38
|
-
|
|
39
|
-
## Hard rules (always apply)
|
|
40
|
-
|
|
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, then immediately load all indexes listed below before doing anything else.**
|
|
7
|
+
|
|
8
|
+
> This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All local docs it
|
|
9
|
+
> references ship inside the `@thatopen/services` package under `docs/`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Session start — load all indexes in parallel
|
|
14
|
+
|
|
15
|
+
Do this before answering any question or writing any code. These are compact description indexes:
|
|
16
|
+
|
|
17
|
+
| What | Where |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Platform built-ins | `node_modules/@thatopen/services/docs/builtin/paths.json` |
|
|
20
|
+
| Platform client API | `node_modules/@thatopen/services/docs/client/paths.json` |
|
|
21
|
+
| CLI commands | `node_modules/@thatopen/services/docs/cli/paths.json` |
|
|
22
|
+
| Engine components (`OBC`, `OBF`) | `https://raw.githubusercontent.com/ThatOpen/engine_components/refs/heads/main/examples/paths.json` |
|
|
23
|
+
| Fragments (`FRAGS`) | `https://raw.githubusercontent.com/ThatOpen/engine_fragment/refs/heads/main/examples/paths.json` |
|
|
24
|
+
| UI components (`BUI`) | `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json` — **skip** entries whose path contains `packages/obc` or `bim-grid` |
|
|
25
|
+
|
|
26
|
+
Once you have these, you know everything available on the platform. Only then fetch a specific example file when you need implementation details.
|
|
27
|
+
|
|
28
|
+
> **Path resolution:** all `path` entries inside the local indexes (`docs/`, `src/`) are relative to the `@thatopen/services` package root — prepend `node_modules/@thatopen/services/` when reading any of those files.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## How to work
|
|
33
|
+
|
|
34
|
+
- **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
|
|
35
|
+
**Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
|
|
36
|
+
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
|
|
37
|
+
- If something already exists in the indexes, use it — don't reimplement it.
|
|
38
|
+
|
|
39
|
+
## Hard rules (always apply)
|
|
40
|
+
|
|
41
41
|
1. **All UI must be built with Lit**, using the web components from `@thatopen/ui` (`BUI`) — `bim-button`, `bim-panel`, `bim-panel-section`, `bim-toolbar`, `bim-dropdown`, `bim-input`, and the rest of `packages/core`. Always consult the design system before writing any UI: `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/DESIGN.md`.
|
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## CDEManager
|
|
3
|
-
---
|
|
4
|
-
CDEManager is the central state machine for a Common Data Environment. It holds
|
|
5
|
-
files, folders, metadata schemas, viewer groups, and user permissions, and exposes a
|
|
6
|
-
typed state machine that all file operations — selecting, editing, uploading,
|
|
7
|
-
viewing — route through. It has no DOM or Three.js dependencies and can run in Node,
|
|
8
|
-
a browser, or a server-side worker wherever `@thatopen/components` is available.
|
|
9
|
-
|
|
10
|
-
This example shows how to initialize CDEManager, populate it with mock and live data,
|
|
11
|
-
subscribe to its events, navigate the state machine, and build file names from a
|
|
12
|
-
naming schema.
|
|
13
|
-
|
|
14
|
-
### 🖖 Importing our libraries
|
|
15
|
-
First, install all necessary dependencies:
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// In production platform apps, CDEManager is loaded through the platform client:
|
|
19
|
-
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
20
|
-
// Here we import directly from source for local testing.
|
|
21
|
-
import * as OBC from "@thatopen/components";
|
|
22
|
-
import { CDEManager } from "./index";
|
|
23
|
-
import type { CDEFile } from "./src/types";
|
|
24
|
-
|
|
25
|
-
/* MD
|
|
26
|
-
### 🚀 Initializing CDEManager
|
|
27
|
-
CDEManager is an `OBC.Component`, so you get it from the shared `components`
|
|
28
|
-
registry with `components.get(CDEManager)`. The constructor registers it
|
|
29
|
-
automatically — calling `get` a second time returns the same instance.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
const components = new OBC.Components();
|
|
33
|
-
const cde = components.get(CDEManager);
|
|
34
|
-
|
|
35
|
-
const output = document.getElementById("output") as HTMLPreElement;
|
|
36
|
-
const log = (msg: string) => {
|
|
37
|
-
console.log(msg);
|
|
38
|
-
output.textContent += msg + "\n";
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/* MD
|
|
42
|
-
### 📋 Subscribing to events
|
|
43
|
-
All CDEManager events are `OBC.Event<T>` instances. Subscribe before setting data
|
|
44
|
-
so you capture the first trigger.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
cde.onFilesChanged.add((files) => {
|
|
48
|
-
log(`[event] onFilesChanged: ${files.length} file(s)`);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
cde.onFoldersChanged.add((folders) => {
|
|
52
|
-
log(`[event] onFoldersChanged: ${folders.length} folder(s)`);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
cde.onMachineStateChanged.add((state) => {
|
|
56
|
-
log(`[event] machineState → ${state.kind}`);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
cde.onSelectedGroupChanged.add((group) => {
|
|
60
|
-
log(`[event] selectedGroup → ${group ?? "(none)"}`);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
/* MD
|
|
64
|
-
### 📂 Populating with mock data
|
|
65
|
-
Setting `cde.files` and `cde.folders` directly triggers `onFilesChanged` and
|
|
66
|
-
`onFoldersChanged`. This lets you build and test views without a live backend.
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
const mockFolder = { _id: "folder-str", name: "Structural", parentId: null };
|
|
70
|
-
cde.folders = [mockFolder];
|
|
71
|
-
|
|
72
|
-
const now = new Date();
|
|
73
|
-
const mockFiles: CDEFile[] = [
|
|
74
|
-
{
|
|
75
|
-
_id: "file-col", name: "column.ifc",
|
|
76
|
-
folderId: "folder-str", createdBy: "alice@example.com",
|
|
77
|
-
createdAt: now, metadata: { discipline: "STR", phase: "DD" },
|
|
78
|
-
itemType: "FILE",
|
|
79
|
-
} as CDEFile,
|
|
80
|
-
{
|
|
81
|
-
_id: "file-fnd", name: "foundations.ifc",
|
|
82
|
-
folderId: "folder-str", createdBy: "bob@example.com",
|
|
83
|
-
createdAt: now, metadata: { discipline: "STR", phase: "CD" },
|
|
84
|
-
itemType: "FILE",
|
|
85
|
-
} as CDEFile,
|
|
86
|
-
{
|
|
87
|
-
_id: "file-pdf", name: "report.pdf",
|
|
88
|
-
folderId: null, createdBy: "alice@example.com",
|
|
89
|
-
createdAt: now, metadata: {},
|
|
90
|
-
itemType: "FILE",
|
|
91
|
-
} as CDEFile,
|
|
92
|
-
];
|
|
93
|
-
cde.files = mockFiles;
|
|
94
|
-
|
|
95
|
-
/* MD
|
|
96
|
-
### 🔍 Filtering files: visibleFiles vs getSelectedFiles
|
|
97
|
-
`visibleFiles` excludes files whose folder was marked as a system folder by
|
|
98
|
-
`loadFolders` (folders whose names start with `__`). With mock data set directly,
|
|
99
|
-
no system folders are marked, so all files are always visible.
|
|
100
|
-
|
|
101
|
-
`getSelectedFiles` is the folder-scoped view used by the UI table. When
|
|
102
|
-
`selectedGroup` is `null` it returns root files only (no folderId); when set to a
|
|
103
|
-
folder id it returns files in that folder.
|
|
104
|
-
*/
|
|
105
|
-
|
|
106
|
-
log(`\nvisibleFiles (all, no system folders excluded): ${cde.visibleFiles.length}`);
|
|
107
|
-
|
|
108
|
-
// getSelectedFiles respects selectedGroup, not visibleFiles.
|
|
109
|
-
cde.selectedGroup = null;
|
|
110
|
-
log(`getSelectedFiles (selectedGroup=null → root only): ${cde.getSelectedFiles().length}`);
|
|
111
|
-
|
|
112
|
-
cde.selectedGroup = "folder-str";
|
|
113
|
-
log(`getSelectedFiles (selectedGroup=folder-str): ${cde.getSelectedFiles().length}`);
|
|
114
|
-
|
|
115
|
-
/* MD
|
|
116
|
-
### 🎛️ State machine navigation
|
|
117
|
-
Every UI-level action goes through `sendMachineEvent`. The current state is readable
|
|
118
|
-
via `cde.machineState`. Events that aren't valid for the current state are silently
|
|
119
|
-
ignored — checking `machineState.kind` before sending prevents unexpected no-ops.
|
|
120
|
-
|
|
121
|
-
The `"selected"` state stores the selected files in `machineState.files`. This is
|
|
122
|
-
separate from `getSelectedFiles()`, which always returns the current folder view
|
|
123
|
-
regardless of machine state.
|
|
124
|
-
*/
|
|
125
|
-
|
|
126
|
-
log(`\nmachineState before SELECT: ${cde.machineState.kind}`);
|
|
127
|
-
|
|
128
|
-
cde.sendMachineEvent({ type: "SELECT", files: [mockFiles[0], mockFiles[1]] });
|
|
129
|
-
// machineState.files holds the selection — getSelectedFiles() returns the folder view.
|
|
130
|
-
const sel = cde.machineState as { kind: string; files?: CDEFile[] };
|
|
131
|
-
log(`machineState.files: ${sel.files?.length ?? 0} file(s) selected`);
|
|
132
|
-
|
|
133
|
-
cde.sendMachineEvent({ type: "DESELECT" });
|
|
134
|
-
log(`machineState after DESELECT: ${cde.machineState.kind}`);
|
|
135
|
-
|
|
136
|
-
/* MD
|
|
137
|
-
### 🏷️ Viewer groups and canView
|
|
138
|
-
`viewerGroups` declares which file extensions can be loaded side by side.
|
|
139
|
-
`multiple: true` allows multi-file loads; omitting it means only one file at a time.
|
|
140
|
-
`canView` checks whether a set of files belongs to one group and respects the flag.
|
|
141
|
-
*/
|
|
142
|
-
|
|
143
|
-
cde.viewerGroups = [
|
|
144
|
-
{ extensions: ["ifc", "frag"], multiple: true },
|
|
145
|
-
{ extensions: ["pdf"] },
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
const ifcFiles = mockFiles.filter(f => f.name.endsWith(".ifc"));
|
|
149
|
-
log(`\ncanView([column.ifc, foundations.ifc]): ${cde.canView(ifcFiles)}`);
|
|
150
|
-
log(`canView([report.pdf]): ${cde.canView([mockFiles[2]])}`);
|
|
151
|
-
// Mixed types never share a viewer group, regardless of multiple.
|
|
152
|
-
log(`canView([column.ifc, report.pdf]): ${cde.canView([mockFiles[0], mockFiles[2]])}`);
|
|
153
|
-
|
|
154
|
-
/* MD
|
|
155
|
-
### 📝 Naming schema
|
|
156
|
-
A naming schema assembles a computed file name from metadata fields. Segments define
|
|
157
|
-
the order and an optional transform; `separator` joins them. If a metadata key is
|
|
158
|
-
absent, `missingMetadataPlaceholder` is used so missing values stay visible.
|
|
159
|
-
*/
|
|
160
|
-
|
|
161
|
-
cde.missingMetadataPlaceholder = "XX";
|
|
162
|
-
cde.setNamingSchema({
|
|
163
|
-
segments: [
|
|
164
|
-
{ key: "discipline" },
|
|
165
|
-
{ key: "phase" },
|
|
166
|
-
],
|
|
167
|
-
separator: "-",
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// column.ifc has both fields → "STR-DD"
|
|
171
|
-
log(`\nbuildFileName(column.ifc): "${cde.buildFileName(mockFiles[0])}"`);
|
|
172
|
-
// report.pdf has no metadata → "XX-XX"
|
|
173
|
-
log(`buildFileName(report.pdf): "${cde.buildFileName(mockFiles[2])}"`);
|
|
174
|
-
|
|
175
|
-
/* MD
|
|
176
|
-
### 🌐 Live data (requires credentials)
|
|
177
|
-
The live section below connects to a real backend. Pass credentials as query
|
|
178
|
-
parameters when opening example.html:
|
|
179
|
-
|
|
180
|
-
example.html?accessToken=<token>&apiUrl=https://...&projectId=<id>
|
|
181
|
-
|
|
182
|
-
Without them, this section is skipped.
|
|
183
|
-
|
|
184
|
-
`loadFolders()` does more than just fetch folders — it also loads user permissions
|
|
185
|
-
and project members in the same call, then excludes any `__`-prefixed system
|
|
186
|
-
folders from `cde.folders`.
|
|
187
|
-
*/
|
|
188
|
-
|
|
189
|
-
const params = new URLSearchParams(location.search);
|
|
190
|
-
const accessToken = params.get("accessToken");
|
|
191
|
-
const apiUrl = params.get("apiUrl");
|
|
192
|
-
const projectId = params.get("projectId");
|
|
193
|
-
|
|
194
|
-
if (accessToken && apiUrl && projectId) {
|
|
195
|
-
const { EngineServicesClient } = await import("@thatopen/services");
|
|
196
|
-
const client = new EngineServicesClient(accessToken, apiUrl);
|
|
197
|
-
cde.ctx = { client, projectId };
|
|
198
|
-
|
|
199
|
-
await cde.loadFolders();
|
|
200
|
-
log(`\nloadFolders: ${cde.folders.length} visible folder(s), project: "${cde.projectName}"`);
|
|
201
|
-
log(`userPermissions: ${JSON.stringify(cde.userPermissions)}`);
|
|
202
|
-
|
|
203
|
-
await cde.loadMetadataSchema();
|
|
204
|
-
log(`loadMetadataSchema: ${Object.keys(cde.metadataSchema).length} field(s) defined`);
|
|
205
|
-
} else {
|
|
206
|
-
log("\nLive data skipped — pass ?accessToken=&apiUrl=&projectId= to run the full cycle.");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/* MD
|
|
210
|
-
### 🎉 Wrap up
|
|
211
|
-
CDEManager handles state, events, filtering, and naming entirely in memory. The
|
|
212
|
-
backend connection through `cde.ctx` is optional — everything above `loadFolders`
|
|
213
|
-
works offline with mock data. See platform_cde for the full production wiring.
|
|
214
|
-
*/
|
|
1
|
+
/* MD
|
|
2
|
+
## CDEManager
|
|
3
|
+
---
|
|
4
|
+
CDEManager is the central state machine for a Common Data Environment. It holds
|
|
5
|
+
files, folders, metadata schemas, viewer groups, and user permissions, and exposes a
|
|
6
|
+
typed state machine that all file operations — selecting, editing, uploading,
|
|
7
|
+
viewing — route through. It has no DOM or Three.js dependencies and can run in Node,
|
|
8
|
+
a browser, or a server-side worker wherever `@thatopen/components` is available.
|
|
9
|
+
|
|
10
|
+
This example shows how to initialize CDEManager, populate it with mock and live data,
|
|
11
|
+
subscribe to its events, navigate the state machine, and build file names from a
|
|
12
|
+
naming schema.
|
|
13
|
+
|
|
14
|
+
### 🖖 Importing our libraries
|
|
15
|
+
First, install all necessary dependencies:
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// In production platform apps, CDEManager is loaded through the platform client:
|
|
19
|
+
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
20
|
+
// Here we import directly from source for local testing.
|
|
21
|
+
import * as OBC from "@thatopen/components";
|
|
22
|
+
import { CDEManager } from "./index";
|
|
23
|
+
import type { CDEFile } from "./src/types";
|
|
24
|
+
|
|
25
|
+
/* MD
|
|
26
|
+
### 🚀 Initializing CDEManager
|
|
27
|
+
CDEManager is an `OBC.Component`, so you get it from the shared `components`
|
|
28
|
+
registry with `components.get(CDEManager)`. The constructor registers it
|
|
29
|
+
automatically — calling `get` a second time returns the same instance.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const components = new OBC.Components();
|
|
33
|
+
const cde = components.get(CDEManager);
|
|
34
|
+
|
|
35
|
+
const output = document.getElementById("output") as HTMLPreElement;
|
|
36
|
+
const log = (msg: string) => {
|
|
37
|
+
console.log(msg);
|
|
38
|
+
output.textContent += msg + "\n";
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/* MD
|
|
42
|
+
### 📋 Subscribing to events
|
|
43
|
+
All CDEManager events are `OBC.Event<T>` instances. Subscribe before setting data
|
|
44
|
+
so you capture the first trigger.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
cde.onFilesChanged.add((files) => {
|
|
48
|
+
log(`[event] onFilesChanged: ${files.length} file(s)`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
cde.onFoldersChanged.add((folders) => {
|
|
52
|
+
log(`[event] onFoldersChanged: ${folders.length} folder(s)`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
cde.onMachineStateChanged.add((state) => {
|
|
56
|
+
log(`[event] machineState → ${state.kind}`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
cde.onSelectedGroupChanged.add((group) => {
|
|
60
|
+
log(`[event] selectedGroup → ${group ?? "(none)"}`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/* MD
|
|
64
|
+
### 📂 Populating with mock data
|
|
65
|
+
Setting `cde.files` and `cde.folders` directly triggers `onFilesChanged` and
|
|
66
|
+
`onFoldersChanged`. This lets you build and test views without a live backend.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
const mockFolder = { _id: "folder-str", name: "Structural", parentId: null };
|
|
70
|
+
cde.folders = [mockFolder];
|
|
71
|
+
|
|
72
|
+
const now = new Date();
|
|
73
|
+
const mockFiles: CDEFile[] = [
|
|
74
|
+
{
|
|
75
|
+
_id: "file-col", name: "column.ifc",
|
|
76
|
+
folderId: "folder-str", createdBy: "alice@example.com",
|
|
77
|
+
createdAt: now, metadata: { discipline: "STR", phase: "DD" },
|
|
78
|
+
itemType: "FILE",
|
|
79
|
+
} as CDEFile,
|
|
80
|
+
{
|
|
81
|
+
_id: "file-fnd", name: "foundations.ifc",
|
|
82
|
+
folderId: "folder-str", createdBy: "bob@example.com",
|
|
83
|
+
createdAt: now, metadata: { discipline: "STR", phase: "CD" },
|
|
84
|
+
itemType: "FILE",
|
|
85
|
+
} as CDEFile,
|
|
86
|
+
{
|
|
87
|
+
_id: "file-pdf", name: "report.pdf",
|
|
88
|
+
folderId: null, createdBy: "alice@example.com",
|
|
89
|
+
createdAt: now, metadata: {},
|
|
90
|
+
itemType: "FILE",
|
|
91
|
+
} as CDEFile,
|
|
92
|
+
];
|
|
93
|
+
cde.files = mockFiles;
|
|
94
|
+
|
|
95
|
+
/* MD
|
|
96
|
+
### 🔍 Filtering files: visibleFiles vs getSelectedFiles
|
|
97
|
+
`visibleFiles` excludes files whose folder was marked as a system folder by
|
|
98
|
+
`loadFolders` (folders whose names start with `__`). With mock data set directly,
|
|
99
|
+
no system folders are marked, so all files are always visible.
|
|
100
|
+
|
|
101
|
+
`getSelectedFiles` is the folder-scoped view used by the UI table. When
|
|
102
|
+
`selectedGroup` is `null` it returns root files only (no folderId); when set to a
|
|
103
|
+
folder id it returns files in that folder.
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
log(`\nvisibleFiles (all, no system folders excluded): ${cde.visibleFiles.length}`);
|
|
107
|
+
|
|
108
|
+
// getSelectedFiles respects selectedGroup, not visibleFiles.
|
|
109
|
+
cde.selectedGroup = null;
|
|
110
|
+
log(`getSelectedFiles (selectedGroup=null → root only): ${cde.getSelectedFiles().length}`);
|
|
111
|
+
|
|
112
|
+
cde.selectedGroup = "folder-str";
|
|
113
|
+
log(`getSelectedFiles (selectedGroup=folder-str): ${cde.getSelectedFiles().length}`);
|
|
114
|
+
|
|
115
|
+
/* MD
|
|
116
|
+
### 🎛️ State machine navigation
|
|
117
|
+
Every UI-level action goes through `sendMachineEvent`. The current state is readable
|
|
118
|
+
via `cde.machineState`. Events that aren't valid for the current state are silently
|
|
119
|
+
ignored — checking `machineState.kind` before sending prevents unexpected no-ops.
|
|
120
|
+
|
|
121
|
+
The `"selected"` state stores the selected files in `machineState.files`. This is
|
|
122
|
+
separate from `getSelectedFiles()`, which always returns the current folder view
|
|
123
|
+
regardless of machine state.
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
log(`\nmachineState before SELECT: ${cde.machineState.kind}`);
|
|
127
|
+
|
|
128
|
+
cde.sendMachineEvent({ type: "SELECT", files: [mockFiles[0], mockFiles[1]] });
|
|
129
|
+
// machineState.files holds the selection — getSelectedFiles() returns the folder view.
|
|
130
|
+
const sel = cde.machineState as { kind: string; files?: CDEFile[] };
|
|
131
|
+
log(`machineState.files: ${sel.files?.length ?? 0} file(s) selected`);
|
|
132
|
+
|
|
133
|
+
cde.sendMachineEvent({ type: "DESELECT" });
|
|
134
|
+
log(`machineState after DESELECT: ${cde.machineState.kind}`);
|
|
135
|
+
|
|
136
|
+
/* MD
|
|
137
|
+
### 🏷️ Viewer groups and canView
|
|
138
|
+
`viewerGroups` declares which file extensions can be loaded side by side.
|
|
139
|
+
`multiple: true` allows multi-file loads; omitting it means only one file at a time.
|
|
140
|
+
`canView` checks whether a set of files belongs to one group and respects the flag.
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
cde.viewerGroups = [
|
|
144
|
+
{ extensions: ["ifc", "frag"], multiple: true },
|
|
145
|
+
{ extensions: ["pdf"] },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const ifcFiles = mockFiles.filter(f => f.name.endsWith(".ifc"));
|
|
149
|
+
log(`\ncanView([column.ifc, foundations.ifc]): ${cde.canView(ifcFiles)}`);
|
|
150
|
+
log(`canView([report.pdf]): ${cde.canView([mockFiles[2]])}`);
|
|
151
|
+
// Mixed types never share a viewer group, regardless of multiple.
|
|
152
|
+
log(`canView([column.ifc, report.pdf]): ${cde.canView([mockFiles[0], mockFiles[2]])}`);
|
|
153
|
+
|
|
154
|
+
/* MD
|
|
155
|
+
### 📝 Naming schema
|
|
156
|
+
A naming schema assembles a computed file name from metadata fields. Segments define
|
|
157
|
+
the order and an optional transform; `separator` joins them. If a metadata key is
|
|
158
|
+
absent, `missingMetadataPlaceholder` is used so missing values stay visible.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
cde.missingMetadataPlaceholder = "XX";
|
|
162
|
+
cde.setNamingSchema({
|
|
163
|
+
segments: [
|
|
164
|
+
{ key: "discipline" },
|
|
165
|
+
{ key: "phase" },
|
|
166
|
+
],
|
|
167
|
+
separator: "-",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// column.ifc has both fields → "STR-DD"
|
|
171
|
+
log(`\nbuildFileName(column.ifc): "${cde.buildFileName(mockFiles[0])}"`);
|
|
172
|
+
// report.pdf has no metadata → "XX-XX"
|
|
173
|
+
log(`buildFileName(report.pdf): "${cde.buildFileName(mockFiles[2])}"`);
|
|
174
|
+
|
|
175
|
+
/* MD
|
|
176
|
+
### 🌐 Live data (requires credentials)
|
|
177
|
+
The live section below connects to a real backend. Pass credentials as query
|
|
178
|
+
parameters when opening example.html:
|
|
179
|
+
|
|
180
|
+
example.html?accessToken=<token>&apiUrl=https://...&projectId=<id>
|
|
181
|
+
|
|
182
|
+
Without them, this section is skipped.
|
|
183
|
+
|
|
184
|
+
`loadFolders()` does more than just fetch folders — it also loads user permissions
|
|
185
|
+
and project members in the same call, then excludes any `__`-prefixed system
|
|
186
|
+
folders from `cde.folders`.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
const params = new URLSearchParams(location.search);
|
|
190
|
+
const accessToken = params.get("accessToken");
|
|
191
|
+
const apiUrl = params.get("apiUrl");
|
|
192
|
+
const projectId = params.get("projectId");
|
|
193
|
+
|
|
194
|
+
if (accessToken && apiUrl && projectId) {
|
|
195
|
+
const { EngineServicesClient } = await import("@thatopen/services");
|
|
196
|
+
const client = new EngineServicesClient(accessToken, apiUrl);
|
|
197
|
+
cde.ctx = { client, projectId };
|
|
198
|
+
|
|
199
|
+
await cde.loadFolders();
|
|
200
|
+
log(`\nloadFolders: ${cde.folders.length} visible folder(s), project: "${cde.projectName}"`);
|
|
201
|
+
log(`userPermissions: ${JSON.stringify(cde.userPermissions)}`);
|
|
202
|
+
|
|
203
|
+
await cde.loadMetadataSchema();
|
|
204
|
+
log(`loadMetadataSchema: ${Object.keys(cde.metadataSchema).length} field(s) defined`);
|
|
205
|
+
} else {
|
|
206
|
+
log("\nLive data skipped — pass ?accessToken=&apiUrl=&projectId= to run the full cycle.");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* MD
|
|
210
|
+
### 🎉 Wrap up
|
|
211
|
+
CDEManager handles state, events, filtering, and naming entirely in memory. The
|
|
212
|
+
backend connection through `cde.ctx` is optional — everything above `loadFolders`
|
|
213
|
+
works offline with mock data. See platform_cde for the full production wiring.
|
|
214
|
+
*/
|