forked from 1b-agent/opencadstudio-upstream
Two reviewers, no blockers, three majors — all fixed. - The remembered-tab focus after a Restore-all batch fired at the message boundary, but the LAST restore's tab inserts one async round later (its OPFS recent-cache write precedes the insertion) and the insertion's own activation overwrote the selection. The focus flag now also consumes at the insertion point, so the batch focus holds in the common all-success case. - The service worker cached ANY successful in-scope navigation under the shell key — a direct navigation to a font or JSON would be served as the app shell for every offline navigation until the next online visit. Only the scope root caches as the shell now. - Local macOS builds broke: sha256sum does not exist there (shasum does) and BSD sed requires an explicit -i suffix. The wrapper picks the available hasher and uses a portable in-place edit. Also: '/app/' gets its own web app manifest (the previous commit's 'installable' claim was true only of the marketing site, whose start_url sits OUTSIDE the service worker's scope — an installed app opened offline showed an error page). The README now states the honest offline timing (assets captured on the second visit — the first visit's preloads outrun the installing worker) and the long-lived-window update behavior.
120 lines
5.0 KiB
HTML
120 lines
5.0 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<link rel="manifest" href="app.webmanifest" />
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="robots" content="noindex, follow" />
|
|
<meta name="theme-color" content="#1e1e1e" />
|
|
<title>Open CAD Studio Web App</title>
|
|
<link data-trunk rel="icon" href="assets/logo.svg" />
|
|
<style>
|
|
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: #1e1e1e; }
|
|
canvas { display: block; }
|
|
#loading {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 22px;
|
|
background: #1e1e1e;
|
|
color: #cfcfcf;
|
|
font-family: system-ui, sans-serif;
|
|
z-index: 9999;
|
|
transition: opacity 0.4s ease;
|
|
}
|
|
#loading.hide { opacity: 0; pointer-events: none; }
|
|
#loading .title { font-size: 22px; font-weight: 600; letter-spacing: 0.5px; }
|
|
#loading .title span { color: #3399e6; }
|
|
#loading .sub { font-size: 12px; color: #777; }
|
|
#loading .spinner {
|
|
width: 46px;
|
|
height: 46px;
|
|
border: 4px solid #2e2e2e;
|
|
border-top-color: #3399e6;
|
|
border-radius: 50%;
|
|
animation: ocs-spin 0.9s linear infinite;
|
|
}
|
|
@keyframes ocs-spin { to { transform: rotate(360deg); } }
|
|
</style>
|
|
<link data-trunk rel="rust" data-bin="OpenCADStudio" />
|
|
<link data-trunk rel="copy-dir" href="web/fonts" />
|
|
<link data-trunk rel="copy-file" href="web/ocs-parse-worker.js" />
|
|
<link data-trunk rel="copy-file" href="web/discussions.json" />
|
|
<link data-trunk rel="copy-file" href="web/videos.json" />
|
|
<link data-trunk rel="copy-file" href="web/locale-labels.json" />
|
|
<link data-trunk rel="copy-file" href="web/ocs-control.js" />
|
|
<link data-trunk rel="copy-file" href="web/sw.js" />
|
|
<link data-trunk rel="copy-file" href="web/app.webmanifest" />
|
|
<link data-trunk rel="copy-file" href="web/logo.svg" />
|
|
</head>
|
|
<body>
|
|
<div id="loading">
|
|
<div class="spinner"></div>
|
|
<div class="title">Open <span>CAD</span> Studio</div>
|
|
<div class="sub">Loading…</div>
|
|
</div>
|
|
<script src="ocs-control.js" defer></script>
|
|
<script>
|
|
// Offline support: register the service worker. It is copied next to
|
|
// this page by Trunk (see the copy-file link in <head>), so the
|
|
// relative URL resolves to /app/sw.js and the registration scope is
|
|
// /app/ — the whole app. Registered immediately (not on load) so the
|
|
// worker starts capturing this visit's asset requests as early as
|
|
// possible. sw.js never reloads the page on update: a new worker
|
|
// waits until every tab on the old version has closed.
|
|
(() => {
|
|
if (!("serviceWorker" in navigator)) return;
|
|
navigator.serviceWorker.register("sw.js").catch(error => {
|
|
console.warn("service worker registration failed; offline support disabled:", error);
|
|
});
|
|
})();
|
|
</script>
|
|
<script>
|
|
(() => {
|
|
const splash = document.getElementById("loading");
|
|
fetch("locale-labels.json").then(response => response.json()).then(labels => {
|
|
let saved;
|
|
try {
|
|
saved = JSON.parse(localStorage.getItem("opencadstudio.settings"))?.settings?.language;
|
|
} catch {}
|
|
const preferred = navigator.languages?.length ? navigator.languages : [navigator.language];
|
|
const requested = typeof saved === "string" && saved !== "system" ? [saved, ...preferred] : preferred;
|
|
const available = Object.keys(labels);
|
|
let locale = "en-US";
|
|
for (const candidate of requested) {
|
|
try {
|
|
const language = new Intl.Locale(candidate);
|
|
const match = available.find(value => value.toLowerCase() === language.baseName.toLowerCase())
|
|
|| (language.language === "zh"
|
|
? (language.maximize().script === "Hant" ? "zh-TW" : "zh-CN")
|
|
: available.find(value => value.startsWith(language.language + "-")));
|
|
if (match) { locale = match; break; }
|
|
} catch {}
|
|
}
|
|
document.title = labels[locale].title;
|
|
document.documentElement.lang = locale;
|
|
document.documentElement.dir = locale === "ar-SA" ? "rtl" : "ltr";
|
|
const caption = splash?.querySelector(".sub");
|
|
if (caption) caption.textContent = labels[locale].loading;
|
|
}).catch(() => {});
|
|
const done = () => {
|
|
if (!splash) return;
|
|
splash.classList.add("hide");
|
|
setTimeout(() => splash.remove(), 450);
|
|
};
|
|
if (document.querySelector("canvas")) return done();
|
|
const observer = new MutationObserver(() => {
|
|
if (document.querySelector("canvas")) {
|
|
observer.disconnect();
|
|
done();
|
|
}
|
|
});
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|