@thatopen/services 0.0.6 → 0.2.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.
Files changed (68) hide show
  1. package/README.md +59 -46
  2. package/dist/built-in/index.d.ts +723 -0
  3. package/dist/cli/lib/npmrc.d.ts +28 -0
  4. package/dist/cli/lib/npmrc.test.d.ts +1 -0
  5. package/dist/cli.js +103 -7
  6. package/dist/core/client.d.ts +20 -1
  7. package/dist/index.cjs.js +2 -2
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.es.js +130 -102
  10. package/dist/types/files.d.ts +6 -0
  11. package/dist/types/npm.d.ts +14 -0
  12. package/dist/vite-env.d.ts +1 -1
  13. package/docs/builtin/paths.json +22 -6
  14. package/package.json +99 -99
  15. package/resources/AGENTS.md +5 -3
  16. package/src/built-in/AppManager/example.ts +86 -86
  17. package/src/built-in/CDEManager/example.ts +214 -214
  18. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  19. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  20. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  21. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  22. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  23. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  24. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  25. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  26. package/src/built-in/FileList/example.ts +107 -107
  27. package/src/built-in/TabbedNavigation/example.ts +115 -115
  28. package/src/built-in/UIManager/src/app/example.ts +99 -0
  29. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +142 -0
  30. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +125 -0
  31. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +92 -0
  32. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +161 -0
  33. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +104 -0
  34. package/src/built-in/ViewportsManager/example.ts +106 -106
  35. package/src/built-in/index.ts +755 -755
  36. package/src/cli/templates/app/README.md +241 -241
  37. package/src/cli/templates/app/index.html +27 -27
  38. package/src/cli/templates/app/package.json +27 -25
  39. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +87 -91
  40. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  41. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  42. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  43. package/src/cli/templates/app/src/main.ts +56 -89
  44. package/src/cli/templates/app/src/setups/cloud-runner.ts +9 -13
  45. package/src/cli/templates/app/src/setups/fragments.ts +18 -0
  46. package/src/cli/templates/app/src/setups/ifc-loader.ts +12 -0
  47. package/src/cli/templates/app/src/setups/index.ts +3 -3
  48. package/src/cli/templates/app/src/ui-components/AppPanel/index.ts +160 -0
  49. package/src/cli/templates/app/src/ui-components/index.ts +1 -2
  50. package/src/cli/templates/app/tsconfig.json +17 -16
  51. package/src/cli/templates/cloud-component/README.md +205 -205
  52. package/src/cli/templates/cloud-component/_thatopen +5 -5
  53. package/src/cli/templates/cloud-component/declarations.json +4 -4
  54. package/src/cli/templates/cloud-component/package.json +21 -21
  55. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  56. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  57. package/src/cli/templates/shared/CLAUDE.md +1 -1
  58. package/src/cli/templates/shared/_gitignore +5 -4
  59. package/src/cli/templates/app/src/app.ts +0 -16
  60. package/src/cli/templates/app/src/globals.ts +0 -1
  61. package/src/cli/templates/app/src/setups/ui-manager.ts +0 -27
  62. package/src/cli/templates/app/src/setups/viewports-manager.ts +0 -22
  63. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +0 -26
  64. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +0 -1
  65. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +0 -15
  66. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +0 -37
  67. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +0 -1
  68. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +0 -14
@@ -1,177 +1,177 @@
1
- /* MD
2
- ## Project configuration — read, write, and reload persistent data
3
- ---
4
- A CDE application needs to store two kinds of configuration: project-wide settings
5
- that all team members share (the metadata schema, custom naming rules) and personal
6
- settings that belong to one user (which columns are visible, which metadata views
7
- are pinned). Both live inside the project's file storage under a reserved system
8
- folder, so they travel with the project and don't rely on a separate configuration
9
- service.
10
-
11
- CDEManager exposes a simple read/write API that handles the folder structure
12
- automatically: finding or creating the right system folders, detecting whether a
13
- file already exists and versioning it instead of duplicating, and returning the
14
- parsed JSON on read. The metadata schema and user config are pre-wired shortcuts on
15
- top of this same mechanism — they just target fixed paths.
16
-
17
- This tutorial covers writing and reading arbitrary JSON under a named application
18
- namespace; demonstrating that writing the same file twice creates a new version
19
- rather than a duplicate; loading a user's personal config and verifying the round-
20
- trip; and writing a metadata schema then loading it back through `loadMetadataSchema`
21
- to confirm the schema is applied to CDEManager correctly.
22
-
23
- By the end, you'll see the full persistence layer that backs the schema editor and
24
- user preferences, and understand how any part of the platform can store structured
25
- data without managing the folder hierarchy directly.
26
- */
27
-
28
- import * as OBC from "@thatopen/components";
29
- // In production platform apps, CDEManager is loaded through the platform client:
30
- // const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
31
- // Here we import directly from source for local testing.
32
- import { CDEManager } from "../../index";
33
-
34
- /* MD
35
- ### 🛠️ Setting up
36
- Project data operations require an active client context throughout — they make real
37
- API calls to create folders and files. We use a stateful mock that behaves like a
38
- real backend: folders and files are created on demand and stored in memory, so reads
39
- always return what was last written.
40
- */
41
-
42
- const components = new OBC.Components();
43
- const cde = components.get(CDEManager);
44
-
45
- const output = document.getElementById("output") as HTMLPreElement;
46
- const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
47
-
48
- // In-memory store that simulates the project file storage.
49
- const store = {
50
- folders: [] as any[],
51
- files: [] as any[],
52
- data: new Map<string, any>(),
53
- fIdx: 0,
54
- dIdx: 0,
55
- };
56
-
57
- const mockClient = {
58
- checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
59
- getProjectData: async () => ({
60
- project: { title: "Demo Project" },
61
- users: [],
62
- currentUser: { user: { _id: "user-alice" } },
63
- }),
64
- listFolders: async ({ parentFolderId }: any) =>
65
- store.folders.filter(f =>
66
- parentFolderId !== undefined
67
- ? String(f.parentId) === String(parentFolderId)
68
- : !f.parentId
69
- ),
70
- createFolder: async (name: string, parentId?: string) => {
71
- const folder = { _id: `folder-${store.fIdx++}`, name, parentId: parentId ?? null };
72
- store.folders.push(folder);
73
- return folder;
74
- },
75
- listFiles: async () => store.files,
76
- createFile: async ({ file: blob, name, versionTag, parentFolderId }: any) => {
77
- const id = `doc-${store.dIdx++}`;
78
- store.data.set(id, JSON.parse(await blob.text()));
79
- store.files.push({ _id: id, name, folderId: parentFolderId, versionTag });
80
- return {
81
- item: { _id: id, name, folderId: parentFolderId, createdAt: new Date().toISOString(), createdBy: "mock", itemType: "FILE" },
82
- version: { tag: versionTag },
83
- };
84
- },
85
- listVersions: async (fileId: string) => {
86
- const f = store.files.find(f => f._id === fileId);
87
- return f ? [{ _id: `v-${fileId}`, tag: f.versionTag ?? "v1" }] : [];
88
- },
89
- createVersion: async (fileId: string, blob: any, tag: string) => {
90
- store.data.set(fileId, JSON.parse(await blob.text()));
91
- const f = store.files.find(f => f._id === fileId);
92
- if (f) f.versionTag = tag;
93
- return { tag };
94
- },
95
- downloadFile: async (id: string) => ({
96
- json: async () => store.data.get(id) ?? null,
97
- arrayBuffer: async () => new ArrayBuffer(0),
98
- }),
99
- };
100
-
101
- cde.ctx = { client: mockClient as any, projectId: "mock" };
102
- await cde.loadFolders();
103
-
104
- /* MD
105
- ### 📝 Writing and reading project data
106
- `writeProjectData` takes an application namespace and a filename. The first time a
107
- file is written, it is created under `__project_data/{app}/`. Subsequent writes to
108
- the same path create a new version instead of a new file, keeping the full history
109
- while the read path always returns the latest content.
110
- */
111
-
112
- log("\n--- write and read ---");
113
- const payload = { lastRun: "2026-06-10", status: "ok", count: 42 };
114
- const tag1 = await cde.writeProjectData("my-app", "run-log.json", payload);
115
- log(` write v1 tag: ${tag1}`);
116
-
117
- const read1 = await cde.readProjectData("my-app", "run-log.json");
118
- log(` read back: status="${read1.status}", count=${read1.count}`);
119
-
120
- /* MD
121
- ### 🔄 Versioning on repeated writes
122
- Writing to the same path a second time detects the existing file and creates a new
123
- version. The read always returns the latest version, so the caller doesn't need to
124
- track version tags — they just write and read at the same path.
125
- */
126
-
127
- log("\n--- auto-versioning ---");
128
- const updated = { ...payload, count: 99, lastRun: "2026-06-11" };
129
- const tag2 = await cde.writeProjectData("my-app", "run-log.json", updated);
130
- log(` write v2 tag: ${tag2}`);
131
-
132
- const read2 = await cde.readProjectData("my-app", "run-log.json");
133
- log(` read after update: count=${read2.count}`);
134
-
135
- /* MD
136
- ### 👤 User configuration round-trip
137
- `saveUserConfig` and `loadUserConfig` are thin wrappers around `writeProjectData`
138
- and `readProjectData` that target a user-specific path (`cde/user_{id}.json`). They
139
- accept and return the same config shape as the rest of the CDE — visible columns and
140
- metadata views — so the schema editor and column picker can persist their state
141
- without knowing the path structure.
142
- */
143
-
144
- log("\n--- user config round-trip ---");
145
- const userConfig = {
146
- visibleColumns: ["name", "discipline", "phase", "createdAt"],
147
- metadataViews: [{ id: "view-str", name: "Structural", fields: ["discipline", "phase"] }],
148
- };
149
- await cde.saveUserConfig(userConfig);
150
-
151
- const loaded = await cde.loadUserConfig();
152
- log(` visibleColumns: [${loaded?.visibleColumns?.join(", ")}]`);
153
- log(` views: ${loaded?.metadataViews?.length} view(s)`);
154
-
155
- /* MD
156
- ### 🗂️ Schema persistence via loadMetadataSchema
157
- `loadMetadataSchema` reads `cde/config.json` from the project storage and applies
158
- the `metadataSchema` section to CDEManager. Writing the config file manually (via
159
- `writeProjectData`) then calling `loadMetadataSchema` is the setup path used by the
160
- schema editor after a successful save — the same round-trip that happens in
161
- production when the app boots.
162
- */
163
-
164
- log("\n--- schema persistence ---");
165
- const schemaPayload = {
166
- metadataSchema: {
167
- discipline: { label: "Discipline", type: "string", required: true },
168
- phase: { label: "Phase", type: "string", required: false },
169
- zone: { label: "Zone", type: "string", required: false },
170
- },
171
- };
172
- await cde.writeProjectData("cde", "config.json", schemaPayload);
173
-
174
- await cde.loadMetadataSchema();
175
- log(` schema keys after load: [${Object.keys(cde.metadataSchema).join(", ")}]`);
176
- log(` discipline.required: ${cde.metadataSchema["discipline"]?.required}`);
177
- log(` zone exists: ${"zone" in cde.metadataSchema}`);
1
+ /* MD
2
+ ## Project configuration — read, write, and reload persistent data
3
+ ---
4
+ A CDE application needs to store two kinds of configuration: project-wide settings
5
+ that all team members share (the metadata schema, custom naming rules) and personal
6
+ settings that belong to one user (which columns are visible, which metadata views
7
+ are pinned). Both live inside the project's file storage under a reserved system
8
+ folder, so they travel with the project and don't rely on a separate configuration
9
+ service.
10
+
11
+ CDEManager exposes a simple read/write API that handles the folder structure
12
+ automatically: finding or creating the right system folders, detecting whether a
13
+ file already exists and versioning it instead of duplicating, and returning the
14
+ parsed JSON on read. The metadata schema and user config are pre-wired shortcuts on
15
+ top of this same mechanism — they just target fixed paths.
16
+
17
+ This tutorial covers writing and reading arbitrary JSON under a named application
18
+ namespace; demonstrating that writing the same file twice creates a new version
19
+ rather than a duplicate; loading a user's personal config and verifying the round-
20
+ trip; and writing a metadata schema then loading it back through `loadMetadataSchema`
21
+ to confirm the schema is applied to CDEManager correctly.
22
+
23
+ By the end, you'll see the full persistence layer that backs the schema editor and
24
+ user preferences, and understand how any part of the platform can store structured
25
+ data without managing the folder hierarchy directly.
26
+ */
27
+
28
+ import * as OBC from "@thatopen/components";
29
+ // In production platform apps, CDEManager is loaded through the platform client:
30
+ // const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
31
+ // Here we import directly from source for local testing.
32
+ import { CDEManager } from "../../index";
33
+
34
+ /* MD
35
+ ### 🛠️ Setting up
36
+ Project data operations require an active client context throughout — they make real
37
+ API calls to create folders and files. We use a stateful mock that behaves like a
38
+ real backend: folders and files are created on demand and stored in memory, so reads
39
+ always return what was last written.
40
+ */
41
+
42
+ const components = new OBC.Components();
43
+ const cde = components.get(CDEManager);
44
+
45
+ const output = document.getElementById("output") as HTMLPreElement;
46
+ const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
47
+
48
+ // In-memory store that simulates the project file storage.
49
+ const store = {
50
+ folders: [] as any[],
51
+ files: [] as any[],
52
+ data: new Map<string, any>(),
53
+ fIdx: 0,
54
+ dIdx: 0,
55
+ };
56
+
57
+ const mockClient = {
58
+ checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
59
+ getProjectData: async () => ({
60
+ project: { title: "Demo Project" },
61
+ users: [],
62
+ currentUser: { user: { _id: "user-alice" } },
63
+ }),
64
+ listFolders: async ({ parentFolderId }: any) =>
65
+ store.folders.filter(f =>
66
+ parentFolderId !== undefined
67
+ ? String(f.parentId) === String(parentFolderId)
68
+ : !f.parentId
69
+ ),
70
+ createFolder: async (name: string, parentId?: string) => {
71
+ const folder = { _id: `folder-${store.fIdx++}`, name, parentId: parentId ?? null };
72
+ store.folders.push(folder);
73
+ return folder;
74
+ },
75
+ listFiles: async () => store.files,
76
+ createFile: async ({ file: blob, name, versionTag, parentFolderId }: any) => {
77
+ const id = `doc-${store.dIdx++}`;
78
+ store.data.set(id, JSON.parse(await blob.text()));
79
+ store.files.push({ _id: id, name, folderId: parentFolderId, versionTag });
80
+ return {
81
+ item: { _id: id, name, folderId: parentFolderId, createdAt: new Date().toISOString(), createdBy: "mock", itemType: "FILE" },
82
+ version: { tag: versionTag },
83
+ };
84
+ },
85
+ listVersions: async (fileId: string) => {
86
+ const f = store.files.find(f => f._id === fileId);
87
+ return f ? [{ _id: `v-${fileId}`, tag: f.versionTag ?? "v1" }] : [];
88
+ },
89
+ createVersion: async (fileId: string, blob: any, tag: string) => {
90
+ store.data.set(fileId, JSON.parse(await blob.text()));
91
+ const f = store.files.find(f => f._id === fileId);
92
+ if (f) f.versionTag = tag;
93
+ return { tag };
94
+ },
95
+ downloadFile: async (id: string) => ({
96
+ json: async () => store.data.get(id) ?? null,
97
+ arrayBuffer: async () => new ArrayBuffer(0),
98
+ }),
99
+ };
100
+
101
+ cde.ctx = { client: mockClient as any, projectId: "mock" };
102
+ await cde.loadFolders();
103
+
104
+ /* MD
105
+ ### 📝 Writing and reading project data
106
+ `writeProjectData` takes an application namespace and a filename. The first time a
107
+ file is written, it is created under `__project_data/{app}/`. Subsequent writes to
108
+ the same path create a new version instead of a new file, keeping the full history
109
+ while the read path always returns the latest content.
110
+ */
111
+
112
+ log("\n--- write and read ---");
113
+ const payload = { lastRun: "2026-06-10", status: "ok", count: 42 };
114
+ const tag1 = await cde.writeProjectData("my-app", "run-log.json", payload);
115
+ log(` write v1 tag: ${tag1}`);
116
+
117
+ const read1 = await cde.readProjectData("my-app", "run-log.json");
118
+ log(` read back: status="${read1.status}", count=${read1.count}`);
119
+
120
+ /* MD
121
+ ### 🔄 Versioning on repeated writes
122
+ Writing to the same path a second time detects the existing file and creates a new
123
+ version. The read always returns the latest version, so the caller doesn't need to
124
+ track version tags — they just write and read at the same path.
125
+ */
126
+
127
+ log("\n--- auto-versioning ---");
128
+ const updated = { ...payload, count: 99, lastRun: "2026-06-11" };
129
+ const tag2 = await cde.writeProjectData("my-app", "run-log.json", updated);
130
+ log(` write v2 tag: ${tag2}`);
131
+
132
+ const read2 = await cde.readProjectData("my-app", "run-log.json");
133
+ log(` read after update: count=${read2.count}`);
134
+
135
+ /* MD
136
+ ### 👤 User configuration round-trip
137
+ `saveUserConfig` and `loadUserConfig` are thin wrappers around `writeProjectData`
138
+ and `readProjectData` that target a user-specific path (`cde/user_{id}.json`). They
139
+ accept and return the same config shape as the rest of the CDE — visible columns and
140
+ metadata views — so the schema editor and column picker can persist their state
141
+ without knowing the path structure.
142
+ */
143
+
144
+ log("\n--- user config round-trip ---");
145
+ const userConfig = {
146
+ visibleColumns: ["name", "discipline", "phase", "createdAt"],
147
+ metadataViews: [{ id: "view-str", name: "Structural", fields: ["discipline", "phase"] }],
148
+ };
149
+ await cde.saveUserConfig(userConfig);
150
+
151
+ const loaded = await cde.loadUserConfig();
152
+ log(` visibleColumns: [${loaded?.visibleColumns?.join(", ")}]`);
153
+ log(` views: ${loaded?.metadataViews?.length} view(s)`);
154
+
155
+ /* MD
156
+ ### 🗂️ Schema persistence via loadMetadataSchema
157
+ `loadMetadataSchema` reads `cde/config.json` from the project storage and applies
158
+ the `metadataSchema` section to CDEManager. Writing the config file manually (via
159
+ `writeProjectData`) then calling `loadMetadataSchema` is the setup path used by the
160
+ schema editor after a successful save — the same round-trip that happens in
161
+ production when the app boots.
162
+ */
163
+
164
+ log("\n--- schema persistence ---");
165
+ const schemaPayload = {
166
+ metadataSchema: {
167
+ discipline: { label: "Discipline", type: "string", required: true },
168
+ phase: { label: "Phase", type: "string", required: false },
169
+ zone: { label: "Zone", type: "string", required: false },
170
+ },
171
+ };
172
+ await cde.writeProjectData("cde", "config.json", schemaPayload);
173
+
174
+ await cde.loadMetadataSchema();
175
+ log(` schema keys after load: [${Object.keys(cde.metadataSchema).join(", ")}]`);
176
+ log(` discipline.required: ${cde.metadataSchema["discipline"]?.required}`);
177
+ log(` zone exists: ${"zone" in cde.metadataSchema}`);
@@ -1,107 +1,107 @@
1
- import * as OBC from "@thatopen/components";
2
- import * as BUI from "@thatopen/ui";
3
- import type { EngineServicesClient, Item } from "thatopen-services";
4
-
5
- import appManagerDef from "../AppManager/index";
6
- import fileListDef from "./index";
7
-
8
- const AppManager = appManagerDef.componentDefinition;
9
- const FileList = fileListDef.componentDefinition;
10
-
11
- // ── A tiny in-memory fake client for the example ──────────────────────
12
- // Real apps should pass their `EngineServicesClient` instance here — this
13
- // stub just implements the subset of the API the FileList component uses.
14
-
15
- let nextId = 1;
16
- const mkFile = (name: string, ext?: string): Item => ({
17
- _id: `file-${nextId++}`,
18
- name,
19
- fileExtension: ext,
20
- itemType: "FILE",
21
- internalId: `internal-${nextId}`,
22
- createdAt: new Date(),
23
- creatingUser: "demo-user",
24
- });
25
-
26
- const files: Item[] = [
27
- mkFile("site-plan", "pdf"),
28
- mkFile("structural-model", "ifc"),
29
- mkFile("mep-coordination", "ifc"),
30
- mkFile("bcf-issues", "bcf"),
31
- ];
32
-
33
- const fakeClient = {
34
- async listFiles() {
35
- // Small delay to show the loading state.
36
- await new Promise((r) => setTimeout(r, 150));
37
- return files.filter((f) => !f.archived);
38
- },
39
- async downloadFile(id: string) {
40
- const file = files.find((f) => f._id === id)!;
41
- const body = `Pretend contents of ${file.name}`;
42
- return new Response(body, { headers: { "Content-Type": "text/plain" } });
43
- },
44
- async updateFile(id: string, data: { name?: string }) {
45
- const file = files.find((f) => f._id === id)!;
46
- if (data.name) file.name = data.name;
47
- return { item: file };
48
- },
49
- async archiveFile(id: string) {
50
- const file = files.find((f) => f._id === id)!;
51
- file.archived = true;
52
- return file;
53
- },
54
- } as unknown as EngineServicesClient;
55
-
56
- // ── App type ──────────────────────────────────────────────────────────
57
-
58
- type FilesState = { components: OBC.Components };
59
-
60
- interface FileListApp {
61
- icons: ["FILES"];
62
- grid: BUI.Grid<
63
- ["Files"],
64
- [{ name: "files"; state: FilesState }]
65
- >;
66
- }
67
-
68
- // ── Initialise OBC & BUI ──────────────────────────────────────────────
69
-
70
- BUI.Manager.init();
71
- const components = new OBC.Components();
72
-
73
- const app = components.get(AppManager<FileListApp>);
74
- const fileList = components.get(FileList);
75
-
76
- // ── Create a file list instance ───────────────────────────────────────
77
-
78
- const { element: fileListElement } = fileList.create({ client: fakeClient });
79
-
80
- const filesPanel: BUI.StatefullComponent<FilesState> = (_state) => BUI.html`
81
- <bim-panel active label="Files" style="display: flex; flex-direction: column;">
82
- <bim-panel-section label="Project files" style="flex: 1; min-height: 0;">
83
- ${fileListElement}
84
- </bim-panel-section>
85
- </bim-panel>
86
- `;
87
-
88
- // ── Init ──────────────────────────────────────────────────────────────
89
-
90
- app.init({
91
- icons: { FILES: "mdi:file-multiple" },
92
- grid: (grid) => {
93
- grid.elements = {
94
- files: { template: filesPanel, initialState: { components } },
95
- };
96
- grid.layouts = {
97
- Files: {
98
- template: `"files" 1fr / 1fr`,
99
- icon: app.icons.FILES,
100
- },
101
- };
102
- grid.layout = "Files";
103
- },
104
- container: document.getElementById("container")!,
105
- });
106
-
107
- components.init();
1
+ import * as OBC from "@thatopen/components";
2
+ import * as BUI from "@thatopen/ui";
3
+ import type { EngineServicesClient, Item } from "thatopen-services";
4
+
5
+ import appManagerDef from "../AppManager/index";
6
+ import fileListDef from "./index";
7
+
8
+ const AppManager = appManagerDef.componentDefinition;
9
+ const FileList = fileListDef.componentDefinition;
10
+
11
+ // ── A tiny in-memory fake client for the example ──────────────────────
12
+ // Real apps should pass their `EngineServicesClient` instance here — this
13
+ // stub just implements the subset of the API the FileList component uses.
14
+
15
+ let nextId = 1;
16
+ const mkFile = (name: string, ext?: string): Item => ({
17
+ _id: `file-${nextId++}`,
18
+ name,
19
+ fileExtension: ext,
20
+ itemType: "FILE",
21
+ internalId: `internal-${nextId}`,
22
+ createdAt: new Date(),
23
+ creatingUser: "demo-user",
24
+ });
25
+
26
+ const files: Item[] = [
27
+ mkFile("site-plan", "pdf"),
28
+ mkFile("structural-model", "ifc"),
29
+ mkFile("mep-coordination", "ifc"),
30
+ mkFile("bcf-issues", "bcf"),
31
+ ];
32
+
33
+ const fakeClient = {
34
+ async listFiles() {
35
+ // Small delay to show the loading state.
36
+ await new Promise((r) => setTimeout(r, 150));
37
+ return files.filter((f) => !f.archived);
38
+ },
39
+ async downloadFile(id: string) {
40
+ const file = files.find((f) => f._id === id)!;
41
+ const body = `Pretend contents of ${file.name}`;
42
+ return new Response(body, { headers: { "Content-Type": "text/plain" } });
43
+ },
44
+ async updateFile(id: string, data: { name?: string }) {
45
+ const file = files.find((f) => f._id === id)!;
46
+ if (data.name) file.name = data.name;
47
+ return { item: file };
48
+ },
49
+ async archiveFile(id: string) {
50
+ const file = files.find((f) => f._id === id)!;
51
+ file.archived = true;
52
+ return file;
53
+ },
54
+ } as unknown as EngineServicesClient;
55
+
56
+ // ── App type ──────────────────────────────────────────────────────────
57
+
58
+ type FilesState = { components: OBC.Components };
59
+
60
+ interface FileListApp {
61
+ icons: ["FILES"];
62
+ grid: BUI.Grid<
63
+ ["Files"],
64
+ [{ name: "files"; state: FilesState }]
65
+ >;
66
+ }
67
+
68
+ // ── Initialise OBC & BUI ──────────────────────────────────────────────
69
+
70
+ BUI.Manager.init();
71
+ const components = new OBC.Components();
72
+
73
+ const app = components.get(AppManager<FileListApp>);
74
+ const fileList = components.get(FileList);
75
+
76
+ // ── Create a file list instance ───────────────────────────────────────
77
+
78
+ const { element: fileListElement } = fileList.create({ client: fakeClient });
79
+
80
+ const filesPanel: BUI.StatefullComponent<FilesState> = (_state) => BUI.html`
81
+ <bim-panel active label="Files" style="display: flex; flex-direction: column;">
82
+ <bim-panel-section label="Project files" style="flex: 1; min-height: 0;">
83
+ ${fileListElement}
84
+ </bim-panel-section>
85
+ </bim-panel>
86
+ `;
87
+
88
+ // ── Init ──────────────────────────────────────────────────────────────
89
+
90
+ app.init({
91
+ icons: { FILES: "mdi:file-multiple" },
92
+ grid: (grid) => {
93
+ grid.elements = {
94
+ files: { template: filesPanel, initialState: { components } },
95
+ };
96
+ grid.layouts = {
97
+ Files: {
98
+ template: `"files" 1fr / 1fr`,
99
+ icon: app.icons.FILES,
100
+ },
101
+ };
102
+ grid.layout = "Files";
103
+ },
104
+ container: document.getElementById("container")!,
105
+ });
106
+
107
+ components.init();