mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Add better workspace file indexing strategy
Improve file indexes initialization on workspace. Instead of initialize indexes for all pages only initialize indexes for the loaded page.
This commit is contained in:
@@ -6,14 +6,29 @@
|
||||
|
||||
(ns app.common.files.indices
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn- generate-index
|
||||
"An optimized algorithm for calculate parents index that walk from top
|
||||
to down starting from a provided shape-id. Usefull when you want to
|
||||
create an index for the whole objects or subpart of the tree."
|
||||
[index objects shape-id parents]
|
||||
(let [shape (get objects shape-id)
|
||||
index (assoc index shape-id parents)
|
||||
parents (cons shape-id parents)]
|
||||
(reduce (fn [index shape-id]
|
||||
(generate-index index objects shape-id parents))
|
||||
index
|
||||
(:shapes shape))))
|
||||
|
||||
(defn generate-child-all-parents-index
|
||||
"Creates an index where the key is the shape id and the value is a set
|
||||
with all the parents"
|
||||
([objects]
|
||||
(generate-child-all-parents-index objects (vals objects)))
|
||||
(generate-index {} objects uuid/zero []))
|
||||
|
||||
([objects shapes]
|
||||
(let [shape->entry
|
||||
@@ -24,24 +39,25 @@
|
||||
(defn create-clip-index
|
||||
"Retrieves the mask information for an object"
|
||||
[objects parents-index]
|
||||
(let [retrieve-clips
|
||||
(let [get-clip-parents
|
||||
(fn [shape]
|
||||
(let [shape-id (dm/get-prop shape :id)]
|
||||
(cond-> []
|
||||
(or (and (cfh/frame-shape? shape)
|
||||
(not (:show-content shape))
|
||||
(not= uuid/zero shape-id))
|
||||
(cfh/bool-shape? shape))
|
||||
(conj shape)
|
||||
|
||||
(:masked-group shape)
|
||||
(conj (get objects (->> shape :shapes first))))))
|
||||
|
||||
xform
|
||||
(comp (map (d/getf objects))
|
||||
(mapcat get-clip-parents))
|
||||
|
||||
populate-with-clips
|
||||
(fn [parents]
|
||||
(let [lookup-object (fn [id] (get objects id))
|
||||
get-clip-parents
|
||||
(fn [shape]
|
||||
(cond-> []
|
||||
(or (and (= :frame (:type shape))
|
||||
(not (:show-content shape))
|
||||
(not= uuid/zero (:id shape)))
|
||||
(cfh/bool-shape? shape))
|
||||
(conj shape)
|
||||
(into [] xform parents))]
|
||||
|
||||
(:masked-group shape)
|
||||
(conj (get objects (->> shape :shapes first)))))]
|
||||
|
||||
(into []
|
||||
(comp (map lookup-object)
|
||||
(mapcat get-clip-parents))
|
||||
parents)))]
|
||||
(-> parents-index
|
||||
(update-vals retrieve-clips))))
|
||||
(d/update-vals parents-index populate-with-clips)))
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
(->> (rx/from changes)
|
||||
(rx/merge-map (fn [[page-id changes]]
|
||||
(log/debug :hint "update-indexes" :page-id page-id :changes (count changes))
|
||||
(mw/ask! {:cmd :index/update-page-index
|
||||
(mw/ask! {:cmd :index/update
|
||||
:page-id page-id
|
||||
:changes changes})))
|
||||
(rx/catch (fn [cause]
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
@@ -70,7 +69,6 @@
|
||||
[app.main.features.pointer-map :as fpmap]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm :as wasm]
|
||||
[app.render-wasm.api :as api]
|
||||
[app.util.dom :as dom]
|
||||
@@ -84,7 +82,7 @@
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(log/set-level! :debug)
|
||||
(log/set-level! :trace)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Workspace Initialization
|
||||
@@ -158,18 +156,9 @@
|
||||
(->> (fpmap/resolve-file file)
|
||||
(rx/map :data)
|
||||
(rx/map process-fills)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [pages-index] :as data}]
|
||||
(->> (rx/from (seq pages-index))
|
||||
(rx/mapcat
|
||||
(fn [[id page]]
|
||||
(let [page (update page :objects ctst/start-page-index)]
|
||||
(->> (mw/ask! {:cmd :index/initialize-page-index :page page})
|
||||
(rx/map (fn [_] [id page]))))))
|
||||
(rx/reduce conj {})
|
||||
(rx/map (fn [pages-index]
|
||||
(let [data (assoc data :pages-index pages-index)]
|
||||
(assoc file :data (d/removem (comp t/pointer? val) data))))))))))
|
||||
(rx/map
|
||||
(fn [data]
|
||||
(assoc file :data (d/removem (comp t/pointer? val) data))))))
|
||||
|
||||
(defn- check-libraries-synchronozation
|
||||
[file-id libraries]
|
||||
@@ -280,6 +269,8 @@
|
||||
(ptk/reify ::fetch-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(log/debug :hint "fetch bundle" :file-id (dm/str file-id))
|
||||
|
||||
(let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)]
|
||||
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
|
||||
(get-file-object-thumbnails file-id))
|
||||
@@ -288,6 +279,7 @@
|
||||
(fn [[file thumbnails]]
|
||||
(->> (resolve-file file)
|
||||
(rx/map (fn [file]
|
||||
(log/trace :hint "file resolved" :file-id file-id)
|
||||
{:file file
|
||||
:file-id file-id
|
||||
:features features
|
||||
@@ -357,6 +349,10 @@
|
||||
(rx/map deref)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [file]}]
|
||||
(log/debug :hint "bundle fetched"
|
||||
:team-id (dm/str team-id)
|
||||
:file-id (dm/str file-id))
|
||||
|
||||
(rx/of (dpj/initialize-project (:project-id file))
|
||||
(dwn/initialize team-id file-id)
|
||||
(dwsl/initialize-shape-layout)
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
(rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)}))
|
||||
(if (cfh/frame-shape? shape)
|
||||
(rx/concat
|
||||
(->> (mw/ask! {:cmd :selection/query
|
||||
(->> (mw/ask! {:cmd :index/query-selection
|
||||
:page-id page-id
|
||||
:rect (:selrect shape)
|
||||
:include-frames? true
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
@@ -29,6 +30,7 @@
|
||||
[app.main.errors]
|
||||
[app.main.features :as features]
|
||||
[app.main.router :as rt]
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.shape :as wasm.shape]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -56,16 +58,21 @@
|
||||
(some? metadata) (cf/resolve-file-media metadata)
|
||||
(some? fill-image) (cf/resolve-file-media fill-image))))))
|
||||
|
||||
(defn- get-page-cache
|
||||
[state file-id page-id]
|
||||
(dm/get-in state [:workspace-cache [file-id page-id]]))
|
||||
|
||||
(defn- initialize-page*
|
||||
"Second phase of page initialization, once we know the page is
|
||||
available in the state"
|
||||
[file-id page-id page]
|
||||
[file-id page-id]
|
||||
(ptk/reify ::initialize-page*
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; selection; when user abandon the current page, the selection is lost
|
||||
(let [local (dm/get-in state [:workspace-cache [file-id page-id]] default-workspace-local)]
|
||||
(let [state (dsh/update-page state file-id page-id #(update % :objects ctst/start-page-index))
|
||||
page (dsh/lookup-page state file-id page-id)
|
||||
local (or (get-page-cache state file-id page-id) default-workspace-local)]
|
||||
|
||||
(-> state
|
||||
(assoc :current-page-id page-id)
|
||||
(assoc :workspace-local (assoc local :selected (d/ordered-set)))
|
||||
@@ -75,11 +82,16 @@
|
||||
(update :workspace-layout layout/load-layout-flags)
|
||||
(update :workspace-global layout/load-layout-state))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(let [uris (into #{} xf:collect-file-media (:objects page))]
|
||||
(->> (rx/from uris)
|
||||
(rx/subs! #(http/fetch-data-uri % false)))))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [page (dsh/lookup-page state file-id page-id)
|
||||
uris (into #{} xf:collect-file-media (:objects page))]
|
||||
(rx/merge
|
||||
(->> (rx/from uris)
|
||||
(rx/map #(http/fetch-data-uri % false))
|
||||
(rx/ignore))
|
||||
(->> (mw/ask! {:cmd :index/initialize :page page})
|
||||
(rx/ignore)))))))
|
||||
|
||||
(defn initialize-page
|
||||
[file-id page-id]
|
||||
@@ -89,9 +101,9 @@
|
||||
(ptk/reify ::initialize-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(if-let [page (dsh/lookup-page state file-id page-id)]
|
||||
(if (dsh/lookup-page state file-id page-id)
|
||||
(rx/concat
|
||||
(rx/of (initialize-page* file-id page-id page)
|
||||
(rx/of (initialize-page* file-id page-id)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes))
|
||||
(let [profile (:profile state)
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
|
||||
(if (some? selrect)
|
||||
(->> (ask-worker
|
||||
{:cmd :selection/query
|
||||
{:cmd :index/query-selection
|
||||
:page-id page-id
|
||||
:rect selrect
|
||||
:include-frames? true
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
(let [value (get point coord)
|
||||
vbox @refs/vbox
|
||||
ranges [[(- value (/ 0.5 zoom)) (+ value (/ 0.5 zoom))]]]
|
||||
(->> (mw/ask! {:cmd :snaps/range-query
|
||||
(->> (mw/ask! {:cmd :index/query-snap
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:axis coord
|
||||
@@ -101,7 +101,7 @@
|
||||
(mapv #(vector (- % snap-accuracy)
|
||||
(+ % snap-accuracy))))
|
||||
vbox @refs/vbox]
|
||||
(->> (mw/ask! {:cmd :snaps/range-query
|
||||
(->> (mw/ask! {:cmd :index/query-snap
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:axis coord
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
(defn select-shapes-area
|
||||
[page-id frame-id selected objects area]
|
||||
(->> (mw/ask! {:cmd :selection/query
|
||||
(->> (mw/ask! {:cmd :index/query-selection
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:include-frames? true
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
(if (mf/ref-val hover-disabled-ref)
|
||||
(rx/of nil)
|
||||
(->> (mw/ask-buffered!
|
||||
{:cmd :selection/query
|
||||
{:cmd :index/query-selection
|
||||
:page-id page-id
|
||||
:rect rect
|
||||
:include-frames? true
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
[app.worker.import]
|
||||
[app.worker.index]
|
||||
[app.worker.messages :as wm]
|
||||
[app.worker.selection]
|
||||
[app.worker.snaps]
|
||||
[app.worker.thumbnails]
|
||||
[beicon.v2.core :as rx]
|
||||
[promesa.core :as p]))
|
||||
|
||||
@@ -9,26 +9,69 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes :as ch]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.logging :as log]
|
||||
[app.common.time :as ct]
|
||||
[app.worker.impl :as impl]
|
||||
[app.worker.selection :as selection]
|
||||
[app.worker.snap :as snap]
|
||||
[okulary.core :as l]))
|
||||
|
||||
(log/set-level! :info)
|
||||
|
||||
(defonce state (l/atom {:pages-index {}}))
|
||||
|
||||
(defmethod impl/handler :index/initialize-page-index
|
||||
(defmethod impl/handler :index/initialize
|
||||
[{:keys [page] :as message}]
|
||||
(swap! state update :pages-index assoc (:id page) page)
|
||||
(impl/handler (assoc message :cmd :selection/initialize-page-index))
|
||||
(impl/handler (assoc message :cmd :snaps/initialize-page-index)))
|
||||
(let [tpoint (ct/tpoint-ms)]
|
||||
(try
|
||||
(swap! state update :pages-index assoc (:id page) page)
|
||||
(swap! state update ::selection selection/add-page page)
|
||||
(swap! state update ::snap snap/add-page page)
|
||||
|
||||
(defmethod impl/handler :index/update-page-index
|
||||
(finally
|
||||
(let [elapsed (tpoint)]
|
||||
(log/dbg :hint "page indexed" :id (:id page) :elapsed elapsed ::log/sync? true))))
|
||||
nil))
|
||||
|
||||
(defmethod impl/handler :index/update
|
||||
[{:keys [page-id changes] :as message}]
|
||||
(let [tpoint (ct/tpoint-ms)]
|
||||
(try
|
||||
(let [old-page (dm/get-in @state [:pages-index page-id])
|
||||
new-page (-> state
|
||||
(swap! ch/process-changes changes false)
|
||||
(dm/get-in [:pages-index page-id]))]
|
||||
|
||||
(let [old-page (dm/get-in @state [:pages-index page-id])
|
||||
new-page (-> state
|
||||
(swap! ch/process-changes changes false)
|
||||
(dm/get-in [:pages-index page-id]))
|
||||
message (assoc message
|
||||
:old-page old-page
|
||||
:new-page new-page)]
|
||||
(impl/handler (assoc message :cmd :selection/update-page-index))
|
||||
(impl/handler (assoc message :cmd :snaps/update-page-index))))
|
||||
(swap! state update ::snap snap/update-page old-page new-page)
|
||||
(swap! state update ::selection selection/update-page old-page new-page))
|
||||
(finally
|
||||
(let [elapsed (tpoint)]
|
||||
(log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true))))
|
||||
nil))
|
||||
|
||||
;; FIXME: schema
|
||||
|
||||
(defmethod impl/handler :index/query-snap
|
||||
[{:keys [page-id frame-id axis ranges bounds] :as message}]
|
||||
(if-let [index (get @state ::snap)]
|
||||
(let [match-bounds?
|
||||
(fn [[_ data]]
|
||||
(some #(or (= :guide (:type %))
|
||||
(= :layout (:type %))
|
||||
(grc/contains-point? bounds (:pt %))) data))
|
||||
|
||||
xform
|
||||
(comp (mapcat #(snap/query index page-id frame-id axis %))
|
||||
(distinct)
|
||||
(filter match-bounds?))]
|
||||
(into [] xform ranges))
|
||||
[]))
|
||||
|
||||
;; FIXME: schema
|
||||
|
||||
(defmethod impl/handler :index/query-selection
|
||||
[message]
|
||||
(if-let [index (get @state ::selection)]
|
||||
(selection/query index message)
|
||||
[]))
|
||||
|
||||
@@ -17,46 +17,62 @@
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.quadtree :as qdt]
|
||||
[app.worker.impl :as impl]
|
||||
[clojure.set :as set]
|
||||
[okulary.core :as l]))
|
||||
[clojure.set :as set]))
|
||||
|
||||
;; FIXME: performance shape & rect static props
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; IMPL
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:const padding-percent 0.10)
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
(defn- index-shape
|
||||
"A reducing function that ads a shape to the index"
|
||||
[objects parents-index clip-index index shape]
|
||||
(let [bounds
|
||||
(cond
|
||||
(and ^boolean (cfh/text-shape? shape)
|
||||
^boolean (some? (:position-data shape))
|
||||
^boolean (d/not-empty? (:position-data shape)))
|
||||
(gst/shape->bounds shape)
|
||||
|
||||
(defn make-index-shape
|
||||
[objects parents-index clip-parents-index]
|
||||
(fn [index shape]
|
||||
(let [{:keys [x y width height]}
|
||||
(cond
|
||||
(and ^boolean (cfh/text-shape? shape)
|
||||
^boolean (some? (:position-data shape))
|
||||
^boolean (d/not-empty? (:position-data shape)))
|
||||
(gst/shape->bounds shape)
|
||||
:else
|
||||
(grc/points->rect (:points shape)))
|
||||
|
||||
:else
|
||||
(grc/points->rect (:points shape)))
|
||||
bound
|
||||
#js {:x (dm/get-prop bounds :x)
|
||||
:y (dm/get-prop bounds :y)
|
||||
:width (dm/get-prop bounds :width)
|
||||
:height (dm/get-prop bounds :height)}
|
||||
|
||||
shape-bound #js {:x x :y y :width width :height height}
|
||||
shape-id
|
||||
(dm/get-prop shape :id)
|
||||
|
||||
parents (get parents-index (:id shape))
|
||||
clip-parents (get clip-parents-index (:id shape))
|
||||
frame-id
|
||||
(dm/get-prop shape :frame-id)
|
||||
|
||||
frame (when (and (not= :frame (:type shape))
|
||||
(not= (:frame-id shape) uuid/zero))
|
||||
(get objects (:frame-id shape)))]
|
||||
(qdt/insert index
|
||||
(:id shape)
|
||||
shape-bound
|
||||
(assoc shape
|
||||
:frame frame
|
||||
:clip-parents clip-parents
|
||||
:parents parents)))))
|
||||
shape-type
|
||||
(dm/get-prop shape :type)
|
||||
|
||||
(defn objects-bounds
|
||||
parents
|
||||
(get parents-index shape-id)
|
||||
|
||||
clip-parents
|
||||
(get clip-index shape-id)
|
||||
|
||||
frame
|
||||
(when (and (not= :frame shape-type)
|
||||
(not= frame-id uuid/zero))
|
||||
(get objects frame-id))]
|
||||
|
||||
(qdt/insert index
|
||||
shape-id
|
||||
bound
|
||||
(assoc shape
|
||||
:frame frame
|
||||
:clip-parents clip-parents
|
||||
:parents parents))))
|
||||
|
||||
(defn- objects-bounds
|
||||
"Calculates the bounds of the quadtree given a objects map."
|
||||
[objects]
|
||||
(-> objects
|
||||
@@ -64,7 +80,7 @@
|
||||
vals
|
||||
gsh/shapes->rect))
|
||||
|
||||
(defn add-padding-bounds
|
||||
(defn- add-padding-bounds
|
||||
"Adds a padding to the bounds defined as a percent in the constant `padding-percent`.
|
||||
For a value of 0.1 will add a 20% width increase (2 x padding)"
|
||||
[bounds]
|
||||
@@ -81,41 +97,48 @@
|
||||
|
||||
(defn- create-index
|
||||
[objects]
|
||||
(let [shapes (-> objects (dissoc uuid/zero) vals)
|
||||
parents-index (cfi/generate-child-all-parents-index objects)
|
||||
clip-parents-index (cfi/create-clip-index objects parents-index)
|
||||
|
||||
root-shapes (cfh/get-immediate-children objects uuid/zero)
|
||||
bounds (-> root-shapes gsh/shapes->rect add-padding-bounds)
|
||||
|
||||
index-shape (make-index-shape objects parents-index clip-parents-index)
|
||||
initial-quadtree (qdt/create (clj->js bounds))
|
||||
|
||||
index (reduce index-shape initial-quadtree shapes)]
|
||||
(let [parents-index (cfi/generate-child-all-parents-index objects)
|
||||
clip-index (cfi/create-clip-index objects parents-index)
|
||||
root-shapes (cfh/get-immediate-children objects uuid/zero)
|
||||
bounds (-> root-shapes gsh/shapes->rect add-padding-bounds)
|
||||
|
||||
index (reduce-kv #(index-shape objects parents-index clip-index %1 %3)
|
||||
(qdt/create (clj->js bounds))
|
||||
(dissoc objects uuid/zero))]
|
||||
{:index index :bounds bounds}))
|
||||
|
||||
;; FIXME: optimize
|
||||
(defn- update-index
|
||||
[{index :index :as data} old-objects new-objects]
|
||||
(let [changes? (fn [id]
|
||||
(not= (get old-objects id)
|
||||
(get new-objects id)))
|
||||
(let [object-changed?
|
||||
(fn [id]
|
||||
(not= (get old-objects id)
|
||||
(get new-objects id)))
|
||||
|
||||
changed-ids (into #{}
|
||||
(comp (filter #(not= % uuid/zero))
|
||||
(filter changes?)
|
||||
(mapcat #(into [%] (cfh/get-children-ids new-objects %))))
|
||||
(set/union (set (keys old-objects))
|
||||
(set (keys new-objects))))
|
||||
changed-ids
|
||||
(into #{}
|
||||
(comp (filter #(not= % uuid/zero))
|
||||
(filter object-changed?)
|
||||
(mapcat #(into [%] (cfh/get-children-ids new-objects %))))
|
||||
|
||||
shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?)))
|
||||
parents-index (cfi/generate-child-all-parents-index new-objects shapes)
|
||||
clip-parents-index (cfi/create-clip-index new-objects parents-index)
|
||||
(set/union (set (keys old-objects))
|
||||
(set (keys new-objects))))
|
||||
|
||||
new-index (qdt/remove-all index changed-ids)
|
||||
shapes
|
||||
(->> changed-ids
|
||||
(map #(get new-objects %))
|
||||
(filterv (comp not nil?)))
|
||||
|
||||
index-shape (make-index-shape new-objects parents-index clip-parents-index)
|
||||
index (reduce index-shape new-index shapes)]
|
||||
parents-index
|
||||
(cfi/generate-child-all-parents-index new-objects shapes)
|
||||
|
||||
clip-index
|
||||
(cfi/create-clip-index new-objects parents-index)
|
||||
|
||||
index
|
||||
(reduce #(index-shape new-objects parents-index clip-index %1 %2)
|
||||
(qdt/remove-all index changed-ids)
|
||||
shapes)]
|
||||
|
||||
(assoc data :index index)))
|
||||
|
||||
@@ -231,35 +254,36 @@
|
||||
(map :id))
|
||||
result)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PUBLIC API
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defmethod impl/handler :selection/initialize-page-index
|
||||
[{:keys [page] :as message}]
|
||||
(letfn [(add-page [state {:keys [id objects] :as page}]
|
||||
(assoc state id (create-index objects)))]
|
||||
(swap! state add-page page)
|
||||
nil))
|
||||
(defn add-page
|
||||
"Add a page index to the state"
|
||||
[state {:keys [id objects] :as page}]
|
||||
(assoc state id (create-index objects)))
|
||||
|
||||
(defmethod impl/handler :selection/update-page-index
|
||||
[{:keys [page-id old-page new-page] :as message}]
|
||||
(swap! state update page-id
|
||||
(fn [index]
|
||||
(let [old-objects (:objects old-page)
|
||||
new-objects (:objects new-page)
|
||||
old-bounds (:bounds index)
|
||||
new-bounds (objects-bounds new-objects)]
|
||||
(defn update-page
|
||||
"Update page index on the state"
|
||||
[state old-page new-page]
|
||||
(let [page-id (get old-page :id)]
|
||||
(update state page-id
|
||||
(fn [index]
|
||||
(let [old-objects (:objects old-page)
|
||||
new-objects (:objects new-page)
|
||||
old-bounds (:bounds index)
|
||||
new-bounds (objects-bounds new-objects)]
|
||||
|
||||
;; If the new bounds are contained within the old bounds
|
||||
;; we can update the index. Otherwise we need to
|
||||
;; re-create it.
|
||||
(if (and (some? index)
|
||||
(grc/contains-rect? old-bounds new-bounds))
|
||||
(update-index index old-objects new-objects)
|
||||
(create-index new-objects)))))
|
||||
nil)
|
||||
;; If the new bounds are contained within the old bounds
|
||||
;; we can update the index. Otherwise we need to
|
||||
;; re-create it.
|
||||
(if (and (some? index)
|
||||
(grc/contains-rect? old-bounds new-bounds))
|
||||
(update-index index old-objects new-objects)
|
||||
(create-index new-objects)))))))
|
||||
|
||||
(defmethod impl/handler :selection/query
|
||||
[{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
|
||||
:or {full-frame? false include-frames? false clip-children? true using-selrect? false}
|
||||
:as message}]
|
||||
(when-let [index (get @state page-id)]
|
||||
(defn query
|
||||
[index {:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
|
||||
:or {full-frame? false include-frames? false clip-children? true using-selrect? false}}]
|
||||
(when-let [index (get index page-id)]
|
||||
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?)))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.snap-data
|
||||
(ns app.worker.snap
|
||||
"Data structure that holds and retrieves the data to make the snaps.
|
||||
Internally is implemented with a balanced binary tree that queries by range.
|
||||
https://en.wikipedia.org/wiki/Range_tree"
|
||||
@@ -1,40 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.worker.snaps
|
||||
(:require
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.util.snap-data :as sd]
|
||||
[app.worker.impl :as impl]
|
||||
[okulary.core :as l]))
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
|
||||
;; Public API
|
||||
(defmethod impl/handler :snaps/initialize-page-index
|
||||
[{:keys [page] :as message}]
|
||||
(swap! state sd/add-page page)
|
||||
nil)
|
||||
|
||||
(defmethod impl/handler :snaps/update-page-index
|
||||
[{:keys [old-page new-page] :as message}]
|
||||
(swap! state sd/update-page old-page new-page)
|
||||
nil)
|
||||
|
||||
(defmethod impl/handler :snaps/range-query
|
||||
[{:keys [page-id frame-id axis ranges bounds] :as message}]
|
||||
(let [match-bounds?
|
||||
(fn [[_ data]]
|
||||
(some #(or (= :guide (:type %))
|
||||
(= :layout (:type %))
|
||||
(grc/contains-point? bounds (:pt %))) data))]
|
||||
(->> (into []
|
||||
(comp (mapcat #(sd/query @state page-id frame-id axis %))
|
||||
(distinct))
|
||||
ranges)
|
||||
(filter match-bounds?))))
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
[frontend-tests.tokens.token-form-test]
|
||||
[frontend-tests.util-range-tree-test]
|
||||
[frontend-tests.util-simple-math-test]
|
||||
[frontend-tests.util-snap-data-test]))
|
||||
[frontend-tests.worker-snap-test]))
|
||||
|
||||
(enable-console-print!)
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
(defn init
|
||||
[]
|
||||
(t/run-tests
|
||||
'frontend-tests.basic-shapes-test
|
||||
'frontend-tests.data.workspace-colors-test
|
||||
'frontend-tests.helpers-shapes-test
|
||||
'frontend-tests.logic.comp-remove-swap-slots-test
|
||||
'frontend-tests.logic.components-and-tokens
|
||||
@@ -38,13 +40,11 @@
|
||||
'frontend-tests.logic.groups-test
|
||||
'frontend-tests.logic.pasting-in-containers-test
|
||||
'frontend-tests.plugins.context-shapes-test
|
||||
'frontend-tests.util-range-tree-test
|
||||
'frontend-tests.util-snap-data-test
|
||||
'frontend-tests.util-simple-math-test
|
||||
'frontend-tests.basic-shapes-test
|
||||
'frontend-tests.data.workspace-colors-test
|
||||
'frontend-tests.tokens.import-export-test
|
||||
'frontend-tests.tokens.logic.token-actions-test
|
||||
'frontend-tests.tokens.logic.token-data-test
|
||||
'frontend-tests.tokens.import-export-test
|
||||
'frontend-tests.tokens.style-dictionary-test
|
||||
'frontend-tests.tokens.token-form-test))
|
||||
'frontend-tests.tokens.token-form-test
|
||||
'frontend-tests.util-range-tree-test
|
||||
'frontend-tests.util-simple-math-test
|
||||
'frontend-tests.worker-snap-test))
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns frontend-tests.util-snap-data-test
|
||||
(ns frontend-tests.worker-snap-test
|
||||
(:require
|
||||
[app.common.files.builder :as fb]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.snap-data :as sd]
|
||||
[app.worker.snap :as snap]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
(fn []
|
||||
(uuid/custom 123456789 (swap! counter inc)))))
|
||||
|
||||
(t/deftest test-create-index
|
||||
(t/deftest create-index
|
||||
(t/testing "Create empty data"
|
||||
(let [data (sd/make-snap-data)]
|
||||
(let [data (snap/make-snap-data)]
|
||||
(t/is (some? data))))
|
||||
|
||||
(t/testing "Add empty page (only root-frame)"
|
||||
@@ -32,8 +32,8 @@
|
||||
(fb/add-page {:name "Page 1"})
|
||||
(fb/get-current-page))
|
||||
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))]
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))]
|
||||
(t/is (some? data))))
|
||||
|
||||
(t/testing "Create simple shape on root"
|
||||
@@ -48,10 +48,10 @@
|
||||
:height 100}))
|
||||
page (fb/get-current-page state)
|
||||
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
result-x (sd/query data (:id page) uuid/zero :x [0 100])]
|
||||
result-x (snap/query data (:id page) uuid/zero :x [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
|
||||
@@ -82,11 +82,11 @@
|
||||
page (fb/get-current-page state)
|
||||
|
||||
;; frame-id (::fb/last-id file)
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-zero-x) 3))
|
||||
@@ -116,11 +116,11 @@
|
||||
|
||||
page (fb/get-current-page state)
|
||||
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-zero-x) 3))
|
||||
@@ -137,13 +137,13 @@
|
||||
frame-id (::fb/last-id state)
|
||||
page (fb/get-current-page state)
|
||||
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
;; We can snap in the root
|
||||
@@ -168,13 +168,13 @@
|
||||
|
||||
page (fb/get-current-page state)
|
||||
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
;; We can snap in the root
|
||||
@@ -185,7 +185,7 @@
|
||||
(t/is (= (count result-frame-x) 1))
|
||||
(t/is (= (count result-frame-y) 0)))))
|
||||
|
||||
(t/deftest test-update-index
|
||||
(t/deftest update-index
|
||||
(t/testing "Create frame on root and then remove it."
|
||||
(let [state (-> (fb/create-state)
|
||||
(fb/add-file {:name "Test"})
|
||||
@@ -200,17 +200,17 @@
|
||||
shape-id (::fb/last-id state)
|
||||
page (fb/get-current-page state)
|
||||
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
state (-> state
|
||||
(fb/delete-shape shape-id))
|
||||
|
||||
new-page (fb/get-current-page state)
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-y (sd/query data (:id page) uuid/zero :y [0 100])]
|
||||
result-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-y (snap/query data (:id page) uuid/zero :y [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-x) 0))
|
||||
@@ -231,16 +231,16 @@
|
||||
page (fb/get-current-page state)
|
||||
|
||||
;; frame-id (::fb/last-id state)
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
state (fb/delete-shape state shape-id)
|
||||
|
||||
new-page (fb/get-current-page state)
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-y (sd/query data (:id page) uuid/zero :y [0 100])]
|
||||
result-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-y (snap/query data (:id page) uuid/zero :y [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-x) 0))
|
||||
@@ -263,16 +263,16 @@
|
||||
state (fb/close-board state)
|
||||
|
||||
page (fb/get-current-page state)
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
state (fb/delete-shape state shape-id)
|
||||
new-page (fb/get-current-page state)
|
||||
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-zero-x) 3))
|
||||
@@ -291,18 +291,18 @@
|
||||
|
||||
frame-id (::fb/last-id state)
|
||||
page (fb/get-current-page state)
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
new-page (-> (fb/delete-guide state guide-id)
|
||||
(fb/get-current-page))
|
||||
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
|
||||
|
||||
(t/is (some? data))
|
||||
;; We can snap in the root
|
||||
@@ -325,17 +325,17 @@
|
||||
guide-id (::fb/last-id file)
|
||||
|
||||
page (fb/get-current-page file)
|
||||
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||
data (-> (snap/make-snap-data) (snap/add-page page))
|
||||
|
||||
new-page (-> (fb/delete-guide file guide-id)
|
||||
(fb/get-current-page))
|
||||
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
|
||||
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
|
||||
(t/is (some? data))
|
||||
;; We can snap in the root
|
||||
(t/is (= (count result-zero-x) 0))
|
||||
@@ -358,8 +358,8 @@
|
||||
|
||||
frame-id (::fb/last-id state)
|
||||
page (fb/get-current-page state)
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
state (fb/update-shape state frame-id
|
||||
(fn [shape]
|
||||
@@ -370,12 +370,12 @@
|
||||
|
||||
|
||||
new-page (fb/get-current-page state)
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100])
|
||||
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])
|
||||
result-frame-x-2 (sd/query data (:id page) frame-id :x [200 300])]
|
||||
result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-frame-x-1 (snap/query data (:id page) frame-id :x [0 100])
|
||||
result-zero-x-2 (snap/query data (:id page) uuid/zero :x [200 300])
|
||||
result-frame-x-2 (snap/query data (:id page) frame-id :x [200 300])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-zero-x-1) 0))
|
||||
@@ -396,8 +396,8 @@
|
||||
|
||||
shape-id (::fb/last-id state)
|
||||
page (fb/get-current-page state)
|
||||
data (-> (sd/make-snap-data)
|
||||
(sd/add-page page))
|
||||
data (-> (snap/make-snap-data)
|
||||
(snap/add-page page))
|
||||
|
||||
state (fb/update-shape state shape-id
|
||||
(fn [shape]
|
||||
@@ -408,10 +408,10 @@
|
||||
|
||||
new-page (fb/get-current-page state)
|
||||
;; FIXME: update
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])]
|
||||
result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-x-2 (snap/query data (:id page) uuid/zero :x [200 300])]
|
||||
|
||||
(t/is (some? data))
|
||||
(t/is (= (count result-zero-x-1) 0))
|
||||
@@ -432,22 +432,22 @@
|
||||
|
||||
frame-id (::fb/last-id state)
|
||||
page (fb/get-current-page state)
|
||||
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||
data (-> (snap/make-snap-data) (snap/add-page page))
|
||||
|
||||
new-page (-> (fb/update-guide state (assoc guide :position 150))
|
||||
(fb/get-current-page))
|
||||
|
||||
data (sd/update-page data page new-page)
|
||||
data (snap/update-page data page new-page)
|
||||
|
||||
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y-1 (sd/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y-1 (sd/query data (:id page) frame-id :y [0 100])
|
||||
result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
|
||||
result-zero-y-1 (snap/query data (:id page) uuid/zero :y [0 100])
|
||||
result-frame-x-1 (snap/query data (:id page) frame-id :x [0 100])
|
||||
result-frame-y-1 (snap/query data (:id page) frame-id :y [0 100])
|
||||
|
||||
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [0 200])
|
||||
result-zero-y-2 (sd/query data (:id page) uuid/zero :y [0 200])
|
||||
result-frame-x-2 (sd/query data (:id page) frame-id :x [0 200])
|
||||
result-frame-y-2 (sd/query data (:id page) frame-id :y [0 200])]
|
||||
result-zero-x-2 (snap/query data (:id page) uuid/zero :x [0 200])
|
||||
result-zero-y-2 (snap/query data (:id page) uuid/zero :y [0 200])
|
||||
result-frame-x-2 (snap/query data (:id page) frame-id :x [0 200])
|
||||
result-frame-y-2 (snap/query data (:id page) frame-id :y [0 200])]
|
||||
|
||||
(t/is (some? data))
|
||||
|
||||
Reference in New Issue
Block a user