0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-12 18:18:24 -05:00

♻️ Redone the snap calculation and added guides

This commit is contained in:
alonso.torres 2022-01-20 22:20:32 +01:00
parent 0766938f98
commit 64e7cad292
11 changed files with 726 additions and 185 deletions

View file

@ -568,4 +568,32 @@
(dissoc :current-component-id)
(update :parent-stack pop))))
(defn add-guide
[file guide]
(let [guide (cond-> guide
(nil? (:id guide))
(assoc :id (uuid/next)))
page-id (:current-page-id file)
old-guides (or (get-in file [:data :pages-index page-id :options :guides]) {})
new-guides (assoc old-guides (:id guide) guide)]
(commit-change
file
{:type :set-option
:page-id page-id
:option :guides
:value new-guides})))
(defn delete-object
[file id]
(let [page-id (:current-page-id file)]
(commit-change
file
{:type :del-obj
:page-id page-id
:id id})))
(defn get-current-page
[file]
(let [page-id (:current-page-id file)]
(-> file (get-in [:data :pages-index page-id]))))

View file

@ -0,0 +1,170 @@
;; 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.diff
"Given a page in its old version and the new will retrieve a map with
the differences that will have an impact in the snap data"
(:require
[app.common.data :as d]
[clojure.set :as set]))
(defn calculate-page-diff
[old-page page check-attrs]
(let [old-objects (get old-page :objects)
old-guides (or (get-in old-page [:options :guides]) [])
new-objects (get page :objects)
new-guides (or (get-in page [:options :guides]) [])
changed-object?
(fn [id]
(let [oldv (get old-objects id)
newv (get new-objects id)]
;; Check first without select-keys because is faster if they are
;; the same reference
(and (not= oldv newv)
(not= (select-keys oldv check-attrs)
(select-keys newv check-attrs)))))
frame?
(fn [id]
(or (= :frame (get-in new-objects [id :type]))
(= :frame (get-in old-objects [id :type]))))
changed-guide?
(fn [id]
(not= (get old-guides id)
(get new-guides id)))
deleted-object?
#(and (contains? old-objects %)
(not (contains? new-objects %)))
deleted-guide?
#(and (contains? old-guides %)
(not (contains? new-guides %)))
new-object?
#(and (not (contains? old-objects %))
(contains? new-objects %))
new-guide?
#(and (not (contains? old-guides %))
(contains? new-guides %))
changed-frame-object?
#(and (contains? new-objects %)
(contains? old-objects %)
(not= (get-in old-objects [% :frame-id])
(get-in new-objects [% :frame-id])))
changed-frame-guide?
#(and (contains? new-guides %)
(contains? old-guides %)
(not= (get-in old-objects [% :frame-id])
(get-in new-objects [% :frame-id])))
changed-attrs-object?
#(and (contains? new-objects %)
(contains? old-objects %)
(= (get-in old-objects [% :frame-id])
(get-in new-objects [% :frame-id])))
changed-attrs-guide?
#(and (contains? new-guides %)
(contains? old-guides %)
(= (get-in old-objects [% :frame-id])
(get-in new-objects [% :frame-id])))
changed-object-ids
(into #{}
(filter changed-object?)
(set/union (set (keys old-objects))
(set (keys new-objects))))
changed-guides-ids
(into #{}
(filter changed-guide?)
(set/union (set (keys old-guides))
(set (keys new-guides))))
get-diff-object (fn [id] [(get old-objects id) (get new-objects id)])
get-diff-guide (fn [id] [(get old-guides id) (get new-guides id)])
;; Shapes with different frame owner
change-frame-shapes
(->> changed-object-ids
(into [] (comp (filter changed-frame-object?)
(map get-diff-object))))
;; Guides that changed frames
change-frame-guides
(->> changed-guides-ids
(into [] (comp (filter changed-frame-guide?)
(map get-diff-guide))))
removed-frames
(->> changed-object-ids
(into [] (comp (filter frame?)
(filter deleted-object?)
(map (d/getf old-objects)))))
removed-shapes
(->> changed-object-ids
(into [] (comp (remove frame?)
(filter deleted-object?)
(map (d/getf old-objects)))))
removed-guides
(->> changed-guides-ids
(into [] (comp (filter deleted-guide?)
(map (d/getf old-guides)))))
updated-frames
(->> changed-object-ids
(into [] (comp (filter frame?)
(filter changed-attrs-object?)
(map get-diff-object))))
updated-shapes
(->> changed-object-ids
(into [] (comp (remove frame?)
(filter changed-attrs-object?)
(map get-diff-object))))
updated-guides
(->> changed-guides-ids
(into [] (comp (filter changed-attrs-guide?)
(map get-diff-guide))))
new-frames
(->> changed-object-ids
(into [] (comp (filter frame?)
(filter new-object?)
(map (d/getf new-objects)))))
new-shapes
(->> changed-object-ids
(into [] (comp (remove frame?)
(filter new-object?)
(map (d/getf new-objects)))))
new-guides
(->> changed-guides-ids
(into [] (comp (filter new-guide?)
(map (d/getf new-guides)))))]
{:change-frame-shapes change-frame-shapes
:change-frame-guides change-frame-guides
:removed-frames removed-frames
:removed-shapes removed-shapes
:removed-guides removed-guides
:updated-frames updated-frames
:updated-shapes updated-shapes
:updated-guides updated-guides
:new-frames new-frames
:new-shapes new-shapes
:new-guides new-guides}))

View file

@ -22,16 +22,9 @@
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
guides (-> state wsh/lookup-page-options (:guides []))
guides-ids? (into #{} (map :id) guides)
guides (-> state wsh/lookup-page-options (:guides {}))
new-guides
(if (guides-ids? (:id guide))
;; Update existing guide
(mapv (make-update-guide guide) guides)
;; Add new guide
(conj guides guide))
new-guides (assoc guides (:id guide) guide)
rch [{:type :set-option
:page-id page-id
@ -52,8 +45,8 @@
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
guides (-> state wsh/lookup-page-options (:guides []))
new-guides (filterv #(not= (:id %) (:id guide)) guides)
guides (-> state wsh/lookup-page-options (:guides {}))
new-guides (dissoc guides (:id guide))
rch [{:type :set-option
:page-id page-id

View file

@ -19,7 +19,7 @@
[beicon.core :as rx]
[clojure.set :as set]))
(def ^:const snap-accuracy 5)
(def ^:const snap-accuracy 10)
(def ^:const snap-path-accuracy 10)
(def ^:const snap-distance-accuracy 10)
@ -27,12 +27,12 @@
[remove-id?]
(fn [query-result]
(->> query-result
(map (fn [[value data]] [value (remove (comp remove-id? second) data)]))
(map (fn [[value data]] [value (remove (comp remove-id? :id) data)]))
(filter (fn [[_ data]] (seq data))))))
(defn- flatten-to-points
[query-result]
(mapcat (fn [[_ data]] (map (fn [[point _]] point) data)) query-result))
(mapcat (fn [[_ data]] (map :pt data)) query-result))
(defn- calculate-distance [query-result point coord]
(->> query-result
@ -62,7 +62,7 @@
(->> (uw/ask! {:cmd :snaps/range-query
:page-id page-id
:frame-id frame-id
:coord coord
:axis coord
:ranges [[(- value 0.5) (+ value 0.5)]]})
(rx/first)
(rx/map (remove-from-snap-points filter-shapes))
@ -78,7 +78,7 @@
(->> (uw/ask! {:cmd :snaps/range-query
:page-id page-id
:frame-id frame-id
:coord coord
:axis coord
:ranges ranges})
(rx/first)
(rx/map (remove-from-snap-points filter-shapes))

View file

@ -396,7 +396,8 @@
[{:keys [zoom vbox hover-frame]}]
(let [page (mf/deref refs/workspace-page)
guides (->> (get-in page [:options :guides] [])
guides (->> (get-in page [:options :guides] {})
(vals)
(filter (guide-inside-vbox? vbox)))
hover-frame-ref (mf/use-ref nil)

View file

@ -28,3 +28,12 @@
(case (:type shape)
:frame (-> shape :selrect frame-snap-points)
(into #{(gsh/center-shape shape)} (:points shape)))))
(defn guide-snap-points
[guide]
;; TODO: The line will be displayed from the position to the axis. Maybe
;; revisit this
(if (= :x (:axis guide))
#{(gpt/point (:position guide) 0)}
#{(gpt/point 0 (:position guide))}))

View file

@ -0,0 +1,244 @@
;; 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.util.snap-data
"Data structure that holds and retrieves the data to make the snaps. Internaly
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.pages :as cp]
[app.common.pages.diff :as diff]
[app.common.uuid :as uuid]
[app.util.geom.grid :as gg]
[app.util.geom.snap-points :as snap]
[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
[shape-data axis]
(fn [tree]
(let [tree (or tree (rt/make-tree))]
(as-> tree $
(reduce (fn [tree data]
(rt/insert tree (get-in data [:pt axis]) data))
$ shape-data)))))
(defn make-delete-tree-data
[shape-data axis]
(fn [tree]
(let [tree (or tree (rt/make-tree))]
(as-> tree $
(reduce (fn [tree data]
(rt/remove tree (get-in data [:pt axis]) data))
$ 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 add-frame
[page-data frame]
(let [frame-id (:id frame)
parent-id (:parent-id frame)
frame-data (->> (snap/shape-snap-points frame)
(map #(hash-map :type :shape
:id frame-id
:pt %)))
grid-x-data (->> (gg/grid-snap-points frame :x)
(map #(hash-map :type :grid-x
:id frame-id
:pt %)))
grid-y-data (->> (gg/grid-snap-points frame :y)
(map #(hash-map :type :grid-y
:id frame-id
:pt %)))]
(-> page-data
;; 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
[page-data shape]
(let [frame-id (:frame-id shape)
snap-points (snap/shape-snap-points shape)
shape-data (->> snap-points
(mapv #(hash-map
:type :shape
:id (:id shape)
:pt %)))]
(-> page-data
(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
[page-data guide]
(let [guide-data (->> (snap/guide-snap-points guide)
(mapv #(hash-map
:type :guide
:id (:id guide)
:pt %)))]
(if-let [frame-id (:frame-id guide)]
;; Guide inside frame, we add the information only on that frame
(-> page-data
(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
[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])]
(-> 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 new-frame))))
(defn update-shape
[page-data [old-shape new-shape]]
(-> page-data
(remove-shape old-shape)
(add-shape new-shape)))
(defn update-guide
[page-data [old-guide new-guide]]
(-> page-data
(remove-guide old-guide)
(add-guide 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 (cp/select-frames objects)
shapes (cp/select-objects #(not= :frame (:type %)) page)
guides (vals (:guides options))
page-data
(as-> {} $
(add-root-frame $)
(reduce add-frame $ frames)
(reduce add-shape $ shapes)
(reduce add-guide $ 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 [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 update-shape $ change-frame-shapes)
(reduce remove-frame $ removed-frames)
(reduce remove-shape $ removed-shapes)
(reduce update-frame $ updated-frames)
(reduce update-shape $ updated-shapes)
(reduce add-frame $ new-frames)
(reduce add-shape $ new-shapes)
(reduce update-guide $ change-frame-guides)
(reduce remove-guide $ removed-guides)
(reduce update-guide $ updated-guides)
(reduce add-guide $ new-guides)))))
;; Page doesn't exist, we create a new entry
(add-page snap-data page)))
(defn query
[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))))

View file

@ -40,14 +40,13 @@
(defmethod handler :update-page-indices
[{:keys [page-id changes] :as message}]
(let [old-objects (get-in @state [:pages-index page-id :objects])]
(let [old-page (get-in @state [:pages-index page-id])]
(swap! state ch/process-changes changes false)
(let [new-objects (get-in @state [:pages-index page-id :objects])
(let [new-page (get-in @state [:pages-index page-id])
message (assoc message
:objects new-objects
:new-objects new-objects
:old-objects old-objects)]
:old-page old-page
:new-page new-page)]
(handler (-> message
(assoc :cmd :selection/update-index)))
(handler (-> message

View file

@ -170,8 +170,10 @@
nil))
(defmethod impl/handler :selection/update-index
[{:keys [page-id old-objects new-objects] :as message}]
(let [update-page-index
[{:keys [page-id old-page new-page] :as message}]
(let [old-objects (:objects old-page)
new-objects (:objects new-page)
update-page-index
(fn [index]
(let [old-bounds (:bounds index)
new-bounds (objects-bounds new-objects)]

View file

@ -6,179 +6,31 @@
(ns app.worker.snaps
(:require
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.util.geom.grid :as gg]
[app.util.geom.snap-points :as snap]
[app.util.range-tree :as rt]
[app.util.snap-data :as sd]
[app.worker.impl :as impl]
[clojure.set :as set]
[okulary.core :as l]))
(defonce state (l/atom {}))
(defn process-shape [frame-id coord]
(fn [shape]
(let [points (when-not (:hidden shape) (snap/shape-snap-points shape))
shape-data (->> points (mapv #(vector % (:id shape))))]
(if (= (:id shape) frame-id)
(into shape-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 frame-id coord))
(reduce into-tree (or data (rt/make-tree))))))
(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 (or data (rt/make-tree))))))
(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 [shapes-data (aggregate-data objects)
create-index
(fn [frame-id shapes]
{:x (-> (rt/make-tree) (add-coord-data frame-id shapes :x))
:y (-> (rt/make-tree) (add-coord-data frame-id shapes :y))})]
(d/mapm create-index shapes-data)))
;; Attributes that will change the values of their snap
(def snap-attrs [:x :y :width :height :hidden :selrect :grids])
(defn- update-snap-data
[snap-data old-objects new-objects]
(let [changed? (fn [id]
(let [oldv (get old-objects id)
newv (get new-objects id)]
;; Check first without select-keys because is faster if they are
;; the same reference
(and (not= oldv newv)
(not= (select-keys oldv snap-attrs)
(select-keys newv snap-attrs)))))
is-deleted-frame? #(and (not= uuid/zero %)
(contains? old-objects %)
(not (contains? new-objects %))
(= :frame (get-in old-objects [% :type])))
is-new-frame? #(and (not= uuid/zero %)
(contains? new-objects %)
(not (contains? old-objects %))
(= :frame (get-in new-objects [% :type])))
changed-ids (into #{}
(filter changed?)
(set/union (set (keys old-objects))
(set (keys new-objects))))
to-delete (aggregate-data old-objects changed-ids)
to-add (aggregate-data new-objects changed-ids)
frames-to-delete (->> changed-ids (filter is-deleted-frame?))
frames-to-add (->> changed-ids (filter is-new-frame?))
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)))
delete-frames
(fn [snap-data frame-id]
(dissoc snap-data frame-id))
add-frames
(fn [snap-data frame-id]
(assoc snap-data frame-id {:x (rt/make-tree)
:y (rt/make-tree)}))]
(as-> snap-data $
(reduce delete-data $ to-delete)
(reduce add-frames $ frames-to-add)
(reduce add-data $ to-add)
(reduce delete-frames $ frames-to-delete))))
;; (defn- log-state
;; "Helper function to print a friendly version of the snap tree. Debugging purposes"
;; []
;; (let [process-frame-data #(d/mapm rt/as-map %)
;; process-page-data #(d/mapm process-frame-data %)]
;; (js/console.log "STATE" (clj->js (d/mapm process-page-data @state)))))
(defn- index-page [state page-id objects]
(let [snap-data (initialize-snap-data objects)]
(assoc state page-id snap-data)))
(defn- update-page [state page-id old-objects new-objects]
(let [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 [data] :as message}]
;; Create the index
(letfn [(process-page [state page]
(let [id (:id page)
objects (:objects page)]
(index-page state id objects)))]
(swap! state #(reduce process-page % (vals (:pages-index data))))
;; (log-state)
;; Return nil so the worker will not answer anything back
nil))
(let [pages (vals (:pages-index data))]
(reset! state (reduce sd/add-page (sd/make-snap-data) pages)))
nil)
(defmethod impl/handler :snaps/update-index
[{:keys [page-id old-objects new-objects] :as message}]
(swap! state update-page page-id old-objects new-objects)
;; Uncomment this to regenerate the index everytime
#_(swap! state index-page page-id new-objects)
;; (log-state)
[{: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 coord ranges] :as message}]
(letfn [(calculate-range [[from to]]
(-> @state
(get-in [page-id frame-id coord])
(rt/range-query from to)))]
(->> ranges
(mapcat calculate-range)
set ;; unique
(into []))))
[{:keys [page-id frame-id axis ranges] :as message}]
(->> ranges
(mapcat #(sd/query @state page-id frame-id axis %))
(set) ;; unique
(into [])))

View file

@ -0,0 +1,243 @@
;; 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.util.snap-data-test
(:require
[app.common.uuid :as uuid]
[cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]
[app.common.pages.init :as init]
[app.common.file-builder :as fb]
[app.util.snap-data :as sd]))
(t/deftest test-create-index
(t/testing "Create empty data"
(let [data (sd/make-snap-data)]
(t/is (some? data))))
(t/testing "Add empty page (only root-frame)"
(let [page (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/get-current-page))
data (-> (sd/make-snap-data)
(sd/add-page page))]
(t/is (some? data))))
(t/testing "Create simple shape on root"
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/create-rect
{:x 0
:y 0
:width 100
:height 100}))
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/add-page page))
result-x (sd/query data (:id page) uuid/zero :x [0 100])]
(t/is (some? data))
;; 3 = left side, center and right side
(t/is (= (count result-x) 3))
;; Left side: two points
(t/is (= (first (nth result-x 0)) 0))
;; Center one point
(t/is (= (first (nth result-x 1)) 50))
;; Right side two points
(t/is (= (first (nth result-x 2)) 100))))
(t/testing "Add page with single empty frame"
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/add-artboard
{:x 0
:y 0
:width 100
:height 100})
(fb/close-artboard))
frame-id (:last-id file)
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/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])]
(t/is (some? data))
(t/is (= (count result-zero-x) 3))
(t/is (= (count result-frame-x) 3))))
(t/testing "Add page with some shapes inside frames"
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/add-artboard
{:x 0
:y 0
:width 100
:height 100}))
frame-id (:last-id file)
file (-> file
(fb/create-rect
{:x 25
:y 25
:width 50
:height 50})
(fb/close-artboard))
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/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])]
(t/is (some? data))
(t/is (= (count result-zero-x) 3))
(t/is (= (count result-frame-x) 5))))
(t/testing "Add a global guide"
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/add-guide {:position 50 :axis :x})
(fb/add-artboard {:x 200 :y 200 :width 100 :height 100})
(fb/close-artboard))
frame-id (:last-id file)
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/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])]
(t/is (some? data))
;; We can snap in the root
(t/is (= (count result-zero-x) 1))
(t/is (= (count result-zero-y) 0))
;; We can snap in the frame
(t/is (= (count result-frame-x) 1))
(t/is (= (count result-frame-y) 0))))
(t/testing "Add a frame guide"
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/add-artboard {:x 200 :y 200 :width 100 :height 100})
(fb/close-artboard))
frame-id (:last-id file)
file (-> file
(fb/add-guide {:position 50 :axis :x :frame-id frame-id}))
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/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])]
(t/is (some? data))
;; We can snap in the root
(t/is (= (count result-zero-x) 0))
(t/is (= (count result-zero-y) 0))
;; We can snap in the frame
(t/is (= (count result-frame-x) 1))
(t/is (= (count result-frame-y) 0)))))
(t/deftest test-update-index
(t/testing "Create frame on root and then remove it."
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/add-artboard
{:x 0
:y 0
:width 100
:height 100})
(fb/close-artboard))
shape-id (:last-id file)
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/add-page page))
file (-> file
(fb/delete-object shape-id))
new-page (fb/get-current-page file)
data (sd/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])]
(t/is (some? data))
(t/is (= (count result-x) 0))
(t/is (= (count result-y) 0))))
(t/testing "Create simple shape on root. Then remove it"
(let [file (-> (fb/create-file "Test")
(fb/add-page {:name "Page-1"})
(fb/create-rect
{:x 0
:y 0
:width 100
:height 100}))
shape-id (:last-id file)
page (fb/get-current-page file)
;; frame-id (:last-id file)
data (-> (sd/make-snap-data)
(sd/add-page page))
file (-> file
(fb/delete-object shape-id))
new-page (fb/get-current-page file)
data (sd/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])]
(t/is (some? data))
(t/is (= (count result-x) 0))
(t/is (= (count result-y) 0))))
(t/testing "Create shape inside frame, then remove it")
(t/testing "Create guide then remove it")
(t/testing "Update frame coordinates")
(t/testing "Update shape coordinates")
(t/testing "Update shape inside frame coordinates")
(t/testing "Update global guide")
(t/testing "Update frame guide")
(t/testing "Change shape frame")
(t/testing "Change guide frame"))