mirror of
https://github.com/penpot/penpot.git
synced 2025-05-29 12:26:10 +02: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)]
|
kw (if (keyword? kw) (name kw) kw)]
|
||||||
(keyword (str prefix 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.changes :as changes]
|
||||||
[app.common.pages.common :as common]
|
[app.common.pages.common :as common]
|
||||||
[app.common.pages.helpers :as helpers]
|
[app.common.pages.helpers :as helpers]
|
||||||
|
[app.common.pages.indices :as indices]
|
||||||
[app.common.pages.init :as init]
|
[app.common.pages.init :as init]
|
||||||
[app.common.pages.spec :as spec]
|
[app.common.pages.spec :as spec]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
@ -42,7 +43,6 @@
|
||||||
(d/export helpers/is-shape-grouped)
|
(d/export helpers/is-shape-grouped)
|
||||||
(d/export helpers/get-parent)
|
(d/export helpers/get-parent)
|
||||||
(d/export helpers/get-parents)
|
(d/export helpers/get-parents)
|
||||||
(d/export helpers/generate-child-parent-index)
|
|
||||||
(d/export helpers/clean-loops)
|
(d/export helpers/clean-loops)
|
||||||
(d/export helpers/calculate-invalid-targets)
|
(d/export helpers/calculate-invalid-targets)
|
||||||
(d/export helpers/valid-frame-target)
|
(d/export helpers/valid-frame-target)
|
||||||
|
@ -60,13 +60,17 @@
|
||||||
(d/export helpers/get-base-shape)
|
(d/export helpers/get-base-shape)
|
||||||
(d/export helpers/is-parent?)
|
(d/export helpers/is-parent?)
|
||||||
(d/export helpers/get-index-in-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/parse-path-name)
|
||||||
(d/export helpers/merge-path-item)
|
(d/export helpers/merge-path-item)
|
||||||
(d/export helpers/compact-path)
|
(d/export helpers/compact-path)
|
||||||
(d/export helpers/compact-name)
|
(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
|
;; Process changes
|
||||||
(d/export changes/process-changes)
|
(d/export changes/process-changes)
|
||||||
|
|
||||||
|
|
|
@ -160,27 +160,6 @@
|
||||||
(when parent-id
|
(when parent-id
|
||||||
(lazy-seq (cons parent-id (get-parents parent-id objects))))))
|
(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
|
(defn clean-loops
|
||||||
"Clean a list of ids from circular references."
|
"Clean a list of ids from circular references."
|
||||||
|
@ -347,40 +326,7 @@
|
||||||
(reduce red-fn cur-idx (reverse (:shapes object)))))]
|
(reduce red-fn cur-idx (reverse (:shapes object)))))]
|
||||||
(into {} (rec-index '() uuid/zero))))
|
(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
|
(defn expand-region-selection
|
||||||
"Given a selection selects all the shapes between the first and last in
|
"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))
|
:index (cp/get-index-in-parent objects (:id shape))
|
||||||
:shapes [(:id shape)]})))]
|
:shapes [(:id shape)]})))]
|
||||||
|
|
||||||
(when-not (empty? rch)
|
(when-not (empty? uch)
|
||||||
(rx/of dwu/pop-undo-into-transaction
|
(rx/of dwu/pop-undo-into-transaction
|
||||||
(dch/commit-changes rch uch {:commit-local? true})
|
(dch/commit-changes rch uch {:commit-local? true})
|
||||||
(dwu/commit-undo-transaction)
|
(dwu/commit-undo-transaction)
|
||||||
|
@ -294,12 +294,15 @@
|
||||||
(rx/take-until stopper)
|
(rx/take-until stopper)
|
||||||
(rx/map #(gpt/to-vec from-position %)))
|
(rx/map #(gpt/to-vec from-position %)))
|
||||||
|
|
||||||
snap-delta (->> position
|
snap-delta (rx/concat
|
||||||
(rx/throttle 20)
|
;; We send the nil first so the stream is not waiting for the first value
|
||||||
(rx/switch-map
|
(rx/of nil)
|
||||||
(fn [pos]
|
(->> position
|
||||||
(->> (snap/closest-snap-move page-id shapes objects layout zoom pos)
|
(rx/throttle 20)
|
||||||
(rx/map #(vector pos %))))))]
|
(rx/switch-map
|
||||||
|
(fn [pos]
|
||||||
|
(->> (snap/closest-snap-move page-id shapes objects layout zoom pos)
|
||||||
|
(rx/map #(vector pos %)))))))]
|
||||||
(if (empty? shapes)
|
(if (empty? shapes)
|
||||||
(rx/empty)
|
(rx/empty)
|
||||||
(rx/concat
|
(rx/concat
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
(defonce ^:private snap-accuracy 10)
|
(defonce ^:private snap-accuracy 5)
|
||||||
(defonce ^:private snap-path-accuracy 10)
|
(defonce ^:private snap-path-accuracy 10)
|
||||||
(defonce ^:private snap-distance-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
|
"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."
|
snap while the new is being processed."
|
||||||
[[position [snap-pos snap-delta]]]
|
[[position [snap-pos snap-delta]]]
|
||||||
(let [dx (if (not= 0 (:x snap-delta))
|
(if (some? snap-delta)
|
||||||
(- (+ (:x snap-pos) (:x snap-delta)) (:x position))
|
(let [dx (if (not= 0 (:x snap-delta))
|
||||||
0)
|
(- (+ (:x snap-pos) (:x snap-delta)) (:x position))
|
||||||
dy (if (not= 0 (:y snap-delta))
|
0)
|
||||||
(- (+ (:y snap-pos) (:y snap-delta)) (:y position))
|
dy (if (not= 0 (:y snap-delta))
|
||||||
0)]
|
(- (+ (:y snap-pos) (:y snap-delta)) (:y position))
|
||||||
|
0)]
|
||||||
|
|
||||||
(cond-> position
|
(cond-> position
|
||||||
(<= (mth/abs dx) snap-accuracy) (update :x + dx)
|
(<= (mth/abs dx) snap-accuracy) (update :x + dx)
|
||||||
(<= (mth/abs dy) snap-accuracy) (update :y + dy))))
|
(<= (mth/abs dy) snap-accuracy) (update :y + dy)))
|
||||||
|
position))
|
||||||
|
|
|
@ -32,12 +32,16 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
goog.provide("app.util.quadtree");
|
goog.provide("app.util.quadtree");
|
||||||
|
goog.require("cljs.core");
|
||||||
|
|
||||||
goog.scope(function() {
|
goog.scope(function() {
|
||||||
const self = app.util.quadtree;
|
const self = app.util.quadtree;
|
||||||
|
const eq = cljs.core._EQ_;
|
||||||
|
const contains = cljs.core.contains_QMARK_;
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
constructor(bounds, data) {
|
constructor(id, bounds, data) {
|
||||||
|
this.id = id;
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
@ -51,8 +55,8 @@ goog.scope(function() {
|
||||||
this.level = level || 0;
|
this.level = level || 0;
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
|
|
||||||
this.objects = [];
|
this.objects = [];
|
||||||
this.indexes = [];
|
this.indexes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
split() {
|
split() {
|
||||||
|
@ -183,14 +187,18 @@ goog.scope(function() {
|
||||||
this.objects = [];
|
this.objects = [];
|
||||||
this.indexes = [];
|
this.indexes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getObjects() {
|
||||||
|
return this.objects;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.create = function(rect) {
|
self.create = function(rect) {
|
||||||
return new Quadtree(rect, 10, 4, 0);
|
return new Quadtree(rect, 10, 4, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.insert = function(index, bounds, data) {
|
self.insert = function(index, id, bounds, data) {
|
||||||
const node = new Node(bounds, data);
|
const node = new Node(id, bounds, data);
|
||||||
index.insert(node);
|
index.insert(node);
|
||||||
return index;
|
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";
|
"use strict";
|
||||||
|
|
||||||
goog.provide("app.util.range_tree");
|
goog.provide("app.util.range_tree");
|
||||||
goog.require("cljs.core")
|
goog.require("cljs.core");
|
||||||
|
|
||||||
goog.scope(function() {
|
goog.scope(function() {
|
||||||
const eq = cljs.core._EQ_;
|
const eq = cljs.core._EQ_;
|
||||||
|
@ -92,7 +92,7 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return this.root === null;
|
return !this.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
@ -116,7 +116,7 @@ goog.scope(function() {
|
||||||
|
|
||||||
// Insert recursively in the tree
|
// Insert recursively in the tree
|
||||||
function recInsert (branch, value, data) {
|
function recInsert (branch, value, data) {
|
||||||
if (branch === null) {
|
if (!branch) {
|
||||||
const ret = new Node(value, data);
|
const ret = new Node(value, data);
|
||||||
ret.color = Color.RED;
|
ret.color = Color.RED;
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -144,7 +144,7 @@ goog.scope(function() {
|
||||||
|
|
||||||
// Search for the min node
|
// Search for the min node
|
||||||
function searchMin(branch) {
|
function searchMin(branch) {
|
||||||
if (branch.left === null) {
|
if (!branch.left) {
|
||||||
return branch;
|
return branch;
|
||||||
} else {
|
} else {
|
||||||
return searchMin(branch.left);
|
return searchMin(branch.left);
|
||||||
|
@ -153,7 +153,7 @@ goog.scope(function() {
|
||||||
|
|
||||||
// Remove the lefmost node of the current branch
|
// Remove the lefmost node of the current branch
|
||||||
function recRemoveMin(branch) {
|
function recRemoveMin(branch) {
|
||||||
if (branch.left === null) {
|
if (!branch.left) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ goog.scope(function() {
|
||||||
// Remove the data element for the value given
|
// Remove the data element for the value given
|
||||||
// this will not remove the node, we have to remove the empty node afterwards
|
// this will not remove the node, we have to remove the empty node afterwards
|
||||||
function recRemoveData(branch, value, data) {
|
function recRemoveData(branch, value, data) {
|
||||||
if (branch === null) {
|
if (!branch) {
|
||||||
// Not found
|
// Not found
|
||||||
return branch;
|
return branch;
|
||||||
} else if (branch.value === value) {
|
} else if (branch.value === value) {
|
||||||
|
@ -193,7 +193,7 @@ goog.scope(function() {
|
||||||
if (isRed(branch.left)) {
|
if (isRed(branch.left)) {
|
||||||
branch = rotateRight(branch);
|
branch = rotateRight(branch);
|
||||||
}
|
}
|
||||||
if (value === branch.value && branch.right === null) {
|
if (value === branch.value && !branch.right) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!isRed(branch.right) && !isRed(branch.right.left)) {
|
if (!isRed(branch.right) && !isRed(branch.right.left)) {
|
||||||
|
@ -214,7 +214,7 @@ goog.scope(function() {
|
||||||
|
|
||||||
// Retrieve all the data related to value
|
// Retrieve all the data related to value
|
||||||
function recGet(branch, value) {
|
function recGet(branch, value) {
|
||||||
if (branch === null) {
|
if (!branch) {
|
||||||
return null;
|
return null;
|
||||||
} else if (branch.value === value) {
|
} else if (branch.value === value) {
|
||||||
return branch.data;
|
return branch.data;
|
||||||
|
@ -226,7 +226,7 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function recUpdate(branch, value, oldData, newData) {
|
function recUpdate(branch, value, oldData, newData) {
|
||||||
if (branch === null) {
|
if (!branch) {
|
||||||
return branch;
|
return branch;
|
||||||
} else if (branch.value === value) {
|
} else if (branch.value === value) {
|
||||||
branch.data = branch.data.map((it) => (eq(it, oldData)) ? newData : it);
|
branch.data = branch.data.map((it) => (eq(it, oldData)) ? newData : it);
|
||||||
|
@ -239,7 +239,7 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function recRangeQuery(branch, fromValue, toValue, result) {
|
function recRangeQuery(branch, fromValue, toValue, result) {
|
||||||
if (branch === null) {
|
if (!branch) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (fromValue < branch.value) {
|
if (fromValue < branch.value) {
|
||||||
|
@ -329,7 +329,7 @@ goog.scope(function() {
|
||||||
// This will return the string representation. We don't care about internal structure
|
// This will return the string representation. We don't care about internal structure
|
||||||
// only the data
|
// only the data
|
||||||
function recToString(branch, result) {
|
function recToString(branch, result) {
|
||||||
if (branch === null) {
|
if (!branch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,11 +39,15 @@
|
||||||
(defmethod handler :update-page-indices
|
(defmethod handler :update-page-indices
|
||||||
[{:keys [page-id changes] :as message}]
|
[{: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])
|
(let [new-objects (get-in @state [:pages-index page-id :objects])
|
||||||
message (assoc message :objects objects)]
|
message (assoc message
|
||||||
(handler (-> message
|
:objects new-objects
|
||||||
(assoc :cmd :selection/update-index)))
|
:new-objects new-objects
|
||||||
(handler (-> message
|
:old-objects old-objects)]
|
||||||
(assoc :cmd :snaps/update-index)))))
|
(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.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.util.quadtree :as qdt]
|
[app.util.quadtree :as qdt]
|
||||||
[app.worker.impl :as impl]))
|
[app.worker.impl :as impl]
|
||||||
|
[clojure.set :as set]))
|
||||||
|
|
||||||
(defonce state (l/atom {}))
|
(defonce state (l/atom {}))
|
||||||
|
|
||||||
(declare index-object)
|
(defn- index-object
|
||||||
(declare create-index)
|
[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
|
(defmethod impl/handler :selection/initialize-index
|
||||||
[{:keys [file-id data] :as message}]
|
[{:keys [file-id data] :as message}]
|
||||||
|
@ -35,96 +133,13 @@
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defmethod impl/handler :selection/update-index
|
(defmethod impl/handler :selection/update-index
|
||||||
[{:keys [page-id objects] :as message}]
|
[{:keys [page-id old-objects new-objects] :as message}]
|
||||||
(let [index (create-index objects)]
|
(swap! state update page-id update-index old-objects new-objects)
|
||||||
(swap! state update page-id (constantly index))
|
nil)
|
||||||
nil))
|
|
||||||
|
|
||||||
(defmethod impl/handler :selection/query
|
(defmethod impl/handler :selection/query
|
||||||
[{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks reverse?]
|
[{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks reverse?]
|
||||||
:or {include-groups? true disabled-masks #{} reverse? false} :as message}]
|
:or {include-groups? true disabled-masks #{} reverse? false} :as message}]
|
||||||
(when-let [index (get @state page-id)]
|
(when-let [index (get @state page-id)]
|
||||||
(let [result (-> (qdt/search index (clj->js rect))
|
(query-index index rect frame-id include-frames? include-groups? disabled-masks reverse?)))
|
||||||
(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))))
|
|
||||||
|
|
||||||
|
|
|
@ -6,48 +6,105 @@
|
||||||
|
|
||||||
(ns app.worker.snaps
|
(ns app.worker.snaps
|
||||||
(:require
|
(:require
|
||||||
[okulary.core :as l]
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.common.pages :as cp]
|
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.worker.impl :as impl]
|
[app.common.pages :as cp]
|
||||||
[app.util.range-tree :as rt]
|
[app.common.uuid :as uuid]
|
||||||
|
[app.util.geom.grid :as gg]
|
||||||
[app.util.geom.snap-points :as snap]
|
[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 {}))
|
(defonce state (l/atom {}))
|
||||||
|
|
||||||
(defn- create-coord-data
|
(defn process-shape [frame-id coord]
|
||||||
"Initializes the range tree given the shapes"
|
(fn [shape]
|
||||||
[frame-id shapes coord]
|
(let [points (snap/shape-snap-points shape)
|
||||||
(let [process-shape (fn [coord]
|
shape-data (->> points (mapv #(vector % (:id shape))))]
|
||||||
(fn [shape]
|
(if (= (:id shape) frame-id)
|
||||||
(concat
|
(d/concat
|
||||||
(let [points (snap/shape-snap-points shape)]
|
shape-data
|
||||||
(map #(vector % (:id shape)) points))
|
|
||||||
|
|
||||||
;; The grid points are only added by the "root" of the coord-dat
|
;; The grid points are only added by the "root" of the coord-dat
|
||||||
(when (= (:id shape) frame-id)
|
(->> (gg/grid-snap-points shape coord)
|
||||||
(let [points (gg/grid-snap-points shape coord)]
|
(map #(vector % :layout))))
|
||||||
(map #(vector % :layout) points))))))
|
|
||||||
into-tree (fn [tree [point _ :as data]]
|
shape-data))))
|
||||||
(rt/insert tree (coord point) 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
|
(->> shapes
|
||||||
(mapcat (process-shape coord))
|
(mapcat (process-shape frame-id coord))
|
||||||
(reduce into-tree (rt/make-tree)))))
|
(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
|
(defn- initialize-snap-data
|
||||||
"Initialize the snap information with the current workspace information"
|
"Initialize the snap information with the current workspace information"
|
||||||
[objects]
|
[objects]
|
||||||
(let [frame-shapes (->> (vals objects)
|
(let [shapes-data (aggregate-data objects)
|
||||||
(filter :frame-id)
|
|
||||||
(group-by :frame-id))
|
|
||||||
frame-shapes (->> (cp/select-frames objects)
|
|
||||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
|
||||||
|
|
||||||
(d/mapm (fn [frame-id shapes] {:x (create-coord-data frame-id shapes :x)
|
create-index
|
||||||
:y (create-coord-data frame-id shapes :y)})
|
(fn [frame-id shapes] {:x (add-coord-data (rt/make-tree) frame-id shapes :x)
|
||||||
frame-shapes)))
|
: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
|
(defn- log-state
|
||||||
"Helper function to print a friendly version of the snap tree. Debugging purposes"
|
"Helper function to print a friendly version of the snap tree. Debugging purposes"
|
||||||
|
@ -60,6 +117,16 @@
|
||||||
(let [snap-data (initialize-snap-data objects)]
|
(let [snap-data (initialize-snap-data objects)]
|
||||||
(assoc state page-id snap-data)))
|
(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
|
;; Public API
|
||||||
(defmethod impl/handler :snaps/initialize-index
|
(defmethod impl/handler :snaps/initialize-index
|
||||||
[{:keys [file-id data] :as message}]
|
[{:keys [file-id data] :as message}]
|
||||||
|
@ -74,9 +141,9 @@
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defmethod impl/handler :snaps/update-index
|
(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
|
;; 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)
|
;; (log-state)
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue