littlejsengine 1.12.6 → 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/littlejs.d.ts +387 -177
- package/dist/littlejs.esm.js +5934 -5294
- package/dist/littlejs.esm.min.js +4 -1
- package/dist/littlejs.js +5887 -5263
- package/dist/littlejs.min.js +4 -1
- package/dist/littlejs.release.js +5426 -4829
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +7 -6
- package/examples/box2d/index.html +1 -1
- package/examples/box2d/scenes.js +7 -7
- 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 +396 -153
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +1 -1
- package/examples/platformer/game.js +4 -4
- 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 +23 -3
- package/examples/shorts/box2dCar.js +8 -12
- 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/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 +4 -7
- package/examples/starter/build.bat +6 -1
- package/examples/starter/game.js +2 -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 +8 -24
- package/examples/uiSystem/index.html +1 -1
- package/package.json +1 -1
- package/plugins/box2d.js +8 -8
- package/plugins/drawUtilities.js +126 -0
- package/plugins/newgrounds.js +1 -1
- package/plugins/pluginExport.js +7 -1
- package/plugins/postProcess.js +2 -2
- package/plugins/uiSystem.js +153 -65
- package/plugins/zzfxm.js +1 -1
- package/reference.md +10 -10
- package/src/engine.js +50 -29
- package/src/engineAudio.js +14 -5
- package/src/engineBuild.bat +6 -1
- package/src/engineBuild.js +20 -5
- package/src/engineDebug.js +53 -26
- package/src/engineDraw.js +108 -57
- package/src/engineExport.js +21 -9
- package/src/engineInput.js +109 -37
- 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 +182 -130
- package/src/engineWebGL.js +47 -36
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,21 +44,21 @@ 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
64
|
new ExampleInfo('Blending', 'blending.js', 'How to use blending modes'),
|
|
@@ -132,15 +71,27 @@ const exampleList =
|
|
|
132
71
|
new ExampleInfo('Animation', 'animation.js', 'How to animate sprites'),
|
|
133
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'),
|
|
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 ---'),
|
|
135
79
|
new ExampleInfo('Platformer Game', 'platformer.js', 'Platformer jumping game'),
|
|
136
|
-
new ExampleInfo('Top Down Game', 'topDown.js', '
|
|
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('
|
|
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 ---'),
|
|
139
90
|
new ExampleInfo('Box2d Demo', 'box2d.js', 'Demo of the Box2D physics plugin'),
|
|
140
91
|
new ExampleInfo('Box2d Car', 'box2dCar.js', 'Controllable vehicle using the Box2D physics plugin'),
|
|
141
|
-
new ExampleInfo('Post Processing', 'postProcess.js', '
|
|
92
|
+
new ExampleInfo('Post Processing', 'postProcess.js', 'Post processing plugin demo'),
|
|
142
93
|
new ExampleInfo('UI System Plugin', 'uiSystem.js', 'Simple example of LittleJS UI plugin'),
|
|
143
|
-
|
|
94
|
+
new ExampleInfo('Nine Slice', 'nineSlice.js', 'Demo of nine slice and three slice drawing'),
|
|
144
95
|
new ExampleInfo('--- FULL EXAMPLES ---'),
|
|
145
96
|
new ExampleInfo('Starter', 'starter', 'Clean starter project', true),
|
|
146
97
|
new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Tutorial Breakout example', true),
|
|
@@ -150,25 +101,23 @@ const exampleList =
|
|
|
150
101
|
new ExampleInfo('Stress Test', 'stress', 'Max sprite/object test and music system demo', true),
|
|
151
102
|
new ExampleInfo('Box2D Plugin', 'box2d', 'Demo of the Box2D physics plugin', true),
|
|
152
103
|
new ExampleInfo('HTML Menus', 'htmlMenu', 'Shows how to combine HTML with LittleJS', true),
|
|
153
|
-
new ExampleInfo('UI System Plugin', 'uiSystem', '
|
|
154
|
-
// Add more examples here!
|
|
104
|
+
new ExampleInfo('UI System Plugin', 'uiSystem', 'How to use LittleJS UI plugin', true),
|
|
155
105
|
];
|
|
156
106
|
|
|
157
107
|
///////////////////////////////////////////////////////////////////////////////
|
|
158
108
|
|
|
159
|
-
|
|
109
|
+
// global variables
|
|
110
|
+
let iframeExample, inputTimeout, codeMirror, consoleAutoScroll, errorLineMarker, codeIsJS;
|
|
160
111
|
|
|
161
112
|
// load the examples into the list
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
const text = example.name + (example.description?' - ' + example.description : '');
|
|
165
|
-
const o = new Option(text);
|
|
166
|
-
o.disabled = !example.filename;
|
|
167
|
-
selectExample.add(o);
|
|
168
|
-
}
|
|
113
|
+
filterExamples();
|
|
169
114
|
|
|
170
115
|
// set the selected example from the URL hash
|
|
171
|
-
|
|
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;
|
|
172
121
|
setExample();
|
|
173
122
|
|
|
174
123
|
onresize = () =>
|
|
@@ -179,55 +128,67 @@ onresize = () =>
|
|
|
179
128
|
let h = w / aspect | 0;
|
|
180
129
|
iframeContainer.style.width = w + 'px';
|
|
181
130
|
iframeContainer.style.height = h + 'px';
|
|
131
|
+
// fix code mirror width
|
|
132
|
+
container3.style.width = w-30 + 'px';
|
|
182
133
|
}
|
|
183
134
|
onresize();
|
|
184
135
|
|
|
185
136
|
///////////////////////////////////////////////////////////////////////////////
|
|
137
|
+
// setting examples
|
|
186
138
|
|
|
187
139
|
function setExample()
|
|
188
140
|
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let
|
|
192
|
-
|
|
193
|
-
|
|
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];
|
|
194
155
|
exampleInfo.innerText = example.name + (example.description ? ' - ' + example.description : '');
|
|
195
156
|
const filename = 'examples/' + example.filename;
|
|
196
157
|
exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
|
|
197
158
|
exampleLink.innerText = filename;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function codeInput()
|
|
203
|
-
{
|
|
204
|
-
if (!checkboxLiveEdit.checked)
|
|
205
|
-
return;
|
|
206
|
-
|
|
207
|
-
// debounce input
|
|
208
|
-
clearTimeout(inputTimeout);
|
|
209
|
-
inputTimeout = setTimeout(() => setCode(textareaCode.value), 500);
|
|
159
|
+
location.hash = example.filename;
|
|
160
|
+
loadFile(example.filename, example.largeExample);
|
|
210
161
|
}
|
|
211
162
|
|
|
212
|
-
async function loadFile(filename,
|
|
163
|
+
async function loadFile(filename, largeExample)
|
|
213
164
|
{
|
|
214
|
-
|
|
215
|
-
|
|
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)
|
|
216
177
|
{
|
|
217
|
-
if (iframeExample)
|
|
218
|
-
iframeContainer.removeChild(iframeExample);
|
|
219
178
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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)
|
|
224
184
|
{
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
textareaCode.value = 'Editor code view not available for large examples.\n';
|
|
185
|
+
codeMirror.off('change', codeInput);
|
|
186
|
+
codeMirror.setOption('mode', 'text');
|
|
187
|
+
codeMirror.setValue(text);
|
|
229
188
|
}
|
|
230
|
-
|
|
189
|
+
else
|
|
190
|
+
textareaCode.value = text;
|
|
191
|
+
clearTimeout(inputTimeout);
|
|
231
192
|
return;
|
|
232
193
|
}
|
|
233
194
|
|
|
@@ -237,7 +198,19 @@ async function loadFile(filename, fullExample)
|
|
|
237
198
|
if (!response.ok)
|
|
238
199
|
throw new Error('Could not load file: ' + filename);
|
|
239
200
|
const text = await response.text();
|
|
240
|
-
|
|
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);
|
|
241
214
|
}
|
|
242
215
|
catch (error)
|
|
243
216
|
{
|
|
@@ -245,17 +218,77 @@ async function loadFile(filename, fullExample)
|
|
|
245
218
|
}
|
|
246
219
|
}
|
|
247
220
|
|
|
248
|
-
function
|
|
221
|
+
function filterExamples(reset=0)
|
|
222
|
+
{
|
|
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++)
|
|
232
|
+
{
|
|
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))
|
|
238
|
+
{
|
|
239
|
+
const o = new Option(text);
|
|
240
|
+
o.disabled = !example.filename;
|
|
241
|
+
o.originalIndex = i;
|
|
242
|
+
selectExample.add(o);
|
|
243
|
+
}
|
|
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
|
+
}
|
|
268
|
+
|
|
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);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function setCode(code, textFilename)
|
|
249
277
|
{
|
|
278
|
+
const largeExample = !!textFilename;
|
|
250
279
|
clearTimeout(inputTimeout);
|
|
251
280
|
if (iframeExample)
|
|
252
281
|
iframeContainer.removeChild(iframeExample);
|
|
253
282
|
|
|
254
|
-
|
|
283
|
+
unsetErrorMessage();
|
|
284
|
+
unsetConsoleMessage();
|
|
255
285
|
iframeExample = document.createElement('iframe');
|
|
256
286
|
iframeContainer.appendChild(iframeExample);
|
|
257
|
-
iframeExample.onload = ()
|
|
287
|
+
iframeExample.onload = ()=>
|
|
258
288
|
{
|
|
289
|
+
if (largeExample)
|
|
290
|
+
return;
|
|
291
|
+
|
|
259
292
|
const iframeContent = iframeExample.contentWindow;
|
|
260
293
|
const iframeDocument = iframeContent.document;
|
|
261
294
|
|
|
@@ -266,24 +299,68 @@ function setCode(code, largeExample)
|
|
|
266
299
|
if (lineno)
|
|
267
300
|
text += ` (Line:${lineno}, Column:${colno})`
|
|
268
301
|
if (error && error.stack)
|
|
269
|
-
text += `\n
|
|
302
|
+
text += `\n` + error.stack;
|
|
270
303
|
setErrorMessage(text);
|
|
304
|
+
setErrorLine(lineno);
|
|
271
305
|
}
|
|
272
306
|
iframeContent.onunhandledrejection = (event) =>
|
|
273
307
|
{
|
|
274
308
|
let text = event.reason;
|
|
275
|
-
if (event.reason.stack)
|
|
309
|
+
if (event.reason && event.reason.stack)
|
|
310
|
+
{
|
|
276
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
|
+
}
|
|
277
317
|
setErrorMessage(text);
|
|
278
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
|
|
279
335
|
const originalAssert = iframeContent.console.assert;
|
|
280
336
|
iframeContent.console.assert = function (condition, output)
|
|
281
337
|
{
|
|
282
|
-
|
|
338
|
+
if (!condition)
|
|
283
339
|
setErrorMessage('Assertion failed!\n' + (output || '') + '\n' + (new Error).stack);
|
|
284
340
|
originalAssert.apply(this, arguments);
|
|
285
341
|
};
|
|
286
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
|
+
|
|
287
364
|
// create a script element that overrides the default functions
|
|
288
365
|
const overrideScript = iframeDocument.createElement('script');
|
|
289
366
|
iframeDocument.body.appendChild(overrideScript);
|
|
@@ -293,23 +370,189 @@ function setCode(code, largeExample)
|
|
|
293
370
|
return; // stop if script error
|
|
294
371
|
|
|
295
372
|
// start LittleJS engine
|
|
296
|
-
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
|
+
}
|
|
297
377
|
|
|
298
|
-
|
|
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)
|
|
299
411
|
{
|
|
300
|
-
|
|
301
|
-
|
|
412
|
+
// limit max lines, prevents slowdown from too many lines
|
|
413
|
+
const excessLines = lines.length - maxConsoleLines;
|
|
414
|
+
element.value = lines.slice(excessLines).join('\n');
|
|
302
415
|
}
|
|
416
|
+
|
|
417
|
+
// auto scroll to bottom only if user hasn't manually scrolled
|
|
418
|
+
if (consoleAutoScroll)
|
|
419
|
+
element.scrollTop = element.scrollHeight;
|
|
303
420
|
}
|
|
304
|
-
iframeExample.src = 'shorts/base.html';
|
|
305
421
|
}
|
|
306
422
|
|
|
307
|
-
function
|
|
423
|
+
function unsetMessage(element)
|
|
424
|
+
{
|
|
425
|
+
element.style.display = 'none';
|
|
426
|
+
element.value = '';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function clearErrorLine()
|
|
308
430
|
{
|
|
309
|
-
|
|
310
|
-
|
|
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()
|
|
539
|
+
{
|
|
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
|
+
}
|
|
311
554
|
}
|
|
312
555
|
|
|
313
556
|
</script>
|
|
314
557
|
</body>
|
|
315
|
-
</html>
|
|
558
|
+
</html>
|