@thatopen/services 0.6.0 → 0.8.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 (73) hide show
  1. package/README.md +62 -62
  2. package/dist/built-in/index.d.ts +485 -100
  3. package/dist/cli/lib/revit-addin.d.ts +8 -0
  4. package/dist/cli.js +135 -24
  5. package/dist/index.cjs.js +2 -2
  6. package/dist/index.es.js +41 -50
  7. package/dist/vite-env.d.ts +1 -1
  8. package/docs/ai-quickstart.md +138 -4
  9. package/docs/cli/create.md +29 -29
  10. package/docs/cli/local-server.md +38 -38
  11. package/docs/cli/login.md +32 -32
  12. package/docs/cli/publish.md +31 -31
  13. package/docs/cli/run.md +26 -26
  14. package/docs/cli/serve.md +43 -43
  15. package/docs/cli/swap.md +30 -30
  16. package/docs/flow-architecture.md +1 -1
  17. package/docs/flow-plugin-guide.md +27 -1
  18. package/docs/platform-token.md +81 -0
  19. package/docs/revit-collab-quickstart.md +115 -35
  20. package/docs/rhino-interop-quickstart.md +458 -0
  21. package/package.json +1 -1
  22. package/resources/AGENTS.md +1 -1
  23. package/src/built-in/CDEManager/example.ts +214 -214
  24. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  25. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  26. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  27. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  28. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  29. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  30. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  31. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  32. package/src/built-in/UIManager/src/app/example.ts +97 -97
  33. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
  34. package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
  35. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
  36. package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
  37. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
  38. package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
  39. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
  40. package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
  41. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
  42. package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
  43. package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
  44. package/src/built-in/UIManager/src/models-list/example.ts +122 -122
  45. package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
  46. package/src/built-in/index.ts +485 -100
  47. package/src/cli/templates/app/index.html +35 -35
  48. package/src/cli/templates/app/package.json +32 -32
  49. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
  50. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  51. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  52. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  53. package/src/cli/templates/app/src/globals.ts +1 -1
  54. package/src/cli/templates/app/src/main.history.ts +191 -0
  55. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
  56. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
  57. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
  58. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
  59. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
  60. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
  61. package/src/cli/templates/app/src/ui-components/index.ts +2 -2
  62. package/src/cli/templates/app/tsconfig.json +16 -16
  63. package/src/cli/templates/app/vite.config.js +57 -57
  64. package/src/cli/templates/cloud-component/README.md +205 -205
  65. package/src/cli/templates/cloud-component/_thatopen +5 -5
  66. package/src/cli/templates/cloud-component/declarations.json +4 -4
  67. package/src/cli/templates/cloud-component/package.json +21 -21
  68. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  69. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  70. package/src/cli/templates/cloud-component/vite.config.js +46 -46
  71. package/src/cli/templates/shared/AGENTS.md +8 -8
  72. package/src/cli/templates/shared/CLAUDE.md +1 -1
  73. package/src/cli/templates/shared/_gitignore +5 -5
@@ -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
+ */