littlejsengine 1.13.4 → 1.14.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.
Files changed (53) hide show
  1. package/README.md +2 -1
  2. package/copyToGithub.bat +28 -0
  3. package/dist/littlejs.d.ts +346 -240
  4. package/dist/littlejs.esm.js +1370 -723
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +1350 -709
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +1333 -692
  9. package/examples/box2d/game.js +1 -1
  10. package/examples/box2d/gameObjects.js +1 -1
  11. package/examples/breakout/game.js +30 -25
  12. package/examples/electron/index.html +2 -2
  13. package/examples/index.html +207 -96
  14. package/examples/platformer/gameEffects.js +19 -18
  15. package/examples/platformer/gameLevel.js +6 -1
  16. package/examples/shorts/base.html +1 -1
  17. package/examples/shorts/flappyGame.js +2 -1
  18. package/examples/shorts/helloWorld.js +1 -1
  19. package/examples/shorts/music.js +106 -0
  20. package/examples/shorts/parallax.js +71 -0
  21. package/examples/shorts/piano.js +7 -8
  22. package/examples/shorts/postProcess.js +25 -8
  23. package/examples/shorts/shapes.js +1 -9
  24. package/examples/shorts/song.mp3 +0 -0
  25. package/examples/shorts/uiSystem.js +15 -8
  26. package/examples/starter/index.html +2 -2
  27. package/examples/stress/index.html +8 -3
  28. package/examples/style.css +14 -4
  29. package/examples/uiSystem/game.js +11 -11
  30. package/package.json +1 -1
  31. package/plugins/box2d.js +10 -8
  32. package/plugins/drawUtilities.js +5 -5
  33. package/plugins/newgrounds.js +6 -6
  34. package/plugins/pluginExport.js +1 -1
  35. package/plugins/uiSystem.js +103 -79
  36. package/plugins/zzfxm.js +10 -10
  37. package/reference.md +90 -54
  38. package/src/engine.js +22 -22
  39. package/src/engineAudio.js +284 -109
  40. package/src/engineBuild.js +9 -9
  41. package/src/engineDebug.js +26 -26
  42. package/src/engineDraw.js +148 -97
  43. package/src/engineExport.js +22 -16
  44. package/src/engineInput.js +57 -67
  45. package/src/engineMedals.js +25 -25
  46. package/src/engineObject.js +25 -22
  47. package/src/engineParticles.js +59 -59
  48. package/src/engineRelease.js +1 -1
  49. package/src/engineSettings.js +20 -13
  50. package/src/engineTileLayer.js +34 -34
  51. package/src/engineUtilities.js +62 -55
  52. package/src/engineWebGL.js +447 -65
  53. /package/examples/shorts/{playSound.js → sound.js} +0 -0
@@ -136,7 +136,7 @@ function gameUpdatePost()
136
136
  ///////////////////////////////////////////////////////////////////////////////
137
137
  function gameRender()
138
138
  {
139
- // draw a grey square in the background without using webgl
139
+ // draw a grey square in the background without using WebGL
140
140
  LJS.drawRect(vec2(20,8), vec2(100), hsl(0,0,.8), 0, false);
141
141
 
142
142
  if (Scenes.scene == 5)
@@ -20,7 +20,7 @@ const restitution = .2;
20
20
 
21
21
  export function spawnBox(pos, size=1, color=LJS.WHITE, type=LJS.box2d.bodyTypeDynamic, applyTexture=true, angle=0)
22
22
  {
23
- size = typeof size == 'number' ? vec2(size) : size; // square
23
+ size = typeof size === 'number' ? vec2(size) : size; // square
24
24
  const o = new LJS.Box2dObject(pos, size, applyTexture && Game.spriteAtlas.squareOutline, angle, color, type);
25
25
  o.drawSize = size.scale(1.02); // slightly enlarge to cover gaps
26
26
  o.addBox(size, vec2(), 0, density, friction, restitution);
@@ -122,38 +122,43 @@ function setupPostProcess()
122
122
  p=fract(p*.3197);
123
123
  return fract(1.+sin(51.*p.x+73.*p.y)*13753.3);
124
124
  }
125
- float noise(vec2 p)
126
- {
127
- vec2 i=floor(p),f=fract(p),u=f*f*(3.-2.*f);
128
- return mix(mix(hash(i),hash(i+vec2(1,0)),u.x),mix(hash(i+vec2(0,1)),hash(i+1.),u.x),u.y);
129
- }
125
+
130
126
  void mainImage(out vec4 c, vec2 p)
131
127
  {
132
- // put uv in texture pixel space
128
+ // setup the shader
129
+ vec2 uv = p;
133
130
  p /= iResolution.xy;
134
-
135
- // apply fuzz as horizontal offset
136
- const float fuzz = .0005;
137
- const float fuzzScale = 800.;
138
- const float fuzzSpeed = 9.;
139
- p.x += fuzz*(noise(vec2(p.y*fuzzScale, iTime*fuzzSpeed))*2.-1.);
140
-
141
- // init output color
142
131
  c = texture(iChannel0, p);
143
132
 
144
- // chromatic aberration
145
- const float chromatic = .002;
146
- c.r = texture(iChannel0, p - vec2(chromatic,0)).r;
147
- c.b = texture(iChannel0, p + vec2(chromatic,0)).b;
148
-
149
- // tv static noise
150
- const float staticNoise = .1;
151
- c += staticNoise * hash(p + mod(iTime, 1e3));
133
+ // static noise
134
+ const float staticAlpha = .1;
135
+ const float staticScale = .002;
136
+ c += staticAlpha * hash(floor(p/staticScale) + mod(iTime*500., 1e3));
152
137
 
153
138
  // scan lines
154
- const float scanlineScale = 1e3;
155
- const float scanlineAlpha = .1;
156
- c *= 1. + scanlineAlpha*sin(p.y*scanlineScale);
139
+ const float scanlineScale = 2.;
140
+ const float scanlineAlpha = .3;
141
+ c *= 1. - scanlineAlpha*cos(p.y*2.*iResolution.y/scanlineScale);
142
+
143
+ {
144
+ // bloom effect
145
+ const float blurSize = .002;
146
+ const float bloomIntensity = .2;
147
+
148
+ // 5-tap Gaussian blur
149
+ vec4 bloom = vec4(0);
150
+ bloom += texture(iChannel0, p + vec2(-2.*blurSize, 0)) * .12;
151
+ bloom += texture(iChannel0, p + vec2( -blurSize, 0)) * .24;
152
+ bloom += texture(iChannel0, p) * .28;
153
+ bloom += texture(iChannel0, p + vec2( blurSize, 0)) * .24;
154
+ bloom += texture(iChannel0, p + vec2( 2.*blurSize, 0)) * .12;
155
+ bloom += texture(iChannel0, p + vec2(0, -2.*blurSize)) * .12;
156
+ bloom += texture(iChannel0, p + vec2(0, -blurSize)) * .24;
157
+ bloom += texture(iChannel0, p) * .28;
158
+ bloom += texture(iChannel0, p + vec2(0, blurSize)) * .24;
159
+ bloom += texture(iChannel0, p + vec2(0, 2.*blurSize)) * .12;
160
+ c += bloom * bloomIntensity;
161
+ }
157
162
 
158
163
  // black vignette around edges
159
164
  const float vignette = 2.;
@@ -7,7 +7,7 @@
7
7
  </head><body>
8
8
 
9
9
  <!-- LittleJS Engine -->
10
- <script src=../../dist/littlejs.js?1133></script>
10
+ <script src=../../dist/littlejs.js?1136></script>
11
11
 
12
12
  <!-- Add your game scripts here -->
13
- <script src=game.js?1133></script>
13
+ <script src=game.js?1136></script>
@@ -1,16 +1,17 @@
1
1
  <!DOCTYPE html><head>
2
2
  <title>LittleJS Example Browser</title>
3
3
  <link rel=icon type=image/png href=favicon.png>
4
- <link rel=stylesheet href=style.css?6>
4
+ <link rel=stylesheet href=style.css?9>
5
5
  <meta charset=utf-8>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
7
  </head><body>
7
8
  <div id=container1>
8
9
  <div id=container3>
9
10
  <p id=titleInfo>LittleJS Example Browser</p>
10
11
  <p id=exampleInfo></p>
11
12
  <a id=exampleLink target='_blank'></a>
13
+ <div id=divCodeOptions>
12
14
  <hr>
13
- <div>
14
15
  Theme:
15
16
  <select style=width:120px onchange=loadTheme() id=selectTheme></select>
16
17
  <select style=width:40px onchange=loadTheme() id=selectFontSize>
@@ -20,24 +21,31 @@ Theme:
20
21
  <option value=20px>L</option>
21
22
  <option value=24px>XL</option>
22
23
  </select>
23
- <input type=checkbox id=checkboxLiveEdit checked>Live Edit
24
- <input type=checkbox id=checkboxJumpToError>Jump to Error
25
- </div>
24
+ <span class=nowrap><input type=checkbox id=checkboxLiveEdit checked>Live Edit</span>
25
+ <span class=nowrap><input type=checkbox id=checkboxJumpToErrors>Jump to Errors</span>
26
+ </div>
27
+ <div id=divTextareas>
26
28
  <textarea id=textareaCode oninput=codeInput() spellcheck=false></textarea>
27
29
  <textarea id=textareaError readonly spellcheck=false></textarea>
28
30
  <textarea id=textareaConsole readonly spellcheck=false></textarea>
29
31
  </div>
32
+ </div>
30
33
  <div id=container2>
34
+ <div style='display:flex;justify-content:center'>
31
35
  <div id=iframeContainer></div>
36
+ </div>
32
37
  <div>
33
- <input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key=="Escape")filterExamples(1)'>
38
+ <input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key==="Escape")filterExamples(1)'>
34
39
  <button id=buttonRestart onclick=restartCode()>Restart</button>
35
40
  <button id=buttonPause>Pause</button>
36
- <button id=buttonScreenshot>Screenshot</button>
41
+ <span id=container4>
37
42
  <button id=buttonFullscreen>Fullscreen</button>
38
- <input type=checkbox id=checkboxWebGL checked>WebGL
43
+ <button id=buttonScreenshot>Screenshot</button>
44
+ <span class=nowrap><input type=checkbox id=checkboxWebGL checked>WebGL</span>
45
+ </span>
39
46
  </div>
40
- <select id=selectExample autofocus onchange=setExample() size=2></select>
47
+ <span id=selectExampleText class=nowrap>Select an example...</span>
48
+ <select id=selectExample onchange=setExample() size=2></select>
41
49
  </div>
42
50
  </div>
43
51
  <script>
@@ -66,21 +74,23 @@ const exampleList =
66
74
  new ExampleInfo('Timers', 'timers.js', 'Timer object and timing functions - [utilities, time]'),
67
75
  new ExampleInfo('Texture', 'texture.js', 'Display and manipulate textures - [graphics, sprites]'),
68
76
  new ExampleInfo('Particles', 'particles.js', 'Particle system basics - [effects, particles]'),
69
- new ExampleInfo('Play Sound', 'playSound.js', 'Sound effects with ZzFX - [audio, sound]'),
77
+ new ExampleInfo('Sound Effects', 'sound.js', 'Example sound effects with ZzFX - [audio, sound]'),
78
+ new ExampleInfo('Music', 'music.js', 'Example sound loading, play, pause, seek, and stop - [audio, music]'),
70
79
  new ExampleInfo('System Font', 'systemFont.js', 'Built-in bitmap font rendering - [text, font]'),
71
80
  new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering - [graphics, sprites]'),
72
81
  new ExampleInfo('Animation', 'animation.js', 'Sprite animation techniques - [animation, sprites]'),
73
82
  new ExampleInfo('Clock', 'clock.js', 'Real-time clock display - [time, ui, utilities]'),
83
+ new ExampleInfo('Medals', 'medals.js', 'Achievement system demo - [ui, achievements]'),
74
84
  new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system - [tiles, levels]'),
75
85
  new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard - [audio, input, ui]'),
76
86
  new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation - [algorithm, generation]'),
77
- new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield - [effects, space]'),
78
- new ExampleInfo('Medals', 'medals.js', 'Achievement system demo - [ui, achievements]'),
87
+ new ExampleInfo('Starfield', 'starfield.js', 'Animated starfield - [effects, space]'),
88
+ new ExampleInfo('Parallax', 'parallax.js', 'Parallax mountains - [graphics, effects]'),
79
89
  new ExampleInfo('--- MINI GAMES ---'),
80
90
  new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run platformer - [game, platformer, physics]'),
81
91
  new ExampleInfo('Top Down Game', 'topDown.js', 'Adventure with collision - [game, topdown, rpg]'),
82
92
  new ExampleInfo('Tilted View', 'tiltedView.js', 'Pseudo 3D tilted perspective - [3d, camera, perspective]'),
83
- new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship with parallax scrolling - [game, space, shooter]'),
93
+ new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship with parallax starfield - [game, space, shooter]'),
84
94
  new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander physics game - [game, physics, thrust]'),
85
95
  new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird clone - [game, arcade, flying]'),
86
96
  new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style glider - [game, physics, flying]'),
@@ -108,32 +118,90 @@ const exampleList =
108
118
  ///////////////////////////////////////////////////////////////////////////////
109
119
 
110
120
  // global variables
111
- let iframeExample, inputTimeout, codeMirror, consoleAutoScroll, errorLineMarker, codeIsJS;
121
+ let iframeExample; // iframe of the current loaded example
122
+ let inputTimeout; // timeout to debounce input
123
+ let consoleAutoScroll; // allow console to scroll automatically
112
124
 
113
- // load the examples into the list
114
- filterExamples();
125
+ function initExampleBrowser()
126
+ {
127
+ // load the examples into the list
128
+ filterExamples();
129
+
130
+ // set the selected example from the URL parameters
131
+ const urlParams = new URLSearchParams(window.location.search);
132
+ const selectedExample = urlParams.get('example') || '';
133
+ let exampleIndex = exampleList.findIndex((example) => example.filename === selectedExample);
134
+ if (exampleIndex < 0)
135
+ exampleIndex = 1;
136
+ selectExample.selectedIndex = exampleIndex;
137
+ setExample();
115
138
 
116
- // set the selected example from the URL parameters
117
- const urlParams = new URLSearchParams(window.location.search);
118
- const selectedExample = urlParams.get('example') || '';
119
- let exampleIndex = exampleList.findIndex((example) => example.filename == selectedExample);
120
- if (exampleIndex < 0)
121
- exampleIndex = 1;
122
- selectExample.selectedIndex = exampleIndex;
123
- setExample();
139
+ // apply responsive layout
140
+ addEventListener('resize', resizeWindow);
141
+ resizeWindow();
142
+ }
124
143
 
125
- onresize = () =>
144
+ function resizeWindow()
126
145
  {
127
- // resize iframe to fit half the window
128
- const aspect = 1920 / 1080;
129
- let w = innerWidth / 2 | 0;
130
- let h = w / aspect | 0;
131
- iframeContainer.style.width = w + 'px';
132
- iframeContainer.style.height = h + 'px';
133
- // fix code mirror width
134
- container3.style.width = w-30 + 'px';
146
+ // tweak layout for touch devices
147
+ const isTouchDevice = window.ontouchstart !== undefined;
148
+ if (isTouchDevice)
149
+ container4.style.display = 'none';
150
+ else
151
+ selectExampleText.style.display = 'none';
152
+
153
+ const windowAspect = innerWidth / innerHeight;
154
+ const verticalLayout = windowAspect < 1;
155
+ if (verticalLayout)
156
+ {
157
+ // vertical layout for thin screens
158
+ if (container2.parentNode != container3)
159
+ container3.insertBefore(container2, divCodeOptions);
160
+ // resize iframe to the window
161
+ setFrameSize(innerWidth-35);
162
+
163
+ // fix code mirror sizing glitch
164
+ codeMirror && codeMirror.setSize(innerWidth-35, null);
165
+ }
166
+ else
167
+ {
168
+ // horizontal layout for wide screens
169
+ if (container2.parentNode != container1)
170
+ container1.appendChild(container2);
171
+
172
+ // show full controls
173
+ container4.style.display = '';
174
+
175
+ // resize iframe to fit half the window
176
+ setFrameSize(innerWidth/2);
177
+
178
+ // fix code mirror sizing glitch
179
+ codeMirror && codeMirror.setSize(innerWidth/2 - 35, null);
180
+ }
181
+
182
+ function setFrameSize(w)
183
+ {
184
+ const aspect = 16 / 9; // HD aspect ratio
185
+ w = w | 0;
186
+ const h = w / aspect | 0;
187
+ iframeContainer.style.width = w + 'px';
188
+ iframeContainer.style.height = h + 'px';
189
+
190
+ if (iframeExample)
191
+ {
192
+ // ensure iframe fills container tightly
193
+ iframeExample.style.width = w + 'px';
194
+ iframeExample.style.height = h + 'px';
195
+ }
196
+
197
+ // fix code mirror after layout changes
198
+ if (codeMirror)
199
+ {
200
+ // fix glitch with code mirror sizing
201
+ setTimeout(()=>codeMirror.refresh(), 500);
202
+ }
203
+ }
135
204
  }
136
- onresize();
137
205
 
138
206
  ///////////////////////////////////////////////////////////////////////////////
139
207
  // setting examples
@@ -157,7 +225,7 @@ function setExample()
157
225
  exampleInfo.innerText = example.name + (example.description ? ' - ' + example.description : '');
158
226
  const filename = 'examples/' + example.filename;
159
227
  exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
160
- exampleLink.innerText = filename;
228
+ exampleLink.innerText = 'View code on GitHub';
161
229
 
162
230
  // update URL parameter
163
231
  const url = new URL(window.location);
@@ -175,11 +243,7 @@ async function loadFile(filename, largeExample)
175
243
  textareaCode.disabled = largeExample;
176
244
 
177
245
  // disable buttons in large example mode
178
- buttonPause.disabled = largeExample;
179
- buttonRestart.disabled = largeExample;
180
- buttonScreenshot.disabled = largeExample;
181
- buttonFullscreen.disabled = largeExample;
182
- checkboxWebGL.disabled = largeExample;
246
+ setFrameControlsEnabled(!largeExample);
183
247
 
184
248
  if (largeExample)
185
249
  {
@@ -281,9 +345,20 @@ function restartCode()
281
345
  setCode(code);
282
346
  }
283
347
 
284
- function setCode(code, textFilename)
348
+ function setFrameControlsEnabled(enabled=true)
285
349
  {
286
- const largeExample = !!textFilename;
350
+ buttonPause.disabled = !enabled;
351
+ buttonRestart.disabled = !enabled;
352
+ buttonScreenshot.disabled = !enabled;
353
+ buttonFullscreen.disabled = !enabled;
354
+ checkboxWebGL.disabled = !enabled;
355
+ }
356
+
357
+ function setCode(code, filename)
358
+ {
359
+ const largeExample = !!filename;
360
+ filename = filename || 'shorts/base.html';
361
+
287
362
  clearTimeout(inputTimeout);
288
363
  if (iframeExample)
289
364
  iframeContainer.removeChild(iframeExample);
@@ -292,18 +367,26 @@ function setCode(code, textFilename)
292
367
  unsetConsoleMessage();
293
368
  iframeExample = document.createElement('iframe');
294
369
  iframeContainer.appendChild(iframeExample);
370
+
295
371
  iframeExample.onload = ()=>
296
372
  {
297
373
  if (largeExample)
298
374
  return;
299
375
 
376
+ // get the iframe content window and document
300
377
  const iframeContent = iframeExample.contentWindow;
301
378
  const iframeDocument = iframeContent.document;
379
+ if (!iframeContent.engineInit)
380
+ {
381
+ setErrorMessage(`Failed to load ${filename}`);
382
+ return;
383
+ }
302
384
 
303
385
  // intercept errors
304
386
  function getErrorLine(stack)
305
387
  {
306
- // try to extract line number from stack trace, look for <anonymous> or injectedScript to find the user code
388
+ // try to extract line number from stack trace
389
+ // look for <anonymous> or injectedScript to find user code
307
390
  const anonymousMatch = stack.match(/(<anonymous>|injectedScript):(\d+)/);
308
391
  return anonymousMatch ? parseInt(anonymousMatch[2]) : -1;
309
392
  }
@@ -332,7 +415,7 @@ function setCode(code, textFilename)
332
415
 
333
416
  // intercept asserts
334
417
  const originalAssert = iframeContent.console.assert;
335
- iframeContent.console.assert = function (condition, output)
418
+ iframeContent.console.assert = function (condition, ...output)
336
419
  {
337
420
  if (!condition)
338
421
  {
@@ -340,7 +423,10 @@ function setCode(code, textFilename)
340
423
  const errorLine = getErrorLine(stack);
341
424
  if (errorLine >= 0)
342
425
  setErrorLine(errorLine);
343
- setErrorMessage('Assertion failed!\n' + (output || '') + '\n' + stack);
426
+
427
+ // format output parameters properly
428
+ const outputMessage = output.length > 0 ? output.map(m => stringifyMessage(m)).join(' ') : '';
429
+ setErrorMessage('Assertion failed!\n' + outputMessage + '\n' + stack);
344
430
  }
345
431
  originalAssert.apply(this, arguments);
346
432
  };
@@ -360,7 +446,7 @@ function setCode(code, textFilename)
360
446
  ['log','info','warn','error','debug'].forEach(f=>interceptConsole(f));
361
447
 
362
448
  {
363
- // setup buttons
449
+ // hook up buttons
364
450
  buttonScreenshot.onclick = () =>
365
451
  {
366
452
  if (iframeContent.debugScreenshot)
@@ -383,22 +469,34 @@ function setCode(code, textFilename)
383
469
  buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
384
470
  }
385
471
 
386
- // set WebGL based on checkbox
387
- iframeContent.setGLEnable(checkboxWebGL.checked);
388
- checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
389
-
390
472
  // create a script element that overrides the default functions
391
473
  const overrideScript = iframeDocument.createElement('script');
392
474
  iframeDocument.body.appendChild(overrideScript);
393
475
  overrideScript.text = code;
394
-
395
- if (textareaError.style.display == 'block')
396
- return; // stop if script error
397
-
476
+
477
+ if (textareaError.style.display === 'block')
478
+ return;
479
+
398
480
  // start LittleJS engine
399
- iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()]);
481
+ iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()])
482
+ .catch(error =>
483
+ {
484
+ const errorLine = getErrorLine(error.stack);
485
+ if (errorLine >= 0)
486
+ setErrorLine(errorLine);
487
+ setErrorMessage(error.message + '\n' + error.stack);
488
+ throw error;
489
+ })
490
+ .then(()=>
491
+ {
492
+ // setup frame controls
493
+ setFrameControlsEnabled();
494
+ iframeContent.setGLEnable(checkboxWebGL.checked);
495
+ checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
496
+ })
400
497
  }
401
- iframeExample.src = textFilename || 'shorts/base.html?' + Date.now();
498
+
499
+ iframeExample.src = filename + '?' + Date.now();
402
500
  }
403
501
 
404
502
  ///////////////////////////////////////////////////////////////////////////////
@@ -427,9 +525,9 @@ function setMessage(message, element, clear=true)
427
525
  element.value = message;
428
526
  else
429
527
  element.value += '\n' + message;
430
- element.style.display = message ? 'block' : 'none';
528
+ element.style.display = message ? 'block' : '';
431
529
 
432
- if (element == textareaConsole)
530
+ if (element === textareaConsole)
433
531
  {
434
532
  const maxConsoleLines = 100;
435
533
  const lines = element.value.split('\n');
@@ -448,7 +546,7 @@ function setMessage(message, element, clear=true)
448
546
 
449
547
  function unsetMessage(element)
450
548
  {
451
- element.style.display = 'none';
549
+ element.style.display = '';
452
550
  element.value = '';
453
551
  }
454
552
 
@@ -469,13 +567,13 @@ function setErrorLine(lineNumber)
469
567
  clearErrorLine();
470
568
  --lineNumber; // codeMirror uses 0-based line numbers
471
569
  errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
472
- if (checkboxJumpToError.checked)
570
+ if (checkboxJumpToErrors.checked)
473
571
  {
474
572
  codeMirror.scrollIntoView({line: lineNumber, ch: 0});
475
573
  codeMirror.focus();
476
574
  }
477
575
  }
478
- else if (textareaCode && checkboxJumpToError.checked)
576
+ else if (textareaCode && checkboxJumpToErrors.checked)
479
577
  {
480
578
  // for textarea, calculate character position and set cursor
481
579
  const lines = textareaCode.value.split('\n');
@@ -485,7 +583,7 @@ function setErrorLine(lineNumber)
485
583
  charPos += lines[i].length + 1; // +1 for newline character
486
584
  }
487
585
  textareaCode.setSelectionRange(charPos, charPos);
488
- if (checkboxJumpToError.checked)
586
+ if (checkboxJumpToErrors.checked)
489
587
  {
490
588
  const line = textareaCode.value.split('\n');
491
589
  lineNumber = Math.min(Math.max(0, lineNumber-1), lines.length-1);
@@ -504,7 +602,8 @@ function setErrorLine(lineNumber)
504
602
  function setErrorMessage(message)
505
603
  {
506
604
  // prevent overwriting an existing error message
507
- const errorMessageIsVisible = textareaError.style.display == 'block';
605
+ const errorMessageIsVisible = textareaError.style.display === 'block';
606
+ setFrameControlsEnabled(false);
508
607
  if (!errorMessageIsVisible)
509
608
  setMessage(message, textareaError);
510
609
  }
@@ -520,10 +619,23 @@ function onScrollConsole()
520
619
  textareaConsole.addEventListener('scroll', onScrollConsole);
521
620
 
522
621
  ///////////////////////////////////////////////////////////////////////////////
523
- // setup code mirror
524
-
622
+ // load saved preferences from localStorage
525
623
  const defaultTheme = 'littlejs';
526
624
  const defaultFontSize = '16px';
625
+ const savePrefix = 'LittleJSExamples_';
626
+ const savedTheme = localStorage.getItem(savePrefix+'theme') || defaultTheme;
627
+ const savedFontSize = localStorage.getItem(savePrefix+'fontSize') || defaultFontSize;
628
+ selectFontSize.value = savedFontSize;
629
+ textareaCode.style.fontSize = savedFontSize;
630
+
631
+ ///////////////////////////////////////////////////////////////////////////////
632
+ // setup code mirror
633
+
634
+ const useCodeMirror = true;
635
+ let codeMirror; // code mirror instance
636
+ let errorLineMarker; // marker for error line in code mirror
637
+ let codeIsJS; // is the current code javascript
638
+
527
639
  const themes =
528
640
  [
529
641
  'littlejs',
@@ -539,46 +651,40 @@ const themes =
539
651
  'yonce'
540
652
  ];
541
653
 
542
- // Load saved preferences from localStorage
543
- const savePrefix = 'LittleJSExamples_';
544
- const savedTheme = localStorage.getItem(savePrefix+'theme') || defaultTheme;
545
- const savedFontSize = localStorage.getItem(savePrefix+'fontSize') || defaultFontSize;
546
-
654
+ // load theme and font size
547
655
  for (const theme of themes)
548
656
  {
549
657
  if (theme != 'littlejs')
550
658
  addCodeMirrorElement(`theme/${theme}.min.css`, 'link', 'stylesheet');
551
659
  const o = new Option(theme);
552
- o.selected = theme == savedTheme;
660
+ o.selected = theme === savedTheme;
553
661
  selectTheme.add(o);
554
662
  }
555
663
 
556
- // Set the saved font size
557
- selectFontSize.value = savedFontSize;
558
- textareaCode.style.fontSize = savedFontSize;
559
- addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
560
- addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
561
- addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
562
- addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
563
- initCodeMirror();
564
-
565
- function initCodeMirror()
664
+ if (useCodeMirror)
566
665
  {
567
- if (codeMirror)
568
- return; // prevent duplicate initialization
569
-
570
- const textareaCode = document.getElementById('textareaCode');
571
- codeMirror = CodeMirror.fromTextArea(textareaCode,
666
+ addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
667
+ addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
668
+ addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
669
+ addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
572
670
  {
573
- theme: savedTheme,
574
- indentUnit: 4,
575
- mode: codeIsJS ? 'javascript' : 'text',
576
- lineNumbers: true,
577
- lineWrapping: true,
578
- matchBrackets: true,
579
- });
580
- codeMirror.on('change', codeInput);
581
- loadTheme();
671
+ if (codeMirror)
672
+ return; // prevent duplicate initialization
673
+
674
+ const textareaCode = document.getElementById('textareaCode');
675
+ codeMirror = CodeMirror.fromTextArea(textareaCode,
676
+ {
677
+ theme: savedTheme,
678
+ indentUnit: 4,
679
+ mode: codeIsJS ? 'javascript' : 'text',
680
+ lineNumbers: true,
681
+ lineWrapping: true,
682
+ matchBrackets: true,
683
+ });
684
+ codeMirror.on('change', codeInput);
685
+ loadTheme();
686
+ resizeWindow(); // fix cursor positioning issue on startup
687
+ }
582
688
  }
583
689
 
584
690
  function addCodeMirrorElement(filename, type, rel)
@@ -587,7 +693,7 @@ function addCodeMirrorElement(filename, type, rel)
587
693
  const e = document.createElement(type);
588
694
  e.rel = rel;
589
695
  filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/' + filename;
590
- if (type == 'link')
696
+ if (type === 'link')
591
697
  e.href = filename;
592
698
  else
593
699
  e.src = filename;
@@ -614,6 +720,11 @@ function loadTheme()
614
720
  }
615
721
  }
616
722
 
723
+ ///////////////////////////////////////////////////////////////////////////////
724
+
725
+ // start up the browser
726
+ initExampleBrowser()
727
+
617
728
  </script>
618
729
  </body>
619
730
  </html>