forked from 1b-agent/opencadstudio-upstream
Session recovery: more than one pending snapshot adds a 'Restore all' button to the Recover Drawing modal — every snapshot goes through the exact same guarded path as a single restore (deleted only after its own open succeeds). The open-tab list and active tab persist to localStorage on web (open/save/close) and the remembered tab is re-selected once a restore batch settles; the modal notes how many drawings the session had. Nothing is auto-opened. Deep links: ?file=<url|name> opens a drawing at boot. http(s) URLs must end in .dwg/.dxf and are fetched with the same streaming, 64 MB-capped, 8 s-deadline reader the remote images use, then go through the guarded open queue; cross-origin opens announce the origin first. Bare names resolve from the browser's recent storage, with a hint when absent. Everything else (file:, javascript:, drive letters, non-drawing tails) is refused with a console note. Offline PWA: the app was installable (manifest) but downloaded its 55 MB bundle on every visit. A cache-first service worker now serves the whole bundle from Cache Storage after one online visit — navigations network-first so updates land, hashed assets stale-while-revalidate, non-GET/cross-origin passthrough, old caches cleaned on activate, and the waiting-worker update flow documented. The build wrapper stamps the cache version from the bundle hash and hard-fails if sw.js is missing or unstamped. Also fixes a locale group mismatch on the DATALINK pick notice (catalog said a display group that does not exist in the ftl sources).
117 lines
4.8 KiB
HTML
117 lines
4.8 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<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" />
|
|
</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>
|