forked from 1b-agent/opencadstudio-upstream
feat: restore-all session recovery, ?file= deep links, offline PWA
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 Pages workflow stamps the cache version from the bundle hash and
fails if sw.js is missing or unstamped.
(cherry picked from commit 5f9b345e)
Ported onto main (#1717 review): the cache-version stamp moved from the
skipped build-web.sh wrapper into the Pages workflow; the tab-close hook
lands in main's on_tab_close, which also cancels pinned control requests.
This commit is contained in:
1 parent
2d2444fcfa
commit
6af8d8ba8c
37 files changed
+1170
-26
No files matched your search
@@ -927,6 +927,25 @@ pub(super) struct OpenCADStudio {
|
||||
/// prompt still has to offer, one at a time.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_pending: Vec<crate::io::web_autosave::WebAutosaveEntry>,
|
||||
/// Snapshots a "Restore all" click still expects to settle (opened,
|
||||
/// failed, cancelled, unreadable, or rejected by the open queue). Each
|
||||
/// settle funnels through `queue_next_web_autosave_prompt`, which counts
|
||||
/// this down; reaching zero arms `web_session_focus_pending`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_restore_all_remaining: usize,
|
||||
/// A Restore-all batch finished settling — apply the remembered
|
||||
/// session's active tab at the next message boundary, after the last
|
||||
/// restored open has actually inserted its tab (`web_autosave_opened`
|
||||
/// runs before that insertion, so applying there would pick against a
|
||||
/// tab list still missing the last restore).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session_focus_pending: bool,
|
||||
/// The tab list the previous session remembered (localStorage), loaded
|
||||
/// after the boot autosave scan. Advisory only: it feeds the restore
|
||||
/// modal's session line and the post-restore tab selection — nothing is
|
||||
/// ever auto-opened from it.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session: Option<crate::io::web_session::WebSession>,
|
||||
/// The snapshot whose recovered open is in flight: `(autosave key,
|
||||
/// open id)`. The OPFS copy is the only one, so it is deleted only
|
||||
/// once the open with exactly that id succeeds — identity, never the
|
||||
@@ -2346,6 +2365,11 @@ pub enum Message {
|
||||
/// Web: an asynchronous OPFS copy written after Save is ready for recents.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebRecentStored(Result<PathBuf, String>),
|
||||
/// Web: the `?file=` boot deep link settled — its bytes entered the
|
||||
/// guarded open queue, or a notice/error text for the command line
|
||||
/// (recent copy missing, refused or failed fetch).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebLinkSettled(crate::io::web_link::LinkOutcome),
|
||||
/// Web: the boot scan finished — these autosave snapshots are newer than
|
||||
/// the drawings' last saved copies (newest first).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -2353,6 +2377,12 @@ pub enum Message {
|
||||
/// Web: restore the offered autosave snapshot into a recovered tab.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveRestore,
|
||||
/// Web: restore EVERY pending autosave snapshot — each is read and
|
||||
/// enqueued through the guarded open queue with its own key (the poll
|
||||
/// opens them sequentially); a snapshot is still consumed only after its
|
||||
/// own open succeeds.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveRestoreAll,
|
||||
/// Web: delete the offered autosave snapshot (and offer the next, if any).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveDiscard,
|
||||
@@ -4524,6 +4554,12 @@ impl OpenCADStudio {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_pending: Vec::new(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_restore_all_remaining: 0,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session_focus_pending: false,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_consumed: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_clear_after: Vec::new(),
|
||||
@@ -5119,6 +5155,46 @@ impl OpenCADStudio {
|
||||
crate::io::web_autosave::scan(),
|
||||
Message::WebAutosavesScanned,
|
||||
);
|
||||
// URL deep link (`?file=…`): a shareable/embeddable link that opens a
|
||||
// drawing at boot. An http(s) URL is fetched with the same capped
|
||||
// streaming reader the remote-image fetch uses and handed to the
|
||||
// guarded drop-open queue; a bare name reads the OPFS recent copy the
|
||||
// same way `open_recent_web` does. Everything else (other schemes,
|
||||
// non-.dwg/.dxf URL tails) is refused with a console warning — the
|
||||
// same scheme gate the image fetch applies. Pure parsing/validation
|
||||
// lives in `io::web_link` and is unit-tested there.
|
||||
let deep_link: Task<Message> = {
|
||||
let search = web_sys::window()
|
||||
.and_then(|window| window.location().search().ok())
|
||||
.unwrap_or_default();
|
||||
match crate::io::web_link::parse_link(&search) {
|
||||
crate::io::web_link::ParsedLink::Target(target) => {
|
||||
// Opening a URL from the query string is an implicit trust
|
||||
// decision: name the origin before the fetch starts (info
|
||||
// only — the bytes still go through the parse + recovery
|
||||
// machinery like any other open). Same-origin links stay
|
||||
// quiet: that drawing already comes from this site.
|
||||
if let crate::io::web_link::LinkTarget::Url { url, name } = &target {
|
||||
if !crate::io::web_link::same_origin(url) {
|
||||
let origin = crate::io::web_link::display_origin(url)
|
||||
.unwrap_or_else(|| url.clone());
|
||||
s.command_line
|
||||
.push_info(&format!("Opening {name} from {origin}…"));
|
||||
}
|
||||
}
|
||||
Task::perform(crate::io::web_link::open_link(target), |outcome| {
|
||||
Message::WebLinkSettled(outcome)
|
||||
})
|
||||
}
|
||||
crate::io::web_link::ParsedLink::Ignored(refusal, value) => {
|
||||
web_sys::console::warn_1(&wasm_bindgen::JsValue::from_str(
|
||||
&crate::io::web_link::refusal_warning(refusal, &value),
|
||||
));
|
||||
Task::none()
|
||||
}
|
||||
crate::io::web_link::ParsedLink::Absent => Task::none(),
|
||||
}
|
||||
};
|
||||
(
|
||||
s,
|
||||
Task::batch([
|
||||
@@ -5129,6 +5205,7 @@ impl OpenCADStudio {
|
||||
discussions,
|
||||
thumbs_fetch,
|
||||
autosave_scan,
|
||||
deep_link,
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user