neo.mjs 3.2.4 → 3.2.7

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.
@@ -0,0 +1,36 @@
1
+ import Neo from '../src/Neo.mjs';
2
+ import * as core from '../src/core/_export.mjs';
3
+ import ServiceBase from '../src/worker/ServiceBase.mjs';
4
+
5
+ /**
6
+ * @class Neo.ServiceWorker
7
+ * @extends Neo.worker.ServiceBase
8
+ * @singleton
9
+ */
10
+ class ServiceWorker extends ServiceBase {
11
+ static getConfig() {return {
12
+ /**
13
+ * @member {String} className='Neo.ServiceWorker'
14
+ * @protected
15
+ */
16
+ className: 'Neo.ServiceWorker',
17
+ /**
18
+ * @member {Boolean} singleton=true
19
+ * @protected
20
+ */
21
+ singleton: true,
22
+ /**
23
+ * @member {String} workerId='service'
24
+ * @protected
25
+ */
26
+ workerId: 'service'
27
+ }}
28
+ }
29
+
30
+ Neo.applyClassConfig(ServiceWorker);
31
+
32
+ let instance = Neo.create(ServiceWorker);
33
+
34
+ Neo.applyToGlobalNs(instance);
35
+
36
+ export default instance;
@@ -61,7 +61,7 @@ if (programOpts.info) {
61
61
  type : 'list',
62
62
  name : 'threads',
63
63
  message: 'Please choose the threads to build:',
64
- choices: ['all', 'app', 'canvas', 'data', 'main', 'vdom'],
64
+ choices: ['all', 'app', 'canvas', 'data', 'main', 'service', 'vdom'],
65
65
  default: 'all'
66
66
  });
67
67
  }
@@ -88,11 +88,12 @@ if (programOpts.info) {
88
88
  }
89
89
 
90
90
  function parseThreads(tPath) {
91
- (threads === 'all' || threads === 'main') && spawnSync(webpack, ['--config', `${tPath}.main.mjs`], cpOpts);
92
- (threads === 'all' || threads === 'app') && spawnSync(webpack, ['--config', `${tPath}.appworker.mjs`, `--env insideNeo=${insideNeo}`], cpOpts);
93
- (threads === 'all' || threads === 'canvas') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=canvas`], cpOpts);
94
- (threads === 'all' || threads === 'data') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=data`], cpOpts);
95
- (threads === 'all' || threads === 'vdom') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=vdom`], cpOpts);
91
+ (threads === 'all' || threads === 'main') && spawnSync(webpack, ['--config', `${tPath}.main.mjs`], cpOpts);
92
+ (threads === 'all' || threads === 'app') && spawnSync(webpack, ['--config', `${tPath}.appworker.mjs`, `--env insideNeo=${insideNeo}`], cpOpts);
93
+ (threads === 'all' || threads === 'canvas') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=canvas`], cpOpts);
94
+ (threads === 'all' || threads === 'data') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=data`], cpOpts);
95
+ (threads === 'all' || threads === 'service') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=service`], cpOpts);
96
+ (threads === 'all' || threads === 'vdom') && spawnSync(webpack, ['--config', `${tPath}.worker.mjs`, `--env insideNeo=${insideNeo} worker=vdom`], cpOpts);
96
97
  }
97
98
 
98
99
  // dist/development
@@ -14,6 +14,10 @@
14
14
  "input": "./src/worker/Data.mjs",
15
15
  "output": "dataworker.js"
16
16
  },
17
+ "service": {
18
+ "input": "./apps/ServiceWorker.mjs",
19
+ "output": "serviceworker.js"
20
+ },
17
21
  "vdom": {
18
22
  "input": "./src/worker/VDom.mjs",
19
23
  "output": "vdomworker.js"
@@ -0,0 +1,36 @@
1
+ import Neo from '../src/Neo.mjs';
2
+ import * as core from '../src/core/_export.mjs';
3
+ import ServiceBase from '../src/worker/ServiceBase.mjs';
4
+
5
+ /**
6
+ * @class Neo.ServiceWorker
7
+ * @extends Neo.worker.ServiceBase
8
+ * @singleton
9
+ */
10
+ class ServiceWorker extends ServiceBase {
11
+ static getConfig() {return {
12
+ /**
13
+ * @member {String} className='Neo.ServiceWorker'
14
+ * @protected
15
+ */
16
+ className: 'Neo.ServiceWorker',
17
+ /**
18
+ * @member {Boolean} singleton=true
19
+ * @protected
20
+ */
21
+ singleton: true,
22
+ /**
23
+ * @member {String} workerId='service'
24
+ * @protected
25
+ */
26
+ workerId: 'service'
27
+ }}
28
+ }
29
+
30
+ Neo.applyClassConfig(ServiceWorker);
31
+
32
+ let instance = Neo.create(ServiceWorker);
33
+
34
+ Neo.applyToGlobalNs(instance);
35
+
36
+ export default instance;
@@ -0,0 +1,38 @@
1
+ import Model from '../../../src/data/Model.mjs';
2
+
3
+ /**
4
+ * @class Neo.examples.component.helix.ImageModel
5
+ * @extends Neo.data.Model
6
+ */
7
+ class ImageModel extends Model {
8
+ static getConfig() {return {
9
+ /**
10
+ * @member {String} className='Neo.examples.component.helix.ImageModel'
11
+ * @protected
12
+ */
13
+ className: 'Neo.examples.component.helix.ImageModel',
14
+ /**
15
+ * @member {Object[]} fields
16
+ */
17
+ fields: [{
18
+ name: 'firstname',
19
+ type: 'String'
20
+ }, {
21
+ name: 'id',
22
+ type: 'Integer'
23
+ }, {
24
+ name: 'image',
25
+ type: 'String'
26
+ }, {
27
+ name: 'isOnline',
28
+ type: 'Boolean'
29
+ }, {
30
+ name: 'lastname',
31
+ type: 'String'
32
+ }]
33
+ }}
34
+ }
35
+
36
+ Neo.applyClassConfig(ImageModel);
37
+
38
+ export default ImageModel;
@@ -0,0 +1,32 @@
1
+ import ImageModel from './ImageModel.mjs';
2
+ import Store from '../../../src/data/Store.mjs';
3
+
4
+ /**
5
+ * @class Neo.examples.component.helix.ImageStore
6
+ * @extends Neo.data.Store
7
+ */
8
+ class ImageStore extends Store {
9
+ static getConfig() {return {
10
+ /**
11
+ * @member {String} className='Neo.examples.component.helix.ImageModel'
12
+ * @protected
13
+ */
14
+ className: 'Neo.examples.component.helix.ImageStore',
15
+ /**
16
+ * @member {Boolean} autoLoad=true
17
+ */
18
+ autoLoad: true,
19
+ /**
20
+ * @member {Neo.data.Model} model=ImageModel
21
+ */
22
+ model: ImageModel,
23
+ /**
24
+ * @member {String} url='../../resources/examples/data/ai_contacts.json'
25
+ */
26
+ url: '../../resources/examples/data/ai_contacts.json'
27
+ }}
28
+ }
29
+
30
+ Neo.applyClassConfig(ImageStore);
31
+
32
+ export default ImageStore;
@@ -1,5 +1,6 @@
1
1
  import CheckBox from '../../../src/form/field/CheckBox.mjs';
2
2
  import Helix from '../../../src/component/Helix.mjs';
3
+ import ImageStore from './ImageStore.mjs';
3
4
  import NumberField from '../../../src/form/field/Number.mjs';
4
5
  import Panel from '../../../src/container/Panel.mjs';
5
6
  import RangeField from '../../../src/form/field/Range.mjs';
@@ -312,6 +313,7 @@ class MainContainer extends Viewport {
312
313
  me.helix = Neo.create({
313
314
  module: Helix,
314
315
  id : 'neo-helix-1',
316
+ store : ImageStore,
315
317
  ...me.helixConfig
316
318
  });
317
319
 
@@ -4,5 +4,6 @@
4
4
  "environment" : "development",
5
5
  "mainPath" : "./Main.mjs",
6
6
  "renderCountDeltas": true,
7
- "themes" : ["neo-theme-dark"]
7
+ "themes" : ["neo-theme-dark"],
8
+ "useServiceWorker" : true
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "3.2.4",
3
+ "version": "3.2.7",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -41,16 +41,16 @@
41
41
  "chalk": "^5.0.0",
42
42
  "clean-webpack-plugin": "^4.0.0",
43
43
  "commander": "^9.0.0",
44
- "cssnano": "^5.0.17",
44
+ "cssnano": "^5.1.0",
45
45
  "envinfo": "^7.8.1",
46
- "fs-extra": "^10.0.0",
46
+ "fs-extra": "^10.0.1",
47
47
  "highlightjs-line-numbers.js": "^2.8.0",
48
48
  "inquirer": "^8.1.5",
49
49
  "neo-jsdoc": "^1.0.1",
50
50
  "neo-jsdoc-x": "^1.0.4",
51
- "postcss": "^8.4.6",
52
- "sass": "^1.49.8",
53
- "webpack": "^5.69.1",
51
+ "postcss": "^8.4.7",
52
+ "sass": "^1.49.9",
53
+ "webpack": "^5.70.0",
54
54
  "webpack-cli": "^4.9.2",
55
55
  "webpack-dev-server": "4.7.4",
56
56
  "webpack-hook-plugin": "^1.0.7",
@@ -16,6 +16,12 @@
16
16
  outline: 0;
17
17
  }
18
18
 
19
+ &.neo-follow-selection {
20
+ .neo-helix-item {
21
+ transition: all 0.1s linear;
22
+ }
23
+ }
24
+
19
25
  &.neo-transition-100 {
20
26
  .neo-helix-item {
21
27
  transition: all 0.1s ease-in-out !important;
@@ -87,4 +93,4 @@
87
93
  right : 0;
88
94
  text-shadow : 0 0 10px #61DFE5;
89
95
  }
90
- }
96
+ }
@@ -134,7 +134,7 @@ const DefaultConfig = {
134
134
  * Experimental flag if an offscreen canvas worker should get created.
135
135
  * @default false
136
136
  * @memberOf! module:Neo
137
- * @name config.useCssVars
137
+ * @name config.useCanvasWorker
138
138
  * @type Boolean
139
139
  */
140
140
  useCanvasWorker: false,
@@ -163,6 +163,15 @@ const DefaultConfig = {
163
163
  * @type Boolean
164
164
  */
165
165
  useGoogleAnalytics: false,
166
+ /**
167
+ * True will add the ServiceWorker main thread addon to support caching of assets (PWA)
168
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
169
+ * @default false
170
+ * @memberOf! module:Neo
171
+ * @name config.useServiceWorker
172
+ * @type Boolean
173
+ */
174
+ useServiceWorker: false,
166
175
  /**
167
176
  * Creates App, Data & VDom as SharedWorkers.
168
177
  * Set this one to true in case you want to connect multiple main threads.
package/src/Main.mjs CHANGED
@@ -188,9 +188,11 @@ class Main extends core.Base {
188
188
  *
189
189
  */
190
190
  async onDomContentLoaded() {
191
- let me = this,
192
- config = Neo.config,
193
- imports = [];
191
+ let me = this,
192
+ config = Neo.config,
193
+ mainThreadAddons = config.mainThreadAddons,
194
+ imports = [],
195
+ modules;
194
196
 
195
197
  DomAccess.onDomContentLoaded();
196
198
 
@@ -203,18 +205,20 @@ class Main extends core.Base {
203
205
  __webpack_require__.p = config.basePath.substring(6);
204
206
  }
205
207
 
206
- config.mainThreadAddons.forEach(addon => {
207
- if (addon !== 'AnalyticsByGoogle') {
208
- imports.push(import(`./main/addon/${addon}.mjs`));
209
- }
210
- });
211
-
212
208
  // intended for the online examples where we need an easy way to add GA to every generated app
213
- if (config.useGoogleAnalytics || config.mainThreadAddons.includes('AnalyticsByGoogle')) {
214
- imports.push(import('./main/addon/AnalyticsByGoogle.mjs'));
209
+ if (config.useGoogleAnalytics && !mainThreadAddons.includes('AnalyticsByGoogle')) {
210
+ mainThreadAddons.push('AnalyticsByGoogle');
215
211
  }
216
212
 
217
- const modules = await Promise.all(imports);
213
+ if (config.useServiceWorker && !mainThreadAddons.includes('ServiceWorker')) {
214
+ mainThreadAddons.push('ServiceWorker');
215
+ }
216
+
217
+ mainThreadAddons.forEach(addon => {
218
+ imports.push(import(`./main/addon/${addon}.mjs`));
219
+ });
220
+
221
+ modules = await Promise.all(imports);
218
222
 
219
223
  me.addon = {};
220
224
 
@@ -500,7 +500,7 @@ class MainContainer extends Container {
500
500
  return import('./SettingsContainer.mjs').then(module => {
501
501
  me.items[1].add({
502
502
  module : module.default,
503
- collapsed : collapsed,
503
+ collapsed,
504
504
  removeInactiveCards: me.removeInactiveCards,
505
505
  style : {marginRight: !collapsed ? '0' : `-${me.settingsContainerWidth}px`},
506
506
  width : me.settingsContainerWidth,
@@ -554,10 +554,7 @@ class Component extends BaseComponent {
554
554
  date.setDate(date.getDate() + 1);
555
555
  }
556
556
 
557
- return {
558
- header: header,
559
- row : row
560
- }
557
+ return {header, row}
561
558
  }
562
559
 
563
560
  /**
@@ -626,16 +623,12 @@ class Component extends BaseComponent {
626
623
  let oldPath = data.oldPath,
627
624
  path = data.path;
628
625
 
629
- if (oldPath) {
630
- if (oldPath[0]?.cls.includes('neo-event')) {
631
- Neo.applyDeltas(this.appName, {id: oldPath[0].id, cls: {remove: ['neo-focus']}});
632
- }
626
+ if (oldPath?.[0]?.cls.includes('neo-event')) {
627
+ Neo.applyDeltas(this.appName, {id: oldPath[0].id, cls: {remove: ['neo-focus']}});
633
628
  }
634
629
 
635
- if (path) {
636
- if (path[0]?.cls.includes('neo-event')) {
637
- Neo.applyDeltas(this.appName, {id: path[0].id, cls: {add: ['neo-focus']}});
638
- }
630
+ if (path?.[0]?.cls.includes('neo-event')) {
631
+ Neo.applyDeltas(this.appName, {id: path[0].id, cls: {add: ['neo-focus']}});
639
632
  }
640
633
  }
641
634
 
@@ -668,9 +661,7 @@ class Component extends BaseComponent {
668
661
 
669
662
  week = me.createWeek(DateUtil.clone(date));
670
663
 
671
- if (week.header) {
672
- container.cn.push(week.header);
673
- }
664
+ week.header && container.cn.push(week.header);
674
665
 
675
666
  container.cn.push(week.row);
676
667
  }
@@ -702,9 +693,7 @@ class Component extends BaseComponent {
702
693
 
703
694
  container.cn.unshift(week.row);
704
695
 
705
- if (week.header) {
706
- container.cn.unshift(week.header);
707
- }
696
+ week.header && container.cn.unshift(week.header);
708
697
  }
709
698
 
710
699
  me.promiseVdomUpdate(me.vdom).then(() => {
@@ -723,9 +712,7 @@ class Component extends BaseComponent {
723
712
  me.vdom = vdom;
724
713
  }
725
714
 
726
- if (me.scrollTaskId) {
727
- clearTimeout(me.scrollTaskId);
728
- }
715
+ me.scrollTaskId && clearTimeout(me.scrollTaskId);
729
716
 
730
717
  me.scrollTaskId = setTimeout(me.onWheelEnd.bind(me), 300);
731
718
  }
@@ -576,15 +576,11 @@ class Gallery extends Component {
576
576
  * @param {Object} data
577
577
  */
578
578
  onMouseWheel(data) {
579
- let me = this,
580
- deltaX = data.deltaX,
581
- deltaY = data.deltaY,
582
- translateX = me.translateX,
583
- translateZ = me.translateZ;
579
+ let me = this;
584
580
 
585
581
  if (me.mouseWheelEnabled) {
586
- me._translateX = translateX - (deltaX * me.mouseWheelDeltaX); // silent update
587
- me._translateZ = translateZ + (deltaY * me.mouseWheelDeltaY); // silent update
582
+ me._translateX = me.translateX - (me.deltaX * me.mouseWheelDeltaX); // silent update
583
+ me._translateZ = me.translateZ + (me.deltaY * me.mouseWheelDeltaY); // silent update
588
584
 
589
585
  me.moveOrigin();
590
586
 
@@ -1,5 +1,4 @@
1
1
  import ClassSystemUtil from '../util/ClassSystem.mjs';
2
- import Collection from '../collection/Base.mjs'
3
2
  import Component from './Base.mjs';
4
3
  import HelixModel from '../selection/HelixModel.mjs';
5
4
  import Matrix from '../util/Matrix.mjs';
@@ -272,6 +271,19 @@ class Helix extends Component {
272
271
  me.domListeners = domListeners;
273
272
  }
274
273
 
274
+ /**
275
+ * Triggered after the followSelection config got changed
276
+ * @param {Boolean} value
277
+ * @param {Boolean} oldValue
278
+ * @protected
279
+ */
280
+ afterSetFollowSelection(value, oldValue) {
281
+ let cls = this.cls;
282
+
283
+ NeoArray[value ? 'add' : 'remove'](cls, 'neo-follow-selection');
284
+ this.cls = cls;
285
+ }
286
+
275
287
  /**
276
288
  * Triggered after the flipped config got changed
277
289
  * @param {Boolean} value
@@ -436,20 +448,9 @@ class Helix extends Component {
436
448
 
437
449
  oldValue?.destroy();
438
450
 
439
- // todo: remove the if check once all demos use stores (instead of collections)
440
- if (value) {
441
- return ClassSystemUtil.beforeSetInstance(value, Store, {
442
- listeners : {
443
- load : me.onStoreLoad,
444
- sort : me.onSort,
445
- scope: me
446
- }
447
- });
448
- }
449
-
450
- return Neo.create(Collection, {
451
- keyProperty: 'id',
451
+ return ClassSystemUtil.beforeSetInstance(value, Store, {
452
452
  listeners : {
453
+ load : me.onStoreLoad,
453
454
  sort : me.onSort,
454
455
  scope: me
455
456
  }
@@ -746,11 +747,8 @@ class Helix extends Component {
746
747
  Neo.currentWorker.promiseMessage('main', {
747
748
  action : 'readDom',
748
749
  appName : me.appName,
749
- vnodeId : me.id,
750
- attributes: [
751
- 'offsetHeight',
752
- 'offsetWidth'
753
- ]
750
+ attributes: ['offsetHeight', 'offsetWidth'],
751
+ vnodeId : me.id
754
752
  }).then(data => {
755
753
  me.offsetHeight = data.attributes.offsetHeight;
756
754
  me.offsetWidth = data.attributes.offsetWidth;
@@ -795,15 +793,7 @@ class Helix extends Component {
795
793
  */
796
794
  onConstructed() {
797
795
  super.onConstructed();
798
-
799
- let me = this;
800
-
801
- me.selectionModel?.register(me);
802
-
803
- // load data for the example collection
804
- if (!(me.store instanceof Store)) {
805
- me.loadData();
806
- }
796
+ this.selectionModel?.register(this);
807
797
  }
808
798
 
809
799
  /**
@@ -825,15 +815,11 @@ class Helix extends Component {
825
815
  * @param {Object} data
826
816
  */
827
817
  onMouseWheel(data) {
828
- let me = this,
829
- deltaX = data.deltaX,
830
- deltaY = data.deltaY,
831
- rotationAngle = me.rotationAngle,
832
- translateZ = me.translateZ;
818
+ let me = this;
833
819
 
834
820
  if (me.mouseWheelEnabled && me[lockWheel]) {
835
- me._rotationAngle = rotationAngle + (deltaX * me.mouseWheelDeltaX); // silent update
836
- me._translateZ = translateZ + (deltaY * me.mouseWheelDeltaY); // silent update
821
+ me._rotationAngle = me.rotationAngle + (data.deltaX * me.mouseWheelDeltaX); // silent update
822
+ me._translateZ = me.translateZ + (data.deltaY * me.mouseWheelDeltaY); // silent update
837
823
 
838
824
  me.refresh();
839
825
 
@@ -849,7 +835,7 @@ class Helix extends Component {
849
835
  onSelectionChange(value, oldValue) {
850
836
  let me = this;
851
837
 
852
- if (me.followSelection && value && value[0]) {
838
+ if (me.followSelection && value?.[0]) {
853
839
  me.applyItemTransitions(me.moveToSelectedItem, 100, value[0]);
854
840
  }
855
841
  }
@@ -860,7 +846,7 @@ class Helix extends Component {
860
846
  onSort() {
861
847
  let me = this;
862
848
 
863
- if (me[itemsMounted] === true) {console.log('sort');
849
+ if (me[itemsMounted] === true) {
864
850
  me.applyItemTransitions(me.sortItems, 1000);
865
851
  }
866
852
  }
@@ -915,9 +901,9 @@ class Helix extends Component {
915
901
  s = Math.sin(angle * Math.PI / 180);
916
902
  c = Math.cos(angle * Math.PI / 180);
917
903
 
918
- x = -300 + radius * s + translateX;
919
- y = -400 + angle * deltaY + translateY;
920
- z = 99800 + radius * c + translateZ;
904
+ x = -300 + radius * s + translateX;
905
+ y = -400 + angle * deltaY + translateY;
906
+ z = 99800 + radius * c + translateZ;
921
907
 
922
908
  matrix.items = [
923
909
  [c, 0, -s, 0],
@@ -0,0 +1,71 @@
1
+ import Base from '../../core/Base.mjs';
2
+ import RemoteMethodAccess from '../../worker/mixin/RemoteMethodAccess.mjs';
3
+ import WorkerManager from '../../worker/Manager.mjs';
4
+
5
+ /**
6
+ * Creates a ServiceWorker instance, in case Neo.config.useServiceWorker is set to true
7
+ * @class Neo.main.addon.ServiceWorker
8
+ * @extends Neo.core.Base
9
+ * @singleton
10
+ */
11
+ class ServiceWorker extends Base {
12
+ static getConfig() {return {
13
+ /**
14
+ * @member {String} className='Neo.main.addon.ServiceWorker'
15
+ * @protected
16
+ */
17
+ className: 'Neo.main.addon.ServiceWorker',
18
+ /**
19
+ * @member {String[]|Neo.core.Base[]|null} mixins=[RemoteMethodAccess]
20
+ */
21
+ mixins: [RemoteMethodAccess],
22
+ /**
23
+ * @member {ServiceWorkerRegistration|null} registration=null
24
+ * @protected
25
+ */
26
+ registration: null,
27
+ /**
28
+ * @member {Boolean} singleton=true
29
+ * @protected
30
+ */
31
+ singleton: true
32
+ }}
33
+
34
+ /**
35
+ * @param {Object} config
36
+ */
37
+ construct(config) {
38
+ if ('serviceWorker' in navigator) {
39
+ let me = this,
40
+ config = Neo.config,
41
+ devMode = config.environment === 'development',
42
+ fileName = devMode ? 'ServiceWorker.mjs' : 'serviceworker.js',
43
+ folder = window.location.pathname.includes('/examples/') ? 'examples/' : 'apps/',
44
+ opts = devMode ? {type: 'module'} : {},
45
+ serviceWorker = navigator.serviceWorker;
46
+
47
+ serviceWorker.register(config.basePath + folder + fileName, opts)
48
+ .then(registration => {
49
+ me.registration = registration;
50
+
51
+ serviceWorker.ready.then(() => {
52
+ serviceWorker.onmessage = WorkerManager.onWorkerMessage.bind(WorkerManager);
53
+
54
+ WorkerManager.sendMessage('service', {
55
+ action: 'registerNeoConfig',
56
+ data : config,
57
+ port : registration.active
58
+ });
59
+ });
60
+ })
61
+ }
62
+ }
63
+ }
64
+
65
+ Neo.applyClassConfig(ServiceWorker);
66
+
67
+ let instance = Neo.create(ServiceWorker);
68
+
69
+ Neo.applyToGlobalNs(instance);
70
+
71
+ export default instance;
@@ -72,7 +72,7 @@ class App extends Base {
72
72
  }
73
73
 
74
74
  /**
75
- * @param {JSON} data
75
+ * @param {Object} data
76
76
  */
77
77
  createThemeMap(data) {
78
78
  Neo.ns('Neo.cssMap.fileInfo', true);
@@ -146,21 +146,19 @@ class App extends Base {
146
146
 
147
147
  themeFolders = Neo.ns(mapClassName || className, false, cssMap.fileInfo);
148
148
 
149
- if (themeFolders) {
150
- if (!Neo.ns(`${lAppName}.${className}`, false, cssMap)) {
151
- classPath = className.split('.');
152
- fileName = classPath.pop();
153
- classPath = classPath.join('.');
154
- ns = Neo.ns(`${lAppName}.${classPath}`, true, cssMap);
149
+ if (themeFolders && !Neo.ns(`${lAppName}.${className}`, false, cssMap)) {
150
+ classPath = className.split('.');
151
+ fileName = classPath.pop();
152
+ classPath = classPath.join('.');
153
+ ns = Neo.ns(`${lAppName}.${classPath}`, true, cssMap);
155
154
 
156
- ns[fileName] = true;
155
+ ns[fileName] = true;
157
156
 
158
- Neo.main.addon.Stylesheet.addThemeFiles({
159
- appName,
160
- className: mapClassName || className,
161
- folders : themeFolders
162
- });
163
- }
157
+ Neo.main.addon.Stylesheet.addThemeFiles({
158
+ appName,
159
+ className: mapClassName || className,
160
+ folders : themeFolders
161
+ });
164
162
  }
165
163
  }
166
164
  }
@@ -236,9 +234,7 @@ class App extends Base {
236
234
  .then(response => response.json())
237
235
  .then(data => {this.createThemeMap(data)});
238
236
 
239
- if (!config.useVdomWorker) {
240
- import('../vdom/Helper.mjs');
241
- }
237
+ !config.useVdomWorker && import('../vdom/Helper.mjs');
242
238
  }
243
239
 
244
240
  /**
@@ -269,7 +265,7 @@ class App extends Base {
269
265
  this.onRegisterApp({ appName });
270
266
 
271
267
  this.sendMessage('main', {
272
- action:'registerAppName',
268
+ action: 'registerAppName',
273
269
  appName
274
270
  });
275
271
  }
@@ -218,6 +218,10 @@ class Manager extends Base {
218
218
  * @returns {Worker}
219
219
  */
220
220
  getWorker(name) {
221
+ if (name === 'service') {
222
+ return navigator.serviceWorker?.controller;
223
+ }
224
+
221
225
  return name instanceof Worker ? name : this.workers[name].worker;
222
226
  }
223
227
 
@@ -386,7 +390,12 @@ class Manager extends Base {
386
390
  message, worker;
387
391
 
388
392
  if (!me.stopCommunication) {
389
- worker = me.getWorker(dest);
393
+ if (opts.port) {
394
+ worker = opts.port;
395
+ delete opts.port;
396
+ } else {
397
+ worker = me.getWorker(dest);
398
+ }
390
399
 
391
400
  if (!worker) {
392
401
  throw new Error('Called sendMessage for a worker that does not exist: ' + dest);
@@ -0,0 +1,298 @@
1
+ import Base from '../core/Base.mjs';
2
+ import Message from './Message.mjs';
3
+ import RemoteMethodAccess from './mixin/RemoteMethodAccess.mjs';
4
+
5
+ /**
6
+ * @class Neo.worker.ServiceBase
7
+ * @extends Neo.core.Base
8
+ * @abstract
9
+ */
10
+ class ServiceBase extends Base {
11
+ /**
12
+ * @member {Object[]|null} channelPorts=null
13
+ * @protected
14
+ */
15
+ channelPorts = null
16
+ /**
17
+ * @member {Client|null} lastClient=null
18
+ * @protected
19
+ */
20
+ lastClient = null
21
+ /**
22
+ * @member {Object[]} promises=[]
23
+ * @protected
24
+ */
25
+ promises = []
26
+ /**
27
+ * @member {String[]} remotes=[]
28
+ * @protected
29
+ */
30
+ remotes = []
31
+
32
+ static getConfig() {return {
33
+ /**
34
+ * @member {String} className='Neo.worker.ServiceBase'
35
+ * @protected
36
+ */
37
+ className: 'Neo.worker.ServiceBase',
38
+ /**
39
+ * @member {String} cacheName='neo-runtime'
40
+ */
41
+ cacheName: 'neo-runtime',
42
+ /**
43
+ * @member {String[]|null} cachePaths
44
+ */
45
+ cachePaths: [
46
+ 'raw.githubusercontent.com/',
47
+ '/dist/production/',
48
+ '/fontawesome',
49
+ '/resources/'
50
+ ],
51
+ /**
52
+ * @member {String[]|Neo.core.Base[]|null} mixins=[RemoteMethodAccess]
53
+ */
54
+ mixins: [RemoteMethodAccess],
55
+ /**
56
+ * Remote method access for other workers
57
+ * @member {Object} remote={app: [//...]}
58
+ * @protected
59
+ */
60
+ remote: {
61
+ app: [
62
+ 'clearCache',
63
+ 'clearCaches'
64
+ ]
65
+ },
66
+ /**
67
+ * @member {String|null} workerId=null
68
+ * @protected
69
+ */
70
+ workerId: null
71
+ }}
72
+
73
+ /**
74
+ * @param {Object} config
75
+ */
76
+ construct(config) {
77
+ super.construct(config);
78
+
79
+ let me = this,
80
+ bind = name => me[name].bind(me);
81
+
82
+ me.channelPorts = [];
83
+
84
+ Object.assign(globalThis, {
85
+ onactivate: bind('onActivate'),
86
+ onfetch : bind('onFetch'),
87
+ oninstall : bind('onInstall'),
88
+ onmessage : bind('onMessage')
89
+ });
90
+
91
+ Neo.currentWorker = me;
92
+ Neo.workerId = me.workerId;
93
+ }
94
+
95
+ /**
96
+ * @param {String} name=this.cacheName
97
+ */
98
+ clearCache(name=this.cacheName) {
99
+ caches.keys()
100
+ .then(cacheNames => cacheNames.filter(cacheName => cacheName === name))
101
+ .then(cachesToDelete => Promise.all(cachesToDelete.map(cacheToDelete => caches.delete(cacheToDelete))))
102
+ }
103
+
104
+ /**
105
+ *
106
+ */
107
+ clearCaches() {
108
+ caches.keys()
109
+ .then(cachesToDelete => Promise.all(cachesToDelete.map(cacheToDelete => caches.delete(cacheToDelete))))
110
+ }
111
+
112
+ /**
113
+ * @param {Client} client
114
+ */
115
+ createMessageChannel(client) {
116
+ let me = this,
117
+ channel = new MessageChannel(),
118
+ port = channel.port2;
119
+
120
+ channel.port1.onmessage = me.onMessage.bind(me);
121
+
122
+ me.sendMessage('app', {action: 'registerPort', transfer: port}, [port]);
123
+
124
+ me.channelPorts.push({
125
+ clientId : client.id,
126
+ destination: 'app',
127
+ port : channel.port1
128
+ });
129
+ }
130
+
131
+ /**
132
+ *
133
+ * @param {String} destination
134
+ * @param {String} clientId=this.lastClient.id
135
+ * @returns {MessagePort|null}
136
+ */
137
+ getPort(destination, clientId=this.lastClient?.id) {
138
+ for (let port of this.channelPorts) {
139
+ if (clientId === port.clientId && destination === port.destination) {
140
+ return port.port;
141
+ }
142
+ }
143
+
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Ignore the call in case there is no connected client in place yet
149
+ */
150
+ initRemote() {
151
+ let me = this,
152
+ lastClientId = me.lastClient?.id;
153
+
154
+ if (lastClientId && !me.remotes.includes(lastClientId)) {
155
+ me.remotes.push(lastClientId);
156
+ super.initRemote();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * @param {ExtendableMessageEvent} event
162
+ */
163
+ onActivate(event) {
164
+ console.log('onActivate', event);
165
+ }
166
+
167
+ /**
168
+ * @param {Client} source
169
+ */
170
+ onConnect(source) {
171
+ console.log('onConnect', source);
172
+
173
+ this.createMessageChannel(source);
174
+ this.initRemote();
175
+ }
176
+
177
+ /**
178
+ * @param {ExtendableMessageEvent} event
179
+ */
180
+ onFetch(event) {
181
+ let hasMatch = false,
182
+ request = event.request,
183
+ key;
184
+
185
+ for (key of this.cachePaths) {
186
+ if (request.url.includes(key)) {
187
+ hasMatch = true;
188
+ break;
189
+ }
190
+ }
191
+
192
+ hasMatch && event.respondWith(
193
+ caches.match(request)
194
+ .then(cachedResponse => cachedResponse || caches.open(this.cacheName)
195
+ .then(cache => fetch(request)
196
+ .then(response => cache.put(request, response.clone())
197
+ .then(() => response)
198
+ )))
199
+ );
200
+ }
201
+
202
+ /**
203
+ * @param {ExtendableMessageEvent} event
204
+ */
205
+ onInstall(event) {
206
+ console.log('onInstall', event);
207
+ globalThis.skipWaiting();
208
+ }
209
+
210
+ /**
211
+ * For a client based message we receive an ExtendableMessageEvent,
212
+ * for a MessageChannel based message a MessageEvent
213
+ * @param {ExtendableMessageEvent|MessageEvent} event
214
+ */
215
+ onMessage(event) {
216
+ let me = this,
217
+ data = event.data,
218
+ action = data.action,
219
+ replyId = data.replyId,
220
+ promise;
221
+
222
+ if (event.source) { // ExtendableMessageEvent
223
+ me.lastClient = event.source;
224
+ }
225
+
226
+ if (!action) {
227
+ throw new Error('Message action is missing: ' + data.id);
228
+ }
229
+
230
+ if (action !== 'reply') {
231
+ me['on' + Neo.capitalize(action)](data, event);
232
+ } else if (promise = action === 'reply' && me.promises[replyId]) {
233
+ promise[data.reject ? 'reject' : 'resolve'](data.data);
234
+ delete me.promises[replyId];
235
+ }
236
+ }
237
+
238
+ /**
239
+ * @param {Object} msg
240
+ * @param {ExtendableMessageEvent} event
241
+ */
242
+ onPing(msg, event) {
243
+ this.resolve(msg, {originMsg: msg});
244
+ }
245
+
246
+ /**
247
+ * @param {Object} msg
248
+ * @param {ExtendableMessageEvent} event
249
+ */
250
+ onRegisterNeoConfig(msg, event) {
251
+ Neo.config = Neo.config || {};
252
+ Object.assign(Neo.config, msg.data);
253
+
254
+ this.onConnect(event.source);
255
+ }
256
+
257
+ /**
258
+ * @param {String} dest app, data, main or vdom (excluding the current worker)
259
+ * @param {Object} opts configs for Neo.worker.Message
260
+ * @param {Array} [transfer] An optional array of Transferable objects to transfer ownership of.
261
+ * If the ownership of an object is transferred, it becomes unusable (neutered) in the context it was sent from
262
+ * and becomes available only to the worker it was sent to.
263
+ * @returns {Promise<any>}
264
+ */
265
+ promiseMessage(dest, opts, transfer) {
266
+ let me = this;
267
+
268
+ return new Promise(function(resolve, reject) {
269
+ let message = me.sendMessage(dest, opts, transfer),
270
+ msgId = message.id;
271
+
272
+ me.promises[msgId] = {reject, resolve};
273
+ });
274
+ }
275
+
276
+ /**
277
+ * @param {String} dest app, data, main or vdom (excluding the current worker)
278
+ * @param {Object} opts configs for Neo.worker.Message
279
+ * @param {Array} [transfer] An optional array of Transferable objects to transfer ownership of.
280
+ * If the ownership of an object is transferred, it becomes unusable (neutered) in the context it was sent from
281
+ * and becomes available only to the worker it was sent to.
282
+ * @returns {Neo.worker.Message}
283
+ * @protected
284
+ */
285
+ sendMessage(dest, opts, transfer) {
286
+ opts.destination = dest;
287
+
288
+ let message = new Message(opts),
289
+ port = this.getPort(dest) || this.lastClient;
290
+
291
+ port.postMessage(message, transfer);
292
+ return message;
293
+ }
294
+ }
295
+
296
+ Neo.applyClassConfig(ServiceBase);
297
+
298
+ export default ServiceBase;
@@ -56,7 +56,7 @@ class RemoteMethodAccess extends Base {
56
56
  methods = remote.methods,
57
57
  pkg = Neo.ns(className, true);
58
58
 
59
- methods.forEach(function(method) {
59
+ methods.forEach(method => {
60
60
  if (remote.origin !== 'main' && pkg[method]) {
61
61
  throw new Error('Duplicate remote method definition ' + className + '.' + method);
62
62
  }