@thatopen/services 0.0.4 → 0.0.6
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 +24 -263
- package/dist/cli/commands/swap.d.ts +3 -0
- package/dist/cli/lib/beta.d.ts +2 -0
- package/dist/cli/lib/config.d.ts +2 -0
- package/dist/cli.js +85 -126
- package/dist/core/examples/apps.d.ts +1 -0
- package/dist/core/examples/components.d.ts +1 -0
- package/dist/core/examples/execution.d.ts +1 -0
- package/dist/core/examples/file-metadata.d.ts +1 -0
- package/dist/core/examples/files.d.ts +1 -0
- package/dist/core/examples/folders.d.ts +1 -0
- package/dist/core/examples/hidden-files.d.ts +1 -0
- package/dist/core/examples/icons.d.ts +1 -0
- package/dist/core/examples/permissions.d.ts +1 -0
- package/dist/types/items.d.ts +2 -0
- package/docs/builtin/paths.json +54 -0
- package/docs/cli/create.md +28 -0
- package/docs/cli/local-server.md +38 -0
- package/docs/cli/login.md +32 -0
- package/docs/cli/paths.json +30 -0
- package/docs/cli/publish.md +31 -0
- package/docs/cli/run.md +26 -0
- package/docs/cli/serve.md +43 -0
- package/docs/cli/swap.md +30 -0
- package/docs/client/paths.json +38 -0
- package/package.json +8 -10
- package/resources/AGENTS.md +39 -0
- package/src/built-in/AppManager/example.ts +86 -0
- package/src/built-in/CDEManager/example.ts +214 -0
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -0
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -0
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -0
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -0
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -0
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -0
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -0
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -0
- package/src/built-in/FileList/example.ts +107 -0
- package/src/built-in/TabbedNavigation/example.ts +115 -0
- package/src/built-in/ViewportsManager/example.ts +106 -0
- package/src/cli/templates/{shared/app → app}/vite.config.js +42 -23
- package/src/cli/templates/{shared/cloud → cloud-component}/vite.config.js +46 -27
- package/src/cli/templates/shared/AGENTS.md +8 -30
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/AGENTS.md +0 -86
- package/CONTEXT.md +0 -258
- package/dist/built-in/index.d.ts +0 -723
- package/dist/cli/commands/create-tests.d.ts +0 -3
- package/dist/cli/commands/serve-tests.d.ts +0 -3
- package/docs/access-backend-data.md +0 -19
- package/docs/app-architecture.md +0 -94
- package/docs/app-layout.md +0 -139
- package/docs/app-wiring.md +0 -123
- package/docs/bim-components/coding-conventions.md +0 -128
- package/docs/bim-components/element-collections.md +0 -69
- package/docs/bim-components/expose-events.md +0 -84
- package/docs/bim-components/library-examples.md +0 -28
- package/docs/bim-components/observable-collections.md +0 -83
- package/docs/bim-components/overview.md +0 -160
- package/docs/bim-components/per-frame-updates.md +0 -20
- package/docs/bim-components/save-and-restore-state.md +0 -21
- package/docs/bim-components/setup-and-cleanup.md +0 -64
- package/docs/bim-components/type-conventions.md +0 -49
- package/docs/bim-components/user-driven-object-creation.md +0 -56
- package/docs/cli-setup.md +0 -54
- package/docs/cloud-components.md +0 -74
- package/docs/connect-logic-to-ui.md +0 -113
- package/docs/previewing.md +0 -45
- package/docs/publishing.md +0 -35
- package/docs/scaffolding.md +0 -71
- package/docs/ui-components/async-actions.md +0 -18
- package/docs/ui-components/confirmation-dialog.md +0 -39
- package/docs/ui-components/data-table.md +0 -185
- package/docs/ui-components/display-text.md +0 -39
- package/docs/ui-components/inline-form.md +0 -44
- package/docs/ui-components/library-examples.md +0 -24
- package/docs/ui-components/overview.md +0 -194
- package/docs/ui-components/rendering-patterns.md +0 -129
- package/docs/ui-components/sections-layout.md +0 -123
- package/docs/update-grid-elements.md +0 -46
- package/docs/using-colors.md +0 -32
- package/docs/using-icons.md +0 -37
- package/src/cli/templates/bim/src/bim-components/CloudRunner/src/index.ts +0 -1
- package/src/cli/templates/bim/src/bim-components/CloudRunner/src/types.ts +0 -5
- package/src/cli/templates/bim/src/bim-components/index.ts +0 -1
- package/src/cli/templates/bim/src/globals.ts +0 -1
- package/src/cli/templates/bim/src/setups/index.ts +0 -3
- package/src/cli/templates/bim/src/ui-components/app-info-section/index.ts +0 -26
- package/src/cli/templates/bim/src/ui-components/app-info-section/src/index.ts +0 -1
- package/src/cli/templates/bim/src/ui-components/cloud-runner-section/index.ts +0 -37
- package/src/cli/templates/bim/src/ui-components/cloud-runner-section/src/index.ts +0 -1
- package/src/cli/templates/bim/src/ui-components/index.ts +0 -2
- package/src/cli/templates/bim-beta/CONTEXT.md +0 -241
- package/src/cli/templates/bim-beta/package.json +0 -25
- package/src/cli/templates/bim-beta/src/app.ts +0 -16
- package/src/cli/templates/bim-beta/src/bim-components/CloudRunner/index.ts +0 -91
- package/src/cli/templates/bim-beta/src/main.ts +0 -93
- package/src/cli/templates/bim-beta/src/setups/cloud-runner.ts +0 -13
- package/src/cli/templates/bim-beta/src/setups/ui-manager.ts +0 -27
- package/src/cli/templates/bim-beta/src/setups/viewports-manager.ts +0 -22
- package/src/cli/templates/bim-beta/src/ui-components/app-info-section/src/types.ts +0 -15
- package/src/cli/templates/bim-beta/src/ui-components/cloud-runner-section/src/types.ts +0 -14
- package/src/cli/templates/cloud/_thatopen +0 -5
- package/src/cli/templates/cloud/package.json +0 -22
- package/src/cli/templates/cloud-test/CONTEXT.md +0 -56
- package/src/cli/templates/cloud-test/src/main.ts +0 -565
- package/src/cli/templates/default/CONTEXT.md +0 -92
- package/src/cli/templates/default/package.json +0 -18
- package/src/cli/templates/default/src/main.ts +0 -62
- package/src/cli/templates/test/CONTEXT.md +0 -53
- package/src/cli/templates/test/package.json +0 -25
- package/src/cli/templates/test/src/main.ts +0 -955
- /package/src/cli/templates/{bim/CONTEXT.md → app/README.md} +0 -0
- /package/src/cli/templates/{shared/app → app}/index.html +0 -0
- /package/src/cli/templates/{bim → app}/package.json +0 -0
- /package/src/cli/templates/{bim → app}/src/app.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/bim-components/CloudRunner/index.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/bim-components/CloudRunner/src/index.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/bim-components/CloudRunner/src/types.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/bim-components/index.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/globals.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/main.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/setups/cloud-runner.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/setups/index.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/setups/ui-manager.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/setups/viewports-manager.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/ui-components/app-info-section/index.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/ui-components/app-info-section/src/index.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/ui-components/app-info-section/src/types.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/ui-components/cloud-runner-section/index.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/ui-components/cloud-runner-section/src/index.ts +0 -0
- /package/src/cli/templates/{bim → app}/src/ui-components/cloud-runner-section/src/types.ts +0 -0
- /package/src/cli/templates/{bim-beta → app}/src/ui-components/index.ts +0 -0
- /package/src/cli/templates/{shared/app → app}/tsconfig.json +0 -0
- /package/src/cli/templates/{cloud/CONTEXT.md → cloud-component/README.md} +0 -0
- /package/src/cli/templates/{cloud-test → cloud-component}/_thatopen +0 -0
- /package/src/cli/templates/{cloud → cloud-component}/declarations.json +0 -0
- /package/src/cli/templates/{cloud-test → cloud-component}/package.json +0 -0
- /package/src/cli/templates/{cloud → cloud-component}/src/main.ts +0 -0
- /package/src/cli/templates/{shared/cloud → cloud-component}/tsconfig.json +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* MD
|
|
2
|
+
## Folder management — create, rename, archive, and commit
|
|
3
|
+
---
|
|
4
|
+
A project reaches the construction phase and the coordination team needs to
|
|
5
|
+
restructure the folder hierarchy: add a "Facades" discipline folder, rename
|
|
6
|
+
"Structural" to its official abbreviation, and archive two superseded folders that
|
|
7
|
+
have been merged. Doing these one at a time through separate API calls means the
|
|
8
|
+
folder tree flickers between invalid states as each change lands — a freshly created
|
|
9
|
+
folder appears before its sibling is renamed, and the archived folders briefly
|
|
10
|
+
disappear before the new one is visible.
|
|
11
|
+
|
|
12
|
+
CDEManager stages all folder changes in memory first. Adds, renames, and archives
|
|
13
|
+
accumulate in a `pendingChanges` list before anything is sent to the backend. The
|
|
14
|
+
full diff is readable from the machine state at any point, so the UI can preview
|
|
15
|
+
exactly what the folder tree will look like after the commit. Cancelling throws away
|
|
16
|
+
all staged changes in one step. Saving applies everything in the right dependency
|
|
17
|
+
order and fires a single completion event with the counts.
|
|
18
|
+
|
|
19
|
+
This tutorial covers entering folder edit mode; staging new folders with a temporary
|
|
20
|
+
ID that gets resolved to a real ID on save; staging renames and observing how
|
|
21
|
+
renaming back to the original value automatically removes the change; the
|
|
22
|
+
archive-folder toggle behavior, where sending the event a second time unstages the
|
|
23
|
+
archive; removing a staged temp folder and all its staged children in one event;
|
|
24
|
+
reverting all staged changes for a single folder; and committing the full batch.
|
|
25
|
+
|
|
26
|
+
By the end, you'll have a working demonstration of every folder-management state
|
|
27
|
+
machine flow — stage, inspect, revert, and commit — against mock data with no
|
|
28
|
+
backend connection needed for the staging sections.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import * as OBC from "@thatopen/components";
|
|
32
|
+
// In production platform apps, CDEManager is loaded through the platform client:
|
|
33
|
+
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
34
|
+
// Here we import directly from source for local testing.
|
|
35
|
+
import { CDEManager } from "../../index";
|
|
36
|
+
|
|
37
|
+
/* MD
|
|
38
|
+
### 🛠️ Setting up
|
|
39
|
+
The save path requires a live client context — `_saveFolderChanges` returns early if
|
|
40
|
+
no context is set and the machine would stay stuck in `savingFolderChanges` forever.
|
|
41
|
+
We keep the mock context active throughout the entire example so the commit section
|
|
42
|
+
works correctly.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const components = new OBC.Components();
|
|
46
|
+
const cde = components.get(CDEManager);
|
|
47
|
+
|
|
48
|
+
const output = document.getElementById("output") as HTMLPreElement;
|
|
49
|
+
const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
|
|
50
|
+
|
|
51
|
+
let folderIdx = 0;
|
|
52
|
+
const mockClient = {
|
|
53
|
+
checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
|
|
54
|
+
getProjectData: async () => ({ project: { title: "" }, users: [] }),
|
|
55
|
+
listFolders: async () => [],
|
|
56
|
+
archiveFolder: async (_id: string) => ({}),
|
|
57
|
+
updateFolder: async (_id: string, _updates: any) => ({}),
|
|
58
|
+
createFolder: async (name: string, parentId?: string) => ({
|
|
59
|
+
_id: `created-${folderIdx++}`, name, parentId: parentId ?? null,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
cde.ctx = { client: mockClient as any, projectId: "mock" };
|
|
64
|
+
await cde.loadFolders();
|
|
65
|
+
|
|
66
|
+
const folders = [
|
|
67
|
+
{ _id: "folder-str", name: "Structural", parentId: null },
|
|
68
|
+
{ _id: "folder-arch", name: "Architecture", parentId: null },
|
|
69
|
+
{ _id: "folder-mep", name: "MEP", parentId: "folder-arch" },
|
|
70
|
+
];
|
|
71
|
+
cde.folders = folders as any;
|
|
72
|
+
|
|
73
|
+
cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
|
|
74
|
+
cde.onFolderChangesCompleted.add(({ created, renamed, archived }) =>
|
|
75
|
+
log(` [onFolderChangesCompleted] created: ${created}, renamed: ${renamed}, archived: ${archived}`)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
/* MD
|
|
79
|
+
### 📁 Staging folder changes
|
|
80
|
+
New folders are added with a temporary ID assigned by the caller — the UI can use it
|
|
81
|
+
to identify the row in the pending list before the real ID arrives from the backend.
|
|
82
|
+
Renames and archives for existing folders are keyed by their real ID. All three
|
|
83
|
+
change kinds accumulate in the same `pendingChanges` array, so the UI can render a
|
|
84
|
+
single diff view for the whole operation.
|
|
85
|
+
|
|
86
|
+
Cancelling discards everything and returns to idle. Unlike file bulk editing, folder
|
|
87
|
+
editing has no concept of "return to previous selection" — it always returns to idle.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
log("\n--- stage changes and cancel ---");
|
|
91
|
+
cde.sendMachineEvent({ type: "START_EDIT_FOLDERS" });
|
|
92
|
+
|
|
93
|
+
// New root-level folder.
|
|
94
|
+
cde.sendMachineEvent({ type: "ADD_FOLDER", tempId: "temp_facades", name: "Facades", parentId: null });
|
|
95
|
+
// New folder nested inside the temp folder above.
|
|
96
|
+
cde.sendMachineEvent({ type: "ADD_FOLDER", tempId: "temp_facades_sub", name: "Glazing", parentId: "temp_facades" });
|
|
97
|
+
|
|
98
|
+
// Rename an existing folder.
|
|
99
|
+
cde.sendMachineEvent({ type: "RENAME_FOLDER", folderId: "folder-str", name: "STR" });
|
|
100
|
+
|
|
101
|
+
// Archive an existing folder.
|
|
102
|
+
cde.sendMachineEvent({ type: "ARCHIVE_FOLDER", folderId: "folder-mep" });
|
|
103
|
+
|
|
104
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
105
|
+
const changes = cde.machineState.pendingChanges;
|
|
106
|
+
const creates = changes.filter(c => c.kind === "create").length;
|
|
107
|
+
const renames = changes.filter(c => c.kind === "rename").length;
|
|
108
|
+
const archives = changes.filter(c => c.kind === "archive").length;
|
|
109
|
+
log(` pending: ${creates} create(s), ${renames} rename(s), ${archives} archive(s)`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
cde.sendMachineEvent({ type: "CANCEL_FOLDER_CHANGES" });
|
|
113
|
+
log(` state after cancel: ${cde.machineState.kind}`);
|
|
114
|
+
log(` folders unchanged: ${cde.folders.length}`);
|
|
115
|
+
|
|
116
|
+
/* MD
|
|
117
|
+
### 🔍 Smart rename diff and archive toggle
|
|
118
|
+
Renaming an existing folder back to its current name automatically removes the
|
|
119
|
+
rename change from the pending list — the same smart diffing used for file metadata.
|
|
120
|
+
This keeps the pending list clean so the commit only sends real changes to the
|
|
121
|
+
backend.
|
|
122
|
+
|
|
123
|
+
Archiving the same existing folder twice toggles the staged archive off. This is
|
|
124
|
+
different from the file-editing archive, which needs an explicit unarchive event to
|
|
125
|
+
remove the mark. For folders, sending the archive event again is the undo gesture.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
log("\n--- smart rename diff ---");
|
|
129
|
+
cde.sendMachineEvent({ type: "START_EDIT_FOLDERS" });
|
|
130
|
+
|
|
131
|
+
cde.sendMachineEvent({ type: "RENAME_FOLDER", folderId: "folder-str", name: "STR" });
|
|
132
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
133
|
+
log(` after rename "STR": ${cde.machineState.pendingChanges.filter(c => c.kind === "rename").length} rename(s)`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Renaming back to the original name removes the change automatically.
|
|
137
|
+
cde.sendMachineEvent({ type: "RENAME_FOLDER", folderId: "folder-str", name: "Structural" });
|
|
138
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
139
|
+
log(` after revert to "Structural": ${cde.machineState.pendingChanges.filter(c => c.kind === "rename").length} rename(s)`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
log("\n--- archive toggle ---");
|
|
143
|
+
cde.sendMachineEvent({ type: "ARCHIVE_FOLDER", folderId: "folder-arch" });
|
|
144
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
145
|
+
log(` after first ARCHIVE_FOLDER: ${cde.machineState.pendingChanges.filter(c => c.kind === "archive").length} archive(s)`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
cde.sendMachineEvent({ type: "ARCHIVE_FOLDER", folderId: "folder-arch" });
|
|
149
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
150
|
+
log(` after second ARCHIVE_FOLDER (toggle): ${cde.machineState.pendingChanges.filter(c => c.kind === "archive").length} archive(s)`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
cde.sendMachineEvent({ type: "CANCEL_FOLDER_CHANGES" });
|
|
154
|
+
|
|
155
|
+
/* MD
|
|
156
|
+
### 🗑️ Removing temp folders and reverting existing ones
|
|
157
|
+
When a staged new folder is archived or reset, it is removed from the pending list
|
|
158
|
+
entirely — there is nothing to archive on the backend yet because it was never
|
|
159
|
+
created. Any folders staged as children of the removed folder are removed along with
|
|
160
|
+
it, so the pending list never contains orphaned children.
|
|
161
|
+
|
|
162
|
+
For existing folders, a reset event clears all staged changes for that folder at
|
|
163
|
+
once — both a pending rename and a pending archive in the same step — without
|
|
164
|
+
affecting any other staged changes.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
log("\n--- remove temp folder cascade ---");
|
|
168
|
+
cde.sendMachineEvent({ type: "START_EDIT_FOLDERS" });
|
|
169
|
+
|
|
170
|
+
// Stage a temp parent with two children.
|
|
171
|
+
cde.sendMachineEvent({ type: "ADD_FOLDER", tempId: "temp_p", name: "Parent", parentId: null });
|
|
172
|
+
cde.sendMachineEvent({ type: "ADD_FOLDER", tempId: "temp_c1", name: "Child A", parentId: "temp_p" });
|
|
173
|
+
cde.sendMachineEvent({ type: "ADD_FOLDER", tempId: "temp_c2", name: "Child B", parentId: "temp_p" });
|
|
174
|
+
|
|
175
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
176
|
+
log(` staged before archive: ${cde.machineState.pendingChanges.length} change(s)`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Archiving a temp folder removes it AND all its staged children.
|
|
180
|
+
cde.sendMachineEvent({ type: "ARCHIVE_FOLDER", folderId: "temp_p" });
|
|
181
|
+
|
|
182
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
183
|
+
log(` staged after archive temp parent: ${cde.machineState.pendingChanges.length} change(s)`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
log("\n--- reset existing folder ---");
|
|
187
|
+
cde.sendMachineEvent({ type: "RENAME_FOLDER", folderId: "folder-str", name: "STR" });
|
|
188
|
+
cde.sendMachineEvent({ type: "ARCHIVE_FOLDER", folderId: "folder-str" });
|
|
189
|
+
|
|
190
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
191
|
+
const changes = cde.machineState.pendingChanges;
|
|
192
|
+
log(` before reset: ${changes.filter(c => c.kind === "rename" || c.kind === "archive").length} change(s) for folder-str`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// RESET_FOLDER removes all staged changes for folder-str in one event.
|
|
196
|
+
cde.sendMachineEvent({ type: "RESET_FOLDER", folderId: "folder-str" });
|
|
197
|
+
|
|
198
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
199
|
+
const changes = cde.machineState.pendingChanges;
|
|
200
|
+
log(` after reset: ${changes.filter(c => c.kind === "rename" || c.kind === "archive").length} change(s) for folder-str`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
cde.sendMachineEvent({ type: "CANCEL_FOLDER_CHANGES" });
|
|
204
|
+
|
|
205
|
+
/* MD
|
|
206
|
+
### 💾 Committing folder changes
|
|
207
|
+
Saving applies all staged changes to the backend in the correct order: archives
|
|
208
|
+
first, then renames on folders that were not archived, then creates in parent-before-
|
|
209
|
+
child order so that nested new folders resolve to real IDs. After everything is
|
|
210
|
+
persisted, the folder list reloads from the backend and a single completion event
|
|
211
|
+
reports how many folders were created, renamed, and archived.
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
log("\n--- commit ---");
|
|
215
|
+
cde.sendMachineEvent({ type: "START_EDIT_FOLDERS" });
|
|
216
|
+
|
|
217
|
+
cde.sendMachineEvent({ type: "ADD_FOLDER", tempId: "temp_facades", name: "Facades", parentId: null });
|
|
218
|
+
cde.sendMachineEvent({ type: "RENAME_FOLDER", folderId: "folder-str", name: "STR" });
|
|
219
|
+
cde.sendMachineEvent({ type: "ARCHIVE_FOLDER", folderId: "folder-mep" });
|
|
220
|
+
|
|
221
|
+
if (cde.machineState.kind === "editingFolders") {
|
|
222
|
+
log(` pending before save: ${cde.machineState.pendingChanges.length} change(s)`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await new Promise<void>(resolve => {
|
|
226
|
+
cde.onFolderChangesCompleted.add(() => resolve());
|
|
227
|
+
cde.sendMachineEvent({ type: "SAVE_FOLDER_CHANGES" });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
log(` state after commit: ${cde.machineState.kind}`);
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* MD
|
|
2
|
+
## Metadata schema — define, stage, and publish field definitions
|
|
3
|
+
---
|
|
4
|
+
Every project has its own vocabulary for describing files: discipline codes,
|
|
5
|
+
revision phases, area zones, review statuses. A metadata schema defines which
|
|
6
|
+
fields exist, what values they accept, and whether they are mandatory. Without a
|
|
7
|
+
structured schema, each user invents their own labels and the file list becomes
|
|
8
|
+
impossible to filter or report on consistently.
|
|
9
|
+
|
|
10
|
+
CDEManager stages schema changes in memory before anything is written. New fields
|
|
11
|
+
start with a placeholder name and get their permanent key from their label at save
|
|
12
|
+
time. Edits to existing fields are tracked per-key so the UI can show a diff, and
|
|
13
|
+
changes can be reverted field by field. Deletions are staged as null markers, which
|
|
14
|
+
lets the UI still show the field as "pending removal" rather than making it disappear
|
|
15
|
+
immediately.
|
|
16
|
+
|
|
17
|
+
This tutorial covers entering schema edit mode; adding new fields and updating their
|
|
18
|
+
definitions; observing that updating a field back to its original values removes it
|
|
19
|
+
from the pending list; staging a deletion and restoring it; and committing the full
|
|
20
|
+
batch, including how new field keys are derived from their label text.
|
|
21
|
+
|
|
22
|
+
By the end, you'll understand the full schema editing lifecycle and how the machine
|
|
23
|
+
state carries a complete diff — additions, updates, and deletions — before any
|
|
24
|
+
changes reach the backend.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as OBC from "@thatopen/components";
|
|
28
|
+
// In production platform apps, CDEManager is loaded through the platform client:
|
|
29
|
+
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
30
|
+
// Here we import directly from source for local testing.
|
|
31
|
+
import { CDEManager } from "../../index";
|
|
32
|
+
import type { CDEMetadataField } from "../../src/types";
|
|
33
|
+
|
|
34
|
+
/* MD
|
|
35
|
+
### 🛠️ Setting up
|
|
36
|
+
Schema editing is gated by the manage permission. We use the mock context pattern
|
|
37
|
+
to load permissions, then clear the context — schema staging is entirely in-memory
|
|
38
|
+
and does not need a backend connection. Saving without a context skips persistence
|
|
39
|
+
but still applies the changes locally, which is enough to verify the machine behavior.
|
|
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
|
+
cde.ctx = {
|
|
49
|
+
client: {
|
|
50
|
+
checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
|
|
51
|
+
getProjectData: async () => ({ project: { title: "" }, users: [] }),
|
|
52
|
+
listFolders: async () => [],
|
|
53
|
+
} as any,
|
|
54
|
+
projectId: "mock",
|
|
55
|
+
};
|
|
56
|
+
await cde.loadFolders();
|
|
57
|
+
cde.ctx = undefined;
|
|
58
|
+
|
|
59
|
+
// Seed an initial schema to work with.
|
|
60
|
+
cde.metadataSchema = {
|
|
61
|
+
discipline: { label: "Discipline", type: "string", required: true },
|
|
62
|
+
phase: { label: "Phase", type: "string", required: false },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
|
|
66
|
+
|
|
67
|
+
/* MD
|
|
68
|
+
### ➕ Adding and updating fields
|
|
69
|
+
New fields are added with a placeholder definition and a machine-generated temporary
|
|
70
|
+
key. The actual key is derived from the field label when the schema is saved, so the
|
|
71
|
+
caller only needs to set a meaningful label — not invent a key manually.
|
|
72
|
+
|
|
73
|
+
Any update to a field is stored as a replacement of the entire field definition under
|
|
74
|
+
that key. If all properties are changed back to the original values, the edit is
|
|
75
|
+
removed from the pending list automatically — no explicit undo step is needed.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
log("\n--- add and update ---");
|
|
79
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
80
|
+
|
|
81
|
+
// New field starts as "New Field" with a temp key like "field_1234567890".
|
|
82
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
83
|
+
|
|
84
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
85
|
+
const tempKey = Object.keys(cde.machineState.pendingEdits).find(k => k.startsWith("field_"))!;
|
|
86
|
+
log(` new field temp key: ${tempKey.slice(0, 10)}…`);
|
|
87
|
+
|
|
88
|
+
// Give it a real label and type.
|
|
89
|
+
cde.sendMachineEvent({
|
|
90
|
+
type: "UPDATE_METADATA_FIELD",
|
|
91
|
+
key: tempKey,
|
|
92
|
+
value: { label: "Issue Phase", type: "list", required: false, options: [{ value: "open", label: "Open" }, { value: "closed", label: "Closed" }] },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const pending = Object.keys(cde.machineState.pendingEdits);
|
|
96
|
+
log(` pending fields after update: ${pending.length}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Update an existing field — changes are tracked separately from new ones.
|
|
100
|
+
cde.sendMachineEvent({
|
|
101
|
+
type: "UPDATE_METADATA_FIELD",
|
|
102
|
+
key: "discipline",
|
|
103
|
+
value: { label: "Discipline", type: "string", required: false },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
107
|
+
log(` discipline pending: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
111
|
+
|
|
112
|
+
/* MD
|
|
113
|
+
### 🔍 Smart update diff
|
|
114
|
+
Updating a field back to exactly its original values removes it from the pending
|
|
115
|
+
list. This keeps the diff clean — the UI doesn't need to track which changes were
|
|
116
|
+
reverted, it just reads the pending list.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
log("\n--- smart diff on update ---");
|
|
120
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
121
|
+
|
|
122
|
+
// Change discipline's required flag.
|
|
123
|
+
cde.sendMachineEvent({
|
|
124
|
+
type: "UPDATE_METADATA_FIELD",
|
|
125
|
+
key: "discipline",
|
|
126
|
+
value: { label: "Discipline", type: "string", required: false },
|
|
127
|
+
});
|
|
128
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
129
|
+
log(` discipline in pending: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Revert to the original — removes it from the pending list automatically.
|
|
133
|
+
cde.sendMachineEvent({
|
|
134
|
+
type: "UPDATE_METADATA_FIELD",
|
|
135
|
+
key: "discipline",
|
|
136
|
+
value: { label: "Discipline", type: "string", required: true },
|
|
137
|
+
});
|
|
138
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
139
|
+
log(` discipline in pending after revert: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
143
|
+
|
|
144
|
+
/* MD
|
|
145
|
+
### 🗑️ Deleting and restoring fields
|
|
146
|
+
Deleting an existing field marks it as null in the pending list rather than removing
|
|
147
|
+
it outright, so the UI can still show it as "pending deletion" and let the user
|
|
148
|
+
change their mind. Restoring removes the null marker — the field is back to its
|
|
149
|
+
current state as if it was never staged for deletion.
|
|
150
|
+
|
|
151
|
+
Deleting a field that was just added (and therefore does not yet exist in the schema)
|
|
152
|
+
removes it from the pending list entirely — there is nothing to mark for deletion
|
|
153
|
+
because there is nothing to undo.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
log("\n--- delete and restore ---");
|
|
157
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
158
|
+
|
|
159
|
+
// Stage deletion of an existing field.
|
|
160
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: "phase" });
|
|
161
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
162
|
+
log(` phase value in pending: ${JSON.stringify(cde.machineState.pendingEdits["phase"])}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Restore it — null marker removed.
|
|
166
|
+
cde.sendMachineEvent({ type: "RESTORE_METADATA_FIELD", key: "phase" });
|
|
167
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
168
|
+
log(` phase in pending after restore: ${"phase" in cde.machineState.pendingEdits}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add a new field, then delete it — removes it entirely (no null marker needed).
|
|
172
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
173
|
+
const newTempKey = Object.keys(
|
|
174
|
+
(cde.machineState as any).pendingEdits
|
|
175
|
+
).find((k: string) => k.startsWith("field_"))!;
|
|
176
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: newTempKey });
|
|
177
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
178
|
+
log(` pending after deleting new field: ${Object.keys(cde.machineState.pendingEdits).length}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
182
|
+
|
|
183
|
+
/* MD
|
|
184
|
+
### 💾 Committing the schema
|
|
185
|
+
When the schema is saved, new fields get their permanent key derived from their label
|
|
186
|
+
— spaces become underscores, special characters are stripped. If the derived key
|
|
187
|
+
would collide with an existing one, a numeric suffix is appended automatically.
|
|
188
|
+
After the commit, the pending list is empty and the schema reflects all the staged
|
|
189
|
+
changes.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
log("\n--- commit ---");
|
|
193
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
194
|
+
|
|
195
|
+
// Add a new field: label "Issue Phase" → key will become "issue_phase".
|
|
196
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
197
|
+
const commitTempKey = Object.keys(
|
|
198
|
+
(cde.machineState as any).pendingEdits
|
|
199
|
+
).find((k: string) => k.startsWith("field_"))!;
|
|
200
|
+
cde.sendMachineEvent({
|
|
201
|
+
type: "UPDATE_METADATA_FIELD",
|
|
202
|
+
key: commitTempKey,
|
|
203
|
+
value: { label: "Issue Phase", type: "string", required: false },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Update an existing field.
|
|
207
|
+
cde.sendMachineEvent({
|
|
208
|
+
type: "UPDATE_METADATA_FIELD",
|
|
209
|
+
key: "discipline",
|
|
210
|
+
value: { label: "Discipline", type: "string", required: false },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Stage deletion of "phase".
|
|
214
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: "phase" });
|
|
215
|
+
|
|
216
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
217
|
+
log(` pending before save: ${Object.keys(cde.machineState.pendingEdits).length} change(s)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Save applies changes locally even without a backend connection.
|
|
221
|
+
await new Promise<void>(resolve => {
|
|
222
|
+
cde.onMachineStateChanged.add((s) => { if (s.kind === "idle") resolve(); });
|
|
223
|
+
cde.sendMachineEvent({ type: "SAVE_METADATA_SCHEMA" });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
log(` state after save: ${cde.machineState.kind}`);
|
|
227
|
+
log(` schema keys: [${Object.keys(cde.metadataSchema).join(", ")}]`);
|
|
228
|
+
log(` discipline.required: ${cde.metadataSchema["discipline"]?.required}`);
|
|
229
|
+
log(` issue_phase exists: ${"issue_phase" in cde.metadataSchema}`);
|
|
230
|
+
log(` phase removed: ${"phase" in cde.metadataSchema ? "no" : "yes"}`);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* MD
|
|
2
|
+
## Metadata views — organize fields into named table layouts
|
|
3
|
+
---
|
|
4
|
+
A structural engineer and an MEP coordinator look at the same file list but care
|
|
5
|
+
about completely different metadata. Showing every field to everyone produces a
|
|
6
|
+
wide table that is hard to navigate. Metadata views let each role define which
|
|
7
|
+
fields appear in which order — saving the layout so the table opens correctly next
|
|
8
|
+
time without requiring a manual setup every session.
|
|
9
|
+
|
|
10
|
+
CDEManager stores a list of named views, each carrying an ordered set of field keys.
|
|
11
|
+
An active view filters both which columns are visible and which metadata groupings
|
|
12
|
+
appear in the sidebar. Changes to the view list are staged in memory first, just like
|
|
13
|
+
schema changes, and saved in one batch — so the user can add, rename, and remove
|
|
14
|
+
views without any partial state reaching the backend.
|
|
15
|
+
|
|
16
|
+
This tutorial covers initializing views from a saved configuration; switching the
|
|
17
|
+
active view and reading the result; adding, updating, and removing views in the
|
|
18
|
+
pending list; and committing the full batch, which saves to the user's personal
|
|
19
|
+
config and fires an event with the final view list.
|
|
20
|
+
|
|
21
|
+
By the end, you'll know how to load, display, edit, and persist named metadata
|
|
22
|
+
layouts through the state machine.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import * as OBC from "@thatopen/components";
|
|
26
|
+
// In production platform apps, CDEManager is loaded through the platform client:
|
|
27
|
+
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
28
|
+
// Here we import directly from source for local testing.
|
|
29
|
+
import { CDEManager } from "../../index";
|
|
30
|
+
import type { CDEMetadataView } from "../../src/types";
|
|
31
|
+
|
|
32
|
+
/* MD
|
|
33
|
+
### 🛠️ Setting up
|
|
34
|
+
Loading views from a saved config and switching the active view don't require
|
|
35
|
+
permissions. We still simulate the startup step to load permissions so the save
|
|
36
|
+
path works later, then clear the context — staging is in-memory only.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const components = new OBC.Components();
|
|
40
|
+
const cde = components.get(CDEManager);
|
|
41
|
+
|
|
42
|
+
const output = document.getElementById("output") as HTMLPreElement;
|
|
43
|
+
const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
|
|
44
|
+
|
|
45
|
+
cde.ctx = {
|
|
46
|
+
client: {
|
|
47
|
+
checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
|
|
48
|
+
getProjectData: async () => ({ project: { title: "" }, users: [] }),
|
|
49
|
+
listFolders: async () => [],
|
|
50
|
+
} as any,
|
|
51
|
+
projectId: "mock",
|
|
52
|
+
};
|
|
53
|
+
await cde.loadFolders();
|
|
54
|
+
cde.ctx = undefined;
|
|
55
|
+
|
|
56
|
+
cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
|
|
57
|
+
cde.onMetadataViewsChanged.add((views) => log(` [onMetadataViewsChanged] ${views.length} view(s)`));
|
|
58
|
+
|
|
59
|
+
/* MD
|
|
60
|
+
### 📋 Initializing views from config
|
|
61
|
+
After loading from the backend, views and the last active view ID are pushed into
|
|
62
|
+
CDEManager through `initMetadataViews`. This populates the internal view list and
|
|
63
|
+
fires the views-changed event so any subscribed component can render immediately.
|
|
64
|
+
The active view controls which fields `getSelectedFiles` uses for grouping when
|
|
65
|
+
metadata mode is on.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
log("\n--- init views ---");
|
|
69
|
+
const savedViews: CDEMetadataView[] = [
|
|
70
|
+
{ id: "view-str", name: "Structural", fields: ["discipline", "phase"] },
|
|
71
|
+
{ id: "view-mep", name: "MEP", fields: ["discipline", "zone"] },
|
|
72
|
+
];
|
|
73
|
+
cde.initMetadataViews(savedViews, "view-str");
|
|
74
|
+
|
|
75
|
+
log(` metadataViews: ${cde.metadataViews.length}`);
|
|
76
|
+
log(` activeMetadataViewId: ${cde.activeMetadataViewId}`);
|
|
77
|
+
log(` activeMetadataView name: ${cde.activeMetadataView?.name}`);
|
|
78
|
+
|
|
79
|
+
/* MD
|
|
80
|
+
### 🔀 Switching the active view
|
|
81
|
+
The active view can be changed from idle, a selection, or while the viewer is open.
|
|
82
|
+
Switching resets the selected metadata group so the table doesn't show a stale
|
|
83
|
+
filter from the previous view's grouping. The transition doesn't change the machine
|
|
84
|
+
state kind — it's a targeted property update, not a full state change.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
log("\n--- switch active view ---");
|
|
88
|
+
cde.sendMachineEvent({ type: "SET_ACTIVE_METADATA_VIEW", id: "view-mep" });
|
|
89
|
+
log(` active: ${cde.activeMetadataView?.name}`);
|
|
90
|
+
|
|
91
|
+
cde.sendMachineEvent({ type: "SET_ACTIVE_METADATA_VIEW", id: "view-str" });
|
|
92
|
+
log(` active: ${cde.activeMetadataView?.name}`);
|
|
93
|
+
|
|
94
|
+
/* MD
|
|
95
|
+
### ✏️ Staging view changes
|
|
96
|
+
Adding, updating, and removing views all operate on the `pendingViews` list inside
|
|
97
|
+
the machine state. The committed view list is unchanged until the save fires. Adding
|
|
98
|
+
a view generates a stable random ID immediately, so the UI can reference it for
|
|
99
|
+
subsequent updates within the same session without waiting for a server response.
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
log("\n--- stage: add, update, delete ---");
|
|
103
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_VIEWS" });
|
|
104
|
+
|
|
105
|
+
// Add a new view — gets a UUID immediately.
|
|
106
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_VIEW" });
|
|
107
|
+
|
|
108
|
+
if (cde.machineState.kind === "configuringMetadataViews") {
|
|
109
|
+
const newView = cde.machineState.pendingViews.find(v => v.name === "New View")!;
|
|
110
|
+
log(` new view id: ${newView.id.slice(0, 8)}…`);
|
|
111
|
+
|
|
112
|
+
// Name it and set its fields.
|
|
113
|
+
cde.sendMachineEvent({
|
|
114
|
+
type: "UPDATE_METADATA_VIEW",
|
|
115
|
+
id: newView.id,
|
|
116
|
+
view: { ...newView, name: "Civil", fields: ["discipline", "zone", "level"] },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
log(` views in pending: ${cde.machineState.pendingViews.length}`);
|
|
120
|
+
log(` updated view name: ${cde.machineState.pendingViews.find(v => v.id === newView.id)?.name}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Remove an existing view from the pending list.
|
|
124
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_VIEW", id: "view-mep" });
|
|
125
|
+
if (cde.machineState.kind === "configuringMetadataViews") {
|
|
126
|
+
log(` views after delete: ${cde.machineState.pendingViews.length}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_VIEWS" });
|
|
130
|
+
// Cancelled — committed view list is unchanged.
|
|
131
|
+
log(` committed views after cancel: ${cde.metadataViews.length}`);
|
|
132
|
+
|
|
133
|
+
/* MD
|
|
134
|
+
### 💾 Committing views
|
|
135
|
+
Saving replaces the committed view list with the pending one and fires the views-
|
|
136
|
+
changed event. The user's personal config is updated at the same time if the account
|
|
137
|
+
has create permission — the same config that `loadUserConfig` reads back on the
|
|
138
|
+
next session load. Without a real user identity in the mock, the persistence step is
|
|
139
|
+
silently skipped but the local update still applies.
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
log("\n--- commit ---");
|
|
143
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_VIEWS" });
|
|
144
|
+
|
|
145
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_VIEW" });
|
|
146
|
+
if (cde.machineState.kind === "configuringMetadataViews") {
|
|
147
|
+
const civil = cde.machineState.pendingViews.find(v => v.name === "New View")!;
|
|
148
|
+
cde.sendMachineEvent({
|
|
149
|
+
type: "UPDATE_METADATA_VIEW",
|
|
150
|
+
id: civil.id,
|
|
151
|
+
view: { ...civil, name: "Civil", fields: ["discipline", "zone"] },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_VIEW", id: "view-mep" });
|
|
155
|
+
|
|
156
|
+
if (cde.machineState.kind === "configuringMetadataViews") {
|
|
157
|
+
log(` pending: ${cde.machineState.pendingViews.length} view(s)`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await new Promise<void>(resolve => {
|
|
161
|
+
cde.onMachineStateChanged.add((s) => { if (s.kind === "idle") resolve(); });
|
|
162
|
+
cde.sendMachineEvent({ type: "SAVE_METADATA_VIEWS" });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
log(` state after save: ${cde.machineState.kind}`);
|
|
166
|
+
log(` committed views: ${cde.metadataViews.map(v => v.name).join(", ")}`);
|