index.html 18.1 KB
Newer Older
1
2
<!DOCTYPE html>
<html>
3
4
5
	<head>
		<meta charset="utf-8">
		<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
6
7
8
		<title>Purr Data</title>
		<meta name="description" content="Purr Data compiled for web with Emscripten" />
		<meta name="keywords" content="purr-data" />
9
		<meta name="generator" content="emsripten" />
10
	
11
		<link rel="stylesheet" type="text/css" href="./css/dejavu.css">
12
		<link id="page_style" rel="stylesheet" type="text/css" href="./css/default.css">
13
		<link rel="stylesheet" type="text/css" href="./css/webapp/webapp.css">
14
15
	</head>

Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
16
17
18
19
20
21
	<body id="container">
		<!-- Modal -->
		<div class="modal fade" id="loading-modal" tabindex="-1" role="dialog" aria-hidden="true">
			<div class="modal-dialog modal-dialog-centered" role="document">
				<div class="modal-content">
					<div class="modal-body text-center">
22
						<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
23
24
							<span class="sr-only">Loading...</span>
						  </div>
Zack Lee's avatar
Zack Lee committed
25
						  <h3>Loading Purr Data...</h3>
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
26
27
28
29
30
					</div>
				</div>
			</div>
		</div>

Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
31
		<div id="container-app" class="container-fluid">
32
			<!-- Menu -->
33
			<nav id="menu"></nav>
34
35

			<!-- Content -->
36
			<div class="d-flex content-webapp" id="content">
37
38

			<!-- Sidebar -->
39
			
40
			<div class="card" id="sidebar">
41
				<div class="card-body text-center" id="sidebar-body">
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
42
43
					<div id="pd-info">
						<img src="https://agraef.github.io/purr-data-intro/purr.png">			
Zack Lee's avatar
Zack Lee committed
44
						<h4>Purr Data</h4>
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
45
46
47
					</div>
					<hr>
					<div id="sidebar-body-dialog"></div>
48
49
50
51
52
53
54
55
56
					<hr>
					<div class="d-flex">
						<i class="fa fa fa-volume-up text-primary" aria-hidden="true"></i>
						<h5>DSP</h5>
						<label class="dsp_toggle">
							<input type="checkbox" id="dsp_control" name="dsp_control" value="on" />
							<span></span>
						</label>

57
					</div>
58
59
60
61
62
63
64
65
66
					<hr>
					<div>
						<div class="d-flex">
							<i class="fa fa-folder-open-o text-primary" aria-hidden="true"></i>
							<h5>Files</h5>
							<i class="fa fa-refresh ml-auto text-primary" id="reload-i" aria-hidden="true"
							onclick="pdbundle.pdgui.update_file_ls()"></i>

						</div>
67

68
						<div class="text-left" id="sidebar-files">
69
							<ul id="file_ls"></ul>
70
71
72
73
							<div class="text-secondary text-center" id="file_ls_empty">
								<p>No files found yet</p>
							</div>

74
						</div>
Zack Lee's avatar
Zack Lee committed
75
					</div>
76
77
78
79
80
				</div>
			</div>

			<!-- Canvas/Pd Windows -->
			<div class="d-flex flex-column flex-fill" id="windows">
Zack Lee's avatar
Zack Lee committed
81

82
				<!-- Canvas Container -->
83
84
				<div class="flex-grow-1 card" id="canvas-content">
					<div class="d-flex" id="canvas-container">
85
86
87
88
89
					</div>
				</div>

				<!-- Console window -->
				<div class="card" id="console-window">
90
91
92
93
					<div class="card-header d-flex justify-content-between">
						<h5>
							<a data-toggle="collapse" data-parent="#windows" href="#console_bottom">Console</a>
						</h5>
Zack Lee's avatar
Zack Lee committed
94
95

						<div class="console-find-webapp" id="console_find" style="display:none;">
96
							<div class="d-flex justify-content-end">
Zack Lee's avatar
Zack Lee committed
97
								<label><input type="text" id="console_find_text" name="console_find_text"
98
									defaultValue="Search in Console" style="width:10em;" />
Zack Lee's avatar
Zack Lee committed
99
								</label>
100
								<label class="highlight-find">Highlight All
Zack Lee's avatar
Zack Lee committed
101
102
103
104
									<input type="checkbox" id="console_find_highlight" name="console_find_highlight"
										onchange="console_find_highlight_all(this);" />
								</label>
							</div>
105
						</div>
106

107
108
					</div>

109
110
111
			  
					<div class="collapse show" id="console_bottom">
					  <div class="card-block">
Zack Lee's avatar
Zack Lee committed
112
113
114
						<div id="printout">
							<pre id="p1" style="white-space: pre-wrap;"></pre>
						</div>
115
					  </div>
116
117
118
119
					</div>
				</div>
			</div>
		</div>
Zack Lee's avatar
Zack Lee committed
120
121
	</div>
	</div>
122

Zack Lee's avatar
Zack Lee committed
123
	<script>
124

Zack Lee's avatar
Zack Lee committed
125
126
127
128
129
130
		//--------------------- emscripten ----------------------------
		var Module
			= {
			preRun: []
			, postRun: []
			, print: function (e) {
131
132
				1 < arguments.length && (e = Array.prototype.slice.call(arguments).join(" "));
				console.log(e);
Zack Lee's avatar
Zack Lee committed
133
134
			}
			, printErr: function (e) {
135
136
				1 < arguments.length && (e = Array.prototype.slice.call(arguments).join(" "));
				console.error(e)
Zack Lee's avatar
Zack Lee committed
137
138
139
			}
			, pd: {} // make pd object accessible from outside of the scope
			, mainInit: function () { // called after Module is ready
140
				Module.pd = new Module.Pd(); // instantiate Pd object
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
141
142
143
144
145
				// Remove modal after everything loaded
				$(document).ready(function(){
					$("#loading-modal").modal('hide');
				});

146
				if (typeof Module.pd != "object") {
147
					alert("Pd: failed to instantiate pd object");
Zack Lee's avatar
Zack Lee committed
148
149
150
					console.error("Pd: failed to instantiate pd object");
					Module.mainExit();
					return;
151
152
153
				}
				var pd = Module.pd;
				pd.setNoGui(false); // set to true if you don't use the pd's gui
154
155
156
				// initializing webapp
				initialize_webapp()

157
158
159
160
161

				// create an AudioContext
				var isWebAudioSupported = false;
				var audioContextList = [];
				(function () {
Zack Lee's avatar
Zack Lee committed
162
163
164
165
166
167
168
169
170
171
172
					var AudioContext = self.AudioContext || self.webkitAudioContext || false;
					if (AudioContext) {
						isWebAudioSupported = true;
						self.AudioContext = new Proxy(AudioContext, {
							construct(target, args) {
								var result = new target(...args);
								audioContextList.push(result);
								return result;
							}
						});
					}
173
174
				})();
				if (isWebAudioSupported) {
Zack Lee's avatar
Zack Lee committed
175
					console.log("Audio: successfully enabled");
176
177
				}
				else {
Zack Lee's avatar
Zack Lee committed
178
179
180
181
					alert("The Web Audio API is not supported in this browser.");
					console.error("Audio: failed to use the web audio");
					Module.mainExit();
					return;
182
				}
Zack Lee's avatar
Zack Lee committed
183

184
185
186
				// check if the web midi library exists and is supported
				var isWebMidiSupported = false;
				if (typeof WebMidi != "object") {
187
188
					alert("Midi: failed to find the 'WebMidi' object");
					console.error("Midi: failed to find the 'WebMidi' object");
Zack Lee's avatar
Zack Lee committed
189
190
					Module.mainExit();
					return;
191
				}
Zack Lee's avatar
Zack Lee committed
192

193
194
195
				// array of enabled midi device ids (without duplicates)
				var midiInIds = [];
				var midiOutIds = [];
Zack Lee's avatar
Zack Lee committed
196

197
198
199
				// 10 input, 10 output device numbers to send with "pd midi-dialog"
				// 0: no device, 1: first available device, 2: second available device...
				var midiarr = [];
Zack Lee's avatar
Zack Lee committed
200

201
202
				// enable midi
				WebMidi.enable(function (err) {
Zack Lee's avatar
Zack Lee committed
203
204
					if (err) {
						// if the browser doesn't support web midi, one can still use pd without it
205
						alert("The Web MIDI API is not supported in this browser.\nPlease check: https://github.com/djipco/webmidi#browser-support");
Zack Lee's avatar
Zack Lee committed
206
						console.error("Midi: failed to enable midi", err);
207
					}
Zack Lee's avatar
Zack Lee committed
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
					else {
						isWebMidiSupported = true;
						console.log("Midi: successfully enabled");

						// select all available input/output devices as default
						midiInIds = [];
						midiOutIds = [];
						for (var i = 0; i < WebMidi.inputs.length; i++) {
							midiInIds.push(WebMidi.inputs[i].id);
						}
						for (var i = 0; i < WebMidi.outputs.length; i++) {
							midiOutIds.push(WebMidi.outputs[i].id);
						}
						midiarr = [];
						for (var i = 0; i < 10; i++) {
							var devno = i < midiInIds.length ? i + 1 : 0;
							midiarr.push(devno);
						}
						for (var i = 0; i < 10; i++) {
							var devno = i < midiOutIds.length ? i + 1 : 0;
							midiarr.push(devno);
						}
						// called whenever input/output devices connection status changes
						function onConnectionChanged() {
							console.log("Midi: connection status changed");
							pdbundle.pdgui.pdsend("pd midi-dialog", midiarr.join(" ")); // send message to pd
						}
						// make sure we get only one callback at a time
						var timerId;
						WebMidi.addListener("connected", function (e) {
							clearTimeout(timerId);
							timerId = setTimeout(() => onConnectionChanged(), 100);
						});
						WebMidi.addListener("disconnected", function (e) {
							clearTimeout(timerId);
							timerId = setTimeout(() => onConnectionChanged(), 100);
						});
245
246
					}
				}, false); // not use sysex
Zack Lee's avatar
Zack Lee committed
247

248
249
				// reinit pd (called by "pd audio-dialog" message)
				Module.Pd.reinit = function (newinchan, newoutchan, newrate) {
Zack Lee's avatar
Zack Lee committed
250
251
252
253
254
255
256
257
258
259
260
					if (pd.init(newinchan, newoutchan, newrate, pd.getTicksPerBuffer())) {

						// print obtained settings
						console.log("Pd: successfully reinitialized");
						console.log("Pd: audio input channels: " + pd.getNumInChannels());
						console.log("Pd: audio output channels: " + pd.getNumOutChannels());
						console.log("Pd: audio sample rate: " + pd.getSampleRate());
						console.log("Pd: audio ticks per buffer: " + pd.getTicksPerBuffer());
					}
					else {
						// failed to reinit pd
261
						alert("Pd: failed to reinitialize pd");
Zack Lee's avatar
Zack Lee committed
262
263
264
						console.error("Pd: failed to reinitialize pd");
						Module.mainExit();
					}
265
				}
Zack Lee's avatar
Zack Lee committed
266

267
268
269
270
				// open midi (called by "pd midi-dialog" message)
				// receives input/output arrays of only selected devices
				// 0: first available device, 1: second available device...
				Module.Pd.openMidi = function (midiinarr, midioutarr) {
Zack Lee's avatar
Zack Lee committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
					if (!isWebMidiSupported)
						return;

					// if the selected device doesn't exist, use first available device instead
					midiinarr = midiinarr.map(item => item >= WebMidi.inputs.length || item < 0 ? 0 : item);
					midioutarr = midioutarr.map(item => item >= WebMidi.outputs.length || item < 0 ? 0 : item);

					// save this settings so we can check again later when connection status changes 
					midiarr = [];
					for (var i = 0; i < 10; i++) {
						var devno = i < midiinarr.length ? midiinarr[i] + 1 : 0;
						midiarr.push(devno);
					}
					for (var i = 0; i < 10; i++) {
						var devno = i < midioutarr.length ? midioutarr[i] + 1 : 0;
						midiarr.push(devno);
					}
					// remove duplicates and convert device numbers to ids
					midiinarr = Array.from(new Set(midiinarr));
					midioutarr = Array.from(new Set(midioutarr));
					midiInIds = midiinarr.map(item => WebMidi.inputs[item].id);
					midiOutIds = midioutarr.map(item => WebMidi.outputs[item].id);

					// print all selected devices to the console
					for (var i = 0; i < midiInIds.length; i++) {
						var input = WebMidi.getInputById(midiInIds[i]);
						console.log("Midi: input" + (i + 1) + ": " + input.name);
					}
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						console.log("Midi: output" + (i + 1) + ": " + output.name);
					}
					// receive midi messages from WebMidi and forward them to pd input
					function receiveNoteOn(e) {
						pd.sendNoteOn(e.channel, e.note.number, e.rawVelocity);
					}

					function receiveNoteOff(e) {
						pd.sendNoteOn(e.channel, e.note.number, 0);
					}

					function receiveControlChange(e) {
						pd.sendControlChange(e.channel, e.controller.number, e.value);
					}

					function receiveProgramChange(e) {
						pd.sendProgramChange(e.channel, e.value + 1);
					}

					function receivePitchBend(e) {
						// [bendin] takes 0 - 16383 while [bendout] returns -8192 - 8192
						pd.sendPitchBend(e.channel, e.value * 8192 + 8192);
					}

					function receiveAftertouch(e) {
						pd.sendAftertouch(e.channel, e.value * 127);
					}

					function receivePolyAftertouch(e) {
						pd.sendPolyAftertouch(e.channel, e.note.number, e.value * 127);
					}

					for (var i = 0; i < midiInIds.length; i++) {
						var input = WebMidi.getInputById(midiInIds[i]);
						if (input) {
							input.removeListener(); // remove all added listeners
							input.addListener("noteon", "all", receiveNoteOn);
							input.addListener("noteoff", "all", receiveNoteOff);
							input.addListener("controlchange", "all", receiveControlChange);
							input.addListener("programchange", "all", receiveProgramChange);
							input.addListener("pitchbend", "all", receivePitchBend);
							input.addListener("channelaftertouch", "all", receiveAftertouch);
							input.addListener("keyaftertouch", "all", receivePolyAftertouch);
						}
345
346
					}
				}
Zack Lee's avatar
Zack Lee committed
347

348
349
				// get midi in device name
				Module.Pd.getMidiInDeviceName = function (devno) {
Zack Lee's avatar
Zack Lee committed
350
351
352
353
354
355
356
357
358
359
					if (!isWebMidiSupported)
						return;
					if (devno >= WebMidi.inputs.length || devno < 0) {
						devno = 0;
					}
					var name = WebMidi.inputs[devno].name;
					var lengthBytes = lengthBytesUTF8(name) + 1;
					var stringOnWasmHeap = _malloc(lengthBytes);
					stringToUTF8(name, stringOnWasmHeap, lengthBytes);
					return stringOnWasmHeap;
360
				}
Zack Lee's avatar
Zack Lee committed
361

362
363
				// get midi out device name
				Module.Pd.getMidiOutDeviceName = function (devno) {
Zack Lee's avatar
Zack Lee committed
364
365
366
367
368
369
370
371
372
373
					if (!isWebMidiSupported)
						return;
					if (devno >= WebMidi.inputs.length || devno < 0) {
						devno = 0;
					}
					var name = WebMidi.outputs[devno].name;
					var lengthBytes = lengthBytesUTF8(name) + 1;
					var stringOnWasmHeap = _malloc(lengthBytes);
					stringToUTF8(name, stringOnWasmHeap, lengthBytes);
					return stringOnWasmHeap;
374
				}
Zack Lee's avatar
Zack Lee committed
375

376
377
				// receive gui commands (only called in gui mode)
				Module.Pd.receiveCommandBuffer = function (data) {
Zack Lee's avatar
Zack Lee committed
378
379
380
					var command_buffer = {
						next_command: ""
					};
381
					pdbundle.pdgui.perfect_parser(data, command_buffer);
382
				}
Zack Lee's avatar
Zack Lee committed
383

384
385
				// receive print messages (only called in no gui mode)
				Module.Pd.receivePrint = function (s) {
Zack Lee's avatar
Zack Lee committed
386
					console.log(s);
387
				}
Zack Lee's avatar
Zack Lee committed
388

389
390
391
				// receive from pd's subscribed sources
				Module.Pd.receiveBang = function (source) {
				}
Zack Lee's avatar
Zack Lee committed
392

393
394
				Module.Pd.receiveFloat = function (source, value) {
				}
Zack Lee's avatar
Zack Lee committed
395

396
397
				Module.Pd.receiveSymbol = function (source, symbol) {
				}
Zack Lee's avatar
Zack Lee committed
398

399
400
				Module.Pd.receiveList = function (source, list) {
				}
Zack Lee's avatar
Zack Lee committed
401

402
403
				Module.Pd.receiveMessage = function (source, symbol, list) {
				}
Zack Lee's avatar
Zack Lee committed
404

405
406
				// receive midi messages from pd and forward them to WebMidi output
				Module.Pd.receiveNoteOn = function (channel, pitch, velocity) {
Zack Lee's avatar
Zack Lee committed
407
408
409
410
411
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.playNote(pitch, channel, { rawVelocity: true, velocity: velocity });
						}
412
413
					}
				}
Zack Lee's avatar
Zack Lee committed
414

415
				Module.Pd.receiveControlChange = function (channel, controller, value) {
Zack Lee's avatar
Zack Lee committed
416
417
418
419
420
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendControlChange(controller, value, channel);
						}
421
422
					}
				}
Zack Lee's avatar
Zack Lee committed
423

424
				Module.Pd.receiveProgramChange = function (channel, value) {
Zack Lee's avatar
Zack Lee committed
425
426
427
428
429
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendProgramChange(value, channel);
						}
430
431
					}
				}
Zack Lee's avatar
Zack Lee committed
432

433
				Module.Pd.receivePitchBend = function (channel, value) {
Zack Lee's avatar
Zack Lee committed
434
435
436
437
438
439
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							// [bendin] takes 0 - 16383 while [bendout] returns -8192 - 8192
							output.sendPitchBend(value / 8192, channel);
						}
440
441
					}
				}
Zack Lee's avatar
Zack Lee committed
442

443
				Module.Pd.receiveAftertouch = function (channel, value) {
Zack Lee's avatar
Zack Lee committed
444
445
446
447
448
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendChannelAftertouch(value / 127, channel);
						}
449
450
					}
				}
Zack Lee's avatar
Zack Lee committed
451

452
				Module.Pd.receivePolyAftertouch = function (channel, pitch, value) {
Zack Lee's avatar
Zack Lee committed
453
454
455
456
457
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendKeyAftertouch(pitch, channel, value / 127);
						}
458
459
					}
				}
Zack Lee's avatar
Zack Lee committed
460

461
462
				Module.Pd.receiveMidiByte = function (port, byte) {
				}
Zack Lee's avatar
Zack Lee committed
463

464
465
466
467
468
				// default audio settings
				var numInChannels = 0; // supported values: 0, 1, 2
				var numOutChannels = 2; // supported values: 1, 2
				var sampleRate = 44100; // might change depending on browser/system
				var ticksPerBuffer = 32; // supported values: 4, 8, 16, 32, 64, 128, 256
Zack Lee's avatar
Zack Lee committed
469

470
471
				// open audio devices, init pd
				if (pd.init(numInChannels, numOutChannels, sampleRate, ticksPerBuffer)) {
Zack Lee's avatar
Zack Lee committed
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493

					// print obtained settings
					console.log("Pd: successfully initialized");
					console.log("Pd: audio input channels:", pd.getNumInChannels());
					console.log("Pd: audio output channels:", pd.getNumOutChannels());
					console.log("Pd: audio sample rate:", pd.getSampleRate());
					console.log("Pd: audio ticks per buffer:", pd.getTicksPerBuffer());

					// add internals/externals help/search paths
					var helpPath = "purr-data/doc/5.reference";
					var extPath = "purr-data/extra";
					pd.addToHelpPath(helpPath);
					pd.addToSearchPath(extPath);
					pd.addToHelpPath(extPath);
					var dir = FS.readdir(extPath);
					for (var i = 0; i < dir.length; i++) {
						var item = dir[i];
						if (item.charAt(0) != ".") {
							var path = extPath + "/" + item;
							pd.addToSearchPath(path); // externals can be created without path prefix
							pd.addToHelpPath(path);
						}
494
495
496
					}
				}
				else { // failed to init pd
497
					alert("Pd: failed to initialize pd");
Zack Lee's avatar
Zack Lee committed
498
499
					console.error("Pd: failed to initialize pd");
					Module.mainExit();
500
				}
Zack Lee's avatar
Zack Lee committed
501
502
503
504
			}
			, mainLoop: function () { // called every frame (use for whatever)
			}
			, mainExit: function () { // this won't be called from emscripten
505
506
				console.error("quiting emscripten...");
				if (typeof Module.pd == "object") {
Zack Lee's avatar
Zack Lee committed
507
508
509
					Module.pd.clear(); // clear pd, close audio devices
					Module.pd.unsubscribeAll(); // unsubscribe all subscribed sources
					Module.pd.delete(); // quit SDL, emscripten
510
511
				}
				if (typeof WebMidi == "object") {
Zack Lee's avatar
Zack Lee committed
512
					WebMidi.disable(); // disable all midi devices
513
514
				}
			}
Zack Lee's avatar
Zack Lee committed
515
		};
516
517
518


			// Add pd module to pd
Zack Lee's avatar
Zack Lee committed
519
520
521
522
523
524
525
526
	</script>

	<!-- Pd Bundle -->
	<script src="dist/bundle.js"></script>

	<script src="index.js"></script>

	<script src="/utils/console_search.js"></script>
527

Zack Lee's avatar
Zack Lee committed
528
529
	<!-- Emscripten backend -->
	<script src="main.js"></script>
530

Zack Lee's avatar
Zack Lee committed
531
532
533
534
	<!-- Utils -->
	<script src="utils/actions.js"></script>
	<!-- <script src="utils/audio_ctx.js"></script>  -->
	<script src="utils/console_find.js"></script>
535

Zack Lee's avatar
Zack Lee committed
536
537
538
	<!-- Components -->
	<script src="components/menu/menu.js"></script>
	<script src="components/canvas/canvas.js"></script>
539
540


Zack Lee's avatar
Zack Lee committed
541
542
543
	<!-- Libs -->
	<!-- Webmidi -->
	<script src="libs/webmidi/webmidi.min.js"></script>
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
544

Zack Lee's avatar
Zack Lee committed
545
546
	<!-- jQuery -->
	<script src="libs/jquery/jquery-3.5.1.min.js"></script>
547

Zack Lee's avatar
Zack Lee committed
548
549
	<!-- jQuery UI -->
	<script src="libs/jquery/jquery-ui.js"></script>
550

Zack Lee's avatar
Zack Lee committed
551
552
	<!-- Fontawesome -->
	<script src="libs/fa/fontawesome_1b8a796d74.js"></script>
553
554


Zack Lee's avatar
Zack Lee committed
555
556
	<!-- Bootstrap -->
	<link rel="stylesheet" href="libs/bootstrap/bootstrap.4.5.0.min.css">
557

Zack Lee's avatar
Zack Lee committed
558
	<script src="libs/bootstrap/popper.1.16.0.min.js"></script>
559

Zack Lee's avatar
Zack Lee committed
560
	<script src="libs/bootstrap/bootstrap.4.5.0.min.js"></script>
561

Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
562
563
564
	<!-- Show modal on load -->
	<script>
		$(document).ready(function(){
565
			$("#loading-modal").modal('show', {backdrop: 'static', keyboard: false},);
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
566
567
		});
	</script>
568
569
570
</body>

</html>