🎉 Add loading pencil on heavy page loading and transition

This commit is contained in:
Elena Torro 2026-06-26 13:13:06 +02:00
parent 10147b6abd
commit 86f7786db6
5 changed files with 55 additions and 52 deletions

View File

@ -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"

View File

@ -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;

View File

@ -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]

View File

@ -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<()> {

View File

@ -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