mirror of
https://github.com/penpot/penpot.git
synced 2025-03-11 23:31:21 -05:00
⚡ Changes indices to update only necesary data
This commit is contained in:
parent
308fd8d4b0
commit
285a0d5f47
11 changed files with 382 additions and 219 deletions
|
@ -461,3 +461,9 @@
|
|||
kw (if (keyword? kw) (name kw) kw)]
|
||||
(keyword (str prefix kw))))
|
||||
|
||||
|
||||
(defn tap
|
||||
"Simpilar to the tap in rxjs but for plain collections"
|
||||
[f coll]
|
||||
(f coll)
|
||||
coll)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.common.pages.changes :as changes]
|
||||
[app.common.pages.common :as common]
|
||||
[app.common.pages.helpers :as helpers]
|
||||
[app.common.pages.indices :as indices]
|
||||
[app.common.pages.init :as init]
|
||||
[app.common.pages.spec :as spec]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
@ -42,7 +43,6 @@
|
|||
(d/export helpers/is-shape-grouped)
|
||||
(d/export helpers/get-parent)
|
||||
(d/export helpers/get-parents)
|
||||
(d/export helpers/generate-child-parent-index)
|
||||
(d/export helpers/clean-loops)
|
||||
(d/export helpers/calculate-invalid-targets)
|
||||
(d/export helpers/valid-frame-target)
|
||||
|
@ -60,13 +60,17 @@
|
|||
(d/export helpers/get-base-shape)
|
||||
(d/export helpers/is-parent?)
|
||||
(d/export helpers/get-index-in-parent)
|
||||
(d/export helpers/calculate-z-index)
|
||||
(d/export helpers/generate-child-all-parents-index)
|
||||
(d/export helpers/parse-path-name)
|
||||
(d/export helpers/merge-path-item)
|
||||
(d/export helpers/compact-path)
|
||||
(d/export helpers/compact-name)
|
||||
|
||||
;; Indices
|
||||
(d/export indices/calculate-z-index)
|
||||
(d/export indices/generate-child-all-parents-index)
|
||||
(d/export indices/generate-child-parent-index)
|
||||
(d/export indices/create-mask-index)
|
||||
|
||||
;; Process changes
|
||||
(d/export changes/process-changes)
|
||||
|
||||
|
|
|
@ -160,27 +160,6 @@
|
|||
(when parent-id
|
||||
(lazy-seq (cons parent-id (get-parents parent-id objects))))))
|
||||
|
||||
(defn generate-child-parent-index
|
||||
[objects]
|
||||
(reduce-kv
|
||||
(fn [index id obj]
|
||||
(assoc index id (:parent-id obj)))
|
||||
{} objects))
|
||||
|
||||
(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)))
|
||||
|
||||
([objects shapes]
|
||||
(let [shape->parents
|
||||
(fn [shape]
|
||||
(->> (get-parents (:id shape) objects)
|
||||
(into [])))]
|
||||
(->> shapes
|
||||
(map #(vector (:id %) (shape->parents %)))
|
||||
(into {})))))
|
||||
|
||||
(defn clean-loops
|
||||
"Clean a list of ids from circular references."
|
||||
|
@ -347,40 +326,7 @@
|
|||
(reduce red-fn cur-idx (reverse (:shapes object)))))]
|
||||
(into {} (rec-index '() uuid/zero))))
|
||||
|
||||
(defn calculate-z-index
|
||||
"Given a collection of shapes calculates their z-index. Greater index
|
||||
means is displayed over other shapes with less index."
|
||||
[objects]
|
||||
|
||||
(let [is-frame? (fn [id] (= :frame (get-in objects [id :type])))
|
||||
root-children (get-in objects [uuid/zero :shapes])
|
||||
num-frames (->> root-children (filter is-frame?) count)]
|
||||
(when (seq root-children)
|
||||
(loop [current (peek root-children)
|
||||
pending (pop root-children)
|
||||
current-idx (+ (count objects) num-frames -1)
|
||||
z-index {}]
|
||||
|
||||
(let [children (->> (get-in objects [current :shapes]))
|
||||
children (cond
|
||||
(and (is-frame? current) (contains? z-index current))
|
||||
[]
|
||||
|
||||
(and (is-frame? current)
|
||||
(not (contains? z-index current)))
|
||||
(into [current] children)
|
||||
|
||||
:else
|
||||
children)
|
||||
pending (into (vec pending) children)]
|
||||
(if (empty? pending)
|
||||
(assoc z-index current current-idx)
|
||||
|
||||
(let []
|
||||
(recur (peek pending)
|
||||
(pop pending)
|
||||
(dec current-idx)
|
||||
(assoc z-index current current-idx)))))))))
|
||||
|
||||
(defn expand-region-selection
|
||||
"Given a selection selects all the shapes between the first and last in
|
||||
|
|
83
common/app/common/pages/indices.cljc
Normal file
83
common/app/common/pages/indices.cljc
Normal file
|
@ -0,0 +1,83 @@
|
|||
;; 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) UXBOX Labs SL
|
||||
|
||||
(ns app.common.pages.indices
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as helpers]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn calculate-z-index
|
||||
"Given a collection of shapes calculates their z-index. Greater index
|
||||
means is displayed over other shapes with less index."
|
||||
[objects]
|
||||
(let [is-frame? (fn [id] (= :frame (get-in objects [id :type])))
|
||||
root-children (or (get-in objects [uuid/zero :shapes]) [])
|
||||
num-frames (->> root-children (filterv is-frame?) count)]
|
||||
|
||||
(when-not (empty? root-children)
|
||||
(loop [current (peek root-children)
|
||||
pending (pop root-children)
|
||||
current-idx (+ (count objects) num-frames -1)
|
||||
z-index (transient {})]
|
||||
|
||||
(let [children (get-in objects [current :shapes])
|
||||
assigned? (contains? z-index current)
|
||||
is-frame? (is-frame? current)
|
||||
|
||||
pending (cond
|
||||
(not is-frame?)
|
||||
(d/concat pending children)
|
||||
|
||||
(not assigned?)
|
||||
(d/concat pending [current] children)
|
||||
|
||||
:else
|
||||
pending)]
|
||||
|
||||
(if (empty? pending)
|
||||
(-> (assoc! z-index current current-idx)
|
||||
(persistent!))
|
||||
(recur (peek pending)
|
||||
(pop pending)
|
||||
(dec current-idx)
|
||||
(assoc! z-index current current-idx))))))))
|
||||
|
||||
(defn generate-child-parent-index
|
||||
[objects]
|
||||
(reduce-kv
|
||||
(fn [index id obj]
|
||||
(assoc index id (:parent-id obj)))
|
||||
{} objects))
|
||||
|
||||
(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)))
|
||||
|
||||
([objects shapes]
|
||||
(let [shape->parents
|
||||
(fn [shape]
|
||||
(->> (helpers/get-parents (:id shape) objects)
|
||||
(into [])))]
|
||||
(->> shapes
|
||||
(map #(vector (:id %) (shape->parents %)))
|
||||
(into {})))))
|
||||
|
||||
(defn create-mask-index
|
||||
"Retrieves the mask information for an object"
|
||||
[objects parents-index]
|
||||
(let [retrieve-masks
|
||||
(fn [id parents]
|
||||
(->> parents
|
||||
(map #(get objects %))
|
||||
(filter #(:masked-group? %))
|
||||
;; Retrieve the masking element
|
||||
(mapv #(get objects (->> % :shapes first)))))]
|
||||
(->> parents-index
|
||||
(d/mapm retrieve-masks))))
|
|
@ -264,7 +264,7 @@
|
|||
:index (cp/get-index-in-parent objects (:id shape))
|
||||
:shapes [(:id shape)]})))]
|
||||
|
||||
(when-not (empty? rch)
|
||||
(when-not (empty? uch)
|
||||
(rx/of dwu/pop-undo-into-transaction
|
||||
(dch/commit-changes rch uch {:commit-local? true})
|
||||
(dwu/commit-undo-transaction)
|
||||
|
@ -294,12 +294,15 @@
|
|||
(rx/take-until stopper)
|
||||
(rx/map #(gpt/to-vec from-position %)))
|
||||
|
||||
snap-delta (->> position
|
||||
(rx/throttle 20)
|
||||
(rx/switch-map
|
||||
(fn [pos]
|
||||
(->> (snap/closest-snap-move page-id shapes objects layout zoom pos)
|
||||
(rx/map #(vector pos %))))))]
|
||||
snap-delta (rx/concat
|
||||
;; We send the nil first so the stream is not waiting for the first value
|
||||
(rx/of nil)
|
||||
(->> position
|
||||
(rx/throttle 20)
|
||||
(rx/switch-map
|
||||
(fn [pos]
|
||||
(->> (snap/closest-snap-move page-id shapes objects layout zoom pos)
|
||||
(rx/map #(vector pos %)))))))]
|
||||
(if (empty? shapes)
|
||||
(rx/empty)
|
||||
(rx/concat
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
[beicon.core :as rx]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defonce ^:private snap-accuracy 10)
|
||||
(defonce ^:private snap-accuracy 5)
|
||||
(defonce ^:private snap-path-accuracy 10)
|
||||
(defonce ^:private snap-distance-accuracy 10)
|
||||
|
||||
|
@ -337,13 +337,15 @@
|
|||
"Snaps a position given an old snap to a different position. We use this to provide a temporal
|
||||
snap while the new is being processed."
|
||||
[[position [snap-pos snap-delta]]]
|
||||
(let [dx (if (not= 0 (:x snap-delta))
|
||||
(- (+ (:x snap-pos) (:x snap-delta)) (:x position))
|
||||
0)
|
||||
dy (if (not= 0 (:y snap-delta))
|
||||
(- (+ (:y snap-pos) (:y snap-delta)) (:y position))
|
||||
0)]
|
||||
(if (some? snap-delta)
|
||||
(let [dx (if (not= 0 (:x snap-delta))
|
||||
(- (+ (:x snap-pos) (:x snap-delta)) (:x position))
|
||||
0)
|
||||
dy (if (not= 0 (:y snap-delta))
|
||||
(- (+ (:y snap-pos) (:y snap-delta)) (:y position))
|
||||
0)]
|
||||
|
||||
(cond-> position
|
||||
(<= (mth/abs dx) snap-accuracy) (update :x + dx)
|
||||
(<= (mth/abs dy) snap-accuracy) (update :y + dy))))
|
||||
(cond-> position
|
||||
(<= (mth/abs dx) snap-accuracy) (update :x + dx)
|
||||
(<= (mth/abs dy) snap-accuracy) (update :y + dy)))
|
||||
position))
|
||||
|
|
|
@ -32,12 +32,16 @@
|
|||
"use strict";
|
||||
|
||||
goog.provide("app.util.quadtree");
|
||||
goog.require("cljs.core");
|
||||
|
||||
goog.scope(function() {
|
||||
const self = app.util.quadtree;
|
||||
const eq = cljs.core._EQ_;
|
||||
const contains = cljs.core.contains_QMARK_;
|
||||
|
||||
class Node {
|
||||
constructor(bounds, data) {
|
||||
constructor(id, bounds, data) {
|
||||
this.id = id;
|
||||
this.bounds = bounds;
|
||||
this.data = data;
|
||||
}
|
||||
|
@ -51,8 +55,8 @@ goog.scope(function() {
|
|||
this.level = level || 0;
|
||||
this.bounds = bounds;
|
||||
|
||||
this.objects = [];
|
||||
this.indexes = [];
|
||||
this.objects = [];
|
||||
this.indexes = [];
|
||||
}
|
||||
|
||||
split() {
|
||||
|
@ -183,14 +187,18 @@ goog.scope(function() {
|
|||
this.objects = [];
|
||||
this.indexes = [];
|
||||
}
|
||||
|
||||
getObjects() {
|
||||
return this.objects;
|
||||
}
|
||||
}
|
||||
|
||||
self.create = function(rect) {
|
||||
return new Quadtree(rect, 10, 4, 0);
|
||||
};
|
||||
|
||||
self.insert = function(index, bounds, data) {
|
||||
const node = new Node(bounds, data);
|
||||
self.insert = function(index, id, bounds, data) {
|
||||
const node = new Node(id, bounds, data);
|
||||
index.insert(node);
|
||||
return index;
|
||||
};
|
||||
|
@ -210,4 +218,29 @@ goog.scope(function() {
|
|||
}
|
||||
};
|
||||
|
||||
self.remove = function(index, id) {
|
||||
const result = self.create(index.bounds);
|
||||
|
||||
for (let node of index.objects) {
|
||||
if (!eq(id, node.id)) {
|
||||
self.insert(result, node.id, node.bounds, node.data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// FIXME: Inefficient to recreate the index. Needs to be improved
|
||||
self.remove_all = function(index, ids) {
|
||||
const result = self.create(index.bounds);
|
||||
|
||||
for (let node of self.search(index, index.bounds)) {
|
||||
if (!contains(ids, node.id)) {
|
||||
self.insert(result, node.id, node.bounds, node.data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"use strict";
|
||||
|
||||
goog.provide("app.util.range_tree");
|
||||
goog.require("cljs.core")
|
||||
goog.require("cljs.core");
|
||||
|
||||
goog.scope(function() {
|
||||
const eq = cljs.core._EQ_;
|
||||
|
@ -92,7 +92,7 @@ goog.scope(function() {
|
|||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.root === null;
|
||||
return !this.root;
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
@ -116,7 +116,7 @@ goog.scope(function() {
|
|||
|
||||
// Insert recursively in the tree
|
||||
function recInsert (branch, value, data) {
|
||||
if (branch === null) {
|
||||
if (!branch) {
|
||||
const ret = new Node(value, data);
|
||||
ret.color = Color.RED;
|
||||
return ret;
|
||||
|
@ -144,7 +144,7 @@ goog.scope(function() {
|
|||
|
||||
// Search for the min node
|
||||
function searchMin(branch) {
|
||||
if (branch.left === null) {
|
||||
if (!branch.left) {
|
||||
return branch;
|
||||
} else {
|
||||
return searchMin(branch.left);
|
||||
|
@ -153,7 +153,7 @@ goog.scope(function() {
|
|||
|
||||
// Remove the lefmost node of the current branch
|
||||
function recRemoveMin(branch) {
|
||||
if (branch.left === null) {
|
||||
if (!branch.left) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -167,7 +167,7 @@ goog.scope(function() {
|
|||
// Remove the data element for the value given
|
||||
// this will not remove the node, we have to remove the empty node afterwards
|
||||
function recRemoveData(branch, value, data) {
|
||||
if (branch === null) {
|
||||
if (!branch) {
|
||||
// Not found
|
||||
return branch;
|
||||
} else if (branch.value === value) {
|
||||
|
@ -193,7 +193,7 @@ goog.scope(function() {
|
|||
if (isRed(branch.left)) {
|
||||
branch = rotateRight(branch);
|
||||
}
|
||||
if (value === branch.value && branch.right === null) {
|
||||
if (value === branch.value && !branch.right) {
|
||||
return null;
|
||||
}
|
||||
if (!isRed(branch.right) && !isRed(branch.right.left)) {
|
||||
|
@ -214,7 +214,7 @@ goog.scope(function() {
|
|||
|
||||
// Retrieve all the data related to value
|
||||
function recGet(branch, value) {
|
||||
if (branch === null) {
|
||||
if (!branch) {
|
||||
return null;
|
||||
} else if (branch.value === value) {
|
||||
return branch.data;
|
||||
|
@ -226,7 +226,7 @@ goog.scope(function() {
|
|||
}
|
||||
|
||||
function recUpdate(branch, value, oldData, newData) {
|
||||
if (branch === null) {
|
||||
if (!branch) {
|
||||
return branch;
|
||||
} else if (branch.value === value) {
|
||||
branch.data = branch.data.map((it) => (eq(it, oldData)) ? newData : it);
|
||||
|
@ -239,7 +239,7 @@ goog.scope(function() {
|
|||
}
|
||||
|
||||
function recRangeQuery(branch, fromValue, toValue, result) {
|
||||
if (branch === null) {
|
||||
if (!branch) {
|
||||
return result;
|
||||
}
|
||||
if (fromValue < branch.value) {
|
||||
|
@ -329,7 +329,7 @@ goog.scope(function() {
|
|||
// This will return the string representation. We don't care about internal structure
|
||||
// only the data
|
||||
function recToString(branch, result) {
|
||||
if (branch === null) {
|
||||
if (!branch) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,11 +39,15 @@
|
|||
(defmethod handler :update-page-indices
|
||||
[{:keys [page-id changes] :as message}]
|
||||
|
||||
(swap! state ch/process-changes changes false)
|
||||
(let [old-objects (get-in @state [:pages-index page-id :objects])]
|
||||
(swap! state ch/process-changes changes false)
|
||||
|
||||
(let [objects (get-in @state [:pages-index page-id :objects])
|
||||
message (assoc message :objects objects)]
|
||||
(handler (-> message
|
||||
(assoc :cmd :selection/update-index)))
|
||||
(handler (-> message
|
||||
(assoc :cmd :snaps/update-index)))))
|
||||
(let [new-objects (get-in @state [:pages-index page-id :objects])
|
||||
message (assoc message
|
||||
:objects new-objects
|
||||
:new-objects new-objects
|
||||
:old-objects old-objects)]
|
||||
(handler (-> message
|
||||
(assoc :cmd :selection/update-index)))
|
||||
(handler (-> message
|
||||
(assoc :cmd :snaps/update-index))))))
|
||||
|
|
|
@ -15,12 +15,110 @@
|
|||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.quadtree :as qdt]
|
||||
[app.worker.impl :as impl]))
|
||||
[app.worker.impl :as impl]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
|
||||
(declare index-object)
|
||||
(declare create-index)
|
||||
(defn- index-object
|
||||
[objects z-index parents-index masks-index index obj]
|
||||
(let [{:keys [x y width height]} (:selrect obj)
|
||||
shape-bound #js {:x x :y y :width width :height height}
|
||||
|
||||
parents (get parents-index (:id obj))
|
||||
masks (get masks-index (:id obj))
|
||||
z (get z-index (:id obj))
|
||||
|
||||
frame (when (and (not= :frame (:type obj))
|
||||
(not= (:frame-id obj) uuid/zero))
|
||||
(get objects (:frame-id obj)))]
|
||||
(qdt/insert index
|
||||
(:id obj)
|
||||
shape-bound
|
||||
(assoc obj :frame frame :masks masks :parents parents :z z))))
|
||||
|
||||
(defn- create-index
|
||||
[objects]
|
||||
(let [shapes (-> objects (dissoc uuid/zero) (vals))
|
||||
z-index (cp/calculate-z-index objects)
|
||||
parents-index (cp/generate-child-all-parents-index objects)
|
||||
masks-index (cp/create-mask-index objects parents-index)
|
||||
bounds (gsh/selection-rect shapes)
|
||||
bounds #js {:x (:x bounds)
|
||||
:y (:y bounds)
|
||||
:width (:width bounds)
|
||||
:height (:height bounds)}]
|
||||
|
||||
(reduce (partial index-object objects z-index parents-index masks-index)
|
||||
(qdt/create bounds)
|
||||
shapes)))
|
||||
|
||||
(defn- update-index
|
||||
[index old-objects new-objects]
|
||||
|
||||
(let [changes? (fn [id]
|
||||
(not= (get old-objects id)
|
||||
(get new-objects id)))
|
||||
|
||||
changed-ids (into #{}
|
||||
(filter changes?)
|
||||
(set/union (keys old-objects)
|
||||
(keys new-objects)))
|
||||
|
||||
shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?)))
|
||||
z-index (cp/calculate-z-index new-objects)
|
||||
parents-index (cp/generate-child-all-parents-index new-objects shapes)
|
||||
masks-index (cp/create-mask-index new-objects parents-index)
|
||||
|
||||
new-index (qdt/remove-all index changed-ids)]
|
||||
|
||||
(reduce (partial index-object new-objects z-index parents-index masks-index)
|
||||
new-index
|
||||
shapes)))
|
||||
|
||||
(defn- query-index
|
||||
[index rect frame-id include-frames? include-groups? disabled-masks reverse?]
|
||||
(let [result (-> (qdt/search index (clj->js rect))
|
||||
(es6-iterator-seq))
|
||||
|
||||
;; Check if the shape matches the filter criteria
|
||||
match-criteria?
|
||||
(fn [shape]
|
||||
(and (not (:hidden shape))
|
||||
(not (:blocked shape))
|
||||
(or (not frame-id) (= frame-id (:frame-id shape)))
|
||||
(case (:type shape)
|
||||
:frame include-frames?
|
||||
:group include-groups?
|
||||
true)))
|
||||
|
||||
overlaps?
|
||||
(fn [shape]
|
||||
(gsh/overlaps? shape rect))
|
||||
|
||||
overlaps-masks?
|
||||
(fn [masks]
|
||||
(->> masks
|
||||
(some (comp not overlaps?))
|
||||
not))
|
||||
|
||||
;; Shapes after filters of overlapping and criteria
|
||||
matching-shapes
|
||||
(into []
|
||||
(comp (map #(unchecked-get % "data"))
|
||||
(filter match-criteria?)
|
||||
(filter (comp overlaps? :frame))
|
||||
(filter (comp overlaps-masks? :masks))
|
||||
(filter overlaps?))
|
||||
result)
|
||||
|
||||
keyfn (if reverse? (comp - :z) :z)]
|
||||
|
||||
(into (d/ordered-set)
|
||||
(->> matching-shapes
|
||||
(sort-by keyfn)
|
||||
(map :id)))))
|
||||
|
||||
|
||||
(defmethod impl/handler :selection/initialize-index
|
||||
[{:keys [file-id data] :as message}]
|
||||
|
@ -35,96 +133,13 @@
|
|||
nil))
|
||||
|
||||
(defmethod impl/handler :selection/update-index
|
||||
[{:keys [page-id objects] :as message}]
|
||||
(let [index (create-index objects)]
|
||||
(swap! state update page-id (constantly index))
|
||||
nil))
|
||||
[{:keys [page-id old-objects new-objects] :as message}]
|
||||
(swap! state update page-id update-index old-objects new-objects)
|
||||
nil)
|
||||
|
||||
(defmethod impl/handler :selection/query
|
||||
[{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks reverse?]
|
||||
:or {include-groups? true disabled-masks #{} reverse? false} :as message}]
|
||||
(when-let [index (get @state page-id)]
|
||||
(let [result (-> (qdt/search index (clj->js rect))
|
||||
(es6-iterator-seq))
|
||||
|
||||
;; Check if the shape matches the filter criteria
|
||||
match-criteria?
|
||||
(fn [shape]
|
||||
(and (not (:hidden shape))
|
||||
(not (:blocked shape))
|
||||
(or (not frame-id) (= frame-id (:frame-id shape)))
|
||||
(case (:type shape)
|
||||
:frame include-frames?
|
||||
:group include-groups?
|
||||
true)))
|
||||
|
||||
overlaps?
|
||||
(fn [shape]
|
||||
(gsh/overlaps? shape rect))
|
||||
|
||||
overlaps-masks?
|
||||
(fn [masks]
|
||||
(->> masks
|
||||
(some (comp not overlaps?))
|
||||
not))
|
||||
|
||||
;; Shapes after filters of overlapping and criteria
|
||||
matching-shapes
|
||||
(into []
|
||||
(comp (map #(unchecked-get % "data"))
|
||||
(filter match-criteria?)
|
||||
(filter (comp overlaps? :frame))
|
||||
(filter (comp overlaps-masks? :masks))
|
||||
(filter overlaps?))
|
||||
result)
|
||||
|
||||
keyfn (if reverse? (comp - :z) :z)]
|
||||
|
||||
(into (d/ordered-set)
|
||||
(->> matching-shapes
|
||||
(sort-by keyfn)
|
||||
(map :id))))))
|
||||
|
||||
(defn create-mask-index
|
||||
"Retrieves the mask information for an object"
|
||||
[objects parents-index]
|
||||
(let [retrieve-masks
|
||||
(fn [id parents]
|
||||
(->> parents
|
||||
(map #(get objects %))
|
||||
(filter #(:masked-group? %))
|
||||
;; Retrieve the masking element
|
||||
(mapv #(get objects (->> % :shapes first)))))]
|
||||
(->> parents-index
|
||||
(d/mapm retrieve-masks))))
|
||||
|
||||
(defn- create-index
|
||||
[objects]
|
||||
(let [shapes (-> objects (dissoc uuid/zero) (vals))
|
||||
z-index (cp/calculate-z-index objects)
|
||||
parents-index (cp/generate-child-all-parents-index objects)
|
||||
masks-index (create-mask-index objects parents-index)
|
||||
bounds (gsh/selection-rect shapes)
|
||||
bounds #js {:x (:x bounds)
|
||||
:y (:y bounds)
|
||||
:width (:width bounds)
|
||||
:height (:height bounds)}]
|
||||
|
||||
(reduce (partial index-object objects z-index parents-index masks-index)
|
||||
(qdt/create bounds)
|
||||
shapes)))
|
||||
|
||||
(defn- index-object
|
||||
[objects z-index parents-index masks-index index obj]
|
||||
(let [{:keys [x y width height]} (:selrect obj)
|
||||
shape-bound #js {:x x :y y :width width :height height}
|
||||
parents (get parents-index (:id obj))
|
||||
masks (get masks-index (:id obj))
|
||||
z (get z-index (:id obj))
|
||||
frame (when (and (not= :frame (:type obj))
|
||||
(not= (:frame-id obj) uuid/zero))
|
||||
(get objects (:frame-id obj)))]
|
||||
(qdt/insert index
|
||||
shape-bound
|
||||
(assoc obj :frame frame :masks masks :parents parents :z z))))
|
||||
(query-index index rect frame-id include-frames? include-groups? disabled-masks reverse?)))
|
||||
|
||||
|
|
|
@ -6,48 +6,105 @@
|
|||
|
||||
(ns app.worker.snaps
|
||||
(:require
|
||||
[okulary.core :as l]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.data :as d]
|
||||
[app.worker.impl :as impl]
|
||||
[app.util.range-tree :as rt]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.geom.grid :as gg]
|
||||
[app.util.geom.snap-points :as snap]
|
||||
[app.util.geom.grid :as gg]))
|
||||
[app.util.range-tree :as rt]
|
||||
[app.worker.impl :as impl]
|
||||
[clojure.set :as set]
|
||||
[okulary.core :as l]))
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
|
||||
(defn- create-coord-data
|
||||
"Initializes the range tree given the shapes"
|
||||
[frame-id shapes coord]
|
||||
(let [process-shape (fn [coord]
|
||||
(fn [shape]
|
||||
(concat
|
||||
(let [points (snap/shape-snap-points shape)]
|
||||
(map #(vector % (:id shape)) points))
|
||||
(defn process-shape [frame-id coord]
|
||||
(fn [shape]
|
||||
(let [points (snap/shape-snap-points shape)
|
||||
shape-data (->> points (mapv #(vector % (:id shape))))]
|
||||
(if (= (:id shape) frame-id)
|
||||
(d/concat
|
||||
shape-data
|
||||
|
||||
;; The grid points are only added by the "root" of the coord-dat
|
||||
(when (= (:id shape) frame-id)
|
||||
(let [points (gg/grid-snap-points shape coord)]
|
||||
(map #(vector % :layout) points))))))
|
||||
into-tree (fn [tree [point _ :as data]]
|
||||
(rt/insert tree (coord point) data))]
|
||||
;; The grid points are only added by the "root" of the coord-dat
|
||||
(->> (gg/grid-snap-points shape coord)
|
||||
(map #(vector % :layout))))
|
||||
|
||||
shape-data))))
|
||||
|
||||
(defn- add-coord-data
|
||||
"Initializes the range tree given the shapes"
|
||||
[data frame-id shapes coord]
|
||||
(letfn [(into-tree [tree [point _ :as data]]
|
||||
(rt/insert tree (coord point) data))]
|
||||
(->> shapes
|
||||
(mapcat (process-shape coord))
|
||||
(reduce into-tree (rt/make-tree)))))
|
||||
(mapcat (process-shape frame-id coord))
|
||||
(reduce into-tree data))))
|
||||
|
||||
(defn remove-coord-data
|
||||
[data frame-id shapes coord]
|
||||
|
||||
(letfn [(remove-tree [tree [point _ :as data]]
|
||||
(rt/remove tree (coord point) data))]
|
||||
(->> shapes
|
||||
(mapcat (process-shape frame-id coord))
|
||||
(reduce remove-tree data))))
|
||||
|
||||
(defn aggregate-data
|
||||
([objects]
|
||||
(aggregate-data objects (keys objects)))
|
||||
|
||||
([objects ids]
|
||||
(->> ids
|
||||
(filter #(contains? objects %))
|
||||
(map #(get objects %))
|
||||
(filter :frame-id)
|
||||
(group-by :frame-id)
|
||||
;; Adds the frame
|
||||
(d/mapm #(conj %2 (get objects %1))))))
|
||||
|
||||
(defn- initialize-snap-data
|
||||
"Initialize the snap information with the current workspace information"
|
||||
[objects]
|
||||
(let [frame-shapes (->> (vals objects)
|
||||
(filter :frame-id)
|
||||
(group-by :frame-id))
|
||||
frame-shapes (->> (cp/select-frames objects)
|
||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
||||
(let [shapes-data (aggregate-data objects)
|
||||
|
||||
(d/mapm (fn [frame-id shapes] {:x (create-coord-data frame-id shapes :x)
|
||||
:y (create-coord-data frame-id shapes :y)})
|
||||
frame-shapes)))
|
||||
create-index
|
||||
(fn [frame-id shapes] {:x (add-coord-data (rt/make-tree) frame-id shapes :x)
|
||||
:y (add-coord-data (rt/make-tree) frame-id shapes :y)})]
|
||||
|
||||
(d/mapm create-index shapes-data)))
|
||||
|
||||
(defn- update-snap-data
|
||||
[snap-data old-objects new-objects]
|
||||
|
||||
(let [changed? #(not= (get old-objects %) (get new-objects %))
|
||||
|
||||
changed-ids (into #{}
|
||||
(filter changed?)
|
||||
(set/union (keys old-objects) (keys new-objects)))
|
||||
|
||||
to-delete (aggregate-data old-objects changed-ids)
|
||||
to-add (aggregate-data new-objects changed-ids)
|
||||
|
||||
delete-data
|
||||
(fn [snap-data [frame-id shapes]]
|
||||
(-> snap-data
|
||||
(update-in [frame-id :x] remove-coord-data frame-id shapes :x)
|
||||
(update-in [frame-id :y] remove-coord-data frame-id shapes :y)))
|
||||
|
||||
add-data
|
||||
(fn [snap-data [frame-id shapes]]
|
||||
(-> snap-data
|
||||
(update-in [frame-id :x] add-coord-data frame-id shapes :x)
|
||||
(update-in [frame-id :y] add-coord-data frame-id shapes :y)))
|
||||
|
||||
snap-data (->> to-delete
|
||||
(reduce delete-data snap-data))
|
||||
|
||||
snap-data (->> to-add
|
||||
(reduce add-data snap-data))]
|
||||
|
||||
snap-data))
|
||||
|
||||
(defn- log-state
|
||||
"Helper function to print a friendly version of the snap tree. Debugging purposes"
|
||||
|
@ -60,6 +117,16 @@
|
|||
(let [snap-data (initialize-snap-data objects)]
|
||||
(assoc state page-id snap-data)))
|
||||
|
||||
(defn- update-page [state page-id old-objects new-objects]
|
||||
(let [changed? #(not= (get old-objects %) (get new-objects %))
|
||||
changed-ids (into #{}
|
||||
(filter changed?)
|
||||
(set/union (keys old-objects) (keys new-objects)))
|
||||
|
||||
snap-data (get state page-id)
|
||||
snap-data (update-snap-data snap-data old-objects new-objects)]
|
||||
(assoc state page-id snap-data)))
|
||||
|
||||
;; Public API
|
||||
(defmethod impl/handler :snaps/initialize-index
|
||||
[{:keys [file-id data] :as message}]
|
||||
|
@ -74,9 +141,9 @@
|
|||
nil))
|
||||
|
||||
(defmethod impl/handler :snaps/update-index
|
||||
[{:keys [page-id objects] :as message}]
|
||||
[{:keys [page-id old-objects new-objects] :as message}]
|
||||
;; TODO: Check the difference and update the index acordingly
|
||||
(swap! state index-page page-id objects)
|
||||
(swap! state update-page page-id old-objects new-objects)
|
||||
;; (log-state)
|
||||
nil)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue