0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-15 11:38:24 -05:00
penpot/frontend/src/app/util/snap_data.cljs
2024-01-08 09:32:50 +01:00

279 lines
10 KiB
Clojure

;; 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.util.snap-data
"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"
(:require
[app.common.data :as d]
[app.common.files.helpers :as cfh]
[app.common.files.page-diff :as diff]
[app.common.geom.grid :as gg]
[app.common.geom.snap :as snap]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.util.range-tree :as rt]))
(def snap-attrs [:frame-id :x :y :width :height :hidden :selrect :grids])
;; PRIVATE FUNCTIONS
(defn- make-insert-tree-data
"Inserts all data in it's corresponding axis bucket"
[shape-data axis]
(fn [tree]
(let [tree (or tree (rt/make-tree))
insert-data
(fn [tree data]
(rt/insert tree (get-in data [:pt axis]) data))]
(reduce insert-data tree shape-data))))
(defn- make-delete-tree-data
"Removes all data in it's corresponding axis bucket"
[shape-data axis]
(fn [tree]
(let [tree (or tree (rt/make-tree))
remove-data
(fn [tree data]
(rt/remove tree (get-in data [:pt axis]) data))]
(reduce remove-data tree shape-data))))
(defn- add-root-frame
[page-data]
(let [frame-id uuid/zero]
(-> page-data
(assoc-in [frame-id :x] (rt/make-tree))
(assoc-in [frame-id :y] (rt/make-tree)))))
(defn get-grids-snap-points
[frame coord]
(if (ctst/rotated-frame? frame)
[]
(let [grid->snap (fn [[grid-type position]]
{:type :layout
:id (:id frame)
:grid grid-type
:pt position})]
(->> (:grids frame)
(mapcat (fn [grid]
(->> (gg/grid-snap-points frame grid coord)
(mapv #(vector (:type grid) %)))))
(mapv grid->snap)))))
(defn- add-frame
[objects page-data frame]
(let [frame-id (:id frame)
parent-id (:parent-id frame)
frame-data (if (:blocked frame)
[]
(->> (snap/shape->snap-points frame)
(mapv #(array-map :type :shape
:id frame-id
:pt %))))
grid-x-data (get-grids-snap-points frame :x)
grid-y-data (get-grids-snap-points frame :y)]
(cond-> page-data
(and (not (ctl/any-layout-descent? objects frame))
(not (:hidden frame))
(not (cfh/hidden-parent? objects frame-id)))
(-> ;; Update root frame information
(assoc-in [uuid/zero :objects-data frame-id] frame-data)
(update-in [parent-id :x] (make-insert-tree-data frame-data :x))
(update-in [parent-id :y] (make-insert-tree-data frame-data :y))
;; Update frame information
(assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data))
(update-in [frame-id :x] #(or % (rt/make-tree)))
(update-in [frame-id :y] #(or % (rt/make-tree)))
(update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x))
(update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y))))))
(defn- add-shape
[objects page-data shape]
(let [frame-id (:frame-id shape)
snap-points (if (:blocked shape)
[]
(snap/shape->snap-points shape))
shape-data (->> snap-points
(mapv #(array-map
:type :shape
:id (:id shape)
:pt %)))]
(cond-> page-data
(and (not (ctl/any-layout-descent? objects shape))
(not (:hidden shape))
(not (cfh/hidden-parent? objects (:id shape))))
(-> (assoc-in [frame-id :objects-data (:id shape)] shape-data)
(update-in [frame-id :x] (make-insert-tree-data shape-data :x))
(update-in [frame-id :y] (make-insert-tree-data shape-data :y))))))
(defn- add-guide
[objects page-data guide]
(let [frame (get objects (:frame-id guide))
guide-data (->> (snap/guide->snap-points guide frame)
(mapv #(array-map
:type :guide
:id (:id guide)
:axis (:axis guide)
:frame-id (:frame-id guide)
:pt %)))]
(if-let [frame-id (:frame-id guide)]
;; Guide inside frame, we add the information only on that frame
(cond-> page-data
(and (not (:hidden frame))
(not (cfh/hidden-parent? objects frame-id)))
(-> (assoc-in [frame-id :objects-data (:id guide)] guide-data)
(update-in [frame-id (:axis guide)] (make-insert-tree-data guide-data (:axis guide)))))
;; Guide outside the frame. We add the information in the global guides data
(-> page-data
(assoc-in [:guides :objects-data (:id guide)] guide-data)
(update-in [:guides (:axis guide)] (make-insert-tree-data guide-data (:axis guide)))))))
(defn- remove-frame
[page-data frame]
(let [frame-id (:id frame)
root-data (get-in page-data [uuid/zero :objects-data frame-id])]
(-> page-data
(d/dissoc-in [uuid/zero :objects-data frame-id])
(update-in [uuid/zero :x] (make-delete-tree-data root-data :x))
(update-in [uuid/zero :y] (make-delete-tree-data root-data :y))
(dissoc frame-id))))
(defn- remove-shape
[page-data shape]
(let [frame-id (:frame-id shape)
shape-data (get-in page-data [frame-id :objects-data (:id shape)])]
(-> page-data
(d/dissoc-in [frame-id :objects-data (:id shape)])
(update-in [frame-id :x] (make-delete-tree-data shape-data :x))
(update-in [frame-id :y] (make-delete-tree-data shape-data :y)))))
(defn- remove-guide
[page-data guide]
(if-let [frame-id (:frame-id guide)]
(let [guide-data (get-in page-data [frame-id :objects-data (:id guide)])]
(-> page-data
(d/dissoc-in [frame-id :objects-data (:id guide)])
(update-in [frame-id (:axis guide)] (make-delete-tree-data guide-data (:axis guide)))))
;; Guide outside the frame. We add the information in the global guides data
(let [guide-data (get-in page-data [:guides :objects-data (:id guide)])]
(-> page-data
(d/dissoc-in [:guides :objects-data (:id guide)])
(update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide)))))))
(defn- update-frame
[objects page-data [_ new-frame]]
(let [frame-id (:id new-frame)
root-data (get-in page-data [uuid/zero :objects-data frame-id])
frame-data (get-in page-data [frame-id :objects-data frame-id])]
(as-> page-data $
(update-in $ [uuid/zero :x] (make-delete-tree-data root-data :x))
(update-in $ [uuid/zero :y] (make-delete-tree-data root-data :y))
(update-in $ [frame-id :x] (make-delete-tree-data frame-data :x))
(update-in $ [frame-id :y] (make-delete-tree-data frame-data :y))
(add-frame objects $ new-frame))))
(defn- update-shape
[objects page-data [old-shape new-shape]]
(as-> page-data $
(remove-shape $ old-shape)
(add-shape objects $ new-shape)))
(defn- update-guide
[objects page-data [old-guide new-guide]]
(as-> page-data $
(remove-guide $ old-guide)
(add-guide objects $ new-guide)))
;; PUBLIC API
(defn make-snap-data
"Creates an empty snap index"
[]
{})
(defn add-page
"Adds page information"
[snap-data {:keys [objects options] :as page}]
(let [frames (ctst/get-frames objects)
shapes (->> (vals (:objects page))
(remove cfh/frame-shape?))
guides (vals (:guides options))
page-data
(as-> {} $
(add-root-frame $)
(reduce (partial add-frame objects) $ frames)
(reduce (partial add-shape objects) $ shapes)
(reduce (partial add-guide objects) $ guides))]
(assoc snap-data (:id page) page-data)))
(defn update-page
"Updates a previously inserted page with new data"
[snap-data old-page page]
(if (contains? snap-data (:id page))
;; Update page
(update snap-data (:id page)
(fn [page-data]
(let [{:keys [objects]} page
{:keys [change-frame-shapes
change-frame-guides
removed-frames
removed-shapes
removed-guides
updated-frames
updated-shapes
updated-guides
new-frames
new-shapes
new-guides]}
(diff/calculate-page-diff old-page page snap-attrs)]
(as-> page-data $
(reduce (partial update-shape objects) $ change-frame-shapes)
(reduce remove-frame $ removed-frames)
(reduce remove-shape $ removed-shapes)
(reduce (partial update-frame objects) $ updated-frames)
(reduce (partial update-shape objects) $ updated-shapes)
(reduce (partial add-frame objects) $ new-frames)
(reduce (partial add-shape objects) $ new-shapes)
;; Guides functions. Need objects to get its frame data
(reduce remove-guide $ removed-guides)
(reduce (partial update-guide objects) $ change-frame-guides)
(reduce (partial update-guide objects) $ updated-guides)
(reduce (partial add-guide objects) $ new-guides)))))
;; Page doesn't exist, we create a new entry
(add-page snap-data page)))
(defn query
"Retrieve the shape data for the snaps in that range"
[snap-data page-id frame-id axis [from to]]
(d/concat-vec
(-> snap-data
(get-in [page-id frame-id axis])
(rt/range-query from to))
(-> snap-data
(get-in [page-id :guides axis])
(rt/range-query from to))))