littlejsengine 1.12.4 → 1.13.1
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/dist/box2d.wasm.js +630 -0
- package/dist/box2d.wasm.wasm +0 -0
- package/dist/littlejs.d.ts +437 -219
- package/dist/littlejs.esm.js +5950 -5307
- package/dist/littlejs.esm.min.js +4 -1
- package/dist/littlejs.js +5921 -5295
- package/dist/littlejs.min.js +4 -1
- package/dist/littlejs.release.js +5485 -4886
- package/examples/box2d/game.js +37 -42
- package/examples/box2d/gameObjects.js +41 -37
- package/examples/box2d/index.html +2 -2
- package/examples/box2d/scenes.js +24 -20
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakout/index.html +1 -1
- package/examples/breakoutTutorial/README.md +2 -2
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/breakoutTutorial/index.html +1 -1
- package/examples/empty/index.html +1 -1
- package/examples/htmlMenu/index.html +1 -1
- package/examples/index.html +410 -159
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +1 -1
- package/examples/platformer/game.js +6 -15
- package/examples/platformer/gameCharacter.js +6 -6
- package/examples/platformer/gameEffects.js +74 -57
- package/examples/platformer/gameLevel.js +61 -70
- package/examples/platformer/gameObjects.js +4 -4
- package/examples/platformer/index.html +1 -1
- package/examples/puzzle/index.html +1 -1
- package/examples/shorts/base.html +24 -3
- package/examples/shorts/box2d.js +46 -0
- package/examples/shorts/box2dCar.js +45 -0
- package/examples/shorts/flappyGame.js +52 -0
- package/examples/shorts/helloWorld.js +6 -3
- package/examples/shorts/hillGlideGame.js +56 -0
- package/examples/shorts/landerGame.js +32 -0
- package/examples/shorts/maze.js +47 -0
- package/examples/shorts/medals.js +42 -0
- package/examples/shorts/nineSlice.js +21 -0
- package/examples/shorts/piano.js +52 -0
- package/examples/shorts/platformer.js +24 -20
- package/examples/shorts/{pong.js → pongGame.js} +1 -1
- package/examples/shorts/postProcess.js +45 -0
- package/examples/shorts/raycasting.js +71 -0
- package/examples/shorts/slidingPuzzle.js +42 -0
- package/examples/shorts/spaceGame.js +56 -0
- package/examples/shorts/starfield.js +17 -0
- package/examples/shorts/tileLayer.js +3 -5
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +8 -7
- package/examples/shorts/topDown.js +18 -26
- package/examples/shorts/uiSystem.js +36 -0
- package/examples/starter/build.bat +6 -1
- package/examples/starter/game.js +4 -2
- package/examples/starter/index.html +23 -13
- package/examples/stress/index.html +2 -3
- package/examples/style.css +136 -0
- package/examples/typescript/build.js +1 -2
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +1 -1
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +9 -25
- package/examples/uiSystem/index.html +1 -1
- package/package.json +1 -1
- package/plugins/box2d.js +29 -35
- package/plugins/drawUtilities.js +126 -0
- package/plugins/newgrounds.js +1 -1
- package/plugins/pluginExport.js +8 -2
- package/plugins/postProcess.js +2 -2
- package/plugins/uiSystem.js +162 -68
- package/plugins/zzfxm.js +1 -1
- package/reference.md +10 -10
- package/src/engine.js +59 -39
- package/src/engineAudio.js +38 -20
- package/src/engineBuild.bat +6 -1
- package/src/engineBuild.js +32 -7
- package/src/engineDebug.js +53 -26
- package/src/engineDraw.js +108 -57
- package/src/engineExport.js +22 -9
- package/src/engineInput.js +112 -40
- package/src/engineObject.js +68 -67
- package/src/engineParticles.js +26 -9
- package/src/engineSettings.js +15 -16
- package/src/engineTileLayer.js +312 -153
- package/src/engineUtilities.js +198 -130
- package/src/engineWebGL.js +58 -35
package/examples/index.html
CHANGED
|
@@ -1,102 +1,41 @@
|
|
|
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?4>
|
|
4
5
|
<meta charset=utf-8>
|
|
5
|
-
<style>
|
|
6
|
-
html, body
|
|
7
|
-
{
|
|
8
|
-
height: 100%;
|
|
9
|
-
display: flex;
|
|
10
|
-
flex-direction: column;
|
|
11
|
-
}
|
|
12
|
-
#container1
|
|
13
|
-
{
|
|
14
|
-
height: 100%;
|
|
15
|
-
display: flex;
|
|
16
|
-
}
|
|
17
|
-
#container2
|
|
18
|
-
{
|
|
19
|
-
display: flex;
|
|
20
|
-
flex-direction: column;
|
|
21
|
-
}
|
|
22
|
-
#iframeContainer
|
|
23
|
-
{
|
|
24
|
-
border: 2px solid;
|
|
25
|
-
background: #000;
|
|
26
|
-
}
|
|
27
|
-
iframe
|
|
28
|
-
{
|
|
29
|
-
width: 100%;
|
|
30
|
-
height: 100%;
|
|
31
|
-
border: none;
|
|
32
|
-
}
|
|
33
|
-
#selectExample
|
|
34
|
-
{
|
|
35
|
-
flex-grow: 1;
|
|
36
|
-
}
|
|
37
|
-
#textareaCode
|
|
38
|
-
{
|
|
39
|
-
flex-grow: 1;
|
|
40
|
-
padding: 5px;
|
|
41
|
-
resize: none;
|
|
42
|
-
color: #fff;
|
|
43
|
-
background: #000;
|
|
44
|
-
}
|
|
45
|
-
#textareaError
|
|
46
|
-
{
|
|
47
|
-
flex-grow: .3;
|
|
48
|
-
padding: 5px;
|
|
49
|
-
resize: none;
|
|
50
|
-
display: none;
|
|
51
|
-
color:#f22;
|
|
52
|
-
background: #111;
|
|
53
|
-
}
|
|
54
|
-
#container3
|
|
55
|
-
{
|
|
56
|
-
width: 100%;
|
|
57
|
-
height: 100%;
|
|
58
|
-
display: flex;
|
|
59
|
-
flex-direction: column;
|
|
60
|
-
}
|
|
61
|
-
#titleInfo
|
|
62
|
-
{
|
|
63
|
-
margin: 5px;
|
|
64
|
-
text-align: center;
|
|
65
|
-
font-size: 50px;
|
|
66
|
-
}
|
|
67
|
-
#exampleLink
|
|
68
|
-
{
|
|
69
|
-
margin: 0px;
|
|
70
|
-
text-align: center;
|
|
71
|
-
font-size: 30px;
|
|
72
|
-
}
|
|
73
|
-
#exampleInfo
|
|
74
|
-
{
|
|
75
|
-
margin: 0px;
|
|
76
|
-
text-align: center;
|
|
77
|
-
font-size: 22px;
|
|
78
|
-
}
|
|
79
|
-
select
|
|
80
|
-
{
|
|
81
|
-
color:#fff;
|
|
82
|
-
background-color: #111;
|
|
83
|
-
width:100%;
|
|
84
|
-
}
|
|
85
|
-
</style>
|
|
86
6
|
</head><body>
|
|
87
7
|
<div id=container1>
|
|
88
8
|
<div id=container3>
|
|
89
9
|
<p id=titleInfo>LittleJS Example Browser</p>
|
|
90
|
-
<
|
|
91
|
-
<
|
|
10
|
+
<p id=exampleInfo></p>
|
|
11
|
+
<a id=exampleLink target='_blank'></a>
|
|
12
|
+
<hr>
|
|
13
|
+
<div>
|
|
14
|
+
Theme:
|
|
15
|
+
<select style=width:120px onchange=loadTheme() id=selectTheme></select>
|
|
16
|
+
<select style=width:40px onchange=loadTheme() id=selectFontSize>
|
|
17
|
+
<option value=10px>XS</option>
|
|
18
|
+
<option value=13px>S</option>
|
|
19
|
+
<option value=16px selected>M</option>
|
|
20
|
+
<option value=20px>L</option>
|
|
21
|
+
<option value=24px>XL</option>
|
|
22
|
+
</select>
|
|
23
|
+
<input type=checkbox id=checkboxLiveEdit checked>Live Edit
|
|
24
|
+
<button id=buttonRefresh onclick=refreshCode()>Refresh</button>
|
|
25
|
+
</div>
|
|
92
26
|
<textarea id=textareaCode oninput=codeInput() spellcheck=false></textarea>
|
|
93
27
|
<textarea id=textareaError readonly spellcheck=false></textarea>
|
|
94
|
-
<
|
|
95
|
-
<button id=buttonScreenshot>Screenshot</button>
|
|
96
|
-
</div>
|
|
28
|
+
<textarea id=textareaConsole readonly spellcheck=false></textarea>
|
|
97
29
|
</div>
|
|
98
30
|
<div id=container2>
|
|
99
31
|
<div id=iframeContainer></div>
|
|
32
|
+
<div>
|
|
33
|
+
<input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key=="Escape")filterExamples(1)'>
|
|
34
|
+
<button onclick=filterExamples(1)>Clear</button>
|
|
35
|
+
<button id=buttonPause>Pause</button>
|
|
36
|
+
<button id=buttonScreenshot>Screenshot</button>
|
|
37
|
+
<button id=buttonFullscreen>Fullscreen</button>
|
|
38
|
+
</div>
|
|
100
39
|
<select id=selectExample autofocus onchange=setExample() size=2></select>
|
|
101
40
|
</div>
|
|
102
41
|
</div>
|
|
@@ -105,24 +44,24 @@ select
|
|
|
105
44
|
|
|
106
45
|
class ExampleInfo
|
|
107
46
|
{
|
|
108
|
-
constructor(name, filename, description,
|
|
47
|
+
constructor(name, filename, description='', largeExample=false)
|
|
109
48
|
{
|
|
110
|
-
if (filename && !
|
|
111
|
-
filename = 'shorts/' + filename;
|
|
49
|
+
if (filename && !largeExample)
|
|
50
|
+
filename = 'shorts/' + filename;
|
|
112
51
|
this.name = name;
|
|
113
52
|
this.filename = filename;
|
|
114
53
|
this.description = description;
|
|
115
|
-
this.
|
|
54
|
+
this.largeExample = largeExample;
|
|
116
55
|
}
|
|
117
56
|
}
|
|
118
57
|
|
|
119
58
|
const exampleList =
|
|
120
59
|
[
|
|
121
60
|
new ExampleInfo('--- SHORT EXAMPLES ---'),
|
|
122
|
-
new ExampleInfo('Hello World', 'helloWorld.js', '
|
|
61
|
+
new ExampleInfo('Hello World', 'helloWorld.js', 'Minimal example'),
|
|
123
62
|
new ExampleInfo('Shapes', 'shapes.js', 'How to draw geometric shapes'),
|
|
124
63
|
new ExampleInfo('Colors', 'colors.js', 'How to use the color object'),
|
|
125
|
-
new ExampleInfo('Blending', 'blending.js', '
|
|
64
|
+
new ExampleInfo('Blending', 'blending.js', 'How to use blending modes'),
|
|
126
65
|
new ExampleInfo('Timers', 'timers.js', 'How to use the timer object'),
|
|
127
66
|
new ExampleInfo('Texture', 'texture.js', 'How to display a full texture'),
|
|
128
67
|
new ExampleInfo('Particles', 'particles.js', 'How to use the particle system'),
|
|
@@ -130,13 +69,29 @@ const exampleList =
|
|
|
130
69
|
new ExampleInfo('System Font', 'systemFont.js', 'Demo of the included system font'),
|
|
131
70
|
new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'How to set up a simple sprite atlas'),
|
|
132
71
|
new ExampleInfo('Animation', 'animation.js', 'How to animate sprites'),
|
|
133
|
-
new ExampleInfo('Clock', 'clock.js', '
|
|
72
|
+
new ExampleInfo('Clock', 'clock.js', 'Simple clock showing the time'),
|
|
134
73
|
new ExampleInfo('Tile Layer', 'tileLayer.js', 'How to use the tile layer object'),
|
|
135
|
-
new ExampleInfo('
|
|
136
|
-
new ExampleInfo('
|
|
74
|
+
new ExampleInfo('Piano', 'piano.js', 'A simple piano keyboard demo'),
|
|
75
|
+
new ExampleInfo('Maze Generator', 'maze.js', 'Generates a random maze'),
|
|
76
|
+
new ExampleInfo('Starfield', 'starfield.js', 'A moving starfield effect'),
|
|
77
|
+
new ExampleInfo('Medals', 'medals.js', 'How to use the medal system'),
|
|
78
|
+
new ExampleInfo('--- MINI GAMES ---'),
|
|
79
|
+
new ExampleInfo('Platformer Game', 'platformer.js', 'Platformer jumping game'),
|
|
80
|
+
new ExampleInfo('Top Down Game', 'topDown.js', 'Top down adventure game'),
|
|
137
81
|
new ExampleInfo('Tilted View', 'tiltedView.js', 'Pseudo 3D view with tilted camera'),
|
|
138
|
-
new ExampleInfo('
|
|
139
|
-
|
|
82
|
+
new ExampleInfo('Space Game', 'spaceGame.js', 'Space ship game with parallax starfield'),
|
|
83
|
+
new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander style game'),
|
|
84
|
+
new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird style game'),
|
|
85
|
+
new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style game'),
|
|
86
|
+
new ExampleInfo('Pong Game', 'pongGame.js', 'Simple pong style game'),
|
|
87
|
+
new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', 'Sliding tile puzzle game'),
|
|
88
|
+
new ExampleInfo('Raycasting Game', 'raycasting.js', 'Pseudo 3D raycasting system'),
|
|
89
|
+
new ExampleInfo('--- PLUGINS ---'),
|
|
90
|
+
new ExampleInfo('Box2d Demo', 'box2d.js', 'Demo of the Box2D physics plugin'),
|
|
91
|
+
new ExampleInfo('Box2d Car', 'box2dCar.js', 'Controllable vehicle using the Box2D physics plugin'),
|
|
92
|
+
new ExampleInfo('Post Processing', 'postProcess.js', 'Post processing plugin demo'),
|
|
93
|
+
new ExampleInfo('UI System Plugin', 'uiSystem.js', 'Simple example of LittleJS UI plugin'),
|
|
94
|
+
new ExampleInfo('Nine Slice', 'nineSlice.js', 'Demo of nine slice and three slice drawing'),
|
|
140
95
|
new ExampleInfo('--- FULL EXAMPLES ---'),
|
|
141
96
|
new ExampleInfo('Starter', 'starter', 'Clean starter project', true),
|
|
142
97
|
new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Tutorial Breakout example', true),
|
|
@@ -146,25 +101,23 @@ const exampleList =
|
|
|
146
101
|
new ExampleInfo('Stress Test', 'stress', 'Max sprite/object test and music system demo', true),
|
|
147
102
|
new ExampleInfo('Box2D Plugin', 'box2d', 'Demo of the Box2D physics plugin', true),
|
|
148
103
|
new ExampleInfo('HTML Menus', 'htmlMenu', 'Shows how to combine HTML with LittleJS', true),
|
|
149
|
-
new ExampleInfo('UI System Plugin', 'uiSystem', '
|
|
150
|
-
// Add more examples here!
|
|
104
|
+
new ExampleInfo('UI System Plugin', 'uiSystem', 'How to use LittleJS UI plugin', true),
|
|
151
105
|
];
|
|
152
106
|
|
|
153
107
|
///////////////////////////////////////////////////////////////////////////////
|
|
154
108
|
|
|
155
|
-
|
|
109
|
+
// global variables
|
|
110
|
+
let iframeExample, inputTimeout, codeMirror, consoleAutoScroll, errorLineMarker, codeIsJS;
|
|
156
111
|
|
|
157
112
|
// load the examples into the list
|
|
158
|
-
|
|
159
|
-
{
|
|
160
|
-
const text = example.name + (example.description?' - ' + example.description : '');
|
|
161
|
-
const o = new Option(text);
|
|
162
|
-
o.disabled = !example.filename;
|
|
163
|
-
selectExample.add(o);
|
|
164
|
-
}
|
|
113
|
+
filterExamples();
|
|
165
114
|
|
|
166
115
|
// set the selected example from the URL hash
|
|
167
|
-
|
|
116
|
+
const selectedExample = location.hash.split`#`[1] || '';
|
|
117
|
+
let exampleIndex = exampleList.findIndex((example) => example.filename == selectedExample);
|
|
118
|
+
if (exampleIndex < 0)
|
|
119
|
+
exampleIndex = 1;
|
|
120
|
+
selectExample.selectedIndex = exampleIndex;
|
|
168
121
|
setExample();
|
|
169
122
|
|
|
170
123
|
onresize = () =>
|
|
@@ -175,79 +128,167 @@ onresize = () =>
|
|
|
175
128
|
let h = w / aspect | 0;
|
|
176
129
|
iframeContainer.style.width = w + 'px';
|
|
177
130
|
iframeContainer.style.height = h + 'px';
|
|
131
|
+
// fix code mirror width
|
|
132
|
+
container3.style.width = w-30 + 'px';
|
|
178
133
|
}
|
|
179
134
|
onresize();
|
|
180
135
|
|
|
181
136
|
///////////////////////////////////////////////////////////////////////////////
|
|
137
|
+
// setting examples
|
|
182
138
|
|
|
183
139
|
function setExample()
|
|
184
140
|
{
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let
|
|
188
|
-
|
|
189
|
-
|
|
141
|
+
// get the original index if this is a filtered result
|
|
142
|
+
const selectedOption = selectExample.options[selectExample.selectedIndex];
|
|
143
|
+
let exampleIndex = selectedOption && selectedOption.originalIndex ?
|
|
144
|
+
parseInt(selectedOption.originalIndex) :
|
|
145
|
+
selectExample.selectedIndex;
|
|
146
|
+
|
|
147
|
+
// make sure we have a valid example
|
|
148
|
+
if (exampleIndex < 0 || exampleIndex >= exampleList.length)
|
|
149
|
+
exampleIndex = 1; // reset to default
|
|
150
|
+
if (!exampleList[exampleIndex].filename)
|
|
151
|
+
exampleIndex = 1;
|
|
152
|
+
|
|
153
|
+
// load the example
|
|
154
|
+
const example = exampleList[exampleIndex];
|
|
190
155
|
exampleInfo.innerText = example.name + (example.description ? ' - ' + example.description : '');
|
|
191
156
|
const filename = 'examples/' + example.filename;
|
|
192
157
|
exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
|
|
193
158
|
exampleLink.innerText = filename;
|
|
194
|
-
|
|
195
|
-
|
|
159
|
+
location.hash = example.filename;
|
|
160
|
+
loadFile(example.filename, example.largeExample);
|
|
196
161
|
}
|
|
197
162
|
|
|
198
|
-
function
|
|
163
|
+
async function loadFile(filename, largeExample)
|
|
199
164
|
{
|
|
200
|
-
if (
|
|
165
|
+
if (codeMirror)
|
|
166
|
+
codeMirror.setOption('readOnly', largeExample);
|
|
167
|
+
else
|
|
168
|
+
textareaCode.disabled = largeExample;
|
|
169
|
+
|
|
170
|
+
// disable buttons in large example mode
|
|
171
|
+
buttonPause.disabled = largeExample;
|
|
172
|
+
buttonRefresh.disabled = largeExample;
|
|
173
|
+
buttonScreenshot.disabled = largeExample;
|
|
174
|
+
buttonFullscreen.disabled = largeExample;
|
|
175
|
+
|
|
176
|
+
if (largeExample)
|
|
177
|
+
{
|
|
178
|
+
|
|
179
|
+
// Show message in code view that full examples can't be edited
|
|
180
|
+
const text = 'Code view not available for large examples.';
|
|
181
|
+
setCode(text, filename);
|
|
182
|
+
codeIsJS = false;
|
|
183
|
+
if (codeMirror)
|
|
184
|
+
{
|
|
185
|
+
codeMirror.off('change', codeInput);
|
|
186
|
+
codeMirror.setOption('mode', 'text');
|
|
187
|
+
codeMirror.setValue(text);
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
textareaCode.value = text;
|
|
191
|
+
clearTimeout(inputTimeout);
|
|
201
192
|
return;
|
|
193
|
+
}
|
|
202
194
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
195
|
+
try
|
|
196
|
+
{
|
|
197
|
+
const response = await fetch(filename);
|
|
198
|
+
if (!response.ok)
|
|
199
|
+
throw new Error('Could not load file: ' + filename);
|
|
200
|
+
const text = await response.text();
|
|
201
|
+
|
|
202
|
+
// set the code in both code mirror and textarea
|
|
203
|
+
codeIsJS = true;
|
|
204
|
+
if (codeMirror)
|
|
205
|
+
{
|
|
206
|
+
codeMirror.on('change', codeInput);
|
|
207
|
+
codeMirror.setOption('mode', 'javascript');
|
|
208
|
+
codeMirror.setValue(text);
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
textareaCode.value = text;
|
|
212
|
+
clearTimeout(inputTimeout);
|
|
213
|
+
setCode(text);
|
|
214
|
+
}
|
|
215
|
+
catch (error)
|
|
216
|
+
{
|
|
217
|
+
setErrorMessage(error.message);
|
|
218
|
+
}
|
|
206
219
|
}
|
|
207
220
|
|
|
208
|
-
function
|
|
221
|
+
function filterExamples(reset=0)
|
|
209
222
|
{
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
if (reset)
|
|
224
|
+
inputSearch.value = '';
|
|
225
|
+
|
|
226
|
+
// clear current options
|
|
227
|
+
selectExample.options.length = 0;
|
|
228
|
+
|
|
229
|
+
// filter and add matching examples
|
|
230
|
+
const searchTerm = inputSearch.value.toLowerCase().trim();
|
|
231
|
+
for (let i = 0; i < exampleList.length; i++)
|
|
212
232
|
{
|
|
213
|
-
if
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
iframeContainer.appendChild(iframeExample);
|
|
219
|
-
iframeExample.onload = () =>
|
|
233
|
+
// check if search term matches name or description
|
|
234
|
+
const example = exampleList[i];
|
|
235
|
+
const text = example.name + (example.description ? ` - ${example.description}` : '');
|
|
236
|
+
if (example.name.toLowerCase().includes(searchTerm) ||
|
|
237
|
+
example.description.toLowerCase().includes(searchTerm))
|
|
220
238
|
{
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
239
|
+
const o = new Option(text);
|
|
240
|
+
o.disabled = !example.filename;
|
|
241
|
+
o.originalIndex = i;
|
|
242
|
+
selectExample.add(o);
|
|
225
243
|
}
|
|
226
|
-
iframeExample.src = filename;
|
|
227
|
-
return;
|
|
228
244
|
}
|
|
245
|
+
|
|
246
|
+
if (!selectExample.options.length)
|
|
247
|
+
{
|
|
248
|
+
// show a message if no matches
|
|
249
|
+
const o = new Option('No examples found matching "' + searchTerm + '"');
|
|
250
|
+
o.disabled = true;
|
|
251
|
+
selectExample.add(o);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
256
|
+
// setting code
|
|
257
|
+
|
|
258
|
+
function codeInput()
|
|
259
|
+
{
|
|
260
|
+
if (!checkboxLiveEdit.checked)
|
|
261
|
+
return;
|
|
262
|
+
|
|
263
|
+
// debounce input - get content from code mirror if available, otherwise from textarea
|
|
264
|
+
clearTimeout(inputTimeout);
|
|
265
|
+
const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
|
|
266
|
+
inputTimeout = setTimeout(() => setCode(code), 500);
|
|
267
|
+
}
|
|
229
268
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
})
|
|
236
|
-
.then(text => setCode(textareaCode.value = text, fullExample))
|
|
237
|
-
.catch(error => setErrorMessage(error.message));
|
|
269
|
+
function refreshCode()
|
|
270
|
+
{
|
|
271
|
+
// manually refresh/run the code regardless of live edit setting
|
|
272
|
+
const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
|
|
273
|
+
setCode(code);
|
|
238
274
|
}
|
|
239
275
|
|
|
240
|
-
function setCode(code,
|
|
276
|
+
function setCode(code, textFilename)
|
|
241
277
|
{
|
|
278
|
+
const largeExample = !!textFilename;
|
|
242
279
|
clearTimeout(inputTimeout);
|
|
243
280
|
if (iframeExample)
|
|
244
281
|
iframeContainer.removeChild(iframeExample);
|
|
245
282
|
|
|
246
|
-
|
|
283
|
+
unsetErrorMessage();
|
|
284
|
+
unsetConsoleMessage();
|
|
247
285
|
iframeExample = document.createElement('iframe');
|
|
248
286
|
iframeContainer.appendChild(iframeExample);
|
|
249
|
-
iframeExample.onload = ()
|
|
287
|
+
iframeExample.onload = ()=>
|
|
250
288
|
{
|
|
289
|
+
if (largeExample)
|
|
290
|
+
return;
|
|
291
|
+
|
|
251
292
|
const iframeContent = iframeExample.contentWindow;
|
|
252
293
|
const iframeDocument = iframeContent.document;
|
|
253
294
|
|
|
@@ -258,24 +299,68 @@ function setCode(code, largeExample)
|
|
|
258
299
|
if (lineno)
|
|
259
300
|
text += ` (Line:${lineno}, Column:${colno})`
|
|
260
301
|
if (error && error.stack)
|
|
261
|
-
text += `\n
|
|
302
|
+
text += `\n` + error.stack;
|
|
262
303
|
setErrorMessage(text);
|
|
304
|
+
setErrorLine(lineno);
|
|
263
305
|
}
|
|
264
306
|
iframeContent.onunhandledrejection = (event) =>
|
|
265
307
|
{
|
|
266
308
|
let text = event.reason;
|
|
267
|
-
if (event.reason.stack)
|
|
309
|
+
if (event.reason && event.reason.stack)
|
|
310
|
+
{
|
|
268
311
|
text += `\n\n` + event.reason.stack;
|
|
312
|
+
|
|
313
|
+
// try to extract line number from stack trace
|
|
314
|
+
const lineMatch = event.reason.stack.match(/:(\d+):(\d+)/);
|
|
315
|
+
setErrorLine(parseInt(lineMatch[1]));
|
|
316
|
+
}
|
|
269
317
|
setErrorMessage(text);
|
|
270
318
|
};
|
|
319
|
+
|
|
320
|
+
// intercept console methods
|
|
321
|
+
const originalConsole = iframeContent.console;
|
|
322
|
+
function interceptConsole(method)
|
|
323
|
+
{
|
|
324
|
+
const original = originalConsole[method];
|
|
325
|
+
iframeContent.console[method] = function(...args)
|
|
326
|
+
{
|
|
327
|
+
const message = args.map(m=>stringifyMessage(m)).join('\n');
|
|
328
|
+
setConsoleMessage(message);
|
|
329
|
+
original.apply(originalConsole, args);
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
['log','info','warn','error','debug'].forEach(f=>interceptConsole(f));
|
|
333
|
+
|
|
334
|
+
// intercept asserts
|
|
271
335
|
const originalAssert = iframeContent.console.assert;
|
|
272
336
|
iframeContent.console.assert = function (condition, output)
|
|
273
337
|
{
|
|
274
|
-
|
|
338
|
+
if (!condition)
|
|
275
339
|
setErrorMessage('Assertion failed!\n' + (output || '') + '\n' + (new Error).stack);
|
|
276
340
|
originalAssert.apply(this, arguments);
|
|
277
341
|
};
|
|
278
342
|
|
|
343
|
+
buttonScreenshot.onclick = () =>
|
|
344
|
+
{
|
|
345
|
+
if (iframeContent.debugScreenshot)
|
|
346
|
+
iframeContent.debugScreenshot();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// pause/resume functionality
|
|
350
|
+
buttonPause.onclick = () =>
|
|
351
|
+
{
|
|
352
|
+
if (!iframeContent.getPaused || !iframeContent.setPaused)
|
|
353
|
+
return;
|
|
354
|
+
|
|
355
|
+
const paused = !iframeContent.getPaused();
|
|
356
|
+
iframeContent.setPaused(paused);
|
|
357
|
+
buttonPause.textContent = paused ? 'Resume' : 'Pause';
|
|
358
|
+
}
|
|
359
|
+
buttonPause.textContent = 'Pause';
|
|
360
|
+
|
|
361
|
+
// fullscreen functionality
|
|
362
|
+
buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
|
|
363
|
+
|
|
279
364
|
// create a script element that overrides the default functions
|
|
280
365
|
const overrideScript = iframeDocument.createElement('script');
|
|
281
366
|
iframeDocument.body.appendChild(overrideScript);
|
|
@@ -285,23 +370,189 @@ function setCode(code, largeExample)
|
|
|
285
370
|
return; // stop if script error
|
|
286
371
|
|
|
287
372
|
// start LittleJS engine
|
|
288
|
-
iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?
|
|
373
|
+
iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()]);
|
|
374
|
+
}
|
|
375
|
+
iframeExample.src = textFilename || 'shorts/base.html?' + Date.now();
|
|
376
|
+
}
|
|
289
377
|
|
|
290
|
-
|
|
378
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
379
|
+
// error and console messages
|
|
380
|
+
|
|
381
|
+
function stringifyMessage(message)
|
|
382
|
+
{
|
|
383
|
+
// make sure message is a string
|
|
384
|
+
if (message === null)
|
|
385
|
+
return 'null';
|
|
386
|
+
if (Number.isNaN(message))
|
|
387
|
+
return 'NaN';
|
|
388
|
+
if (message === undefined)
|
|
389
|
+
return 'undefined';
|
|
390
|
+
if (message === 0)
|
|
391
|
+
return '0';
|
|
392
|
+
if (message === false)
|
|
393
|
+
return 'false';
|
|
394
|
+
return message;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function setMessage(message, element, clear=true)
|
|
398
|
+
{
|
|
399
|
+
message = stringifyMessage(message);
|
|
400
|
+
if (clear || !element.value)
|
|
401
|
+
element.value = message;
|
|
402
|
+
else
|
|
403
|
+
element.value += '\n' + message;
|
|
404
|
+
element.style.display = message ? 'block' : 'none';
|
|
405
|
+
|
|
406
|
+
if (element == textareaConsole)
|
|
407
|
+
{
|
|
408
|
+
const maxConsoleLines = 100;
|
|
409
|
+
const lines = element.value.split('\n');
|
|
410
|
+
if (lines.length > maxConsoleLines)
|
|
291
411
|
{
|
|
292
|
-
|
|
293
|
-
|
|
412
|
+
// limit max lines, prevents slowdown from too many lines
|
|
413
|
+
const excessLines = lines.length - maxConsoleLines;
|
|
414
|
+
element.value = lines.slice(excessLines).join('\n');
|
|
294
415
|
}
|
|
416
|
+
|
|
417
|
+
// auto scroll to bottom only if user hasn't manually scrolled
|
|
418
|
+
if (consoleAutoScroll)
|
|
419
|
+
element.scrollTop = element.scrollHeight;
|
|
295
420
|
}
|
|
296
|
-
iframeExample.src = 'shorts/base.html';
|
|
297
421
|
}
|
|
298
422
|
|
|
299
|
-
function
|
|
423
|
+
function unsetMessage(element)
|
|
424
|
+
{
|
|
425
|
+
element.style.display = 'none';
|
|
426
|
+
element.value = '';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function clearErrorLine()
|
|
430
|
+
{
|
|
431
|
+
if (!codeMirror || !errorLineMarker)
|
|
432
|
+
return;
|
|
433
|
+
codeMirror.getDoc().removeLineClass(errorLineMarker, 'background', 'error-line');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function setErrorLine(lineNumber)
|
|
437
|
+
{
|
|
438
|
+
if (!codeMirror || !(lineNumber > 0))
|
|
439
|
+
return;
|
|
440
|
+
|
|
441
|
+
clearErrorLine();
|
|
442
|
+
--lineNumber; // codeMirror uses 0-based line numbers
|
|
443
|
+
errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
|
|
444
|
+
//codeMirror.scrollIntoView({line: lineNumber});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function setErrorMessage(message)
|
|
448
|
+
{
|
|
449
|
+
// prevent overwriting an existing error message
|
|
450
|
+
const errorMessageIsVisible = textareaError.style.display == 'block';
|
|
451
|
+
if (!errorMessageIsVisible)
|
|
452
|
+
setMessage(message, textareaError);
|
|
453
|
+
}
|
|
454
|
+
function unsetErrorMessage() { unsetMessage(textareaError); clearErrorLine(); }
|
|
455
|
+
function setConsoleMessage(message) { setMessage(message, textareaConsole, false); }
|
|
456
|
+
function unsetConsoleMessage() { unsetMessage(textareaConsole); consoleAutoScroll = true; }
|
|
457
|
+
function onScrollConsole()
|
|
458
|
+
{
|
|
459
|
+
// only auto scroll console is it is scrolled to bottom
|
|
460
|
+
const tolerance = 5; // pixels of tolerance
|
|
461
|
+
consoleAutoScroll = Math.abs(textareaConsole.scrollTop - (textareaConsole.scrollHeight - textareaConsole.clientHeight)) < tolerance;
|
|
462
|
+
}
|
|
463
|
+
textareaConsole.addEventListener('scroll', onScrollConsole);
|
|
464
|
+
|
|
465
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
466
|
+
// setup code mirror
|
|
467
|
+
|
|
468
|
+
const defaultTheme = 'littlejs';
|
|
469
|
+
const defaultFontSize = '16px';
|
|
470
|
+
const themes =
|
|
471
|
+
[
|
|
472
|
+
'littlejs',
|
|
473
|
+
'3024-night',
|
|
474
|
+
'abcdef',
|
|
475
|
+
'ambiance',
|
|
476
|
+
'blackboard',
|
|
477
|
+
'monokai',
|
|
478
|
+
'duotone-light',
|
|
479
|
+
'icecoder',
|
|
480
|
+
'lesser-dark',
|
|
481
|
+
'night',
|
|
482
|
+
'yonce'
|
|
483
|
+
];
|
|
484
|
+
|
|
485
|
+
// Load saved preferences from localStorage
|
|
486
|
+
const savedTheme = localStorage.getItem('littleJS-theme') || defaultTheme;
|
|
487
|
+
const savedFontSize = localStorage.getItem('littleJS-fontSize') || defaultFontSize;
|
|
488
|
+
|
|
489
|
+
for (const theme of themes)
|
|
490
|
+
{
|
|
491
|
+
if (theme != 'littlejs')
|
|
492
|
+
addCodeMirrorElement(`theme/${theme}.min.css`, 'link', 'stylesheet');
|
|
493
|
+
const o = new Option(theme);
|
|
494
|
+
o.selected = theme == savedTheme;
|
|
495
|
+
selectTheme.add(o);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Set the saved font size
|
|
499
|
+
selectFontSize.value = savedFontSize;
|
|
500
|
+
textareaCode.style.fontSize = savedFontSize;
|
|
501
|
+
addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet');
|
|
502
|
+
addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
|
|
503
|
+
addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
|
|
504
|
+
addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
|
|
505
|
+
initCodeMirror();
|
|
506
|
+
|
|
507
|
+
function initCodeMirror()
|
|
508
|
+
{
|
|
509
|
+
const textareaCode = document.getElementById('textareaCode');
|
|
510
|
+
codeMirror = CodeMirror.fromTextArea(textareaCode,
|
|
511
|
+
{
|
|
512
|
+
theme: savedTheme,
|
|
513
|
+
indentUnit: 4,
|
|
514
|
+
mode: codeIsJS ? 'javascript' : 'text',
|
|
515
|
+
lineNumbers: true,
|
|
516
|
+
lineWrapping: true,
|
|
517
|
+
matchBrackets: true,
|
|
518
|
+
});
|
|
519
|
+
codeMirror.on('change', codeInput);
|
|
520
|
+
loadTheme();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function addCodeMirrorElement(filename, type, rel)
|
|
524
|
+
{
|
|
525
|
+
// add element for code mirror
|
|
526
|
+
const e = document.createElement(type);
|
|
527
|
+
e.rel = rel;
|
|
528
|
+
filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/' + filename;
|
|
529
|
+
if (type == 'link')
|
|
530
|
+
e.href = filename;
|
|
531
|
+
else
|
|
532
|
+
e.src = filename;
|
|
533
|
+
e.crossOrigin = 'anonymous';
|
|
534
|
+
document.head.appendChild(e);
|
|
535
|
+
return e;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function loadTheme()
|
|
300
539
|
{
|
|
301
|
-
|
|
302
|
-
|
|
540
|
+
const theme = selectTheme.value;
|
|
541
|
+
const size = selectFontSize.value;
|
|
542
|
+
|
|
543
|
+
// Save preferences to localStorage
|
|
544
|
+
localStorage.setItem('littleJSExamples_theme', theme);
|
|
545
|
+
localStorage.setItem('littleJSExamples_fontSize', size);
|
|
546
|
+
|
|
547
|
+
// Apply the theme and font size
|
|
548
|
+
textareaCode.style.fontSize = size;
|
|
549
|
+
if (codeMirror)
|
|
550
|
+
{
|
|
551
|
+
codeMirror.getWrapperElement().style.fontSize = size;
|
|
552
|
+
codeMirror.setOption('theme', theme);
|
|
553
|
+
}
|
|
303
554
|
}
|
|
304
555
|
|
|
305
556
|
</script>
|
|
306
557
|
</body>
|
|
307
|
-
</html>
|
|
558
|
+
</html>
|