@abi-software/scaffoldvuer 0.1.50 → 0.1.52-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/scaffoldvuer",
3
- "version": "0.1.50",
3
+ "version": "0.1.52-beta.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,23 +22,22 @@
22
22
  "*.js"
23
23
  ],
24
24
  "dependencies": {
25
- "@abi-software/svg-sprite": "^0.1.13",
25
+ "@abi-software/svg-sprite": "^0.1.14",
26
26
  "axios": "^0.21.2",
27
27
  "core-js": "^3.3.2",
28
28
  "current-script-polyfill": "^1.0.0",
29
29
  "element-ui": "^2.13.0",
30
30
  "google-spreadsheet": "^3.1.15",
31
31
  "lodash": "^4.17.21",
32
- "physiomeportal": "^0.4.27",
33
32
  "query-string": "^6.11.1",
34
33
  "vue": "^2.6.10",
35
34
  "vue-drag-resize": "^1.3.2",
36
35
  "vue-router": "^3.5.1",
37
- "zincjs": "^0.40.0"
36
+ "zincjs": "^0.50.0-beta.1"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@vue/cli-plugin-babel": "^4.0.0",
41
- "@vue/cli-plugin-eslint": "^4.0.0",
40
+ "@vue/cli-plugin-eslint": "^4.5.15",
42
41
  "@vue/cli-service": "^4.5.13",
43
42
  "babel-eslint": "^10.0.3",
44
43
  "babel-plugin-component": "^1.1.1",
package/src/App.vue CHANGED
@@ -18,6 +18,7 @@
18
18
  :region="region"
19
19
  :view-u-r-l="viewURL"
20
20
  @scaffold-selected="onSelected"
21
+ @scaffold-navigated="onNavigated"
21
22
  @timeChanged="updateCurrentTime"
22
23
  />
23
24
  <el-popover
@@ -100,16 +101,93 @@
100
101
  >
101
102
  Restore Settings
102
103
  </el-button>
104
+ <el-button
105
+ size="mini"
106
+ @click="exportGLB()"
107
+ >
108
+ Export GLTF
109
+ </el-button>
103
110
  </el-row>
104
- <el-row :gutter="20">
105
- <el-row :gutter="20">
111
+ <el-row :gutter="30">
112
+ <el-col
113
+ :span="7"
114
+ :offset="2"
115
+ >
116
+ <el-switch
117
+ v-model="syncMode"
118
+ active-text="Sync Mode"
119
+ active-color="#8300bf"
120
+ />
121
+ <el-row v-if="syncMode">
122
+ <el-input-number
123
+ v-model="zoom"
124
+ :min="1.0"
125
+ :controls="false"
126
+ placeholder="Please input"
127
+ label="zoom"
128
+ />
129
+ <el-input-number
130
+ v-model="pos[0]"
131
+ :min="-1.0"
132
+ :max="1.0"
133
+ :controls="false"
134
+ placeholder="Please input"
135
+ label="x"
136
+ />
137
+ <el-input-number
138
+ v-model="pos[1]"
139
+ :min="-1.0"
140
+ :max="1.0"
141
+ :controls="false"
142
+ label="y"
143
+ />
144
+ </el-row>
145
+ </el-col>
146
+ </el-row>
147
+ <el-row :gutter="30">
148
+ <el-col
149
+ :span="7"
150
+ :offset="4"
151
+ >
106
152
  <el-switch
107
153
  v-model="render"
108
154
  active-text="Rendering"
109
155
  active-color="#8300bf"
110
156
  />
111
- </el-row>
157
+ </el-col>
158
+ <el-col
159
+ :span="8"
160
+ :offset="1"
161
+ >
162
+ <el-switch
163
+ v-model="renderInfoOn"
164
+ active-text="Renderer Info"
165
+ active-color="#8300bf"
166
+ />
167
+ </el-col>
112
168
  </el-row>
169
+ <template v-if="renderInfoOn && rendererInfo">
170
+ <el-row>
171
+ <el-col
172
+ v-for="(value, name) in rendererInfo.memory"
173
+ :key="name"
174
+ :offset="4"
175
+ :span="6"
176
+ >
177
+ {{ name }} : {{ value }}
178
+ </el-col>
179
+ </el-row>
180
+ <el-row>
181
+ <el-col
182
+ v-for="(value, name) in rendererInfo.render"
183
+ :key="name"
184
+ :offset="1"
185
+ :span="6"
186
+ >
187
+ {{ name }} : {{ value }}
188
+ </el-col>
189
+ </el-row>
190
+ </template>
113
191
  <el-input
114
192
  v-model="input"
115
193
  type="textarea"
@@ -149,7 +227,7 @@
149
227
  import { ScaffoldVuer } from "./components/index.js";
150
228
  import ModelsTable from "./components/ModelsTable.vue";
151
229
  import Vue from "vue";
152
- import { Button, Col, Icon, Input, Popover, Row, Switch } from "element-ui";
230
+ import { Button, Col, Icon, Input, InputNumber, Popover, Row, Switch } from "element-ui";
153
231
  import lang from "element-ui/lib/locale/lang/en";
154
232
  import locale from "element-ui/lib/locale";
155
233
 
@@ -158,6 +236,7 @@ Vue.use(Button);
158
236
  Vue.use(Col);
159
237
  Vue.use(Icon);
160
238
  Vue.use(Input);
239
+ Vue.use(InputNumber);
161
240
  Vue.use(Popover);
162
241
  Vue.use(Row);
163
242
  Vue.use(Switch);
@@ -199,6 +278,7 @@ export default {
199
278
  selectedCoordinates: undefined,
200
279
  helpMode: false,
201
280
  displayMarkers: true,
281
+ syncMode: false,
202
282
  currentTime: 0,
203
283
  displayMinimap: true,
204
284
  tumbleOn: false,
@@ -212,7 +292,11 @@ export default {
212
292
  },
213
293
  render: true,
214
294
  region: "",
215
- viewURL: ""
295
+ viewURL: "",
296
+ renderInfoOn: false,
297
+ rendererInfo: undefined,
298
+ zoom: 1,
299
+ pos: [0, 0],
216
300
  };
217
301
  },
218
302
  watch: {
@@ -226,14 +310,43 @@ export default {
226
310
  handler: "parseQuery",
227
311
  deep: true,
228
312
  immediate: true
313
+ },
314
+ syncMode: function(val) {
315
+ this.$refs.scaffold.toggleSyncControl(val);
229
316
  }
230
317
  },
231
318
 
232
319
  mounted: function() {
233
320
  this._sceneSettings = [];
234
321
  this.selectedCoordinates = this.$refs.scaffold.getDynamicSelectedCoordinates();
322
+ this.rendererInfo = this.$refs.scaffold.getRendererInfo();
235
323
  },
236
324
  methods: {
325
+ exportGLTF: function() {
326
+ this.$refs.scaffold.exportGLTF(false)
327
+ .then(data =>{
328
+ let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data));
329
+ let hrefElement = document.createElement("a");
330
+ document.body.append(hrefElement);
331
+ hrefElement.download = `export.gltf`;
332
+ hrefElement.href = dataStr;
333
+ hrefElement.click();
334
+ hrefElement.remove();
335
+ })
336
+ },
337
+ exportGLB: function() {
338
+ this.$refs.scaffold.exportGLTF(true)
339
+ .then(data =>{
340
+ let blob = new Blob([data], {type: "octet/stream"});
341
+ let url = window.URL.createObjectURL(blob);
342
+ let hrefElement = document.createElement("a");
343
+ document.body.append(hrefElement);
344
+ hrefElement.download = `export.glb`;
345
+ hrefElement.href = url;
346
+ hrefElement.click();
347
+ hrefElement.remove();
348
+ })
349
+ },
237
350
  saveSettings: function() {
238
351
  this._sceneSettings.push(this.$refs.scaffold.getState());
239
352
  },
@@ -266,6 +379,12 @@ export default {
266
379
  });
267
380
  }
268
381
  },
382
+ onNavigated: function(data) {
383
+ console.log(data)
384
+ this.zoom = data.zoom;
385
+ this.pos[0] = data.target[0];
386
+ this.pos[1] = data.target[1];
387
+ },
269
388
  parseInput: function() {
270
389
  if (this.$route.query.url !== this.input)
271
390
  this.$router.replace({
@@ -325,6 +444,12 @@ body {
325
444
 
326
445
  .options-container {
327
446
  text-align: center;
447
+ .el-row {
448
+ margin-bottom: 8px;
449
+ &:last-child {
450
+ margin-bottom: 0;
451
+ }
452
+ }
328
453
  }
329
454
 
330
455
  .vuer {
@@ -7,7 +7,7 @@
7
7
  element-loading-spinner="el-icon-loading"
8
8
  element-loading-background="rgba(0, 0, 0, 0.3)"
9
9
  >
10
- <SvgSpriteColor />
10
+ <map-svg-sprite-color />
11
11
  <div
12
12
  id="organsDisplayArea"
13
13
  ref="display"
@@ -76,13 +76,13 @@
76
76
  <el-tabs type="card">
77
77
  <el-tab-pane label="Animate scaffold">
78
78
  <el-row class="tab-content">
79
- <SvgIcon
79
+ <map-svg-icon
80
80
  v-if="isPlaying"
81
81
  icon="pause"
82
82
  class="icon-button video-button"
83
83
  @click.native="play(false)"
84
84
  />
85
- <SvgIcon
85
+ <map-svg-icon
86
86
  v-else
87
87
  icon="play"
88
88
  class="video-button icon-button"
@@ -146,7 +146,7 @@
146
146
  trigger="manual"
147
147
  popper-class="scaffold-popper left-popper non-selectable"
148
148
  >
149
- <SvgIcon
149
+ <map-svg-icon
150
150
  slot="reference"
151
151
  icon="zoomIn"
152
152
  class="icon-button zoomIn"
@@ -163,7 +163,7 @@
163
163
  trigger="manual"
164
164
  popper-class="scaffold-popper popper-zoomout non-selectable"
165
165
  >
166
- <SvgIcon
166
+ <map-svg-icon
167
167
  slot="reference"
168
168
  icon="zoomOut"
169
169
  class="icon-button zoomOut"
@@ -184,7 +184,7 @@
184
184
  <br>
185
185
  window
186
186
  </div>
187
- <SvgIcon
187
+ <map-svg-icon
188
188
  slot="reference"
189
189
  icon="fitWindow"
190
190
  class="icon-button fitWindow"
@@ -222,7 +222,7 @@
222
222
  trigger="manual"
223
223
  popper-class="scaffold-popper right-popper non-selectable"
224
224
  >
225
- <SvgIcon
225
+ <map-svg-icon
226
226
  slot="reference"
227
227
  v-popover:backgroundPopover
228
228
  icon="changeBckgd"
@@ -241,7 +241,7 @@
241
241
  import Vue from "vue";
242
242
  import OpacityControls from "./OpacityControls";
243
243
  import TraditionalControls from "./TraditionalControls";
244
- import { SvgIcon, SvgSpriteColor } from "@abi-software/svg-sprite";
244
+ import { MapSvgIcon, MapSvgSpriteColor } from "@abi-software/svg-sprite";
245
245
 
246
246
  import {
247
247
  Col,
@@ -268,10 +268,8 @@ Vue.use(Slider);
268
268
  Vue.use(TabPane);
269
269
  Vue.use(Tabs);
270
270
 
271
- const OrgansViewer = require("physiomeportal/src/modules/organsRenderer")
272
- .OrgansViewer;
273
- const EventNotifier = require("physiomeportal/src/utilities/eventNotifier")
274
- .EventNotifier;
271
+ const OrgansViewer = require("../scripts/organsRenderer").OrgansViewer;
272
+ const EventNotifier = require("../scripts/eventNotifier").EventNotifier;
275
273
 
276
274
  /**
277
275
  * A vue component of the scaffold viewer.
@@ -283,8 +281,8 @@ export default {
283
281
  name: "ScaffoldVuer",
284
282
  components: {
285
283
  OpacityControls,
286
- SvgIcon,
287
- SvgSpriteColor,
284
+ MapSvgIcon,
285
+ MapSvgSpriteColor,
288
286
  TraditionalControls
289
287
  },
290
288
  props: {
@@ -535,7 +533,6 @@ export default {
535
533
  this.$module.addOrganPartAddedCallback(this.organsAdded);
536
534
  this.$module.initialiseRenderer(this.$refs.display);
537
535
  this.toggleRendering(this.render);
538
- this.$module.toolTip = undefined;
539
536
  this.ro = new ResizeObserver(this.adjustLayout).observe(
540
537
  this.$refs.scaffoldContainer
541
538
  );
@@ -668,7 +665,11 @@ export default {
668
665
  let objects = this.$module.scene.findObjectsWithGroupName(name);
669
666
  let box = this.$module.scene.getBoundingBoxOfZincObjects(objects);
670
667
  if (box) {
671
- this.$module.scene.viewAllWithBoundingBox(box);
668
+ if (this.$module.isSyncControl()) {
669
+ this.$module.setSyncControlZoomToBox(box);
670
+ } else {
671
+ this.$module.scene.viewAllWithBoundingBox(box);
672
+ }
672
673
  }
673
674
  }
674
675
  },
@@ -695,6 +696,12 @@ export default {
695
696
  }
696
697
  }
697
698
  },
699
+ getRendererInfo: function() {
700
+ if (this.$module.zincRenderer) {
701
+ return this.$module.zincRenderer.getThreeJSRenderer().info;
702
+ }
703
+ return undefined;
704
+ },
698
705
  /**
699
706
  * Function used to rotate the scene.
700
707
  * Also called when the associated button is pressed.
@@ -721,9 +728,9 @@ export default {
721
728
  let id = event.identifiers[0].data.id
722
729
  ? event.identifiers[0].data.id
723
730
  : event.identifiers[0].data.group;
724
- this.$refs.traditionalControl.changeActiveByName(id);
731
+ this.$refs.traditionalControl.changeActiveByName(id, true);
725
732
  } else {
726
- this.$refs.traditionalControl.removeActive();
733
+ this.$refs.traditionalControl.removeActive(true);
727
734
  }
728
735
  }
729
736
  /**
@@ -739,8 +746,8 @@ export default {
739
746
  let id = event.identifiers[0].data.id
740
747
  ? event.identifiers[0].data.id
741
748
  : event.identifiers[0].data.group;
742
- this.$refs.traditionalControl.changeHoverByName(id);
743
- } else this.$refs.traditionalControl.removeHover();
749
+ this.$refs.traditionalControl.changeHoverByName(id, true);
750
+ } else this.$refs.traditionalControl.removeHover(true);
744
751
  }
745
752
  /**
746
753
  * Triggers when an object has been highlighted
@@ -781,30 +788,52 @@ export default {
781
788
  this.$module.updateTime(normalizedTime);
782
789
  },
783
790
  /**
784
- * Set the selected zinc object
791
+ * A callback used by children components. Set the selected zinc object
785
792
  *
786
793
  * @param {object} object Zinc object
787
794
  */
788
- objectSelected: function(object) {
795
+ objectSelected: function(object, propagate) {
789
796
  if (object !== this.selectedObject) {
790
797
  this.selectedObject = object;
791
798
  this.$refs.opacityControl.setObject(this.selectedObject);
792
- if (object) this.$module.setSelectedByZincObject(object, true);
793
- else this.$module.setSelectedByObjects([], true);
799
+ if (object) this.$module.setSelectedByZincObject(object, propagate);
800
+ else this.$module.setSelectedByObjects([], propagate);
794
801
  }
795
802
  },
796
803
  /**
797
- * Set the highlighted zinc object
804
+ * A callback used by children components. Set the highlighted zinc object
798
805
  *
799
806
  * @param {object} object Zinc object
800
807
  */
801
- objectHovered: function(object) {
808
+ objectHovered: function(object, propagate) {
802
809
  if (object !== this.hoveredObject) {
803
810
  this.hoveredObject = object;
804
- if (object) this.$module.setHighlightedByZincObject(object, true);
805
- else this.$module.setHighlightedByObjects([], true);
811
+ if (object) this.$module.setHighlightedByZincObject(object, propagate);
812
+ else this.$module.setHighlightedByObjects([], propagate);
806
813
  }
807
814
  },
815
+ /**
816
+ * Set the selected by name.
817
+ *
818
+ * @param {name} name Name of the region
819
+ */
820
+ changeActiveByName: function(name, propagate) {
821
+ if (name === undefined)
822
+ this.$refs.traditionalControl.removeActive(propagate);
823
+ else
824
+ this.$refs.traditionalControl.changeActiveByName(name, propagate);
825
+ },
826
+ /**
827
+ * Set the highlighted by name.
828
+ *
829
+ * @param {name} name Name of the region
830
+ */
831
+ changeHighlightedByName: function(name, propagate) {
832
+ if (name === undefined)
833
+ this.$refs.traditionalControl.removeHover(propagate);
834
+ else
835
+ this.$refs.traditionalControl.changeHoverByName(name, propagate);
836
+ },
808
837
  /**
809
838
  * Start the animation.
810
839
  *
@@ -955,6 +984,9 @@ export default {
955
984
  }
956
985
  }
957
986
  },
987
+ exportGLTF: function(binary) {
988
+ return this.$module.scene.exportGLTF(binary);
989
+ },
958
990
  /**
959
991
  * Function used for reading in new scaffold metadata and a custom
960
992
  * viewport. This function will ignore the state prop and
@@ -1011,7 +1043,6 @@ export default {
1011
1043
  },
1012
1044
  /**
1013
1045
  * Callback using ResizeObserver.
1014
-
1015
1046
  */
1016
1047
  adjustLayout: function() {
1017
1048
  let width = this.$refs.scaffoldContainer.clientWidth;
@@ -1035,6 +1066,14 @@ export default {
1035
1066
  if (this.$module.zincRenderer) {
1036
1067
  this.$module.zincRenderer.onWindowResize();
1037
1068
  }
1069
+ },
1070
+ syncControlCallback: function() {
1071
+ const payload = this.$module.NDCCameraControl.getPanZoom();
1072
+ this.$emit("scaffold-navigated", payload);
1073
+ },
1074
+ toggleSyncControl: function(flag) {
1075
+ this.$module.toggleSyncControl(flag);
1076
+ this.$module.setSyncControlCallback(this.syncControlCallback);
1038
1077
  }
1039
1078
  }
1040
1079
  };
@@ -130,7 +130,6 @@ export default {
130
130
  tmpArray = uniq(tmpArray.concat(this.module.sceneData.pointset));
131
131
  this.sortedPrimitiveGroups = orderBy(tmpArray);
132
132
  this.module.addOrganPartAddedCallback(this.organsAdded);
133
- this.module.graphicsHighlight.selectColour = 0x444444;
134
133
  },
135
134
  destroyed: function() {
136
135
  this.sortedPrimitiveGroups = undefined;
@@ -153,47 +152,41 @@ export default {
153
152
  /**
154
153
  * Select a region by its name.
155
154
  */
156
- changeActiveByName: function(name) {
155
+ changeActiveByName: function(name, propagate) {
157
156
  let targetObject = this.getFirstZincObjectWithGroupName(name);
158
157
  if (targetObject && targetObject.getVisibility()) {
159
158
  this.activeRegion = name;
160
- /**
161
- * Triggers when an item has been selected.
162
- *
163
- * @property {object} target selected object.
164
- */
165
- this.$emit("object-selected", targetObject);
159
+ this.$emit("object-selected", targetObject, propagate);
160
+ } else {
161
+ this.removeActive(propagate);
166
162
  }
167
- this.removeHover();
163
+ this.removeHover(propagate);
168
164
  },
169
165
  /**
170
166
  * Hover a region by its name.
171
167
  */
172
- changeHoverByName: function(name) {
168
+ changeHoverByName: function(name, propagate) {
173
169
  let targetObject = this.getFirstZincObjectWithGroupName(name);
174
170
  if (targetObject) {
175
171
  this.hoverRegion = name;
176
- /**
177
- * Triggers when an item has been hovered over.
178
- *
179
- * @property {object} target hovered object.
180
- */
181
- this.$emit("object-hovered", targetObject);
172
+ this.$emit("object-hovered", targetObject, propagate);
173
+ } else {
174
+ this.removeHover(propagate);
182
175
  }
183
176
  },
184
177
  /**
185
178
  * Unselect the current selected region.
186
179
  */
187
- removeActive: function() {
180
+ removeActive: function(propagate) {
188
181
  this.activeRegion = "";
189
- this.$emit("object-selected", undefined);
182
+ this.$emit("object-selected", undefined, propagate);
190
183
  },
191
184
  /**
192
185
  * Unselect the current hover region.
193
186
  */
194
- removeHover: function() {
187
+ removeHover: function(propagate) {
195
188
  this.hoverRegion = "";
196
- this.$emit("object-hovered", undefined);
189
+ this.$emit("object-hovered", undefined, propagate);
197
190
  },
198
191
  /**
199
192
  * Reset the controls.
@@ -236,7 +229,7 @@ export default {
236
229
  }
237
230
  },
238
231
  checkboxHover: function(name) {
239
- this.changeHoverByName(name);
232
+ this.changeHoverByName(name, true);
240
233
  },
241
234
  itemClicked: function(name, event) {
242
235
  if (
@@ -245,7 +238,7 @@ export default {
245
238
  event.target.classList.contains("el-checkbox__original")
246
239
  )
247
240
  ) {
248
- this.changeActiveByName(name);
241
+ this.changeActiveByName(name, true);
249
242
  event.preventDefault();
250
243
  }
251
244
  },
@@ -272,10 +265,10 @@ export default {
272
265
  this.module.changeOrganPartsVisibility(item, event);
273
266
  if (event == false) {
274
267
  if (this.activeRegion === item) {
275
- this.removeActive();
268
+ this.removeActive(true);
276
269
  }
277
270
  if (this.hoverRegion === item) {
278
- this.removeHover();
271
+ this.removeHover(true);
279
272
  }
280
273
  }
281
274
  },
@@ -0,0 +1,80 @@
1
+ const MODULE_CHANGE = { ALL: 0, DESTROYED: 1, NAME_CHANGED: 2, SETTINGS_CHANGED: 3 };
2
+
3
+ const BaseModule = function() {
4
+ this.typeName = "Base Module";
5
+ this.instanceName = "default";
6
+ this.onChangedCallbacks = [];
7
+ /** Notifier handle for informing other modules of any changes **/
8
+ this.eventNotifiers = [];
9
+ }
10
+
11
+ BaseModule.prototype.setName = function(name) {
12
+ if (name && this.instanceName !== name) {
13
+ this.instanceName = name;
14
+ const callbackArray = this.onChangedCallbacks.slice();
15
+ for (let i = 0; i < callbackArray.length; i++) {
16
+ callbackArray[i]( this, MODULE_CHANGE.NAME_CHANGED );
17
+ }
18
+ }
19
+ }
20
+
21
+ BaseModule.prototype.settingsChanged = function() {
22
+ const callbackArray = this.onChangedCallbacks.slice();
23
+ for (let i = 0; i < callbackArray.length; i++) {
24
+ callbackArray[i]( this, MODULE_CHANGE.SETTINGS_CHANGED );
25
+ }
26
+ }
27
+
28
+ BaseModule.prototype.exportSettings = function() {
29
+ const settings = {};
30
+ settings.dialog = this.typeName;
31
+ settings.name = this.instanceName;
32
+ return settings;
33
+ }
34
+
35
+ BaseModule.prototype.importSettings = function(settings) {
36
+ if (settings.dialog == this.typeName) {
37
+ this.setName(settings.name);
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ BaseModule.prototype.publishChanges = function(annotations, eventType) {
44
+ for (let i = 0; i < this.eventNotifiers.length; i++) {
45
+ this.eventNotifiers[i].publish(this, eventType, annotations);
46
+ }
47
+ }
48
+
49
+ BaseModule.prototype.getName = function() {
50
+ return this.instanceName;
51
+ }
52
+
53
+ BaseModule.prototype.destroy = function() {
54
+ //Make a temorary copy as the array may be altered during the loop
55
+ const callbackArray = this.onChangedCallbacks.slice();
56
+ for (let i = 0; i < callbackArray.length; i++) {
57
+ callbackArray[i]( this, MODULE_CHANGE.DESTROYED );
58
+ }
59
+
60
+ delete this;
61
+ }
62
+
63
+ BaseModule.prototype.addChangedCallback = function(callback) {
64
+ if (this.onChangedCallbacks.includes(callback) == false)
65
+ this.onChangedCallbacks.push(callback);
66
+ }
67
+
68
+ BaseModule.prototype.removeChangedCallback = function(callback) {
69
+ const index = this.onChangedCallbacks.indexOf(callback);
70
+ if (index > -1) {
71
+ this.onChangedCallbacks.splice(index, 1);
72
+ }
73
+ }
74
+
75
+ BaseModule.prototype.addNotifier = function(eventNotifier) {
76
+ this.eventNotifiers.push(eventNotifier);
77
+ }
78
+
79
+ exports.BaseModule = BaseModule;
80
+ exports.MODULE_CHANGE = MODULE_CHANGE;