neo.mjs 6.35.0 → 6.37.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 (36) hide show
  1. package/README.md +15 -1
  2. package/apps/ServiceWorker.mjs +2 -2
  3. package/apps/colors/view/ViewportController.mjs +27 -17
  4. package/apps/portal/view/home/FeatureSection.mjs +209 -0
  5. package/apps/portal/view/home/parts/Colors.mjs +34 -84
  6. package/apps/portal/view/home/parts/Features.mjs +1 -1
  7. package/apps/portal/view/home/parts/Helix.mjs +36 -95
  8. package/apps/portal/view/home/parts/How.mjs +31 -48
  9. package/apps/portal/view/learn/ContentView.mjs +53 -20
  10. package/examples/ServiceWorker.mjs +2 -2
  11. package/examples/videoMove/MainContainer.mjs +3 -4
  12. package/examples/videoMove/neo-config.json +2 -1
  13. package/package.json +2 -2
  14. package/resources/data/deck/learnneo/pages/benefits/Multi-Threading.md +221 -0
  15. package/resources/data/deck/learnneo/tree.json +12 -13
  16. package/resources/scss/src/apps/portal/Viewport.scss +19 -0
  17. package/resources/scss/src/apps/portal/home/ContentBox.scss +9 -2
  18. package/resources/scss/src/apps/portal/home/FeatureSection.scss +68 -0
  19. package/resources/scss/src/apps/portal/home/parts/Colors.scss +1 -5
  20. package/resources/scss/src/apps/portal/home/parts/Helix.scss +1 -7
  21. package/resources/scss/src/apps/portal/home/parts/How.scss +0 -22
  22. package/resources/scss/src/apps/portal/learn/ContentView.scss +22 -10
  23. package/resources/scss/src/code/LivePreview.scss +1 -0
  24. package/resources/scss/src/examples/videoMove/MainContainer.scss +31 -0
  25. package/resources/scss/theme-neo-light/design-tokens/Core.scss +1 -0
  26. package/src/DefaultConfig.mjs +2 -2
  27. package/src/Neo.mjs +5 -1
  28. package/src/code/LivePreview.mjs +16 -19
  29. package/src/component/Toast.mjs +8 -8
  30. package/src/component/Video.mjs +22 -28
  31. package/src/component/wrapper/AmChart.mjs +15 -8
  32. package/src/main/addon/AmCharts.mjs +1 -1
  33. package/src/manager/DomEvent.mjs +1 -1
  34. package/src/worker/App.mjs +1 -1
  35. package/src/worker/mixin/RemoteMethodAccess.mjs +1 -3
  36. package/resources/data/deck/learnneo/pages/WhyNeo-Multi-Threaded.md +0 -15
@@ -0,0 +1,68 @@
1
+ .portal-home-feature-section {
2
+ align-items : stretch;
3
+ display : flex;
4
+ flex-wrap : nowrap;
5
+ justify-content : center;
6
+ min-height : 100%;
7
+ scroll-snap-align: center;
8
+
9
+ @media (max-width: 840px) {
10
+ flex-direction: column;
11
+ }
12
+
13
+ &.portal-position-end {
14
+ flex-direction: row-reverse;
15
+ }
16
+
17
+ .neo-content {
18
+ font-size: min(max(2.3vw, 16px), 30px);
19
+ }
20
+
21
+ .neo-h1 {
22
+ font-size : min(max(5.5vw, 30px), 64px);
23
+ line-height: 1em;
24
+ margin : 0;
25
+ text-align : center;
26
+ }
27
+
28
+ .neo-h2 {
29
+ font-size : min(max(3.5vw, 24px), 44px);
30
+ font-weight: 600;
31
+ line-height: 1em;
32
+ margin : 0;
33
+ text-align : center;
34
+ }
35
+
36
+ .page-live-preview {
37
+ height: 100%;
38
+ margin: 0;
39
+ }
40
+
41
+ .portal-content-text {
42
+ align-items : center;
43
+ display : flex;
44
+ flex : 1;
45
+ flex-direction : column;
46
+ flex-wrap : nowrap;
47
+ justify-content: center;
48
+ padding : 2rem;
49
+
50
+ @media (max-width: 600px) {
51
+ flex : .5 !important;
52
+ justify-content: start;
53
+ padding : 1rem;
54
+ }
55
+ }
56
+
57
+ .portal-content-wrapper {
58
+ background-color: lightgray;
59
+ flex : 2;
60
+ padding : 20px;
61
+
62
+ @media (max-width: 600px) {
63
+ max-height: 35em;
64
+ min-height: 35em;
65
+ padding : 5px;
66
+ }
67
+ }
68
+ }
@@ -1,7 +1,3 @@
1
1
  .portal-home-parts-colors {
2
- @media (max-width: 840px) {
3
- &.neo-flex-container {
4
- flex-direction: column;
5
- }
6
- }
2
+
7
3
  }
@@ -1,13 +1,7 @@
1
1
  .portal-home-parts-helix {
2
- @media (max-width: 840px) {
3
- &.neo-flex-container {
4
- flex-direction: column;
5
- }
6
- }
7
-
8
2
  @media (max-width: 600px) {
9
3
  .portal-content-text {
10
- min-height: 40em;
4
+ min-height: 23em;
11
5
  }
12
6
  }
13
7
  }
@@ -1,25 +1,7 @@
1
1
  .portal-home-parts-how {
2
- .neo-worker-setup {
3
- align-items : center;
4
- display : flex;
5
- height : 100%;
6
- justify-content: center;
7
- width : 100%;
8
-
9
- --fill-opacity : 0.05;
10
- --stroke-opacity: 0.05;
11
-
12
- &:hover {
13
- --fill-opacity : 1;
14
- --stroke-opacity: 1;
15
- }
16
- }
17
-
18
2
  .portal-content-container {
19
- background-color: #17141c;
20
3
  border : 1px solid #e6e6e6;
21
4
  border-radius : 8px;
22
- padding : 20px;
23
5
  }
24
6
 
25
7
  @media (max-width: 500px) {
@@ -33,10 +15,6 @@
33
15
  @media (max-width: 840px) {
34
16
  min-height: 100%;
35
17
 
36
- &.neo-flex-container {
37
- flex-direction: column;
38
- }
39
-
40
18
  .portal-content-text {
41
19
  flex: 0.8 !important;
42
20
  }
@@ -12,15 +12,16 @@
12
12
  padding: 1rem 1rem 0;
13
13
  }
14
14
 
15
- pre[data-javascript] {
16
- border : thin solid lightgray;
17
- border-radius: 4px;
18
- overflow-x : scroll;
19
- padding : 12px;
15
+ a {
16
+ color: #3E63DD;;
20
17
  }
21
18
 
22
- p {
23
- margin: 0.5em 0 0.7em 0;
19
+ blockquote {
20
+ background-color: var(--gray-100);
21
+ border-left : 4px solid var(--sem-color-surface-primary-background);
22
+ font-style : italic;
23
+ margin-left : 0;
24
+ padding : 5px 5px 5px 15px;
24
25
  }
25
26
 
26
27
  details {
@@ -72,11 +73,22 @@
72
73
  padding : 2px 16px;
73
74
  font-size : 1em;
74
75
  margin-bottom: 1em;
76
+
77
+ &:hover {
78
+ /* On mouse-over, add a deeper shadow */
79
+ box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
80
+ }
75
81
  }
76
82
 
77
- /* On mouse-over, add a deeper shadow */
78
- .lab:hover {
79
- box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
83
+ p {
84
+ margin: 0.5em 0 0.7em 0;
85
+ }
86
+
87
+ pre[data-javascript] {
88
+ border : thin solid lightgray;
89
+ border-radius: 4px;
90
+ overflow-x : scroll;
91
+ padding : 12px;
80
92
  }
81
93
  }
82
94
 
@@ -1,4 +1,5 @@
1
1
  .neo-code-live-preview {
2
+ height : 400px;
2
3
  margin-bottom : 2em;
3
4
  transition-duration : .3s;
4
5
  transition-property : height, left, top, width;
@@ -0,0 +1,31 @@
1
+ .examples-videomove-maincontainer.neo-viewport {
2
+ padding: 50px;
3
+
4
+ .video-wrapper {
5
+ .neo-container {
6
+ background-color: rgb(139,166,255);
7
+ padding : 50px;
8
+
9
+ &:last-child {
10
+ margin-left: 50px;
11
+ }
12
+ }
13
+ }
14
+
15
+ @media (max-width: 840px) {
16
+ padding: 1em;
17
+
18
+ .video-wrapper {
19
+ flex-direction: column;
20
+
21
+ .neo-container {
22
+ padding: 1em;
23
+
24
+ &:last-child {
25
+ margin-left: 0;
26
+ margin-top : 1em;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
@@ -9,6 +9,7 @@
9
9
  --core-fontsize-body : 16px;
10
10
  --core-fontsize-h1 : 2.5rem;
11
11
  --core-fontsize-h2 : 1.75rem;
12
+ --core-fontsize-h3 : 1.25rem;
12
13
  --core-fontsize-label : 1rem;
13
14
  --core-fontweight-bold : 700;
14
15
  --core-fontweight-medium : 500;
@@ -260,12 +260,12 @@ const DefaultConfig = {
260
260
  useVdomWorker: true,
261
261
  /**
262
262
  * buildScripts/injectPackageVersion.mjs will update this value
263
- * @default '6.35.0'
263
+ * @default '6.37.0'
264
264
  * @memberOf! module:Neo
265
265
  * @name config.version
266
266
  * @type String
267
267
  */
268
- version: '6.35.0'
268
+ version: '6.37.0'
269
269
  };
270
270
 
271
271
  Object.assign(DefaultConfig, {
package/src/Neo.mjs CHANGED
@@ -633,7 +633,7 @@ function autoGenerateGetSet(proto, key) {
633
633
  }
634
634
 
635
635
  if (!Neo[getSetCache]) {
636
- Neo[getSetCache] = {};
636
+ Neo[getSetCache] = {}
637
637
  }
638
638
 
639
639
  if (!Neo[getSetCache][key]) {
@@ -667,6 +667,10 @@ function autoGenerateGetSet(proto, key) {
667
667
  },
668
668
 
669
669
  set(value) {
670
+ if (value === undefined) {
671
+ return
672
+ }
673
+
670
674
  let me = this,
671
675
  _key = '_' + key,
672
676
  uKey = key[0].toUpperCase() + key.slice(1),
@@ -48,10 +48,6 @@ class LivePreview extends Container {
48
48
  * @member {Boolean} enableFullscreen=true
49
49
  */
50
50
  enableFullscreen: true,
51
- /**
52
- * @member {Number} height=400
53
- */
54
- height: 400,
55
51
  /**
56
52
  * @member {Object|String} layout='fit'
57
53
  */
@@ -226,11 +222,14 @@ class LivePreview extends Container {
226
222
  }
227
223
 
228
224
  let me = this,
225
+ container = me.getPreviewContainer(),
229
226
  source = me.editorValue || me.value,
230
227
  cleanLines = [],
231
- importModuleNames = [],
232
228
  moduleNameAndPath = [],
233
- className = me.findLastClassName(source);
229
+ className = me.findLastClassName(source),
230
+ params = [],
231
+ vars = [],
232
+ codeString, promises;
234
233
 
235
234
  source.split('\n').forEach(line => {
236
235
  let importMatch = line.match(importRegex);
@@ -239,18 +238,14 @@ class LivePreview extends Container {
239
238
  let moduleName = importMatch[1],
240
239
  path = importMatch[2];
241
240
 
242
- moduleNameAndPath.push({moduleName, path});
243
-
244
- importModuleNames.push(moduleName);
241
+ moduleNameAndPath.push({moduleName, path})
245
242
  } else if (line.match(exportRegex)) {
246
243
  // Skip export statements
247
244
  } else {
248
- cleanLines.push(` ${line}`);
245
+ cleanLines.push(` ${line}`)
249
246
  }
250
247
  });
251
248
 
252
- var params = [];
253
- var vars = [];
254
249
  // Figure out the parts of the source we'll be running.
255
250
  // o The promises/import() corresponding to the user's import statements
256
251
  // o The vars holding the name of the imported module based on the module name for each import
@@ -266,14 +261,13 @@ class LivePreview extends Container {
266
261
  // });
267
262
  // Making the promise part of the eval seems weird, but it made it easier to
268
263
  // set up the import vars.
269
-
270
- let promises = moduleNameAndPath.map(item => {
264
+ promises = moduleNameAndPath.map(item => {
271
265
  params.push(`${item.moduleName}Module`);
272
266
  vars.push(` const ${item.moduleName} = ${item.moduleName}Module.default;`);
273
267
  return `import('${item.path}')`
274
268
  });
275
269
 
276
- const codeString = [
270
+ codeString = [
277
271
  'Promise.all([',
278
272
  ` ${promises.join(',\n')}`,
279
273
  `]).then(([${params.join(', ')}]) => {`,
@@ -287,7 +281,6 @@ class LivePreview extends Container {
287
281
  '.catch(error => container.add({ntype:\'component\', html:error.message}));'
288
282
  ].join('\n')
289
283
 
290
- const container = me.getPreviewContainer();
291
284
  container.removeAll();
292
285
 
293
286
  try {
@@ -338,14 +331,18 @@ class LivePreview extends Container {
338
331
  * @param {Number} data.value
339
332
  */
340
333
  onActiveIndexChange(data) {
341
- let me = this,
342
- hidden = data.value !== 1;
334
+ let me = this,
335
+ isPreview = data.value === 1;
343
336
 
344
337
  if (data.item.reference === 'preview') {
345
338
  me.doRunSource()
346
339
  }
340
+ // Navigating to the source view should destroy the app, in case the preview view is not popped out
341
+ else if (!isPreview && !me.previewContainer) {
342
+ me.getReference('preview').removeAll()
343
+ }
347
344
 
348
- me.getReference('popout-window-button').hidden = hidden
345
+ me.getReference('popout-window-button').hidden = !isPreview
349
346
  me.disableRunSource = false;
350
347
  }
351
348
 
@@ -114,17 +114,17 @@ class Toast extends Base {
114
114
  }]}
115
115
  }
116
116
 
117
+ /**
118
+ * Timeout in ms after which the toast is removed
119
+ * @member {Number} removeDelay=3000
120
+ */
121
+ removeDelay = 3000
117
122
  /**
118
123
  * Used by the ToastManager
119
124
  * @member {Boolean} running=false
120
125
  * @private
121
126
  */
122
127
  running = false
123
- /**
124
- * Timeout in ms after which the toast is removed
125
- * @member {Number} timeout=3000
126
- */
127
- timeout = 3000 // todo: conflicting class field name => timeout()
128
128
 
129
129
  /**
130
130
  * @param {Object} config
@@ -206,9 +206,9 @@ class Toast extends Base {
206
206
  let me = this;
207
207
 
208
208
  if (!me.closable && value) {
209
- setTimeout(() => {
210
- this.destroy(true)
211
- }, me.timeout)
209
+ me.timeout(me.removeDelay).then(() => {
210
+ me.destroy(true)
211
+ })
212
212
  }
213
213
  }
214
214
 
@@ -38,8 +38,7 @@ class Video extends BaseComponent {
38
38
  */
39
39
  autoplay: false,
40
40
  /**
41
- * In case the browser does not support the video source
42
- * the component should show an error.
41
+ * In case the browser does not support the video source the component should show an error.
43
42
  * @member {Boolean} errorMsg='The browser does not support the video'
44
43
  */
45
44
  errorMsg: 'Your browser does not support the video tag.',
@@ -90,25 +89,7 @@ class Video extends BaseComponent {
90
89
  let me = this;
91
90
 
92
91
  me.handleAutoplay();
93
- me.addDomListeners(
94
- {click: me.play, delegate: '.neo-video-ghost'},
95
- {click: me.pause, delegate: '.neo-video-media'}
96
- )
97
- }
98
-
99
- /**
100
- * beforeSetPlaying autgen by playing_
101
- *
102
- * @param {Boolean} value
103
- * @param {Boolean} oldValue
104
- * @returns {Boolean}
105
- */
106
- beforeSetPlaying(value, oldValue) {
107
- if (!Neo.isBoolean(value)) {
108
- return oldValue
109
- }
110
-
111
- return value
92
+ me.addDomListeners({click: me.play, delegate: '.neo-video-ghost'});
112
93
  }
113
94
 
114
95
  /**
@@ -137,23 +118,37 @@ class Video extends BaseComponent {
137
118
  afterSetUrl(value, oldValue) {
138
119
  if (!value) return;
139
120
 
140
- let {vdom} = this,
141
- media = VDomUtil.getFlags(vdom, 'media')[0];
121
+ let {vdom} = this,
122
+ media = VDomUtil.getFlags(vdom, 'media')[0],
123
+ ua = navigator.userAgent || navigator.vendor || window.opera,
124
+ isOperaMini = /Opera Mini/i.test(ua);
142
125
 
143
126
  media.cn = [{
144
127
  tag: 'source',
145
128
  src: value,
146
129
  type: this.type
147
- }, {
148
- tag: 'span',
149
- html: this.errorMsg,
150
130
  }];
151
131
 
132
+ // Opera Mini might not support the video-source => check the user agent string
133
+ if (isOperaMini) {
134
+ media.cn.push({
135
+ tag: 'span',
136
+ html: this.errorMsg,
137
+ });
138
+ }
139
+
152
140
  this.update()
153
141
  }
154
142
 
155
143
  /**
156
144
  * autoplay - run the event listeners
145
+ * Only called in constructor and sets playing => calls update already
146
+ *
147
+ * Rationale: update() sends the vdom & vnode on a workers roundtrip to get the deltas.
148
+ * While this is happening, the component locks itself for future updates until the new vnode got back (async).
149
+ * After the delay the framework would trigger a 2nd roundtrip to get the deltas for the visible node.
150
+ *
151
+ * @protected
157
152
  */
158
153
  handleAutoplay() {
159
154
  if (!this.autoplay) return;
@@ -166,12 +161,11 @@ class Video extends BaseComponent {
166
161
  // Allows inline playback on iOS devices
167
162
  media.playsInline = true;
168
163
 
169
- this.update();
170
164
  this.playing = true;
171
165
  }
172
166
 
173
167
  /**
174
- * Clicked media
168
+ * Simulates `Clicked media` programmatically
175
169
  */
176
170
  pause() {
177
171
  this.playing = false
@@ -89,24 +89,23 @@ class AmChart extends Component {
89
89
  * @protected
90
90
  */
91
91
  afterSetMounted(value, oldValue) {
92
- let me = this,
93
- {appName, id, windowId} = me;
92
+ let me = this,
93
+ {appName, id, windowId} = me,
94
+ opts = {appName, id, windowId};
94
95
 
95
96
  if (value === false && oldValue !== undefined) {
96
- Neo.main.addon.AmCharts.destroy({appName, id, windowId})
97
+ Neo.main.addon.AmCharts.destroy(opts)
97
98
  }
98
99
 
99
100
  super.afterSetMounted(value, oldValue);
100
101
 
101
102
  if (value) {
102
- let opts = {
103
- appName,
103
+ opts = {
104
+ ...opts,
104
105
  combineSeriesTooltip: me.combineSeriesTooltip,
105
106
  config : me.chartConfig,
106
- id,
107
107
  package : me.package,
108
- type : me.chartType,
109
- windowId
108
+ type : me.chartType
110
109
  };
111
110
 
112
111
  if (me.chartData) {
@@ -136,6 +135,14 @@ class AmChart extends Component {
136
135
  return value
137
136
  }
138
137
 
138
+ destroy(...args) {
139
+ let {appName, id, windowId} = this;
140
+
141
+ Neo.main.addon.AmCharts.destroy({appName, id, windowId})
142
+
143
+ super.destroy(...args)
144
+ }
145
+
139
146
  /**
140
147
  *
141
148
  */
@@ -181,7 +181,7 @@ class AmCharts extends Base {
181
181
  * @param {String} data.id
182
182
  */
183
183
  destroy(data) {
184
- this.charts[data.id].dispose();
184
+ this.charts[data.id]?.dispose?.();
185
185
  delete this.charts[data.id]
186
186
  }
187
187
 
@@ -427,7 +427,7 @@ class DomEvent extends Base {
427
427
  })
428
428
  });
429
429
 
430
- if (component.mounted) {
430
+ if (component.mounted && domListeners?.length > 0) {
431
431
  me.timeout(100).then(() => {
432
432
  me.mountDomListeners(component)
433
433
  })
@@ -384,7 +384,7 @@ class App extends Base {
384
384
  * @param {Object} data
385
385
  * @param {Number} data.angle
386
386
  * @param {String} data.layout landscape|portrait
387
- * @param {Number} data.type landscape-primary|landscape-secondary|portrait-primary|portrait-secondary
387
+ * @param {String} data.type landscape-primary|landscape-secondary|portrait-primary|portrait-secondary
388
388
  */
389
389
  onOrientationChange(data) {
390
390
  Object.values(Neo.apps).forEach(app => {
@@ -67,9 +67,7 @@ class RemoteMethodAccess extends Base {
67
67
  throw new Error('Duplicate remote method definition ' + className + '.' + method)
68
68
  }
69
69
 
70
- if (!pkg[method] ) {
71
- pkg[method] = me.generateRemote(remote, method)
72
- }
70
+ pkg[method] ??= me.generateRemote(remote, method)
73
71
  })
74
72
  }
75
73
  }
@@ -1,15 +0,0 @@
1
- When a Neo.mjs application starts, the framework spawns three web-workers, in addition
2
- to the main browser thread, resulting in:
3
-
4
- 1. The <b>main</b> browser thread, where DOM updates are applied
5
- 2. An <b>application</b> web-worker where normal application logic is run
6
- 3. A <b>data</b> web-worker were HTTP and socket calls are run
7
- 4. A <b>view</b> web-worker that manages delta updates
8
-
9
- <img src="https://s3.amazonaws.com/mjs.neo.learning.images/why/IndexHtmlFlow.png" width="120%"></img>
10
-
11
- The benefits of using web workers is that each runs in parallel its own thread. In a typical framework
12
- all code is run in the main thread, so processes compete for CPU cycles.
13
-
14
- Neo.mjs also allows you to easily spawn additional threads in order to have processor-intensive
15
- tasks to be run separately.