Merge pull request #46796 from Faless/js/4.x_pwa_simple
[HTML5] Add PWA support to the editor page.
This commit is contained in:
commit
8507b69c13
|
@ -2,8 +2,18 @@
|
|||
<html xmlns='http://www.w3.org/1999/xhtml' lang='' xml:lang=''>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width, user-scalable=no' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no' />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="application-name" content="Godot" />
|
||||
<meta name="apple-mobile-web-app-title" content="Godot" />
|
||||
<meta name="theme-color" content="#478cbf" />
|
||||
<meta name="msapplication-navbutton-color" content="#478cbf" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="msapplication-starturl" content="/latest" />
|
||||
<link id='-gd-engine-icon' rel='icon' type='image/png' href='favicon.png' />
|
||||
<link rel="apple-touch-icon" type="image/png" href="favicon.png" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<title>Godot Engine Web Editor (@GODOT_VERSION@)</title>
|
||||
<style>
|
||||
*:focus {
|
||||
|
@ -250,7 +260,13 @@
|
|||
<div id='status-notice' class='godot' style='display: none;'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.addEventListener("load", () => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("service.worker.js");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src='godot.tools.js'></script>
|
||||
<script>//<![CDATA[
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "Godot Engine",
|
||||
"short_name": "Godot",
|
||||
"description": "Multi-platform 2D and 3D game engine with a feature-rich editor",
|
||||
"lang": "en",
|
||||
"start_url": "/godot.tools.html",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"theme_color": "#478cbf",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"background_color": "#333b4f"
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>You are offline</title>
|
||||
<style>
|
||||
html {
|
||||
background-color: #333b4f;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body {
|
||||
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";
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
padding: 1rem 2rem;
|
||||
margin: 3rem auto 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>You are offline</h1>
|
||||
<p>This application requires an Internet connection to run for the first time.</p>
|
||||
<p>Press the button below to try reloading:</p>
|
||||
<button type="button">Reload</button>
|
||||
|
||||
<script>
|
||||
document.querySelector("button").addEventListener("click", () => {
|
||||
window.location.reload();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,84 @@
|
|||
// This service worker is required to expose an exported Godot project as a
|
||||
// Progressive Web App. It provides an offline fallback page telling the user
|
||||
// that they need an Internet conneciton to run the project if desired.
|
||||
// Incrementing CACHE_VERSION will kick off the install event and force
|
||||
// previously cached resources to be updated from the network.
|
||||
const CACHE_VERSION = "@GODOT_VERSION@";
|
||||
const CACHE_NAME = "@GODOT_NAME@-cache";
|
||||
const OFFLINE_URL = "offline.html";
|
||||
// Files that will be cached on load.
|
||||
const CACHED_FILES = [
|
||||
"godot.tools.html",
|
||||
"offline.html",
|
||||
"godot.tools.js",
|
||||
"godot.tools.worker.js",
|
||||
"godot.tools.audio.worklet.js",
|
||||
"logo.svg",
|
||||
"favicon.png",
|
||||
];
|
||||
|
||||
// Files that we might not want the user to preload, and will only be cached on first load.
|
||||
const CACHABLE_FILES = [
|
||||
"godot.tools.wasm",
|
||||
];
|
||||
const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES);
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(async function () {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
// Clear old cache (including optionals).
|
||||
await Promise.all(FULL_CACHE.map(path => cache.delete(path)));
|
||||
// Insert new one.
|
||||
const done = await cache.addAll(CACHED_FILES);
|
||||
return done;
|
||||
}());
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(async function () {
|
||||
if ("navigationPreload" in self.registration) {
|
||||
await self.registration.navigationPreload.enable();
|
||||
}
|
||||
}());
|
||||
// Tell the active service worker to take control of the page immediately.
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const isNavigate = event.request.mode === "navigate";
|
||||
const url = event.request.url || "";
|
||||
const referrer = event.request.referrer || "";
|
||||
const base = referrer.slice(0, referrer.lastIndexOf("/") + 1);
|
||||
const local = url.startsWith(base) ? url.replace(base, "") : "";
|
||||
const isCachable = FULL_CACHE.some(v => v === local) || (base === referrer && base.endsWith(CACHED_FILES[0]));
|
||||
if (isNavigate || isCachable) {
|
||||
event.respondWith(async function () {
|
||||
try {
|
||||
// Use the preloaded response, if it's there
|
||||
let request = event.request.clone();
|
||||
let response = await event.preloadResponse;
|
||||
if (!response) {
|
||||
// Or, go over network.
|
||||
response = await fetch(event.request);
|
||||
}
|
||||
if (isCachable) {
|
||||
// Update the cache
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
if (event.request.mode === "navigate") {
|
||||
// Check if we have full cache.
|
||||
const cached = await Promise.all(FULL_CACHE.map(name => cache.match(name)));
|
||||
const missing = cached.some(v => v === undefined);
|
||||
const cachedResponse = missing ? await caches.match(OFFLINE_URL) : await caches.match(CACHED_FILES[0]);
|
||||
return cachedResponse;
|
||||
}
|
||||
const cachedResponse = await caches.match(event.request);
|
||||
return cachedResponse;
|
||||
}
|
||||
}());
|
||||
}
|
||||
});
|
|
@ -86,40 +86,6 @@ wrap_list = [
|
|||
]
|
||||
js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js")
|
||||
|
||||
zip_dir = env.Dir("#bin/.javascript_zip")
|
||||
binary_name = "godot.tools" if env["tools"] else "godot"
|
||||
out_files = [
|
||||
zip_dir.File(binary_name + ".js"),
|
||||
zip_dir.File(binary_name + ".wasm"),
|
||||
zip_dir.File(binary_name + ".html"),
|
||||
zip_dir.File(binary_name + ".audio.worklet.js"),
|
||||
]
|
||||
html_file = "#misc/dist/html/full-size.html"
|
||||
if env["tools"]:
|
||||
subst_dict = {"@GODOT_VERSION@": env.GetBuildVersion()}
|
||||
html_file = env.Substfile(
|
||||
target="#bin/godot${PROGSUFFIX}.html", source="#misc/dist/html/editor.html", SUBST_DICT=subst_dict
|
||||
)
|
||||
|
||||
in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"]
|
||||
if env["gdnative_enabled"]:
|
||||
in_files.append(build[2]) # Runtime
|
||||
out_files.append(zip_dir.File(binary_name + ".side.wasm"))
|
||||
elif env["threads_enabled"]:
|
||||
in_files.append(build[2]) # Worker
|
||||
out_files.append(zip_dir.File(binary_name + ".worker.js"))
|
||||
|
||||
if env["tools"]:
|
||||
in_files.append("#misc/dist/html/logo.svg")
|
||||
out_files.append(zip_dir.File("logo.svg"))
|
||||
in_files.append("#icon.png")
|
||||
out_files.append(zip_dir.File("favicon.png"))
|
||||
|
||||
zip_files = env.InstallAs(out_files, in_files)
|
||||
env.Zip(
|
||||
"#bin/godot",
|
||||
zip_files,
|
||||
ZIPROOT=zip_dir,
|
||||
ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}",
|
||||
ZIPCOMSTR="Archiving $SOURCES as $TARGET",
|
||||
)
|
||||
# Extra will be the thread worker, or the GDNative side, or None
|
||||
extra = build[2] if len(build) > 2 else None
|
||||
env.CreateTemplateZip(js_wrapped, build[1], extra)
|
||||
|
|
|
@ -7,7 +7,7 @@ from emscripten_helpers import (
|
|||
add_js_libraries,
|
||||
add_js_pre,
|
||||
add_js_externs,
|
||||
get_build_version,
|
||||
create_template_zip,
|
||||
)
|
||||
from methods import get_compiler_version
|
||||
from SCons.Util import WhereIs
|
||||
|
@ -147,12 +147,12 @@ def configure(env):
|
|||
env.AddMethod(add_js_pre, "AddJSPre")
|
||||
env.AddMethod(add_js_externs, "AddJSExterns")
|
||||
|
||||
# Add method for getting build version string.
|
||||
env.AddMethod(get_build_version, "GetBuildVersion")
|
||||
|
||||
# Add method that joins/compiles our Engine files.
|
||||
env.AddMethod(create_engine_file, "CreateEngineFile")
|
||||
|
||||
# Add method for creating the final zip file
|
||||
env.AddMethod(create_template_zip, "CreateTemplateZip")
|
||||
|
||||
# Closure compiler extern and support for ecmascript specs (const, let, etc).
|
||||
env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT6"
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ def run_closure_compiler(target, source, env, for_signature):
|
|||
return " ".join(cmd)
|
||||
|
||||
|
||||
def get_build_version(env):
|
||||
def get_build_version():
|
||||
import version
|
||||
|
||||
name = "custom_build"
|
||||
|
@ -30,6 +30,65 @@ def create_engine_file(env, target, source, externs):
|
|||
return env.Textfile(target, [env.File(s) for s in source])
|
||||
|
||||
|
||||
def create_template_zip(env, js, wasm, extra):
|
||||
binary_name = "godot.tools" if env["tools"] else "godot"
|
||||
zip_dir = env.Dir("#bin/.javascript_zip")
|
||||
in_files = [
|
||||
js,
|
||||
wasm,
|
||||
"#platform/javascript/js/libs/audio.worklet.js",
|
||||
]
|
||||
out_files = [
|
||||
zip_dir.File(binary_name + ".js"),
|
||||
zip_dir.File(binary_name + ".wasm"),
|
||||
zip_dir.File(binary_name + ".audio.worklet.js"),
|
||||
]
|
||||
# GDNative/Threads specific
|
||||
if env["gdnative_enabled"]:
|
||||
in_files.append(extra) # Runtime
|
||||
out_files.append(zip_dir.File(binary_name + ".side.wasm"))
|
||||
elif env["threads_enabled"]:
|
||||
in_files.append(extra) # Worker
|
||||
out_files.append(zip_dir.File(binary_name + ".worker.js"))
|
||||
|
||||
service_worker = "#misc/dist/html/service-worker.js"
|
||||
if env["tools"]:
|
||||
# HTML
|
||||
html = "#misc/dist/html/editor.html"
|
||||
subst_dict = {"@GODOT_VERSION@": get_build_version(), "@GODOT_NAME@": "GodotEngine"}
|
||||
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
|
||||
in_files.append(html)
|
||||
out_files.append(zip_dir.File(binary_name + ".html"))
|
||||
# And logo/favicon
|
||||
in_files.append("#misc/dist/html/logo.svg")
|
||||
out_files.append(zip_dir.File("logo.svg"))
|
||||
in_files.append("#icon.png")
|
||||
out_files.append(zip_dir.File("favicon.png"))
|
||||
# PWA
|
||||
service_worker = env.Substfile(
|
||||
target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict
|
||||
)
|
||||
in_files.append(service_worker)
|
||||
out_files.append(zip_dir.File("service.worker.js"))
|
||||
in_files.append("#misc/dist/html/manifest.json")
|
||||
out_files.append(zip_dir.File("manifest.json"))
|
||||
in_files.append("#misc/dist/html/offline.html")
|
||||
out_files.append(zip_dir.File("offline.html"))
|
||||
else:
|
||||
# HTML
|
||||
in_files.append("#misc/dist/html/full-size.html")
|
||||
out_files.append(zip_dir.File(binary_name + ".html"))
|
||||
|
||||
zip_files = env.InstallAs(out_files, in_files)
|
||||
env.Zip(
|
||||
"#bin/godot",
|
||||
zip_files,
|
||||
ZIPROOT=zip_dir,
|
||||
ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}",
|
||||
ZIPCOMSTR="Archiving $SOURCES as $TARGET",
|
||||
)
|
||||
|
||||
|
||||
def add_js_libraries(env, libraries):
|
||||
env.Append(JS_LIBS=env.File(libraries))
|
||||
|
||||
|
|
|
@ -238,6 +238,9 @@ const GodotAudioWorklet = {
|
|||
|
||||
close: function () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (GodotAudioWorklet.promise === null) {
|
||||
return;
|
||||
}
|
||||
GodotAudioWorklet.promise.then(function () {
|
||||
GodotAudioWorklet.worklet.port.postMessage({
|
||||
'cmd': 'stop',
|
||||
|
@ -247,7 +250,7 @@ const GodotAudioWorklet = {
|
|||
GodotAudioWorklet.worklet = null;
|
||||
GodotAudioWorklet.promise = null;
|
||||
resolve();
|
||||
});
|
||||
}).catch(function (err) { /* aborted? */ });
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue