🐛 Fix problem with position-data not present

* 🐛 Fix problem with position-data not present

* 🐛 Async set-objects wait before calculate-position-data
This commit is contained in:
Alonso Torres 2026-05-26 09:50:23 +02:00 committed by GitHub
parent eafd261ca6
commit 5a3a855b24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 201 additions and 35 deletions

View File

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.features :as cfeat]
[app.common.files.changes :as cpc]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.schema :as sm]
@ -20,9 +21,11 @@
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
[app.main.data.fonts :as df]
[app.main.data.helpers :as dsh]
[app.main.features :as features]
[app.main.repo :as rp]
[app.main.router :as rt]
[app.render-wasm.api :as wasm.api]
[app.util.globals :as ug]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@ -171,6 +174,88 @@
(declare go-to-frame-by-index)
(declare go-to-frame-auto)
;; Applies to the viewer the changes passed as parameters
;; will not save the data but just modify the data localy
(defn- apply-changes-viewer
[changes]
(ptk/reify ::apply-changes-viewer
ptk/UpdateEvent
(update [_ state]
(let [file (-> (dm/get-in state [:viewer :file])
(update :data cpc/process-changes changes false))
pages
(->> (dm/get-in file [:data :pages])
(map (fn [page-id]
(let [data (get-in file [:data :pages-index page-id])]
[page-id (assoc data
:frames (ctt/get-viewer-frames (:objects data))
:all-frames (ctt/get-viewer-frames (:objects data) {:all-frames? true}))])))
(into {}))]
(-> state
(assoc-in [:viewer :file] file)
(assoc-in [:viewer :pages] pages))))))
(defn- generate-update-position-data-changes
[shapes page-id]
(reduce
(fn [result shape]
(conj result
{:type :mod-obj
:id (:id shape)
:page-id page-id
:operations
[{:type :set
:attr :position-data
:val (wasm.api/calculate-position-data shape)
:ignore-touched true
:ignore-geometry true}]}))
[]
shapes))
(defn update-page-position-data
[file-id page-id]
(ptk/reify ::update-page-position-data
ptk/WatchEvent
(watch [_ state _]
(if (features/active-feature? state "render-wasm/v1")
(let [objects (dsh/lookup-page-objects state file-id page-id)
shapes
(reduce-kv
(fn [result _ shape]
(cond-> result
(and (cfh/text-shape? shape) (nil? (:position-data shape)))
(conj shape)))
[]
objects)
;; Creates a stream from the async callback. This stream will only
;; emit one single value after the objects have finished loading
;; in the wasm memory.
set-objects-stream
(rx/create
(fn [subs]
(wasm.api/init-canvas-context (js/OffscreenCanvas. 0 0))
(wasm.api/set-objects-callback shapes #(rx/push! subs :done))
nil))]
(if (d/not-empty? shapes)
(->> (rx/from @wasm.api/module)
(rx/mapcat (constantly set-objects-stream))
(rx/mapcat
(fn []
(let [changes (generate-update-position-data-changes shapes page-id)
_ (wasm.api/clear-canvas)]
(if (d/not-empty? changes)
(rx/of (apply-changes-viewer changes))
(rx/empty))))))
(rx/empty)))
;; Render wasm disabled, we do nothing
(rx/empty)))))
(defn bundle-fetched
[{:keys [project file team share-links libraries users permissions thumbnails] :as bundle}]
(let [pages (->> (dm/get-in file [:data :pages])
@ -205,13 +290,17 @@
(let [route (:route state)
qparams (:query-params route)
index (some-> (rt/get-query-param qparams :index) parse-long)
frame-id (some-> (:frame-id qparams) uuid/parse)]
frame-id (some-> (:frame-id qparams) uuid/parse)
page-id (some-> (rt/get-query-param qparams :page-id) uuid/parse)
file-id (some-> (rt/get-query-param qparams :file-id) uuid/parse)]
(rx/merge
(rx/of (case (:zoom qparams)
"fit" zoom-to-fit
"fill" zoom-to-fill
nil))
(rx/of
(update-page-position-data file-id page-id)
(cond
(some? frame-id) (go-to-frame frame-id)
(some? index) (go-to-frame-by-index index)

View File

@ -204,36 +204,40 @@
(rx/take-until stopper-s))))))
(defn check-file-position-data
[file-id]
(ptk/reify ::fix-position-data
ptk/WatchEvent
(watch [it state _]
(let [file (dsh/lookup-file state file-id)
changes
(->> file :data :pages
(mapcat
(fn [page-id]
(->> (dsh/lookup-page-objects state file-id page-id)
(vals)
(filter cfh/text-shape?)
(filter #(nil? (:position-data %)))
(map (fn [shape]
{:type :mod-obj
:id (:id shape)
:page-id page-id
:operations
[{:type :set
:attr :position-data
:val (wasm.api/calculate-position-data shape)
:ignore-touched true
:ignore-geometry true}]})))))
(into []))]
(rx/of (dch/commit-changes
{:redo-changes changes :undo-changes []
:save-undo? false
:origin it
:tags #{:position-data}}))))))
(defn update-page-position-data
([]
(update-page-position-data nil nil))
([file-id page-id]
(ptk/reify ::update-page-position-data
ptk/WatchEvent
(watch [it state _]
(let [file-id (or file-id (:current-file-id state))
page-id (or page-id (:current-page-id state))
changes
(reduce-kv
(fn [result _ shape]
(if (and (cfh/text-shape? shape)
(nil? (:position-data shape)))
(conj result
{:type :mod-obj
:id (:id shape)
:page-id page-id
:operations
[{:type :set
:attr :position-data
:val (wasm.api/calculate-position-data shape)
:ignore-touched true
:ignore-geometry true}]})
result))
[]
(dsh/lookup-page-objects state file-id page-id))]
(if (d/not-empty? changes)
(rx/of (dch/commit-changes
{:redo-changes changes :undo-changes []
:save-undo? false
:origin it
:tags #{:position-data}}))
(rx/empty)))))))
(defn- workspace-initialized
[file-id]

View File

@ -17,7 +17,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.main.data.modal :as modal]
;; [app.main.data.workspace :as dw]
[app.main.data.workspace :as dw]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.variants :as dwv]
[app.main.features :as features]
@ -459,9 +459,9 @@
;; is set.
(wasm.api/initialize-viewport base-objects zoom vbox
:background background
;; :on-shapes-ready
;; (st/emit! (dw/check-file-position-data file-id))
)
:on-shapes-ready
(fn []
(st/emit! (dw/update-page-position-data))))
(reset! initialized? true))
(when (and (some? vern) (not= vern (mf/ref-val last-vern-ref)))

View File

@ -438,6 +438,19 @@
(aget buffer 2)
(aget buffer 3)))))
(defn has-shape
[id]
(when wasm/context-initialized?
(let [buffer (uuid/get-u32 id)
result
(h/call wasm/internal-module "_has_shape"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))]
(= result 1))))
(defn set-shape-text-content
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
[shape-id content]
@ -1404,6 +1417,53 @@
noop-fn)))))))]
(process-next-chunk 0 [] []))))))
;; This is a version of process-pending that doesn't have sideffects
;; with like request render or update layout.
(defn- process-pending-no-sideffects
[thumbnails full on-complete]
(let [pending-thumbnails
(d/index-by :key :callback thumbnails)
pending-full
(d/index-by :key :callback full)]
(if (or (seq pending-thumbnails) (seq pending-full))
(->> (rx/concat
(->> (rx/from (vals pending-thumbnails))
(rx/merge-map (fn [callback] (if (fn? callback) (callback) (rx/empty))))
(rx/reduce conj [])
(rx/catch #(rx/empty)))
(->> (rx/from (vals pending-full))
(rx/mapcat (fn [callback] (if (fn? callback) (callback) (rx/empty))))
(rx/reduce conj [])
(rx/catch #(rx/empty))))
(rx/subs!
noop-fn
noop-fn
(fn []
(when (fn? on-complete) (on-complete)))))
;; No pending images — complete immediately.
(when on-complete (on-complete)))))
(defn set-objects-callback
"Sets the shapes and when the async operations are done calls the callback. Won't
interact with the rendering pipeline, this call is only to set the model (used currently
in the viewer)."
[shapes set-objects-cb]
(let [total-shapes (count shapes)
{:keys [thumbnails full]}
(loop [index 0 thumbnails-acc (transient []) full-acc (transient [])]
(if (< index total-shapes)
(let [shape (nth shapes index)
{:keys [thumbnails full]} (set-object shape)]
(recur (inc index)
(reduce conj! thumbnails-acc thumbnails)
(reduce conj! full-acc full)))
{:thumbnails (persistent! thumbnails-acc) :full (persistent! full-acc)}))]
(process-pending-no-sideffects thumbnails full set-objects-cb)))
(defn- set-objects-sync
"Synchronously process all shapes (for small shape counts)."
[shapes render-callback on-shapes-ready]

View File

@ -407,6 +407,15 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
Ok(())
}
#[no_mangle]
#[wasm_error]
pub extern "C" fn has_shape(a: u32, b: u32, c: u32, d: u32) -> Result<bool> {
with_state!(state, {
let id = uuid_from_u32_quartet(a, b, c, d);
return Ok(state.has_shape(id));
});
}
#[no_mangle]
#[wasm_error]
pub extern "C" fn touch_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> {

View File

@ -119,6 +119,10 @@ impl State {
self.current_id = Some(id);
}
pub fn has_shape(&mut self, id: Uuid) -> bool {
self.shapes.has(&id)
}
pub fn delete_shape_children(&mut self, parent_id: Uuid, id: Uuid) {
let render_state = get_render_state();