From 86f7786db67df165d21ed6f21068b544fe5fcdcb Mon Sep 17 00:00:00 2001 From: Elena Torro Date: Fri, 26 Jun 2026 13:13:06 +0200 Subject: [PATCH] :tada: Add loading pencil on heavy page loading and transition --- .../app/main/ui/workspace/viewport_wasm.cljs | 34 +++++++++++++++++- .../app/main/ui/workspace/viewport_wasm.scss | 22 ++++++++++++ frontend/src/app/render_wasm/api.cljs | 3 -- render-wasm/src/main.rs | 13 ------- render-wasm/src/render.rs | 35 ------------------- 5 files changed, 55 insertions(+), 52 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 285c43e223..d613740cf1 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -24,6 +24,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] + [app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.flex-controls :as mfc] [app.main.ui.hooks :as ui-hooks] [app.main.ui.measurements :as msr] @@ -155,6 +156,12 @@ :pointer-events "none" :clip-path clip-path}}]))) +;; Minimum loading time (ms) before the viewport pencil loader is shown. +;; Loads shorter than this never display the loader, avoiding a flash. +;; Page switches use a longer threshold since they're usually quick. +(def ^:private viewport-loader-delay 300) +(def ^:private viewport-loader-delay-page-switch 2000) + (mf/defc viewport* [{:keys [selected wglobal layout file page palete-size]}] (let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check @@ -210,6 +217,7 @@ space? (mf/use-state false) z? (mf/use-state false) cursor (mf/use-state (utils/get-cursor :pointer-inner)) + show-loader? (mf/use-state false) hover-ids (mf/use-state nil) hover (mf/use-state nil) measure-hover (mf/use-state nil) @@ -219,6 +227,7 @@ active-frames (mf/use-state #{}) canvas-init? (mf/use-state false) initialized? (mf/use-state false) + page-switch?* (mf/use-ref false) dragging-guide-id* (mf/use-state nil) guide-hover-axis* (mf/use-state nil) @@ -433,7 +442,27 @@ preview-blend (-> refs/workspace-preview-blend (mf/deref)) shapes-loading? (mf/deref wasm.api/shapes-loading?) - transition-image (mf/deref wasm.api/transition-image*)] + transition-image (mf/deref wasm.api/transition-image*) + loading? (or page-transition? context-loss-overlay? shapes-loading?)] + + (mf/with-effect [] + (when page-transition? + (mf/set-ref-val! page-switch?* true)) + nil) + + (mf/with-effect [page-transition?] + (when-not page-transition? + (mf/set-ref-val! page-switch?* false)) + nil) + + (mf/with-effect [loading?] + (if loading? + (let [delay (if (mf/ref-val page-switch?*) + viewport-loader-delay-page-switch + viewport-loader-delay) + timeout-id (js/setTimeout #(reset! show-loader? true) delay)] + (fn [] (js/clearTimeout timeout-id))) + (reset! show-loader? false))) ;; NOTE: We need this page-id dependency to react to it and reset the ;; canvas, even though we are not using `page-id` inside the hook. @@ -673,6 +702,9 @@ (dm/str "inset(" strip "px 0 0 " strip "px round " rulers/canvas-border-radius "px)")))}]) + (when @show-loader? + [:> loader* {:class (stl/css :viewport-loader) + :overlay true}]) [:svg.viewport-controls {:xmlns "http://www.w3.org/2000/svg" diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.scss b/frontend/src/app/main/ui/workspace/viewport_wasm.scss index 1c721e7cbe..581f82b75d 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.scss +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.scss @@ -33,6 +33,28 @@ z-index: 10; } +.viewport-loader { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5; + pointer-events: none; + background-color: var(--color-background-secondary); + opacity: 0.35; +} + +@keyframes viewport-loader-fade-in { + from { + opacity: 0; + } + + to { + opacity: 0.35; + } +} + .context-lost { position: fixed; inset: 0; diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index f2a344fc25..b6ea1a5f29 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -1712,9 +1712,6 @@ (do (begin-shapes-loading!) (h/call wasm/internal-module "_begin_loading") - ;; NOTE: to render a loading overlay in the future - ;; (when-not on-shapes-ready - ;; (h/call wasm/internal-module "_render_loading_overlay")) (try (-> (set-objects-async shapes render-callback on-shapes-ready) (p/catch (fn [error] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 0aa3fae930..5961f2be34 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -273,19 +273,6 @@ pub extern "C" fn end_loading() -> Result<()> { Ok(()) } -/// Draw a full-screen loading overlay (background + "Loading…" text). -/// Called from CLJS right after begin_loading so the user sees -/// immediate feedback while shapes are being processed. -/// NOTE: -/// This is currently not being used, but it's set there for testing purposes on -/// upcoming tasks -#[no_mangle] -#[wasm_error] -pub extern "C" fn render_loading_overlay() -> Result<()> { - get_render_state().render_loading_overlay(); - Ok(()) -} - #[no_mangle] #[wasm_error] pub extern "C" fn reset_canvas() -> Result<()> { diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index ee4ebe3bd0..be9ab9834f 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -940,41 +940,6 @@ impl RenderState { self.surfaces.invalidate_tile_cache(); } - /// NOTE: - /// This is currently not being used, but it's set there for testing purposes on - /// upcoming tasks - pub fn render_loading_overlay(&mut self) { - let canvas = self.surfaces.canvas(SurfaceId::Backbuffer); - let skia::ISize { width, height } = canvas.base_layer_size(); - - canvas.save(); - - // Full-screen background rect - let rect = skia::Rect::from_wh(width as f32, height as f32); - let mut bg_paint = skia::Paint::default(); - bg_paint.set_color(self.background_color); - bg_paint.set_style(skia::PaintStyle::Fill); - canvas.draw_rect(rect, &bg_paint); - - // Centered "Loading…" text - let mut text_paint = skia::Paint::default(); - text_paint.set_color(skia::Color::GRAY); - text_paint.set_anti_alias(true); - - let font = self.fonts.debug_font(); - // FIXME - let text = "Loading…"; - let (text_width, _) = font.measure_str(text, None); - let metrics = font.metrics(); - let text_height = metrics.1.cap_height; - let x = (width as f32 - text_width) / 2.0; - let y = (height as f32 + text_height) / 2.0; - canvas.draw_str(text, skia::Point::new(x, y), font, &text_paint); - - canvas.restore(); - self.flush_and_submit(); - } - pub fn apply_render_to_final_canvas(&mut self) -> Result<()> { // During interactive transforms we render tiles directly into Target; updating the cache // (snapshot -> atlas blit -> tiles.add) can force GPU stalls. Defer cache rebuild until