thomwolf HF staff commited on
Commit
3829b28
1 Parent(s): 669cd52

add more code

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
favicon.png ADDED
godot.tools.audio.worklet.js ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*************************************************************************/
2
+ /* audio.worklet.js */
3
+ /*************************************************************************/
4
+ /* This file is part of: */
5
+ /* GODOT ENGINE */
6
+ /* https://godotengine.org */
7
+ /*************************************************************************/
8
+ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
9
+ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
10
+ /* */
11
+ /* Permission is hereby granted, free of charge, to any person obtaining */
12
+ /* a copy of this software and associated documentation files (the */
13
+ /* "Software"), to deal in the Software without restriction, including */
14
+ /* without limitation the rights to use, copy, modify, merge, publish, */
15
+ /* distribute, sublicense, and/or sell copies of the Software, and to */
16
+ /* permit persons to whom the Software is furnished to do so, subject to */
17
+ /* the following conditions: */
18
+ /* */
19
+ /* The above copyright notice and this permission notice shall be */
20
+ /* included in all copies or substantial portions of the Software. */
21
+ /* */
22
+ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
+ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
+ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25
+ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
+ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
+ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
+ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
+ /*************************************************************************/
30
+
31
+ class RingBuffer {
32
+ constructor(p_buffer, p_state, p_threads) {
33
+ this.buffer = p_buffer;
34
+ this.avail = p_state;
35
+ this.threads = p_threads;
36
+ this.rpos = 0;
37
+ this.wpos = 0;
38
+ }
39
+
40
+ data_left() {
41
+ return this.threads ? Atomics.load(this.avail, 0) : this.avail;
42
+ }
43
+
44
+ space_left() {
45
+ return this.buffer.length - this.data_left();
46
+ }
47
+
48
+ read(output) {
49
+ const size = this.buffer.length;
50
+ let from = 0;
51
+ let to_write = output.length;
52
+ if (this.rpos + to_write > size) {
53
+ const high = size - this.rpos;
54
+ output.set(this.buffer.subarray(this.rpos, size));
55
+ from = high;
56
+ to_write -= high;
57
+ this.rpos = 0;
58
+ }
59
+ if (to_write) {
60
+ output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);
61
+ }
62
+ this.rpos += to_write;
63
+ if (this.threads) {
64
+ Atomics.add(this.avail, 0, -output.length);
65
+ Atomics.notify(this.avail, 0);
66
+ } else {
67
+ this.avail -= output.length;
68
+ }
69
+ }
70
+
71
+ write(p_buffer) {
72
+ const to_write = p_buffer.length;
73
+ const mw = this.buffer.length - this.wpos;
74
+ if (mw >= to_write) {
75
+ this.buffer.set(p_buffer, this.wpos);
76
+ this.wpos += to_write;
77
+ if (mw === to_write) {
78
+ this.wpos = 0;
79
+ }
80
+ } else {
81
+ const high = p_buffer.subarray(0, mw);
82
+ const low = p_buffer.subarray(mw);
83
+ this.buffer.set(high, this.wpos);
84
+ this.buffer.set(low);
85
+ this.wpos = low.length;
86
+ }
87
+ if (this.threads) {
88
+ Atomics.add(this.avail, 0, to_write);
89
+ Atomics.notify(this.avail, 0);
90
+ } else {
91
+ this.avail += to_write;
92
+ }
93
+ }
94
+ }
95
+
96
+ class GodotProcessor extends AudioWorkletProcessor {
97
+ constructor() {
98
+ super();
99
+ this.threads = false;
100
+ this.running = true;
101
+ this.lock = null;
102
+ this.notifier = null;
103
+ this.output = null;
104
+ this.output_buffer = new Float32Array();
105
+ this.input = null;
106
+ this.input_buffer = new Float32Array();
107
+ this.port.onmessage = (event) => {
108
+ const cmd = event.data['cmd'];
109
+ const data = event.data['data'];
110
+ this.parse_message(cmd, data);
111
+ };
112
+ }
113
+
114
+ process_notify() {
115
+ if (this.notifier) {
116
+ Atomics.add(this.notifier, 0, 1);
117
+ Atomics.notify(this.notifier, 0);
118
+ }
119
+ }
120
+
121
+ parse_message(p_cmd, p_data) {
122
+ if (p_cmd === 'start' && p_data) {
123
+ const state = p_data[0];
124
+ let idx = 0;
125
+ this.threads = true;
126
+ this.lock = state.subarray(idx, ++idx);
127
+ this.notifier = state.subarray(idx, ++idx);
128
+ const avail_in = state.subarray(idx, ++idx);
129
+ const avail_out = state.subarray(idx, ++idx);
130
+ this.input = new RingBuffer(p_data[1], avail_in, true);
131
+ this.output = new RingBuffer(p_data[2], avail_out, true);
132
+ } else if (p_cmd === 'stop') {
133
+ this.running = false;
134
+ this.output = null;
135
+ this.input = null;
136
+ } else if (p_cmd === 'start_nothreads') {
137
+ this.output = new RingBuffer(p_data[0], p_data[0].length, false);
138
+ } else if (p_cmd === 'chunk') {
139
+ this.output.write(p_data);
140
+ }
141
+ }
142
+
143
+ static array_has_data(arr) {
144
+ return arr.length && arr[0].length && arr[0][0].length;
145
+ }
146
+
147
+ process(inputs, outputs, parameters) {
148
+ if (!this.running) {
149
+ return false; // Stop processing.
150
+ }
151
+ if (this.output === null) {
152
+ return true; // Not ready yet, keep processing.
153
+ }
154
+ const process_input = GodotProcessor.array_has_data(inputs);
155
+ if (process_input) {
156
+ const input = inputs[0];
157
+ const chunk = input[0].length * input.length;
158
+ if (this.input_buffer.length !== chunk) {
159
+ this.input_buffer = new Float32Array(chunk);
160
+ }
161
+ if (!this.threads) {
162
+ GodotProcessor.write_input(this.input_buffer, input);
163
+ this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer });
164
+ } else if (this.input.space_left() >= chunk) {
165
+ GodotProcessor.write_input(this.input_buffer, input);
166
+ this.input.write(this.input_buffer);
167
+ } else {
168
+ this.port.postMessage('Input buffer is full! Skipping input frame.');
169
+ }
170
+ }
171
+ const process_output = GodotProcessor.array_has_data(outputs);
172
+ if (process_output) {
173
+ const output = outputs[0];
174
+ const chunk = output[0].length * output.length;
175
+ if (this.output_buffer.length !== chunk) {
176
+ this.output_buffer = new Float32Array(chunk);
177
+ }
178
+ if (this.output.data_left() >= chunk) {
179
+ this.output.read(this.output_buffer);
180
+ GodotProcessor.write_output(output, this.output_buffer);
181
+ if (!this.threads) {
182
+ this.port.postMessage({ 'cmd': 'read', 'data': chunk });
183
+ }
184
+ } else {
185
+ this.port.postMessage('Output buffer has not enough frames! Skipping output frame.');
186
+ }
187
+ }
188
+ this.process_notify();
189
+ return true;
190
+ }
191
+
192
+ static write_output(dest, source) {
193
+ const channels = dest.length;
194
+ for (let ch = 0; ch < channels; ch++) {
195
+ for (let sample = 0; sample < dest[ch].length; sample++) {
196
+ dest[ch][sample] = source[sample * channels + ch];
197
+ }
198
+ }
199
+ }
200
+
201
+ static write_input(dest, source) {
202
+ const channels = source.length;
203
+ for (let ch = 0; ch < channels; ch++) {
204
+ for (let sample = 0; sample < source[ch].length; sample++) {
205
+ dest[sample * channels + ch] = source[ch][sample];
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ registerProcessor('godot-processor', GodotProcessor);
godot.tools.html ADDED
@@ -0,0 +1,714 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html xmlns="https://www.w3.org/1999/xhtml" lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
6
+ <meta name="author" content="Godot Engine" />
7
+ <meta name="description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." />
8
+ <meta name="mobile-web-app-capable" content="yes" />
9
+ <meta name="apple-mobile-web-app-capable" content="yes" />
10
+ <meta name="application-name" content="Godot" />
11
+ <meta name="apple-mobile-web-app-title" content="Godot" />
12
+ <meta name="theme-color" content="#202531" />
13
+ <meta name="msapplication-navbutton-color" content="#202531" />
14
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
15
+ <meta name="msapplication-starturl" content="/latest" />
16
+ <meta property="og:site_name" content="Godot Engine Web Editor" />
17
+ <meta property="og:url" name="twitter:url" content="https://editor.godotengine.org/releases/latest/" />
18
+ <meta property="og:title" name="twitter:title" content="Free and open source 2D and 3D game engine" />
19
+ <meta property="og:description" name="twitter:description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." />
20
+ <meta property="og:image" name="twitter:image" content="https://godotengine.org/themes/godotengine/assets/og_image.png" />
21
+ <meta property="og:type" content="website" />
22
+ <meta name="twitter:card" content="summary" />
23
+ <link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png" />
24
+ <link rel="apple-touch-icon" type="image/png" href="favicon.png" />
25
+ <link rel="manifest" href="manifest.json" />
26
+ <title>Godot Engine Web Editor (4.0.alpha.custom_build)</title>
27
+ <style>
28
+ *:focus {
29
+ /* More visible outline for better keyboard navigation. */
30
+ outline: 0.125rem solid hsl(220, 100%, 62.5%);
31
+ /* Make the outline always appear above other elements. */
32
+ /* Otherwise, one of its sides can be hidden by tabs in the Download and More layouts. */
33
+ position: relative;
34
+ }
35
+
36
+ body {
37
+ touch-action: none;
38
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
39
+ margin: 0;
40
+ border: 0 none;
41
+ padding: 0;
42
+ text-align: center;
43
+ background-color: #333b4f;
44
+ overflow: hidden;
45
+ }
46
+
47
+ a {
48
+ color: hsl(205, 100%, 75%);
49
+ text-decoration-color: hsla(205, 100%, 75%, 0.3);
50
+ text-decoration-thickness: 0.125rem;
51
+ }
52
+
53
+ a:hover {
54
+ filter: brightness(117.5%);
55
+ }
56
+
57
+ a:active {
58
+ filter: brightness(82.5%);
59
+ }
60
+
61
+ .welcome-modal {
62
+ display: none;
63
+ position: fixed;
64
+ z-index: 1;
65
+ left: 0;
66
+ top: 0;
67
+ width: 100%;
68
+ height: 100%;
69
+ overflow: auto;
70
+ background-color: hsla(0, 0%, 0%, 0.5);
71
+ }
72
+
73
+ .welcome-modal-content {
74
+ background-color: #333b4f;
75
+ box-shadow: 0 0.25rem 0.25rem hsla(0, 0%, 0%, 0.5);
76
+ line-height: 1.5;
77
+ max-width: 38rem;
78
+ margin: 4rem auto 0 auto;
79
+ color: white;
80
+ border-radius: 0.5rem;
81
+ padding: 1rem 1rem 2rem 1rem;
82
+ }
83
+
84
+ #tabs-buttons {
85
+ /* Match the default background color of the editor window for a seamless appearance. */
86
+ background-color: #202531;
87
+ }
88
+
89
+ #tab-game {
90
+ /* Use a pure black background to better distinguish the running project */
91
+ /* from the editor window, and to use a more neutral background color (no tint). */
92
+ background-color: black;
93
+ /* Make the background span the entire page height. */
94
+ min-height: 100vh;
95
+ }
96
+
97
+ #canvas, #gameCanvas {
98
+ display: block;
99
+ margin: 0;
100
+ color: white;
101
+ }
102
+
103
+ /* Don't show distracting focus outlines for the main tabs' contents. */
104
+ #tab-editor canvas:focus,
105
+ #tab-game canvas:focus,
106
+ #canvas:focus,
107
+ #gameCanvas:focus {
108
+ outline: none;
109
+ }
110
+
111
+ .godot {
112
+ color: #e0e0e0;
113
+ background-color: #3b3943;
114
+ background-image: linear-gradient(to bottom, #403e48, #35333c);
115
+ border: 1px solid #45434e;
116
+ box-shadow: 0 0 1px 1px #2f2d35;
117
+ }
118
+
119
+ .btn {
120
+ appearance: none;
121
+ color: #e0e0e0;
122
+ background-color: #262c3b;
123
+ border: 1px solid #202531;
124
+ padding: 0.5rem 1rem;
125
+ margin: 0 0.5rem;
126
+ }
127
+
128
+ .btn:not(:disabled):hover {
129
+ color: #e0e1e5;
130
+ border-color: #666c7b;
131
+ }
132
+
133
+ .btn:active {
134
+ border-color: #699ce8;
135
+ color: #699ce8;
136
+ }
137
+
138
+ .btn:disabled {
139
+ color: #aaa;
140
+ border-color: #242937;
141
+ }
142
+
143
+ .btn.tab-btn {
144
+ padding: 0.3rem 1rem;
145
+ }
146
+
147
+ .btn.close-btn {
148
+ padding: 0.3rem 1rem;
149
+ margin-left: -0.75rem;
150
+ font-weight: 700;
151
+ }
152
+
153
+
154
+ /* Status display
155
+ * ============== */
156
+
157
+ #status {
158
+ position: absolute;
159
+ left: 0;
160
+ top: 0;
161
+ right: 0;
162
+ bottom: 0;
163
+ display: flex;
164
+ justify-content: center;
165
+ align-items: center;
166
+ /* don't consume click events - make children visible explicitly */
167
+ visibility: hidden;
168
+ }
169
+
170
+ #status-progress {
171
+ width: 366px;
172
+ height: 7px;
173
+ background-color: #38363A;
174
+ border: 1px solid #444246;
175
+ padding: 1px;
176
+ box-shadow: 0 0 2px 1px #1B1C22;
177
+ border-radius: 2px;
178
+ visibility: visible;
179
+ }
180
+
181
+ @media only screen and (orientation:portrait) {
182
+ #status-progress {
183
+ width: 61.8%;
184
+ }
185
+ }
186
+
187
+ #status-progress-inner {
188
+ height: 100%;
189
+ width: 0;
190
+ box-sizing: border-box;
191
+ transition: width 0.5s linear;
192
+ background-color: #202020;
193
+ border: 1px solid #222223;
194
+ box-shadow: 0 0 1px 1px #27282E;
195
+ border-radius: 3px;
196
+ }
197
+
198
+ #status-indeterminate {
199
+ visibility: visible;
200
+ position: relative;
201
+ }
202
+
203
+ #status-indeterminate > div {
204
+ width: 4.5px;
205
+ height: 0;
206
+ border-style: solid;
207
+ border-width: 9px 3px 0 3px;
208
+ border-color: #2b2b2b transparent transparent transparent;
209
+ transform-origin: center 21px;
210
+ position: absolute;
211
+ }
212
+
213
+ #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); }
214
+ #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); }
215
+ #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); }
216
+ #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); }
217
+ #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); }
218
+ #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); }
219
+ #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); }
220
+ #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); }
221
+
222
+ #status-notice {
223
+ margin: 0 100px;
224
+ line-height: 1.3;
225
+ visibility: visible;
226
+ padding: 4px 6px;
227
+ visibility: visible;
228
+ }
229
+ </style>
230
+ </head>
231
+ <body>
232
+ <div
233
+ id="welcome-modal"
234
+ class="welcome-modal"
235
+ role="dialog"
236
+ aria-labelledby="welcome-modal-title"
237
+ aria-describedby="welcome-modal-description"
238
+ onclick="if (event.target === this) closeWelcomeModal(false)"
239
+ >
240
+ <div class="welcome-modal-content">
241
+ <h2 id="welcome-modal-title">Important - Please read before continuing</h2>
242
+ <div id="welcome-modal-description">
243
+ <p>
244
+ The Godot Web Editor has some limitations compared to the native version.
245
+ Its main focus is education and experimentation;
246
+ <strong>it is not recommended for production</strong>.
247
+ </p>
248
+ <p>
249
+ Refer to the
250
+ <a
251
+ href="https://docs.godotengine.org/en/latest/tutorials/editor/using_the_web_editor.html"
252
+ target="_blank"
253
+ rel="noopener"
254
+ >Web editor documentation</a> for usage instructions and limitations.
255
+ </p>
256
+ </div>
257
+ <button id="welcome-modal-dismiss" class="btn" type="button" onclick="closeWelcomeModal(true)" style="margin-top: 1rem">
258
+ OK, don't show again
259
+ </button>
260
+ </div>
261
+ </div>
262
+ <div id="tabs-buttons">
263
+ <button id="btn-tab-loader" class="btn tab-btn" onclick="showTab('loader')">Loader</button>
264
+ <button id="btn-tab-editor" class="btn tab-btn" disabled="disabled" onclick="showTab('editor')">Editor</button>
265
+ <button id="btn-close-editor" class="btn close-btn" disabled="disabled" onclick="closeEditor()">×</button>
266
+ <button id="btn-tab-game" class="btn tab-btn" disabled="disabled" onclick="showTab('game')">Game</button>
267
+ <button id="btn-close-game" class="btn close-btn" disabled="disabled" onclick="closeGame()">×</button>
268
+ <button id="btn-tab-update" class="btn tab-btn" style="display: none;">Update</button>
269
+ </div>
270
+ <div id="tabs">
271
+ <div id="tab-loader">
272
+ <div style="color: #e0e0e0;" id="persistence">
273
+ <br />
274
+ <img src="logo.svg" alt="Godot Engine logo" width="1024" height="414" style="width: auto; height: auto; max-width: min(85%, 50vh); max-height: 250px" />
275
+ <br />
276
+ 4.0.alpha.custom_build
277
+ <br />
278
+ <a href="releases/">Need an old version?</a>
279
+ <br />
280
+ <br />
281
+ <br />
282
+ <label for="videoMode" style="margin-right: 1rem">Video driver:</label>
283
+ <select id="videoMode">
284
+ <option value="" selected="selected">Auto</option>
285
+ <option value="opengl3">WebGL 2</option>
286
+ </select>
287
+ <br />
288
+ <br />
289
+ <label for="zip-file" style="margin-right: 1rem">Preload project ZIP:</label> <input id="zip-file" type="file" name="files" style="margin-bottom: 1rem"/>
290
+ <br />
291
+ <a href="demo.zip">(Try this for example)</a>
292
+ <br />
293
+ <br />
294
+ <button id="startButton" class="btn" style="margin-bottom: 4rem; font-weight: 700">Start Godot editor</button>
295
+ <br />
296
+ <button class="btn" onclick="clearPersistence()" style="margin-bottom: 1.5rem">Clear persistent data</button>
297
+ <br />
298
+ <a href="https://docs.godotengine.org/en/latest/tutorials/editor/using_the_web_editor.html">Web editor documentation</a>
299
+ </div>
300
+ </div>
301
+ <div id="tab-editor" style="display: none;">
302
+ <canvas id="editor-canvas" tabindex="1">
303
+ HTML5 canvas appears to be unsupported in the current browser.<br />
304
+ Please try updating or use a different browser.
305
+ </canvas>
306
+ </div>
307
+ <div id="tab-game" style="display: none;">
308
+ <canvas id="game-canvas" tabindex="2">
309
+ HTML5 canvas appears to be unsupported in the current browser.<br />
310
+ Please try updating or use a different browser.
311
+ </canvas>
312
+ </div>
313
+ <div id="tab-status" style="display: none;">
314
+ <div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();"><div id="status-progress-inner"></div></div>
315
+ <div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();">
316
+ <div></div>
317
+ <div></div>
318
+ <div></div>
319
+ <div></div>
320
+ <div></div>
321
+ <div></div>
322
+ <div></div>
323
+ <div></div>
324
+ </div>
325
+ <div id="status-notice" class="godot" style="display: none;"></div>
326
+ </div>
327
+ </div>
328
+ <script>//<![CDATA[
329
+ window.addEventListener("load", () => {
330
+ function notifyUpdate(sw) {
331
+ const btn = document.getElementById("btn-tab-update");
332
+ btn.onclick = function () {
333
+ if (!window.confirm("Are you sure you want to update?\nClicking \"OK\" will reload all active instances!")) {
334
+ return;
335
+ }
336
+ sw.postMessage("update");
337
+ btn.innerHTML = "Updating...";
338
+ btn.disabled = true;
339
+ };
340
+ btn.style.display = "";
341
+ }
342
+ if ("serviceWorker" in navigator) {
343
+ navigator.serviceWorker.register("service.worker.js").then(function (reg) {
344
+ if (reg.waiting) {
345
+ notifyUpdate(reg.waiting);
346
+ }
347
+ reg.addEventListener("updatefound", function () {
348
+ const update = reg.installing;
349
+ update.addEventListener("statechange", function () {
350
+ if (update.state === "installed") {
351
+ // It's a new install, claim and perform aggressive caching.
352
+ if (!reg.active) {
353
+ update.postMessage("claim");
354
+ } else {
355
+ notifyUpdate(update);
356
+ }
357
+ }
358
+ });
359
+ });
360
+ });
361
+ }
362
+
363
+ if (localStorage.getItem("welcomeModalDismissed") !== 'true') {
364
+ document.getElementById("welcome-modal").style.display = "block";
365
+ document.getElementById("welcome-modal-dismiss").focus();
366
+ }
367
+ });
368
+
369
+ function closeWelcomeModal(dontShowAgain) {
370
+ document.getElementById("welcome-modal").style.display = "none";
371
+ if (dontShowAgain) {
372
+ localStorage.setItem("welcomeModalDismissed", 'true');
373
+ }
374
+ }
375
+ //]]></script>
376
+ <script src="godot.tools.js"></script>
377
+ <script>//<![CDATA[
378
+
379
+ var editor = null;
380
+ var game = null;
381
+ var setStatusMode;
382
+ var setStatusNotice;
383
+ var video_driver = "";
384
+
385
+ function clearPersistence() {
386
+ function deleteDB(path) {
387
+ return new Promise(function(resolve, reject) {
388
+ var req = indexedDB.deleteDatabase(path);
389
+ req.onsuccess = function() {
390
+ resolve();
391
+ };
392
+ req.onerror = function(err) {
393
+ reject(err);
394
+ };
395
+ req.onblocked = function(err) {
396
+ reject(err);
397
+ }
398
+
399
+ });
400
+ }
401
+ if (!window.confirm("Are you sure you want to delete all the locally stored files?\nClicking \"OK\" will permanently remove your projects and editor settings!")) {
402
+ return;
403
+ }
404
+ Promise.all([
405
+ deleteDB("/home/web_user"),
406
+ ]).then(function(results) {
407
+ alert("Done.");
408
+ }).catch(function (err) {
409
+ alert("Error deleting local files. Please retry after reloading the page.");
410
+ });
411
+ }
412
+
413
+ function selectVideoMode() {
414
+ var select = document.getElementById('videoMode');
415
+ video_driver = select.selectedOptions[0].value;
416
+ }
417
+
418
+ var tabs = [
419
+ document.getElementById('tab-loader'),
420
+ document.getElementById('tab-editor'),
421
+ document.getElementById('tab-game')
422
+ ]
423
+ function showTab(name) {
424
+ tabs.forEach(function (elem) {
425
+ if (elem.id == 'tab-' + name) {
426
+ elem.style.display = 'block';
427
+ if (name == 'editor' || name == 'game') {
428
+ const canvas = document.getElementById(name + '-canvas');
429
+ canvas.focus();
430
+ }
431
+ } else {
432
+ elem.style.display = 'none';
433
+ }
434
+ });
435
+ }
436
+
437
+ function setButtonEnabled(id, enabled) {
438
+ if (enabled) {
439
+ document.getElementById(id).disabled = "";
440
+ } else {
441
+ document.getElementById(id).disabled = "disabled";
442
+ }
443
+ }
444
+
445
+ function setLoaderEnabled(enabled) {
446
+ setButtonEnabled('btn-tab-loader', enabled);
447
+ setButtonEnabled('btn-tab-editor', !enabled);
448
+ setButtonEnabled('btn-close-editor', !enabled);
449
+ }
450
+
451
+ function setGameTabEnabled(enabled) {
452
+ setButtonEnabled('btn-tab-game', enabled);
453
+ setButtonEnabled('btn-close-game', enabled);
454
+ }
455
+
456
+ function closeGame() {
457
+ if (game) {
458
+ game.requestQuit();
459
+ }
460
+ }
461
+
462
+ function closeEditor() {
463
+ closeGame();
464
+ if (editor) {
465
+ editor.requestQuit();
466
+ }
467
+ }
468
+
469
+ function startEditor(zip) {
470
+ const INDETERMINATE_STATUS_STEP_MS = 100;
471
+ const persistentPaths = ['/home/web_user'];
472
+
473
+ var editorCanvas = document.getElementById('editor-canvas');
474
+ var gameCanvas = document.getElementById('game-canvas');
475
+ var statusProgress = document.getElementById('status-progress');
476
+ var statusProgressInner = document.getElementById('status-progress-inner');
477
+ var statusIndeterminate = document.getElementById('status-indeterminate');
478
+ var statusNotice = document.getElementById('status-notice');
479
+ var headerDiv = document.getElementById('tabs-buttons');
480
+
481
+ var initializing = true;
482
+ var statusMode = 'hidden';
483
+
484
+ showTab('status');
485
+
486
+ var animationCallbacks = [];
487
+ function animate(time) {
488
+ animationCallbacks.forEach(callback => callback(time));
489
+ requestAnimationFrame(animate);
490
+ }
491
+ requestAnimationFrame(animate);
492
+
493
+ var lastScale = 0;
494
+ var lastWidth = 0;
495
+ var lastHeight = 0;
496
+ function adjustCanvasDimensions() {
497
+ var scale = window.devicePixelRatio || 1;
498
+ var headerHeight = headerDiv.offsetHeight + 1;
499
+ var width = window.innerWidth;
500
+ var height = window.innerHeight - headerHeight;
501
+ if (lastScale !== scale || lastWidth !== width || lastHeight !== height) {
502
+ editorCanvas.width = width * scale;
503
+ editorCanvas.height = height * scale;
504
+ editorCanvas.style.width = width + "px";
505
+ editorCanvas.style.height = height + "px";
506
+ lastScale = scale;
507
+ lastWidth = width;
508
+ lastHeight = height;
509
+ }
510
+ }
511
+ animationCallbacks.push(adjustCanvasDimensions);
512
+ adjustCanvasDimensions();
513
+
514
+ function replaceCanvas(from) {
515
+ const out = document.createElement("canvas");
516
+ out.id = from.id;
517
+ out.tabIndex = from.tabIndex;
518
+ from.parentNode.replaceChild(out, from);
519
+ lastScale = 0;
520
+ return out;
521
+ }
522
+
523
+ setStatusMode = function setStatusMode(mode) {
524
+ if (statusMode === mode || !initializing)
525
+ return;
526
+ [statusProgress, statusIndeterminate, statusNotice].forEach(elem => {
527
+ elem.style.display = 'none';
528
+ });
529
+ animationCallbacks = animationCallbacks.filter(function(value) {
530
+ return (value != animateStatusIndeterminate);
531
+ });
532
+ switch (mode) {
533
+ case 'progress':
534
+ statusProgress.style.display = 'block';
535
+ break;
536
+ case 'indeterminate':
537
+ statusIndeterminate.style.display = 'block';
538
+ animationCallbacks.push(animateStatusIndeterminate);
539
+ break;
540
+ case 'notice':
541
+ statusNotice.style.display = 'block';
542
+ break;
543
+ case 'hidden':
544
+ break;
545
+ default:
546
+ throw new Error('Invalid status mode');
547
+ }
548
+ statusMode = mode;
549
+ };
550
+
551
+ function animateStatusIndeterminate(ms) {
552
+ var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8);
553
+ if (statusIndeterminate.children[i].style.borderTopColor == '') {
554
+ Array.prototype.slice.call(statusIndeterminate.children).forEach(child => {
555
+ child.style.borderTopColor = '';
556
+ });
557
+ statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf';
558
+ }
559
+ }
560
+
561
+ setStatusNotice = function setStatusNotice(text) {
562
+ while (statusNotice.lastChild) {
563
+ statusNotice.removeChild(statusNotice.lastChild);
564
+ }
565
+ var lines = text.split('\n');
566
+ lines.forEach((line) => {
567
+ statusNotice.appendChild(document.createTextNode(line));
568
+ statusNotice.appendChild(document.createElement('br'));
569
+ });
570
+ };
571
+
572
+ const gameConfig = {
573
+ 'persistentPaths': persistentPaths,
574
+ 'unloadAfterInit': false,
575
+ 'canvas': gameCanvas,
576
+ 'canvasResizePolicy': 1,
577
+ 'onExit': function () {
578
+ gameCanvas = replaceCanvas(gameCanvas);
579
+ setGameTabEnabled(false);
580
+ showTab('editor');
581
+ game = null;
582
+ },
583
+ };
584
+
585
+ var OnEditorExit = function () {
586
+ showTab('loader');
587
+ setLoaderEnabled(true);
588
+ };
589
+ function Execute(args) {
590
+ const is_editor = args.filter(function(v) { return v == '--editor' || v == '-e' }).length != 0;
591
+ const is_project_manager = args.filter(function(v) { return v == '--project-manager' }).length != 0;
592
+ const is_game = !is_editor && !is_project_manager;
593
+ if (video_driver) {
594
+ args.push('--rendering-driver', video_driver);
595
+ }
596
+
597
+ if (is_game) {
598
+ if (game) {
599
+ console.error("A game is already running. Close it first");
600
+ return;
601
+ }
602
+ setGameTabEnabled(true);
603
+ game = new Engine(gameConfig);
604
+ showTab('game');
605
+ game.init().then(function() {
606
+ requestAnimationFrame(function() {
607
+ game.start({'args': args, 'canvas': gameCanvas}).then(function() {
608
+ gameCanvas.focus();
609
+ });
610
+ });
611
+ });
612
+ } else { // New editor instances will be run in the same canvas. We want to wait for it to exit.
613
+ OnEditorExit = function(code) {
614
+ setLoaderEnabled(true);
615
+ setTimeout(function() {
616
+ editor.init().then(function() {
617
+ setLoaderEnabled(false);
618
+ OnEditorExit = function() {
619
+ showTab('loader');
620
+ setLoaderEnabled(true);
621
+ };
622
+ editor.start({'args': args, 'persistentDrops': is_project_manager, 'canvas': editorCanvas});
623
+ });
624
+ }, 0);
625
+ OnEditorExit = null;
626
+ };
627
+ }
628
+ }
629
+
630
+ const editorConfig = {
631
+ 'unloadAfterInit': false,
632
+ 'onProgress': function progressFunction (current, total) {
633
+ if (total > 0) {
634
+ statusProgressInner.style.width = current/total * 100 + '%';
635
+ setStatusMode('progress');
636
+ if (current === total) {
637
+ // wait for progress bar animation
638
+ setTimeout(() => {
639
+ setStatusMode('indeterminate');
640
+ }, 100);
641
+ }
642
+ } else {
643
+ setStatusMode('indeterminate');
644
+ }
645
+ },
646
+ 'canvas': editorCanvas,
647
+ 'canvasResizePolicy': 0,
648
+ 'onExit': function() {
649
+ editorCanvas = replaceCanvas(editorCanvas);
650
+ if (OnEditorExit) {
651
+ OnEditorExit();
652
+ }
653
+ },
654
+ 'onExecute': Execute,
655
+ 'persistentPaths': persistentPaths,
656
+ };
657
+ editor = new Engine(editorConfig);
658
+
659
+ function displayFailureNotice(err) {
660
+ var msg = err.message || err;
661
+ console.error(msg);
662
+ setStatusNotice(msg);
663
+ setStatusMode('notice');
664
+ initializing = false;
665
+ };
666
+
667
+ if (!Engine.isWebGLAvailable()) {
668
+ displayFailureNotice('WebGL not available');
669
+ } else {
670
+ setStatusMode('indeterminate');
671
+ editor.init('godot.tools').then(function() {
672
+ if (zip) {
673
+ editor.copyToFS("/tmp/preload.zip", zip);
674
+ }
675
+ try {
676
+ // Avoid user creating project in the persistent root folder.
677
+ editor.copyToFS("/home/web_user/keep", new Uint8Array());
678
+ } catch(e) {
679
+ // File exists
680
+ }
681
+ selectVideoMode();
682
+ showTab('editor');
683
+ setLoaderEnabled(false);
684
+ const args = ['--project-manager', '--single-window'];
685
+ if (video_driver) {
686
+ args.push('--rendering-driver', video_driver);
687
+ }
688
+ editor.start({'args': args, 'persistentDrops': true}).then(function() {
689
+ setStatusMode('hidden');
690
+ initializing = false;
691
+ });
692
+ }).catch(displayFailureNotice);
693
+ }
694
+ };
695
+ document.getElementById("startButton").onclick = function() {
696
+ preloadZip(document.getElementById('zip-file')).then(function(zip) {
697
+ startEditor(zip);
698
+ });
699
+ }
700
+
701
+ function preloadZip(target) {
702
+ return new Promise(function(resolve, reject) {
703
+ if (target.files.length > 0) {
704
+ target.files[0].arrayBuffer().then(function(data) {
705
+ resolve(data);
706
+ });
707
+ } else {
708
+ resolve();
709
+ }
710
+ });
711
+ }
712
+ //]]></script>
713
+ </body>
714
+ </html>
godot.tools.js ADDED
The diff for this file is too large to render. See raw diff
 
godot.tools.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b78c77d76f67d77ed74206ab81993952200f85c344c59e1403d69535783fd5a7
3
+ size 57390540
godot.tools.worker.js ADDED
@@ -0,0 +1 @@
 
 
1
+ "use strict";var Module={};function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=((info,receiveInstance)=>{var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports});self.onmessage=(e=>{try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}Godot(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else if(e.data.cmd==="processProxyingQueue"){if(Module["_pthread_self"]()){Module["_emscripten_proxy_execute_queue"](e.data.queue)}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}});
logo.svg ADDED
manifest.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Godot Engine Web Editor",
3
+ "short_name": "Godot",
4
+ "description": "Multi-platform 2D and 3D game engine with a feature-rich editor (Web edition)",
5
+ "lang": "en",
6
+ "start_url": "./godot.tools.html",
7
+ "display": "standalone",
8
+ "theme_color": "#202531",
9
+ "icons": [
10
+ {
11
+ "src": "favicon.png",
12
+ "sizes": "256x256",
13
+ "type": "image/png"
14
+ }
15
+ ],
16
+ "background_color": "#333b4f"
17
+ }
offline.html ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#202531" />
8
+ <meta name="msapplication-navbutton-color" content="#202531" />
9
+ <title>You are offline</title>
10
+ <style>
11
+ html {
12
+ background-color: #333b4f;
13
+ color: #e0e0e0;
14
+ }
15
+
16
+ body {
17
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
18
+ margin: 2rem;
19
+ }
20
+
21
+ p {
22
+ margin-block: 1rem;
23
+ }
24
+
25
+ button {
26
+ display: block;
27
+ padding: 1rem 2rem;
28
+ margin: 3rem auto 0;
29
+ }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <h1>You are offline</h1>
34
+ <p>This application requires an Internet connection to run for the first time.</p>
35
+ <p>Press the button below to try reloading:</p>
36
+ <button type="button">Reload</button>
37
+
38
+ <script>
39
+ document.querySelector("button").addEventListener("click", () => {
40
+ window.location.reload();
41
+ });
42
+ </script>
43
+ </body>
44
+ </html>
service.worker.js ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This service worker is required to expose an exported Godot project as a
2
+ // Progressive Web App. It provides an offline fallback page telling the user
3
+ // that they need an Internet connection to run the project if desired.
4
+ // Incrementing CACHE_VERSION will kick off the install event and force
5
+ // previously cached resources to be updated from the network.
6
+ const CACHE_VERSION = "4.0.alpha.custom_build";
7
+ const CACHE_PREFIX = "GodotEngine-sw-cache-";
8
+ const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
9
+ const OFFLINE_URL = "offline.html";
10
+ // Files that will be cached on load.
11
+ const CACHED_FILES = ["godot.tools.html", "offline.html", "godot.tools.js", "godot.tools.worker.js", "godot.tools.audio.worklet.js", "logo.svg", "favicon.png"];
12
+ // Files that we might not want the user to preload, and will only be cached on first load.
13
+ const CACHABLE_FILES = ["godot.tools.wasm"];
14
+ const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES);
15
+
16
+ self.addEventListener("install", (event) => {
17
+ event.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(CACHED_FILES)));
18
+ });
19
+
20
+ self.addEventListener("activate", (event) => {
21
+ event.waitUntil(caches.keys().then(
22
+ function (keys) {
23
+ // Remove old caches.
24
+ return Promise.all(keys.filter(key => key.startsWith(CACHE_PREFIX) && key != CACHE_NAME).map(key => caches.delete(key)));
25
+ }).then(function() {
26
+ // Enable navigation preload if available.
27
+ return ("navigationPreload" in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve();
28
+ })
29
+ );
30
+ });
31
+
32
+ async function fetchAndCache(event, cache, isCachable) {
33
+ // Use the preloaded response, if it's there
34
+ let response = await event.preloadResponse;
35
+ if (!response) {
36
+ // Or, go over network.
37
+ response = await self.fetch(event.request);
38
+ }
39
+ if (isCachable) {
40
+ // And update the cache
41
+ cache.put(event.request, response.clone());
42
+ }
43
+ return response;
44
+ }
45
+
46
+ self.addEventListener("fetch", (event) => {
47
+ const isNavigate = event.request.mode === "navigate";
48
+ const url = event.request.url || "";
49
+ const referrer = event.request.referrer || "";
50
+ const base = referrer.slice(0, referrer.lastIndexOf("/") + 1);
51
+ const local = url.startsWith(base) ? url.replace(base, "") : "";
52
+ const isCachable = FULL_CACHE.some(v => v === local) || (base === referrer && base.endsWith(CACHED_FILES[0]));
53
+ if (isNavigate || isCachable) {
54
+ event.respondWith(async function () {
55
+ // Try to use cache first
56
+ const cache = await caches.open(CACHE_NAME);
57
+ if (event.request.mode === "navigate") {
58
+ // Check if we have full cache during HTML page request.
59
+ const fullCache = await Promise.all(FULL_CACHE.map(name => cache.match(name)));
60
+ const missing = fullCache.some(v => v === undefined);
61
+ if (missing) {
62
+ try {
63
+ // Try network if some cached file is missing (so we can display offline page in case).
64
+ return await fetchAndCache(event, cache, isCachable);
65
+ } catch (e) {
66
+ // And return the hopefully always cached offline page in case of network failure.
67
+ console.error("Network error: ", e);
68
+ return await caches.match(OFFLINE_URL);
69
+ }
70
+ }
71
+ }
72
+ const cached = await cache.match(event.request);
73
+ if (cached) {
74
+ return cached;
75
+ } else {
76
+ // Try network if don't have it in cache.
77
+ return await fetchAndCache(event, cache, isCachable);
78
+ }
79
+ }());
80
+ }
81
+ });
82
+
83
+ self.addEventListener("message", (event) => {
84
+ // No cross origin
85
+ if (event.origin != self.origin) {
86
+ return;
87
+ }
88
+ const id = event.source.id || "";
89
+ const msg = event.data || "";
90
+ // Ensure it's one of our clients.
91
+ self.clients.get(id).then(function (client) {
92
+ if (!client) {
93
+ return; // Not a valid client.
94
+ }
95
+ if (msg === "claim") {
96
+ self.skipWaiting().then(() => self.clients.claim());
97
+ } else if (msg === "clear") {
98
+ caches.delete(CACHE_NAME);
99
+ } else if (msg === "update") {
100
+ self.skipWaiting().then(() => self.clients.claim()).then(() => self.clients.matchAll()).then(all => all.forEach(c => c.navigate(c.url)));
101
+ } else {
102
+ onClientMessage(event);
103
+ }
104
+ });
105
+ });