mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🔧 Add loading state
This commit is contained in:
parent
e8e7900911
commit
5706d57ffe
@ -1132,46 +1132,40 @@
|
||||
|
||||
(defn- set-objects-async
|
||||
"Asynchronously process shapes in chunks, yielding to the browser between chunks.
|
||||
Returns a promise that resolves when all shapes are processed.
|
||||
|
||||
Renders a preview only periodically during loading to show progress,
|
||||
then does a full tile-based render at the end."
|
||||
Returns a promise that resolves when all shapes are processed."
|
||||
[shapes render-callback]
|
||||
(let [total-shapes (count shapes)
|
||||
total-chunks (mth/ceil (/ total-shapes SHAPES_CHUNK_SIZE))
|
||||
;; Render at 25%, 50%, 75% of loading
|
||||
render-at-chunks (set [(mth/floor (* total-chunks 0.25))
|
||||
(mth/floor (* total-chunks 0.5))
|
||||
(mth/floor (* total-chunks 0.75))])]
|
||||
(let [total-shapes (count shapes)]
|
||||
(p/create
|
||||
(fn [resolve _reject]
|
||||
(letfn [(process-next-chunk [index thumbnails-acc full-acc chunk-count]
|
||||
(letfn [(process-next-chunk [index thumbnails-acc full-acc]
|
||||
(if (< index total-shapes)
|
||||
;; Process one chunk
|
||||
(let [{:keys [thumbnails full next-index]}
|
||||
(process-shapes-chunk shapes index SHAPES_CHUNK_SIZE
|
||||
thumbnails-acc full-acc)
|
||||
new-chunk-count (inc chunk-count)]
|
||||
;; Only render at specific progress milestones
|
||||
(when (contains? render-at-chunks new-chunk-count)
|
||||
(render-preview!))
|
||||
|
||||
thumbnails-acc full-acc)]
|
||||
;; Yield to browser, then continue with next chunk
|
||||
(-> (yield-to-browser)
|
||||
(p/then (fn [_]
|
||||
(process-next-chunk next-index thumbnails full new-chunk-count)))))
|
||||
(process-next-chunk next-index thumbnails full)))))
|
||||
;; All chunks done - finalize
|
||||
(do
|
||||
(perf/end-measure "set-objects")
|
||||
(process-pending shapes thumbnails-acc full-acc noop-fn
|
||||
;; Rebuild tiles while loading=true so the first
|
||||
;; render can use the flag (e.g. for placeholders)
|
||||
(h/call wasm/internal-module "_rebuild_all_tiles")
|
||||
;; Unblock rendering so shapes appear immediately
|
||||
(end-shapes-loading!)
|
||||
(process-pending shapes thumbnails-acc full-acc
|
||||
;; on-render: first render done, now clear loading flag
|
||||
(fn []
|
||||
(h/call wasm/internal-module "_end_loading"))
|
||||
(fn []
|
||||
(end-shapes-loading!)
|
||||
(if render-callback
|
||||
(render-callback)
|
||||
(render-finish))
|
||||
(ug/dispatch! (ug/event "penpot:wasm:set-objects"))
|
||||
(resolve nil))))))]
|
||||
(process-next-chunk 0 [] [] 0))))))
|
||||
(process-next-chunk 0 [] []))))))
|
||||
|
||||
(defn- set-objects-sync
|
||||
"Synchronously process all shapes (for small shape counts)."
|
||||
@ -1238,12 +1232,16 @@
|
||||
(set-objects-sync shapes render-callback)
|
||||
(do
|
||||
(begin-shapes-loading!)
|
||||
(h/call wasm/internal-module "_begin_loading")
|
||||
(h/call wasm/internal-module "_render_loading_overlay")
|
||||
(try
|
||||
(-> (set-objects-async shapes render-callback)
|
||||
(p/catch (fn [error]
|
||||
(h/call wasm/internal-module "_end_loading")
|
||||
(end-shapes-loading!)
|
||||
(js/console.error "Async WASM shape loading failed" error))))
|
||||
(catch :default error
|
||||
(h/call wasm/internal-module "_end_loading")
|
||||
(end-shapes-loading!)
|
||||
(js/console.error "Async WASM shape loading failed" error)
|
||||
(throw error)))
|
||||
|
||||
@ -244,6 +244,51 @@ pub extern "C" fn render_preview() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enter bulk-loading mode. While active, `state.loading` is `true`.
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn begin_loading() -> Result<()> {
|
||||
with_state_mut!(state, {
|
||||
state.loading = true;
|
||||
});
|
||||
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.
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn render_loading_overlay() -> Result<()> {
|
||||
with_state_mut!(state, {
|
||||
state.render_state.render_loading_overlay();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rebuild the full tile index after bulk loading.
|
||||
/// Called while `loading` is still `true` so the first render
|
||||
/// can use the loading flag (e.g. for placeholders).
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn rebuild_all_tiles() -> Result<()> {
|
||||
with_state_mut!(state, {
|
||||
state.rebuild_tiles();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Leave bulk-loading mode. Should be called after the first
|
||||
/// render so the loading flag is available during that render.
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn end_loading() -> Result<()> {
|
||||
with_state_mut!(state, {
|
||||
state.loading = false;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> {
|
||||
|
||||
@ -654,6 +654,39 @@ impl RenderState {
|
||||
self.surfaces.reset(self.background_color);
|
||||
}
|
||||
|
||||
/// FIXME
|
||||
pub fn render_loading_overlay(&mut self) {
|
||||
let canvas = self.surfaces.canvas(SurfaceId::Target);
|
||||
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();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_canvas_at(&mut self, surface_id: SurfaceId) -> &skia::Canvas {
|
||||
self.surfaces.canvas(surface_id)
|
||||
|
||||
@ -26,6 +26,8 @@ pub(crate) struct State {
|
||||
pub current_browser: u8,
|
||||
pub shapes: ShapesPool,
|
||||
pub saved_shapes: Option<ShapesPool>,
|
||||
/// True while the first bulk load of shapes is in progress.
|
||||
pub loading: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@ -36,8 +38,8 @@ impl State {
|
||||
current_id: None,
|
||||
current_browser: 0,
|
||||
shapes: ShapesPool::new(),
|
||||
// TODO: Maybe this can be moved to a different object
|
||||
saved_shapes: None,
|
||||
loading: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user