mirror of
https://github.com/penpot/penpot.git
synced 2025-06-08 10:51:37 +02:00
commit
a10dcbd918
33 changed files with 1904 additions and 414 deletions
12
CHANGES.md
12
CHANGES.md
|
@ -2,13 +2,19 @@
|
||||||
|
|
||||||
## :rocket: Next
|
## :rocket: Next
|
||||||
|
|
||||||
|
### :sparkles: New features
|
||||||
|
|
||||||
|
- Guides [Taiga #290](https://tree.taiga.io/project/penpot/us/290?milestone=307334)
|
||||||
|
- Improve file menu by adding semantically groups [Github #1203](https://github.com/penpot/penpot/issues/1203).
|
||||||
|
- Create e2e tests for drawing basic fors [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608).
|
||||||
|
- Create firsts e2e test [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608).
|
||||||
|
|
||||||
|
## 1.11.0-beta
|
||||||
|
|
||||||
### :boom: Breaking changes
|
### :boom: Breaking changes
|
||||||
|
|
||||||
### :sparkles: New features
|
### :sparkles: New features
|
||||||
|
|
||||||
- Create e2e tests for drawing basic fors [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608).
|
|
||||||
- Improve file menu by adding semantically groups [Github #1203](https://github.com/penpot/penpot/issues/1203).
|
|
||||||
- Create firsts e2e test [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608).
|
|
||||||
- Add an option to hide artboards names on the viewport [Taiga #2034](https://tree.taiga.io/project/penpot/issue/2034).
|
- Add an option to hide artboards names on the viewport [Taiga #2034](https://tree.taiga.io/project/penpot/issue/2034).
|
||||||
- Limit pasted object position to container boundaries [Taiga #2449](https://tree.taiga.io/project/penpot/us/2449).
|
- Limit pasted object position to container boundaries [Taiga #2449](https://tree.taiga.io/project/penpot/us/2449).
|
||||||
- Add new options for zoom widget in workspace and viewer mode [Taiga #896](https://tree.taiga.io/project/penpot/us/896).
|
- Add new options for zoom widget in workspace and viewer mode [Taiga #896](https://tree.taiga.io/project/penpot/us/896).
|
||||||
|
|
|
@ -15,4 +15,5 @@
|
||||||
(def info "#59B9E2")
|
(def info "#59B9E2")
|
||||||
(def test "#fabada")
|
(def test "#fabada")
|
||||||
(def white "#FFFFFF")
|
(def white "#FFFFFF")
|
||||||
|
(def primary "#31EFB8")
|
||||||
|
|
||||||
|
|
|
@ -568,4 +568,78 @@
|
||||||
(dissoc :current-component-id)
|
(dissoc :current-component-id)
|
||||||
(update :parent-stack pop))))
|
(update :parent-stack pop))))
|
||||||
|
|
||||||
|
(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 update-object
|
||||||
|
[file old-obj new-obj]
|
||||||
|
(let [page-id (:current-page-id file)
|
||||||
|
new-obj (setup-selrect new-obj)
|
||||||
|
attrs (d/concat-set (keys old-obj) (keys new-obj))
|
||||||
|
generate-operation
|
||||||
|
(fn [changes attr]
|
||||||
|
(let [old-val (get old-obj attr)
|
||||||
|
new-val (get new-obj attr)]
|
||||||
|
(if (= old-val new-val)
|
||||||
|
changes
|
||||||
|
(conj changes {:type :set :attr attr :val new-val}))))]
|
||||||
|
(-> file
|
||||||
|
(commit-change
|
||||||
|
{:type :mod-obj
|
||||||
|
:operations (reduce generate-operation [] attrs)
|
||||||
|
:page-id page-id
|
||||||
|
:id (:id old-obj)}))))
|
||||||
|
|
||||||
|
(defn get-current-page
|
||||||
|
[file]
|
||||||
|
(let [page-id (:current-page-id file)]
|
||||||
|
(-> file (get-in [:data :pages-index page-id]))))
|
||||||
|
|
||||||
|
(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)]
|
||||||
|
(-> file
|
||||||
|
(commit-change
|
||||||
|
{:type :set-option
|
||||||
|
:page-id page-id
|
||||||
|
:option :guides
|
||||||
|
:value new-guides})
|
||||||
|
(assoc :last-id (:id guide)))))
|
||||||
|
|
||||||
|
(defn delete-guide
|
||||||
|
[file id]
|
||||||
|
|
||||||
|
(let [page-id (:current-page-id file)
|
||||||
|
old-guides (or (get-in file [:data :pages-index page-id :options :guides]) {})
|
||||||
|
new-guides (dissoc old-guides id)]
|
||||||
|
(-> file
|
||||||
|
(commit-change
|
||||||
|
{:type :set-option
|
||||||
|
:page-id page-id
|
||||||
|
:option :guides
|
||||||
|
:value new-guides}))))
|
||||||
|
|
||||||
|
(defn update-guide
|
||||||
|
[file guide]
|
||||||
|
|
||||||
|
(let [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)]
|
||||||
|
(-> file
|
||||||
|
(commit-change
|
||||||
|
{:type :set-option
|
||||||
|
:page-id page-id
|
||||||
|
:option :guides
|
||||||
|
:value new-guides}))))
|
||||||
|
|
|
@ -13,13 +13,22 @@
|
||||||
;; Auxiliary functions to help create a set of changes (undo + redo)
|
;; Auxiliary functions to help create a set of changes (undo + redo)
|
||||||
|
|
||||||
(defn empty-changes
|
(defn empty-changes
|
||||||
[origin page-id]
|
([origin page-id]
|
||||||
(let [changes {:redo-changes []
|
(let [changes (empty-changes origin)]
|
||||||
:undo-changes []
|
|
||||||
:origin origin}]
|
|
||||||
(with-meta changes
|
(with-meta changes
|
||||||
{::page-id page-id})))
|
{::page-id page-id})))
|
||||||
|
|
||||||
|
([origin]
|
||||||
|
{:redo-changes []
|
||||||
|
:undo-changes []
|
||||||
|
:origin origin}))
|
||||||
|
|
||||||
|
(defn with-page [changes page]
|
||||||
|
(vary-meta changes assoc
|
||||||
|
::page page
|
||||||
|
::page-id (:id page)
|
||||||
|
::objects (:objects page)))
|
||||||
|
|
||||||
(defn with-objects [changes objects]
|
(defn with-objects [changes objects]
|
||||||
(vary-meta changes assoc ::objects objects))
|
(vary-meta changes assoc ::objects objects))
|
||||||
|
|
||||||
|
@ -167,10 +176,25 @@
|
||||||
(reduce add-undo-change-parent $ ids)
|
(reduce add-undo-change-parent $ ids)
|
||||||
(reduce add-undo-change-shape $ ids))))))
|
(reduce add-undo-change-shape $ ids))))))
|
||||||
|
|
||||||
|
|
||||||
(defn move-page
|
(defn move-page
|
||||||
[chdata index prev-index]
|
[chdata index prev-index]
|
||||||
(let [page-id (::page-id (meta chdata))]
|
(let [page-id (::page-id (meta chdata))]
|
||||||
(-> chdata
|
(-> chdata
|
||||||
(update :redo-changes conj {:type :mov-page :id page-id :index index})
|
(update :redo-changes conj {:type :mov-page :id page-id :index index})
|
||||||
(update :undo-changes conj {:type :mov-page :id page-id :index prev-index}))))
|
(update :undo-changes conj {:type :mov-page :id page-id :index prev-index}))))
|
||||||
|
|
||||||
|
(defn set-page-option
|
||||||
|
[chdata option-key option-val]
|
||||||
|
(let [page-id (::page-id (meta chdata))
|
||||||
|
page (::page (meta chdata))
|
||||||
|
old-val (get-in page [:options option-key])]
|
||||||
|
|
||||||
|
(-> chdata
|
||||||
|
(update :redo-changes conj {:type :set-option
|
||||||
|
:page-id page-id
|
||||||
|
:option option-key
|
||||||
|
:value option-val})
|
||||||
|
(update :undo-changes conj {:type :set-option
|
||||||
|
:page-id page-id
|
||||||
|
:option option-key
|
||||||
|
:value old-val}))))
|
||||||
|
|
170
common/src/app/common/pages/diff.cljc
Normal file
170
common/src/app/common/pages/diff.cljc
Normal 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}))
|
|
@ -61,12 +61,29 @@
|
||||||
(s/def ::flows
|
(s/def ::flows
|
||||||
(s/coll-of ::flow :kind vector?))
|
(s/coll-of ::flow :kind vector?))
|
||||||
|
|
||||||
|
;; --- Guides
|
||||||
|
|
||||||
|
(s/def :guides/id ::us/uuid)
|
||||||
|
(s/def :guides/axis #{:x :y})
|
||||||
|
(s/def :guides/position ::us/safe-number)
|
||||||
|
(s/def :guides/frame-id (s/nilable ::us/uuid))
|
||||||
|
|
||||||
|
(s/def ::guide
|
||||||
|
(s/keys :req-un [:guides/id
|
||||||
|
:guides/axis
|
||||||
|
:guides/position]
|
||||||
|
:opt-un [:guides/frame-id]))
|
||||||
|
|
||||||
|
(s/def ::guides
|
||||||
|
(s/map-of uuid? ::guide))
|
||||||
|
|
||||||
;; --- Options
|
;; --- Options
|
||||||
|
|
||||||
(s/def ::options
|
(s/def ::options
|
||||||
(s/keys :opt-un [::background
|
(s/keys :opt-un [::background
|
||||||
::saved-grids
|
::saved-grids
|
||||||
::flows]))
|
::flows
|
||||||
|
::guides]))
|
||||||
|
|
||||||
;; --- Helpers for flow
|
;; --- Helpers for flow
|
||||||
|
|
||||||
|
|
1
frontend/resources/images/cursors/resize-h-2.svg
Normal file
1
frontend/resources/images/cursors/resize-h-2.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="#fff" d="m 9.8669998,8.8590002 h -3.734 V 10.732 L -0.00100019,8.0000002 6.1329998,5.2680001 v 1.873 h 3.734 v -1.873 L 16.001,8.0000002 9.8669998,10.732"/><path fill="#000" d="M 10.4,8.3900002 H 5.5999998 V 9.9509999 L 0.79999981,8.0000002 5.5999998,6.0490001 v 1.56 H 10.4 v -1.56 l 4.8,1.9510001 -4.8,1.9509997 V 8.3910002 Z"/></svg>
|
After Width: | Height: | Size: 421 B |
|
@ -30,6 +30,7 @@
|
||||||
[app.main.data.workspace.drawing :as dwd]
|
[app.main.data.workspace.drawing :as dwd]
|
||||||
[app.main.data.workspace.fix-bool-contents :as fbc]
|
[app.main.data.workspace.fix-bool-contents :as fbc]
|
||||||
[app.main.data.workspace.groups :as dwg]
|
[app.main.data.workspace.groups :as dwg]
|
||||||
|
[app.main.data.workspace.guides :as dwgu]
|
||||||
[app.main.data.workspace.interactions :as dwi]
|
[app.main.data.workspace.interactions :as dwi]
|
||||||
[app.main.data.workspace.layers :as dwly]
|
[app.main.data.workspace.layers :as dwly]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
|
@ -83,7 +84,8 @@
|
||||||
:snap-grid
|
:snap-grid
|
||||||
:scale-text
|
:scale-text
|
||||||
:dynamic-alignment
|
:dynamic-alignment
|
||||||
:display-artboard-names})
|
:display-artboard-names
|
||||||
|
:snap-guides})
|
||||||
|
|
||||||
(s/def ::layout-flags (s/coll-of ::layout-flag))
|
(s/def ::layout-flags (s/coll-of ::layout-flag))
|
||||||
|
|
||||||
|
@ -95,7 +97,8 @@
|
||||||
:display-grid
|
:display-grid
|
||||||
:snap-grid
|
:snap-grid
|
||||||
:dynamic-alignment
|
:dynamic-alignment
|
||||||
:display-artboard-names})
|
:display-artboard-names
|
||||||
|
:snap-guides})
|
||||||
|
|
||||||
(def layout-presets
|
(def layout-presets
|
||||||
{:assets
|
{:assets
|
||||||
|
@ -2033,3 +2036,8 @@
|
||||||
|
|
||||||
;; Shapes to path
|
;; Shapes to path
|
||||||
(d/export dwps/convert-selected-to-path)
|
(d/export dwps/convert-selected-to-path)
|
||||||
|
|
||||||
|
;; Guides
|
||||||
|
(d/export dwgu/update-guides)
|
||||||
|
(d/export dwgu/remove-guide)
|
||||||
|
|
||||||
|
|
88
frontend/src/app/main/data/workspace/guides.cljs
Normal file
88
frontend/src/app/main/data/workspace/guides.cljs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
;; 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.main.data.workspace.guides
|
||||||
|
(:require
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.pages.changes-builder :as pcb]
|
||||||
|
[app.common.spec :as us]
|
||||||
|
[app.common.types.page-options :as tpo]
|
||||||
|
[app.main.data.workspace.changes :as dwc]
|
||||||
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[cljs.spec.alpha :as s]
|
||||||
|
[potok.core :as ptk]))
|
||||||
|
|
||||||
|
(defn make-update-guide [guide]
|
||||||
|
(fn [other]
|
||||||
|
(cond-> other
|
||||||
|
(= (:id other) (:id guide))
|
||||||
|
(merge guide))))
|
||||||
|
|
||||||
|
(defn update-guides [guide]
|
||||||
|
(us/verify ::tpo/guide guide)
|
||||||
|
(ptk/reify ::update-guides
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [page (wsh/lookup-page state)
|
||||||
|
guides (get-in page [:options :guides] {})
|
||||||
|
new-guides (assoc guides (:id guide) guide)
|
||||||
|
|
||||||
|
changes
|
||||||
|
(-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-page page)
|
||||||
|
(pcb/set-page-option :guides new-guides))]
|
||||||
|
(rx/of (dwc/commit-changes changes))))))
|
||||||
|
|
||||||
|
(defn remove-guide [guide]
|
||||||
|
(us/verify ::tpo/guide guide)
|
||||||
|
(ptk/reify ::remove-guide
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [page (wsh/lookup-page state)
|
||||||
|
guides (get-in page [:options :guides] {})
|
||||||
|
new-guides (dissoc guides (:id guide))
|
||||||
|
|
||||||
|
changes
|
||||||
|
(-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-page page)
|
||||||
|
(pcb/set-page-option :guides new-guides))]
|
||||||
|
(rx/of (dwc/commit-changes changes))))))
|
||||||
|
|
||||||
|
(defn move-frame-guides
|
||||||
|
"Move guides that are inside a frame when that frame is moved"
|
||||||
|
[ids]
|
||||||
|
(us/verify (s/coll-of uuid?) ids)
|
||||||
|
|
||||||
|
(ptk/reify ::move-frame-guides
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [objects (wsh/lookup-page-objects state)
|
||||||
|
|
||||||
|
is-frame? (fn [id] (= :frame (get-in objects [id :type])))
|
||||||
|
frame-ids? (into #{} (filter is-frame?) ids)
|
||||||
|
|
||||||
|
object-modifiers (get state :workspace-modifiers)
|
||||||
|
|
||||||
|
build-move-event
|
||||||
|
(fn [guide]
|
||||||
|
(let [frame (get objects (:frame-id guide))
|
||||||
|
frame' (-> (merge frame (get object-modifiers (:frame-id guide)))
|
||||||
|
(gsh/transform-shape))
|
||||||
|
|
||||||
|
moved (gpt/to-vec (gpt/point (:x frame) (:y frame))
|
||||||
|
(gpt/point (:x frame') (:y frame')))
|
||||||
|
|
||||||
|
guide (update guide :position + (get moved (:axis guide)))]
|
||||||
|
(update-guides guide)))]
|
||||||
|
|
||||||
|
(->> (wsh/lookup-page-options state)
|
||||||
|
:guides
|
||||||
|
(vals)
|
||||||
|
(filter (comp frame-ids? :frame-id))
|
||||||
|
(map build-move-event)
|
||||||
|
(rx/from))))))
|
|
@ -58,6 +58,10 @@
|
||||||
:command (ds/c-mod "shift+'")
|
:command (ds/c-mod "shift+'")
|
||||||
:fn #(st/emit! (dw/toggle-layout-flags :snap-grid))}
|
:fn #(st/emit! (dw/toggle-layout-flags :snap-grid))}
|
||||||
|
|
||||||
|
:toggle-snap-guide {:tooltip (ds/meta-shift "G")
|
||||||
|
:command (ds/c-mod "shift+G")
|
||||||
|
:fn #(st/emit! (dw/toggle-layout-flags :snap-guides))}
|
||||||
|
|
||||||
:toggle-alignment {:tooltip (ds/meta "\\")
|
:toggle-alignment {:tooltip (ds/meta "\\")
|
||||||
:command (ds/c-mod "\\")
|
:command (ds/c-mod "\\")
|
||||||
:fn #(st/emit! (dw/toggle-layout-flags :dynamic-alignment))}
|
:fn #(st/emit! (dw/toggle-layout-flags :dynamic-alignment))}
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.pages :as cp]))
|
[app.common.pages :as cp]))
|
||||||
|
|
||||||
|
(defn lookup-page
|
||||||
|
([state]
|
||||||
|
(lookup-page state (:current-page-id state)))
|
||||||
|
([state page-id]
|
||||||
|
(get-in state [:workspace-data :pages-index page-id])))
|
||||||
|
|
||||||
(defn lookup-page-objects
|
(defn lookup-page-objects
|
||||||
([state]
|
([state]
|
||||||
(lookup-page-objects state (:current-page-id state)))
|
(lookup-page-objects state (:current-page-id state)))
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
|
[app.main.data.workspace.guides :as dwg]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
[app.main.data.workspace.state-helpers :as wsh]
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
|
@ -155,6 +156,8 @@
|
||||||
|
|
||||||
(update state :workspace-modifiers #(reduce update-shape % shapes)))))))
|
(update state :workspace-modifiers #(reduce update-shape % shapes)))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn- apply-modifiers
|
(defn- apply-modifiers
|
||||||
[ids]
|
[ids]
|
||||||
(us/verify (s/coll-of uuid?) ids)
|
(us/verify (s/coll-of uuid?) ids)
|
||||||
|
@ -168,6 +171,7 @@
|
||||||
ignore-tree (get-ignore-tree object-modifiers objects ids)]
|
ignore-tree (get-ignore-tree object-modifiers objects ids)]
|
||||||
|
|
||||||
(rx/of (dwu/start-undo-transaction)
|
(rx/of (dwu/start-undo-transaction)
|
||||||
|
(dwg/move-frame-guides ids-with-children)
|
||||||
(dch/update-shapes
|
(dch/update-shapes
|
||||||
ids-with-children
|
ids-with-children
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
|
|
|
@ -204,7 +204,9 @@
|
||||||
:height "100%"
|
:height "100%"
|
||||||
:background background-color}}
|
:background background-color}}
|
||||||
|
|
||||||
[:& export/export-page {:options (:options data)}]
|
(when include-metadata?
|
||||||
|
[:& export/export-page {:options (:options data)}])
|
||||||
|
|
||||||
[:& ff/fontfaces-style {:shapes root-children}]
|
[:& ff/fontfaces-style {:shapes root-children}]
|
||||||
(for [item shapes]
|
(for [item shapes]
|
||||||
(let [frame? (= (:type item) :frame)]
|
(let [frame? (= (:type item) :frame)]
|
||||||
|
|
|
@ -19,20 +19,38 @@
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
(def ^:const snap-accuracy 5)
|
(def ^:const snap-accuracy 10)
|
||||||
(def ^:const snap-path-accuracy 10)
|
(def ^:const snap-path-accuracy 10)
|
||||||
(def ^:const snap-distance-accuracy 10)
|
(def ^:const snap-distance-accuracy 10)
|
||||||
|
|
||||||
(defn- remove-from-snap-points
|
(defn- remove-from-snap-points
|
||||||
[remove-id?]
|
[remove-snap?]
|
||||||
(fn [query-result]
|
(fn [query-result]
|
||||||
(->> query-result
|
(->> query-result
|
||||||
(map (fn [[value data]] [value (remove (comp remove-id? second) data)]))
|
(map (fn [[value data]] [value (remove remove-snap? data)]))
|
||||||
(filter (fn [[_ data]] (seq data))))))
|
(filter (fn [[_ data]] (seq data))))))
|
||||||
|
|
||||||
|
(defn make-remove-snap
|
||||||
|
"Creates a filter for the snap data. Used to disable certain layouts"
|
||||||
|
[layout filter-shapes]
|
||||||
|
|
||||||
|
(fn [{:keys [type id]}]
|
||||||
|
(cond
|
||||||
|
(= type :layout)
|
||||||
|
(or (not (contains? layout :display-grid))
|
||||||
|
(not (contains? layout :snap-grid)))
|
||||||
|
|
||||||
|
(= type :guide)
|
||||||
|
(or (not (contains? layout :rules))
|
||||||
|
(not (contains? layout :snap-guides)))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(or (contains? filter-shapes id)
|
||||||
|
(not (contains? layout :dynamic-alignment))))))
|
||||||
|
|
||||||
(defn- flatten-to-points
|
(defn- flatten-to-points
|
||||||
[query-result]
|
[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]
|
(defn- calculate-distance [query-result point coord]
|
||||||
(->> query-result
|
(->> query-result
|
||||||
|
@ -57,19 +75,19 @@
|
||||||
;; Otherwise the root frame is the common
|
;; Otherwise the root frame is the common
|
||||||
:else zero)))
|
:else zero)))
|
||||||
|
|
||||||
(defn get-snap-points [page-id frame-id filter-shapes point coord]
|
(defn get-snap-points [page-id frame-id remove-snap? point coord]
|
||||||
(let [value (get point coord)]
|
(let [value (get point coord)]
|
||||||
(->> (uw/ask! {:cmd :snaps/range-query
|
(->> (uw/ask! {:cmd :snaps/range-query
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:frame-id frame-id
|
:frame-id frame-id
|
||||||
:coord coord
|
:axis coord
|
||||||
:ranges [[(- value 0.5) (+ value 0.5)]]})
|
:ranges [[(- value 0.5) (+ value 0.5)]]})
|
||||||
(rx/first)
|
(rx/first)
|
||||||
(rx/map (remove-from-snap-points filter-shapes))
|
(rx/map (remove-from-snap-points remove-snap?))
|
||||||
(rx/map flatten-to-points))))
|
(rx/map flatten-to-points))))
|
||||||
|
|
||||||
(defn- search-snap
|
(defn- search-snap
|
||||||
[page-id frame-id points coord filter-shapes zoom]
|
[page-id frame-id points coord remove-snap? zoom]
|
||||||
(let [snap-accuracy (/ snap-accuracy zoom)
|
(let [snap-accuracy (/ snap-accuracy zoom)
|
||||||
ranges (->> points
|
ranges (->> points
|
||||||
(map coord)
|
(map coord)
|
||||||
|
@ -78,10 +96,10 @@
|
||||||
(->> (uw/ask! {:cmd :snaps/range-query
|
(->> (uw/ask! {:cmd :snaps/range-query
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:frame-id frame-id
|
:frame-id frame-id
|
||||||
:coord coord
|
:axis coord
|
||||||
:ranges ranges})
|
:ranges ranges})
|
||||||
(rx/first)
|
(rx/first)
|
||||||
(rx/map (remove-from-snap-points filter-shapes))
|
(rx/map (remove-from-snap-points remove-snap?))
|
||||||
(rx/map (get-min-distance-snap points coord)))))
|
(rx/map (get-min-distance-snap points coord)))))
|
||||||
|
|
||||||
(defn snap->vector [[[from-x to-x] [from-y to-y]]]
|
(defn snap->vector [[[from-x to-x] [from-y to-y]]]
|
||||||
|
@ -91,13 +109,12 @@
|
||||||
(gpt/to-vec from to))))
|
(gpt/to-vec from to))))
|
||||||
|
|
||||||
(defn- closest-snap
|
(defn- closest-snap
|
||||||
[page-id frame-id points filter-shapes zoom]
|
[page-id frame-id points remove-snap? zoom]
|
||||||
(let [snap-x (search-snap page-id frame-id points :x filter-shapes zoom)
|
(let [snap-x (search-snap page-id frame-id points :x remove-snap? zoom)
|
||||||
snap-y (search-snap page-id frame-id points :y filter-shapes zoom)]
|
snap-y (search-snap page-id frame-id points :y remove-snap? zoom)]
|
||||||
(->> (rx/combine-latest snap-x snap-y)
|
(->> (rx/combine-latest snap-x snap-y)
|
||||||
(rx/map snap->vector))))
|
(rx/map snap->vector))))
|
||||||
|
|
||||||
|
|
||||||
(defn sr-distance [coord sr1 sr2]
|
(defn sr-distance [coord sr1 sr2]
|
||||||
(let [c1 (if (= coord :x) :x1 :y1)
|
(let [c1 (if (= coord :x) :x1 :y1)
|
||||||
c2 (if (= coord :x) :x2 :y2)
|
c2 (if (= coord :x) :x2 :y2)
|
||||||
|
@ -209,12 +226,8 @@
|
||||||
[page-id shapes layout zoom point]
|
[page-id shapes layout zoom point]
|
||||||
(let [frame-id (snap-frame-id shapes)
|
(let [frame-id (snap-frame-id shapes)
|
||||||
filter-shapes (into #{} (map :id shapes))
|
filter-shapes (into #{} (map :id shapes))
|
||||||
filter-shapes (fn [id] (if (= id :layout)
|
remove-snap? (make-remove-snap layout filter-shapes)]
|
||||||
(or (not (contains? layout :display-grid))
|
(->> (closest-snap page-id frame-id [point] remove-snap? zoom)
|
||||||
(not (contains? layout :snap-grid)))
|
|
||||||
(or (filter-shapes id)
|
|
||||||
(not (contains? layout :dynamic-alignment)))))]
|
|
||||||
(->> (closest-snap page-id frame-id [point] filter-shapes zoom)
|
|
||||||
(rx/map #(or % (gpt/point 0 0)))
|
(rx/map #(or % (gpt/point 0 0)))
|
||||||
(rx/map #(gpt/add point %)))))
|
(rx/map #(gpt/add point %)))))
|
||||||
|
|
||||||
|
@ -222,11 +235,8 @@
|
||||||
[page-id shapes objects layout zoom movev]
|
[page-id shapes objects layout zoom movev]
|
||||||
(let [frame-id (snap-frame-id shapes)
|
(let [frame-id (snap-frame-id shapes)
|
||||||
filter-shapes (into #{} (map :id shapes))
|
filter-shapes (into #{} (map :id shapes))
|
||||||
filter-shapes (fn [id] (if (= id :layout)
|
remove-snap? (make-remove-snap layout filter-shapes)
|
||||||
(or (not (contains? layout :display-grid))
|
|
||||||
(not (contains? layout :snap-grid)))
|
|
||||||
(or (filter-shapes id)
|
|
||||||
(not (contains? layout :dynamic-alignment)))))
|
|
||||||
shape (if (> (count shapes) 1)
|
shape (if (> (count shapes) 1)
|
||||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
||||||
(->> shapes (first)))
|
(->> shapes (first)))
|
||||||
|
@ -236,7 +246,7 @@
|
||||||
;; Move the points in the translation vector
|
;; Move the points in the translation vector
|
||||||
(map #(gpt/add % movev)))]
|
(map #(gpt/add % movev)))]
|
||||||
|
|
||||||
(->> (rx/merge (closest-snap page-id frame-id shapes-points filter-shapes zoom)
|
(->> (rx/merge (closest-snap page-id frame-id shapes-points remove-snap? zoom)
|
||||||
(when (contains? layout :dynamic-alignment)
|
(when (contains? layout :dynamic-alignment)
|
||||||
(closest-distance-snap page-id shapes objects zoom movev)))
|
(closest-distance-snap page-id shapes objects zoom movev)))
|
||||||
(rx/reduce gpt/min)
|
(rx/reduce gpt/min)
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
(def resize-nwse (cursor-fn :resize-h 135))
|
(def resize-nwse (cursor-fn :resize-h 135))
|
||||||
(def rotate (cursor-fn :rotate 90))
|
(def rotate (cursor-fn :rotate 90))
|
||||||
|
|
||||||
|
;;
|
||||||
|
(def resize-ew-2 (cursor-fn :resize-h-2 0))
|
||||||
|
(def resize-ns-2 (cursor-fn :resize-h-2 90))
|
||||||
|
|
||||||
(mf/defc debug-preview
|
(mf/defc debug-preview
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
|
|
|
@ -155,20 +155,30 @@
|
||||||
:name name
|
:name name
|
||||||
:starting-frame starting-frame}])])
|
:starting-frame starting-frame}])])
|
||||||
|
|
||||||
|
(mf/defc export-guides
|
||||||
|
[{:keys [guides]}]
|
||||||
|
[:> "penpot:guides" #js {}
|
||||||
|
(for [{:keys [position frame-id axis]} (vals guides)]
|
||||||
|
[:> "penpot:guide" #js {:position position
|
||||||
|
:frame-id frame-id
|
||||||
|
:axis (d/name axis)}])])
|
||||||
|
|
||||||
(mf/defc export-page
|
(mf/defc export-page
|
||||||
[{:keys [options]}]
|
[{:keys [options]}]
|
||||||
(let [saved-grids (get options :saved-grids)
|
(let [saved-grids (get options :saved-grids)
|
||||||
flows (get options :flows)]
|
flows (get options :flows)
|
||||||
(when (or (seq saved-grids) (seq flows))
|
guides (get options :guides)]
|
||||||
(let [parse-grid
|
|
||||||
(fn [[type params]]
|
|
||||||
{:type type :params params})
|
|
||||||
grids (->> saved-grids (mapv parse-grid))]
|
|
||||||
[:> "penpot:page" #js {}
|
[:> "penpot:page" #js {}
|
||||||
(when (seq saved-grids)
|
(when (d/not-empty? saved-grids)
|
||||||
[:& export-grid-data {:grids grids}])
|
(let [parse-grid (fn [[type params]] {:type type :params params})
|
||||||
(when (seq flows)
|
grids (->> saved-grids (mapv parse-grid))]
|
||||||
[:& export-flows {:flows flows}])]))))
|
[:& export-grid-data {:grids grids}]))
|
||||||
|
|
||||||
|
(when (d/not-empty? flows)
|
||||||
|
[:& export-flows {:flows flows}])
|
||||||
|
|
||||||
|
(when (d/not-empty? guides)
|
||||||
|
[:& export-guides {:guides guides}])]))
|
||||||
|
|
||||||
(defn- export-shadow-data [{:keys [shadow]}]
|
(defn- export-shadow-data [{:keys [shadow]}]
|
||||||
(mf/html
|
(mf/html
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
[app.main.ui.workspace.header :refer [header]]
|
[app.main.ui.workspace.header :refer [header]]
|
||||||
[app.main.ui.workspace.left-toolbar :refer [left-toolbar]]
|
[app.main.ui.workspace.left-toolbar :refer [left-toolbar]]
|
||||||
[app.main.ui.workspace.libraries]
|
[app.main.ui.workspace.libraries]
|
||||||
[app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
|
||||||
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
|
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
|
||||||
[app.main.ui.workspace.viewport :refer [viewport]]
|
[app.main.ui.workspace.viewport :refer [viewport]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
@ -31,45 +30,22 @@
|
||||||
|
|
||||||
;; --- Workspace
|
;; --- Workspace
|
||||||
|
|
||||||
(mf/defc workspace-rules
|
|
||||||
{::mf/wrap-props false
|
|
||||||
::mf/wrap [mf/memo]}
|
|
||||||
[props]
|
|
||||||
(let [zoom (or (obj/get props "zoom") 1)
|
|
||||||
vbox (obj/get props "vbox")
|
|
||||||
vport (obj/get props "vport")
|
|
||||||
colorpalette? (obj/get props "colorpalette?")]
|
|
||||||
|
|
||||||
[:*
|
|
||||||
[:div.empty-rule-square]
|
|
||||||
[:& horizontal-rule {:zoom zoom
|
|
||||||
:vbox vbox
|
|
||||||
:vport vport}]
|
|
||||||
[:& vertical-rule {:zoom zoom
|
|
||||||
:vbox vbox
|
|
||||||
:vport vport}]
|
|
||||||
[:& coordinates/coordinates {:colorpalette? colorpalette?}]]))
|
|
||||||
|
|
||||||
(mf/defc workspace-content
|
(mf/defc workspace-content
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [selected (mf/deref refs/selected-shapes)
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
local (mf/deref refs/viewport-data)
|
local (mf/deref refs/viewport-data)
|
||||||
|
|
||||||
{:keys [zoom vbox vport options-mode]} local
|
{:keys [options-mode]} local
|
||||||
file (obj/get props "file")
|
file (obj/get props "file")
|
||||||
layout (obj/get props "layout")]
|
layout (obj/get props "layout")
|
||||||
|
colorpalette? (:colorpalette layout)]
|
||||||
[:*
|
[:*
|
||||||
(when (:colorpalette layout)
|
(when colorpalette? [:& colorpalette])
|
||||||
[:& colorpalette])
|
|
||||||
|
|
||||||
[:section.workspace-content
|
[:section.workspace-content
|
||||||
[:section.workspace-viewport
|
[:section.workspace-viewport
|
||||||
(when (contains? layout :rules)
|
[:& coordinates/coordinates {:colorpalette? colorpalette?}]
|
||||||
[:& workspace-rules {:zoom zoom
|
|
||||||
:vbox vbox
|
|
||||||
:vport vport
|
|
||||||
:colorpalette? (contains? layout :colorpalette)}])
|
|
||||||
|
|
||||||
[:& viewport {:file file
|
[:& viewport {:file file
|
||||||
:local local
|
:local local
|
||||||
|
|
|
@ -194,7 +194,13 @@
|
||||||
(fn [_error]
|
(fn [_error]
|
||||||
(st/emit! (dm/error (tr "errors.unexpected-error"))))
|
(st/emit! (dm/error (tr "errors.unexpected-error"))))
|
||||||
(st/emitf dm/hide)))))))
|
(st/emitf dm/hide)))))))
|
||||||
on-item-click (fn [item] (fn [event] (do (dom/stop-propagation event) (reset! show-sub-menu? item))))]
|
|
||||||
|
on-item-click
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [item]
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(reset! show-sub-menu? item))))]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps @editing?)
|
(mf/deps @editing?)
|
||||||
|
@ -314,12 +320,12 @@
|
||||||
[:& dropdown {:show (= @show-sub-menu? :preferences)
|
[:& dropdown {:show (= @show-sub-menu? :preferences)
|
||||||
:on-close #(reset! show-sub-menu? false)}
|
:on-close #(reset! show-sub-menu? false)}
|
||||||
[:ul.sub-menu.preferences
|
[:ul.sub-menu.preferences
|
||||||
#_[:li {:on-click #()}
|
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :snap-guides))}
|
||||||
[:span
|
[:span
|
||||||
(if (contains? layout :snap-guide)
|
(if (contains? layout :snap-guides)
|
||||||
(tr "workspace.header.menu.disable-snap-guides")
|
(tr "workspace.header.menu.disable-snap-guides")
|
||||||
(tr "workspace.header.menu.enable-snap-guides"))]
|
(tr "workspace.header.menu.enable-snap-guides"))]
|
||||||
[:span.shortcut (sc/get-tooltip :toggle-snap-grid)]]
|
[:span.shortcut (sc/get-tooltip :toggle-snap-guide)]]
|
||||||
|
|
||||||
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :snap-grid))}
|
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :snap-grid))}
|
||||||
[:span
|
[:span
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.main.ui.workspace.rules
|
|
||||||
(:require
|
|
||||||
[app.common.colors :as colors]
|
|
||||||
[app.common.math :as mth]
|
|
||||||
[app.util.object :as obj]
|
|
||||||
[rumext.alpha :as mf]))
|
|
||||||
|
|
||||||
(defn- calculate-step-size
|
|
||||||
[zoom]
|
|
||||||
(cond
|
|
||||||
(< 0 zoom 0.008) 10000
|
|
||||||
(< 0.008 zoom 0.015) 5000
|
|
||||||
(< 0.015 zoom 0.04) 2500
|
|
||||||
(< 0.04 zoom 0.07) 1000
|
|
||||||
(< 0.07 zoom 0.2) 500
|
|
||||||
(< 0.2 zoom 0.5) 250
|
|
||||||
(< 0.5 zoom 1) 100
|
|
||||||
(<= 1 zoom 2) 50
|
|
||||||
(< 2 zoom 4) 25
|
|
||||||
(< 4 zoom 6) 10
|
|
||||||
(< 6 zoom 15) 5
|
|
||||||
(< 15 zoom 25) 2
|
|
||||||
(< 25 zoom) 1
|
|
||||||
:else 1))
|
|
||||||
|
|
||||||
(defn draw-rule!
|
|
||||||
[dctx {:keys [zoom size start type]}]
|
|
||||||
(when start
|
|
||||||
(let [txfm (- (* (- 0 start) zoom) 20)
|
|
||||||
step (calculate-step-size zoom)
|
|
||||||
|
|
||||||
minv (max (mth/round start) -100000)
|
|
||||||
minv (* (mth/ceil (/ minv step)) step)
|
|
||||||
|
|
||||||
maxv (min (mth/round (+ start (/ size zoom))) 100000)
|
|
||||||
maxv (* (mth/floor (/ maxv step)) step)
|
|
||||||
|
|
||||||
path (js/Path2D.)]
|
|
||||||
|
|
||||||
(if (= type :horizontal)
|
|
||||||
(.translate dctx txfm 0)
|
|
||||||
(.translate dctx 0 txfm))
|
|
||||||
|
|
||||||
(obj/set! dctx "font" "12px worksans")
|
|
||||||
(obj/set! dctx "fillStyle" colors/gray-30)
|
|
||||||
(obj/set! dctx "strokeStyle" colors/gray-30)
|
|
||||||
(obj/set! dctx "textAlign" "center")
|
|
||||||
|
|
||||||
(loop [i minv]
|
|
||||||
(if (<= i maxv)
|
|
||||||
(let [pos (+ (* i zoom) 0)]
|
|
||||||
(.save dctx)
|
|
||||||
(if (= type :horizontal)
|
|
||||||
(do
|
|
||||||
;; Write the rule numbers
|
|
||||||
(.fillText dctx (str i) pos 13)
|
|
||||||
|
|
||||||
;; Build the rules lines
|
|
||||||
(.moveTo path pos 17)
|
|
||||||
(.lineTo path pos 20))
|
|
||||||
(do
|
|
||||||
;; Write the rule numbers
|
|
||||||
(.translate dctx 12 pos)
|
|
||||||
(.rotate dctx (/ (* 270 js/Math.PI) 180))
|
|
||||||
(.fillText dctx (str i) 0 0)
|
|
||||||
|
|
||||||
;; Build the rules lines
|
|
||||||
(.moveTo path 17 pos)
|
|
||||||
(.lineTo path 20 pos)))
|
|
||||||
(.restore dctx)
|
|
||||||
(recur (+ i step)))
|
|
||||||
|
|
||||||
;; Put the path in the canvas
|
|
||||||
(.stroke dctx path))))))
|
|
||||||
|
|
||||||
|
|
||||||
(mf/defc horizontal-rule
|
|
||||||
[{:keys [zoom vbox vport] :as props}]
|
|
||||||
(let [canvas (mf/use-ref)
|
|
||||||
width (- (:width vport) 20)]
|
|
||||||
(mf/use-layout-effect
|
|
||||||
(mf/deps zoom width (:x vbox))
|
|
||||||
(fn []
|
|
||||||
(let [node (mf/ref-val canvas)
|
|
||||||
dctx (.getContext ^js node "2d")]
|
|
||||||
(obj/set! node "width" width)
|
|
||||||
(draw-rule! dctx {:zoom zoom
|
|
||||||
:type :horizontal
|
|
||||||
:size width
|
|
||||||
:start (+ (:x vbox) (:left-offset vbox))}))))
|
|
||||||
|
|
||||||
[:canvas.horizontal-rule
|
|
||||||
{:ref canvas
|
|
||||||
:width width
|
|
||||||
:height 20}]))
|
|
||||||
|
|
||||||
(mf/defc vertical-rule
|
|
||||||
[{:keys [zoom vbox vport] :as props}]
|
|
||||||
(let [canvas (mf/use-ref)
|
|
||||||
height (- (:height vport) 20)]
|
|
||||||
(mf/use-layout-effect
|
|
||||||
(mf/deps zoom height (:y vbox))
|
|
||||||
(fn []
|
|
||||||
(let [node (mf/ref-val canvas)
|
|
||||||
dctx (.getContext ^js node "2d")]
|
|
||||||
(obj/set! node "height" height)
|
|
||||||
(draw-rule! dctx {:zoom zoom
|
|
||||||
:type :vertical
|
|
||||||
:size height
|
|
||||||
:count 100
|
|
||||||
:start (:y vbox)}))))
|
|
||||||
|
|
||||||
[:canvas.vertical-rule
|
|
||||||
{:ref canvas
|
|
||||||
:width 20
|
|
||||||
:height height}]))
|
|
|
@ -21,11 +21,13 @@
|
||||||
[app.main.ui.workspace.viewport.drawarea :as drawarea]
|
[app.main.ui.workspace.viewport.drawarea :as drawarea]
|
||||||
[app.main.ui.workspace.viewport.frame-grid :as frame-grid]
|
[app.main.ui.workspace.viewport.frame-grid :as frame-grid]
|
||||||
[app.main.ui.workspace.viewport.gradients :as gradients]
|
[app.main.ui.workspace.viewport.gradients :as gradients]
|
||||||
|
[app.main.ui.workspace.viewport.guides :as guides]
|
||||||
[app.main.ui.workspace.viewport.hooks :as hooks]
|
[app.main.ui.workspace.viewport.hooks :as hooks]
|
||||||
[app.main.ui.workspace.viewport.interactions :as interactions]
|
[app.main.ui.workspace.viewport.interactions :as interactions]
|
||||||
[app.main.ui.workspace.viewport.outline :as outline]
|
[app.main.ui.workspace.viewport.outline :as outline]
|
||||||
[app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay]
|
[app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay]
|
||||||
[app.main.ui.workspace.viewport.presence :as presence]
|
[app.main.ui.workspace.viewport.presence :as presence]
|
||||||
|
[app.main.ui.workspace.viewport.rules :as rules]
|
||||||
[app.main.ui.workspace.viewport.selection :as selection]
|
[app.main.ui.workspace.viewport.selection :as selection]
|
||||||
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
||||||
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
||||||
|
@ -89,6 +91,13 @@
|
||||||
;; STREAMS
|
;; STREAMS
|
||||||
move-stream (mf/use-memo #(rx/subject))
|
move-stream (mf/use-memo #(rx/subject))
|
||||||
|
|
||||||
|
frame-parent (mf/use-memo
|
||||||
|
(mf/deps @hover-ids base-objects)
|
||||||
|
(fn []
|
||||||
|
(let [parent (get base-objects (last @hover-ids))]
|
||||||
|
(when (= :frame (:type parent))
|
||||||
|
parent))))
|
||||||
|
|
||||||
zoom (d/check-num zoom 1)
|
zoom (d/check-num zoom 1)
|
||||||
drawing-tool (:tool drawing)
|
drawing-tool (:tool drawing)
|
||||||
drawing-obj (:object drawing)
|
drawing-obj (:object drawing)
|
||||||
|
@ -145,7 +154,10 @@
|
||||||
(or drawing-obj transform))
|
(or drawing-obj transform))
|
||||||
show-selrect? (and selrect (empty? drawing))
|
show-selrect? (and selrect (empty? drawing))
|
||||||
show-measures? (and (not transform) (not node-editing?) show-distances?)
|
show-measures? (and (not transform) (not node-editing?) show-distances?)
|
||||||
show-artboard-names? (contains? layout :display-artboard-names)]
|
show-artboard-names? (contains? layout :display-artboard-names)
|
||||||
|
show-rules? (contains? layout :rules)
|
||||||
|
|
||||||
|
disabled-guides? (or drawing-tool transform)]
|
||||||
|
|
||||||
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
|
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
|
||||||
(hooks/setup-viewport-size viewport-ref)
|
(hooks/setup-viewport-size viewport-ref)
|
||||||
|
@ -157,6 +169,8 @@
|
||||||
(hooks/setup-shortcuts node-editing? drawing-path?)
|
(hooks/setup-shortcuts node-editing? drawing-path?)
|
||||||
(hooks/setup-active-frames base-objects vbox hover active-frames)
|
(hooks/setup-active-frames base-objects vbox hover active-frames)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[:div.viewport
|
[:div.viewport
|
||||||
[:div.viewport-overlays
|
[:div.viewport-overlays
|
||||||
|
|
||||||
|
@ -294,7 +308,9 @@
|
||||||
|
|
||||||
(when show-grids?
|
(when show-grids?
|
||||||
[:& frame-grid/frame-grid
|
[:& frame-grid/frame-grid
|
||||||
{:zoom zoom :selected selected :transform transform}])
|
{:zoom zoom
|
||||||
|
:selected selected
|
||||||
|
:transform transform}])
|
||||||
|
|
||||||
(when show-pixel-grid?
|
(when show-pixel-grid?
|
||||||
[:& widgets/pixel-grid
|
[:& widgets/pixel-grid
|
||||||
|
@ -325,12 +341,6 @@
|
||||||
{:zoom zoom
|
{:zoom zoom
|
||||||
:tooltip tooltip}])
|
:tooltip tooltip}])
|
||||||
|
|
||||||
(when show-presence?
|
|
||||||
[:& presence/active-cursors
|
|
||||||
{:page-id page-id}])
|
|
||||||
|
|
||||||
[:& widgets/viewport-actions]
|
|
||||||
|
|
||||||
(when show-prototypes?
|
(when show-prototypes?
|
||||||
[:& interactions/interactions
|
[:& interactions/interactions
|
||||||
{:selected selected
|
{:selected selected
|
||||||
|
@ -341,5 +351,24 @@
|
||||||
|
|
||||||
(when show-selrect?
|
(when show-selrect?
|
||||||
[:& widgets/selection-rect {:data selrect
|
[:& widgets/selection-rect {:data selrect
|
||||||
:zoom zoom}])]]]))
|
:zoom zoom}])
|
||||||
|
|
||||||
|
(when show-presence?
|
||||||
|
[:& presence/active-cursors
|
||||||
|
{:page-id page-id}])
|
||||||
|
|
||||||
|
[:& widgets/viewport-actions]
|
||||||
|
|
||||||
|
(when show-rules?
|
||||||
|
[:*
|
||||||
|
[:& rules/rules
|
||||||
|
{:zoom zoom
|
||||||
|
:vbox vbox}]
|
||||||
|
|
||||||
|
[:& guides/viewport-guides
|
||||||
|
{:zoom zoom
|
||||||
|
:vbox vbox
|
||||||
|
:hover-frame frame-parent
|
||||||
|
:modifiers modifiers
|
||||||
|
:disabled-guides? disabled-guides?}]])]]]))
|
||||||
|
|
||||||
|
|
468
frontend/src/app/main/ui/workspace/viewport/guides.cljs
Normal file
468
frontend/src/app/main/ui/workspace/viewport/guides.cljs
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
;; 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.main.ui.workspace.viewport.guides
|
||||||
|
(:require
|
||||||
|
[app.common.colors :as colors]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.main.data.workspace :as dw]
|
||||||
|
[app.main.refs :as refs]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.main.streams :as ms]
|
||||||
|
[app.main.ui.cursors :as cur]
|
||||||
|
[app.main.ui.workspace.viewport.rules :as rules]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(def guide-width 1)
|
||||||
|
(def guide-opacity 0.7)
|
||||||
|
(def guide-opacity-hover 1)
|
||||||
|
(def guide-color colors/primary)
|
||||||
|
(def guide-pill-width 34)
|
||||||
|
(def guide-pill-height 20)
|
||||||
|
(def guide-pill-corner-radius 4)
|
||||||
|
(def guide-active-area 16)
|
||||||
|
|
||||||
|
(defn use-guide
|
||||||
|
"Hooks to support drag/drop for existing guides and new guides"
|
||||||
|
[on-guide-change get-hover-frame zoom {:keys [position axis frame-id]}]
|
||||||
|
(let [dragging-ref (mf/use-ref false)
|
||||||
|
start-ref (mf/use-ref nil)
|
||||||
|
start-pos-ref (mf/use-ref nil)
|
||||||
|
state (mf/use-state {:hover false
|
||||||
|
:new-position nil
|
||||||
|
:new-frame-id frame-id})
|
||||||
|
|
||||||
|
frame-id (:new-frame-id @state)
|
||||||
|
|
||||||
|
frame-ref (mf/use-memo (mf/deps frame-id) #(refs/object-by-id frame-id))
|
||||||
|
frame (mf/deref frame-ref)
|
||||||
|
|
||||||
|
on-pointer-enter
|
||||||
|
(mf/use-callback
|
||||||
|
(fn []
|
||||||
|
(swap! state assoc :hover true)))
|
||||||
|
|
||||||
|
on-pointer-leave
|
||||||
|
(mf/use-callback
|
||||||
|
(fn []
|
||||||
|
(swap! state assoc :hover false)))
|
||||||
|
|
||||||
|
on-pointer-down
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [event]
|
||||||
|
(dom/capture-pointer event)
|
||||||
|
(mf/set-ref-val! dragging-ref true)
|
||||||
|
(mf/set-ref-val! start-ref (dom/get-client-position event))
|
||||||
|
(mf/set-ref-val! start-pos-ref (get @ms/mouse-position axis))))
|
||||||
|
|
||||||
|
on-pointer-up
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps (select-keys @state [:new-position :new-frame-id]) on-guide-change)
|
||||||
|
(fn []
|
||||||
|
(when (some? on-guide-change)
|
||||||
|
(when (some? (:new-position @state))
|
||||||
|
(on-guide-change {:position (:new-position @state)
|
||||||
|
:frame-id (:new-frame-id @state)})))))
|
||||||
|
|
||||||
|
on-lost-pointer-capture
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [event]
|
||||||
|
(dom/release-pointer event)
|
||||||
|
(mf/set-ref-val! dragging-ref false)
|
||||||
|
(mf/set-ref-val! start-ref nil)
|
||||||
|
(mf/set-ref-val! start-pos-ref nil)
|
||||||
|
(swap! state assoc :new-position nil)))
|
||||||
|
|
||||||
|
on-mouse-move
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps position zoom)
|
||||||
|
(fn [event]
|
||||||
|
|
||||||
|
(when-let [_ (mf/ref-val dragging-ref)]
|
||||||
|
(let [start-pt (mf/ref-val start-ref)
|
||||||
|
start-pos (mf/ref-val start-pos-ref)
|
||||||
|
current-pt (dom/get-client-position event)
|
||||||
|
delta (/ (- (get current-pt axis) (get start-pt axis)) zoom)
|
||||||
|
new-position (if (some? position)
|
||||||
|
(+ position delta)
|
||||||
|
(+ start-pos delta))
|
||||||
|
|
||||||
|
;; TODO: Change when pixel-grid flag exists
|
||||||
|
new-position (mth/round new-position)
|
||||||
|
new-frame-id (:id (get-hover-frame))]
|
||||||
|
(swap! state assoc
|
||||||
|
:new-position new-position
|
||||||
|
:new-frame-id new-frame-id)))))]
|
||||||
|
{:on-pointer-enter on-pointer-enter
|
||||||
|
:on-pointer-leave on-pointer-leave
|
||||||
|
:on-pointer-down on-pointer-down
|
||||||
|
:on-pointer-up on-pointer-up
|
||||||
|
:on-lost-pointer-capture on-lost-pointer-capture
|
||||||
|
:on-mouse-move on-mouse-move
|
||||||
|
:state state
|
||||||
|
:frame frame}))
|
||||||
|
|
||||||
|
;; This functions are auxiliary to get the coords of components depending on the axis
|
||||||
|
;; we're handling
|
||||||
|
|
||||||
|
(defn guide-area-axis
|
||||||
|
[pos vbox zoom frame axis]
|
||||||
|
(let [rules-pos (/ rules/rules-pos zoom)
|
||||||
|
guide-active-area (/ guide-active-area zoom)]
|
||||||
|
(cond
|
||||||
|
(and (some? frame) (= axis :x))
|
||||||
|
{:x (- pos (/ guide-active-area 2))
|
||||||
|
:y (:y frame)
|
||||||
|
:width guide-active-area
|
||||||
|
:height (:height frame)}
|
||||||
|
|
||||||
|
(some? frame)
|
||||||
|
{:x (:x frame)
|
||||||
|
:y (- pos (/ guide-active-area 2))
|
||||||
|
:width (:width frame)
|
||||||
|
:height guide-active-area}
|
||||||
|
|
||||||
|
(= axis :x)
|
||||||
|
{:x (- pos (/ guide-active-area 2))
|
||||||
|
:y (+ (:y vbox) rules-pos)
|
||||||
|
:width guide-active-area
|
||||||
|
:height (:height vbox)}
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:x (+ (:x vbox) rules-pos)
|
||||||
|
:y (- pos (/ guide-active-area 2))
|
||||||
|
:width (:width vbox)
|
||||||
|
:height guide-active-area})))
|
||||||
|
|
||||||
|
(defn guide-line-axis
|
||||||
|
([pos vbox axis]
|
||||||
|
(if (= axis :x)
|
||||||
|
{:x1 pos
|
||||||
|
:y1 (:y vbox)
|
||||||
|
:x2 pos
|
||||||
|
:y2 (+ (:y vbox) (:height vbox))}
|
||||||
|
|
||||||
|
{:x1 (:x vbox)
|
||||||
|
:y1 pos
|
||||||
|
:x2 (+ (:x vbox) (:width vbox))
|
||||||
|
:y2 pos}))
|
||||||
|
|
||||||
|
([pos vbox frame axis]
|
||||||
|
(if (= axis :x)
|
||||||
|
{:l1-x1 pos
|
||||||
|
:l1-y1 (:y vbox)
|
||||||
|
:l1-x2 pos
|
||||||
|
:l1-y2 (:y frame)
|
||||||
|
:l2-x1 pos
|
||||||
|
:l2-y1 (:y frame)
|
||||||
|
:l2-x2 pos
|
||||||
|
:l2-y2 (+ (:y frame) (:height frame))
|
||||||
|
:l3-x1 pos
|
||||||
|
:l3-y1 (+ (:y frame) (:height frame))
|
||||||
|
:l3-x2 pos
|
||||||
|
:l3-y2 (+ (:y vbox) (:height vbox))}
|
||||||
|
{:l1-x1 (:x vbox)
|
||||||
|
:l1-y1 pos
|
||||||
|
:l1-x2 (:x frame)
|
||||||
|
:l1-y2 pos
|
||||||
|
:l2-x1 (:x frame)
|
||||||
|
:l2-y1 pos
|
||||||
|
:l2-x2 (+ (:x frame) (:width frame))
|
||||||
|
:l2-y2 pos
|
||||||
|
:l3-x1 (+ (:x frame) (:width frame))
|
||||||
|
:l3-y1 pos
|
||||||
|
:l3-x2 (+ (:x vbox) (:width vbox))
|
||||||
|
:l3-y2 pos})))
|
||||||
|
|
||||||
|
(defn guide-pill-axis
|
||||||
|
[pos vbox zoom axis]
|
||||||
|
(let [rules-pos (/ rules/rules-pos zoom)
|
||||||
|
guide-pill-width (/ guide-pill-width zoom)
|
||||||
|
guide-pill-height (/ guide-pill-height zoom)]
|
||||||
|
|
||||||
|
(if (= axis :x)
|
||||||
|
{:rect-x (- pos (/ guide-pill-width 2))
|
||||||
|
:rect-y (+ (:y vbox) rules-pos (- (/ guide-pill-width 2)) (/ 2 zoom))
|
||||||
|
:rect-width guide-pill-width
|
||||||
|
:rect-height guide-pill-height
|
||||||
|
:text-x pos
|
||||||
|
:text-y (+ (:y vbox) rules-pos (- (/ 3 zoom)))}
|
||||||
|
|
||||||
|
{:rect-x (+ (:x vbox) rules-pos (- (/ guide-pill-height 2)) (- (/ 5 zoom)))
|
||||||
|
:rect-y (- pos (/ guide-pill-width 2))
|
||||||
|
:rect-width guide-pill-height
|
||||||
|
:rect-height guide-pill-width
|
||||||
|
:text-x (+ (:x vbox) rules-pos (- (/ 3 zoom)))
|
||||||
|
:text-y pos})))
|
||||||
|
|
||||||
|
(defn guide-inside-vbox?
|
||||||
|
([vbox]
|
||||||
|
(partial guide-inside-vbox? vbox))
|
||||||
|
|
||||||
|
([{:keys [x y width height]} {:keys [axis position]}]
|
||||||
|
(let [x1 x
|
||||||
|
x2 (+ x width)
|
||||||
|
y1 y
|
||||||
|
y2 (+ y height)]
|
||||||
|
(if (= axis :x)
|
||||||
|
(and (>= position x1)
|
||||||
|
(<= position x2))
|
||||||
|
(and (>= position y1)
|
||||||
|
(<= position y2))))))
|
||||||
|
|
||||||
|
(defn guide-creation-area
|
||||||
|
[vbox zoom axis]
|
||||||
|
(if (= axis :x)
|
||||||
|
{:x (:x vbox)
|
||||||
|
:y (:y vbox)
|
||||||
|
:width (/ 24 zoom)
|
||||||
|
:height (:height vbox)}
|
||||||
|
|
||||||
|
{:x (:x vbox)
|
||||||
|
:y (:y vbox)
|
||||||
|
:width (:width vbox)
|
||||||
|
:height (/ 24 zoom)}))
|
||||||
|
|
||||||
|
(defn is-guide-inside-frame?
|
||||||
|
[guide frame]
|
||||||
|
|
||||||
|
(if (= :x (:axis guide))
|
||||||
|
(and (>= (:position guide) (:x frame) )
|
||||||
|
(<= (:position guide) (+ (:x frame) (:width frame)) ))
|
||||||
|
|
||||||
|
(and (>= (:position guide) (:y frame) )
|
||||||
|
(<= (:position guide) (+ (:y frame) (:height frame)) ))))
|
||||||
|
|
||||||
|
(mf/defc guide
|
||||||
|
{::mf/wrap [mf/memo]}
|
||||||
|
[{:keys [guide hover? on-guide-change get-hover-frame vbox zoom hover-frame disabled-guides? frame-modifier]}]
|
||||||
|
|
||||||
|
(let [axis (:axis guide)
|
||||||
|
|
||||||
|
handle-change-position
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps on-guide-change)
|
||||||
|
(fn [changes]
|
||||||
|
(when on-guide-change
|
||||||
|
(on-guide-change (merge guide changes)))))
|
||||||
|
|
||||||
|
{:keys [on-pointer-enter
|
||||||
|
on-pointer-leave
|
||||||
|
on-pointer-down
|
||||||
|
on-pointer-up
|
||||||
|
on-lost-pointer-capture
|
||||||
|
on-mouse-move
|
||||||
|
state
|
||||||
|
frame]} (use-guide handle-change-position get-hover-frame zoom guide)
|
||||||
|
|
||||||
|
base-frame (or frame hover-frame)
|
||||||
|
frame (gsh/transform-shape (merge base-frame frame-modifier))
|
||||||
|
|
||||||
|
move-vec (gpt/to-vec (gpt/point (:x base-frame) (:y base-frame))
|
||||||
|
(gpt/point (:x frame) (:y frame)))
|
||||||
|
|
||||||
|
pos (+ (or (:new-position @state) (:position guide)) (get move-vec axis))
|
||||||
|
guide-width (/ guide-width zoom)
|
||||||
|
guide-pill-corner-radius (/ guide-pill-corner-radius zoom)]
|
||||||
|
|
||||||
|
(when (or (nil? frame)
|
||||||
|
(is-guide-inside-frame? (assoc guide :position pos) frame)
|
||||||
|
(:hover @state true))
|
||||||
|
[:g.guide-area
|
||||||
|
(when-not disabled-guides?
|
||||||
|
(let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)]
|
||||||
|
[:rect {:x x
|
||||||
|
:y y
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:style {:fill "none"
|
||||||
|
:pointer-events "fill"
|
||||||
|
:cursor (if (= axis :x) (cur/resize-ew 0) (cur/resize-ns 0))}
|
||||||
|
:on-pointer-enter on-pointer-enter
|
||||||
|
:on-pointer-leave on-pointer-leave
|
||||||
|
:on-pointer-down on-pointer-down
|
||||||
|
:on-pointer-up on-pointer-up
|
||||||
|
:on-lost-pointer-capture on-lost-pointer-capture
|
||||||
|
:on-mouse-move on-mouse-move}]))
|
||||||
|
|
||||||
|
(if (some? frame)
|
||||||
|
(let [{:keys [l1-x1 l1-y1 l1-x2 l1-y2
|
||||||
|
l2-x1 l2-y1 l2-x2 l2-y2
|
||||||
|
l3-x1 l3-y1 l3-x2 l3-y2]}
|
||||||
|
(guide-line-axis pos vbox frame axis)]
|
||||||
|
[:g
|
||||||
|
(when (or hover? (:hover @state))
|
||||||
|
[:line {:x1 l1-x1
|
||||||
|
:y1 l1-y1
|
||||||
|
:x2 l1-x2
|
||||||
|
:y2 l1-y2
|
||||||
|
:style {:stroke guide-color
|
||||||
|
:stroke-opacity guide-opacity-hover
|
||||||
|
:stroke-dasharray (str "0, " (/ 6 zoom))
|
||||||
|
:stroke-linecap "round"
|
||||||
|
:stroke-width guide-width}}])
|
||||||
|
[:line {:x1 l2-x1
|
||||||
|
:y1 l2-y1
|
||||||
|
:x2 l2-x2
|
||||||
|
:y2 l2-y2
|
||||||
|
:style {:stroke guide-color
|
||||||
|
:stroke-width guide-width
|
||||||
|
:stroke-opacity (if (or hover? (:hover @state))
|
||||||
|
guide-opacity-hover
|
||||||
|
guide-opacity)}}]
|
||||||
|
(when (or hover? (:hover @state))
|
||||||
|
[:line {:x1 l3-x1
|
||||||
|
:y1 l3-y1
|
||||||
|
:x2 l3-x2
|
||||||
|
:y2 l3-y2
|
||||||
|
:style {:stroke guide-color
|
||||||
|
:stroke-opacity guide-opacity-hover
|
||||||
|
:stroke-width guide-width
|
||||||
|
:stroke-dasharray (str "0, " (/ 6 zoom))
|
||||||
|
:stroke-linecap "round"}}])])
|
||||||
|
|
||||||
|
(let [{:keys [x1 y1 x2 y2]} (guide-line-axis pos vbox axis)]
|
||||||
|
[:line {:x1 x1
|
||||||
|
:y1 y1
|
||||||
|
:x2 x2
|
||||||
|
:y2 y2
|
||||||
|
:style {:stroke guide-color
|
||||||
|
:stroke-width guide-width
|
||||||
|
:stroke-opacity (if (or hover? (:hover @state))
|
||||||
|
guide-opacity-hover
|
||||||
|
guide-opacity)}}]))
|
||||||
|
|
||||||
|
(when (or hover? (:hover @state))
|
||||||
|
(let [{:keys [rect-x rect-y rect-width rect-height text-x text-y]}
|
||||||
|
(guide-pill-axis pos vbox zoom axis)]
|
||||||
|
[:g.guide-pill
|
||||||
|
[:rect {:x rect-x
|
||||||
|
:y rect-y
|
||||||
|
:width rect-width
|
||||||
|
:height rect-height
|
||||||
|
:rx guide-pill-corner-radius
|
||||||
|
:ry guide-pill-corner-radius
|
||||||
|
:style {:fill guide-color}}]
|
||||||
|
|
||||||
|
[:text {:x text-x
|
||||||
|
:y text-y
|
||||||
|
:text-anchor "middle"
|
||||||
|
:dominant-baseline "middle"
|
||||||
|
:transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")"))
|
||||||
|
:style {:font-size (/ 13 zoom)
|
||||||
|
:font-family "sourcesanspro"
|
||||||
|
:fill colors/black}}
|
||||||
|
(str (mth/round pos))]]))])))
|
||||||
|
|
||||||
|
(mf/defc new-guide-area
|
||||||
|
[{:keys [vbox zoom axis get-hover-frame disabled-guides?]}]
|
||||||
|
|
||||||
|
(let [on-guide-change
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps vbox)
|
||||||
|
(fn [guide]
|
||||||
|
(let [guide (-> guide
|
||||||
|
(assoc :id (uuid/next)
|
||||||
|
:axis axis))]
|
||||||
|
(when (guide-inside-vbox? vbox guide)
|
||||||
|
(st/emit! (dw/update-guides guide))))))
|
||||||
|
|
||||||
|
{:keys [on-pointer-enter
|
||||||
|
on-pointer-leave
|
||||||
|
on-pointer-down
|
||||||
|
on-pointer-up
|
||||||
|
on-lost-pointer-capture
|
||||||
|
on-mouse-move
|
||||||
|
state
|
||||||
|
frame]} (use-guide on-guide-change get-hover-frame zoom {:axis axis})]
|
||||||
|
|
||||||
|
[:g.new-guides
|
||||||
|
(when-not disabled-guides?
|
||||||
|
(let [{:keys [x y width height]} (guide-creation-area vbox zoom axis)]
|
||||||
|
[:rect {:x x
|
||||||
|
:y y
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:on-pointer-enter on-pointer-enter
|
||||||
|
:on-pointer-leave on-pointer-leave
|
||||||
|
:on-pointer-down on-pointer-down
|
||||||
|
:on-pointer-up on-pointer-up
|
||||||
|
:on-lost-pointer-capture on-lost-pointer-capture
|
||||||
|
:on-mouse-move on-mouse-move
|
||||||
|
:style {:fill "none"
|
||||||
|
:pointer-events "fill"
|
||||||
|
:cursor (if (= axis :x) (cur/resize-ew 0) (cur/resize-ns 0))}}]))
|
||||||
|
|
||||||
|
(when (:new-position @state)
|
||||||
|
[:& guide {:guide {:axis axis
|
||||||
|
:position (:new-position @state)}
|
||||||
|
:get-hover-frame get-hover-frame
|
||||||
|
:vbox vbox
|
||||||
|
:zoom zoom
|
||||||
|
:hover? true
|
||||||
|
:hover-frame frame}])]))
|
||||||
|
|
||||||
|
(mf/defc viewport-guides
|
||||||
|
{::mf/wrap [mf/memo]}
|
||||||
|
[{:keys [zoom vbox hover-frame disabled-guides? modifiers]}]
|
||||||
|
|
||||||
|
(let [page (mf/deref refs/workspace-page)
|
||||||
|
|
||||||
|
guides (mf/use-memo
|
||||||
|
(mf/deps page vbox)
|
||||||
|
#(->> (get-in page [:options :guides] {})
|
||||||
|
(vals)
|
||||||
|
(filter (guide-inside-vbox? vbox))))
|
||||||
|
|
||||||
|
hover-frame-ref (mf/use-ref nil)
|
||||||
|
|
||||||
|
;; We use the ref to not redraw every guide everytime the hovering frame change
|
||||||
|
;; we're only interested to get the frame in the guide we're moving
|
||||||
|
get-hover-frame
|
||||||
|
(mf/use-callback
|
||||||
|
(fn []
|
||||||
|
(mf/ref-val hover-frame-ref)))
|
||||||
|
|
||||||
|
on-guide-change
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps vbox)
|
||||||
|
(fn [guide]
|
||||||
|
(if (guide-inside-vbox? vbox guide)
|
||||||
|
(st/emit! (dw/update-guides guide))
|
||||||
|
(st/emit! (dw/remove-guide guide)))))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps hover-frame)
|
||||||
|
(fn []
|
||||||
|
(mf/set-ref-val! hover-frame-ref hover-frame)))
|
||||||
|
|
||||||
|
[:g.guides {:pointer-events "none"}
|
||||||
|
[:& new-guide-area {:vbox vbox
|
||||||
|
:zoom zoom
|
||||||
|
:axis :x
|
||||||
|
:get-hover-frame get-hover-frame
|
||||||
|
:disabled-guides? disabled-guides?}]
|
||||||
|
|
||||||
|
[:& new-guide-area {:vbox vbox
|
||||||
|
:zoom zoom
|
||||||
|
:axis :y
|
||||||
|
:get-hover-frame get-hover-frame
|
||||||
|
:disabled-guides? disabled-guides?}]
|
||||||
|
|
||||||
|
(for [current guides]
|
||||||
|
[:& guide {:key (str "guide-" (:id current))
|
||||||
|
:guide current
|
||||||
|
:vbox vbox
|
||||||
|
:zoom zoom
|
||||||
|
:frame-modifier (get modifiers (:frame-id current))
|
||||||
|
:get-hover-frame get-hover-frame
|
||||||
|
:on-guide-change on-guide-change
|
||||||
|
:disabled-guides? disabled-guides?}])]))
|
||||||
|
|
137
frontend/src/app/main/ui/workspace/viewport/rules.cljs
Normal file
137
frontend/src/app/main/ui/workspace/viewport/rules.cljs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
;; 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.main.ui.workspace.viewport.rules
|
||||||
|
(:require
|
||||||
|
[app.common.colors :as colors]
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.util.object :as obj]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(def rules-pos 15)
|
||||||
|
(def rules-size 4)
|
||||||
|
(def rules-width 1)
|
||||||
|
|
||||||
|
;; ----------------
|
||||||
|
;; RULES
|
||||||
|
;; ----------------
|
||||||
|
|
||||||
|
(defn- calculate-step-size
|
||||||
|
[zoom]
|
||||||
|
(cond
|
||||||
|
(< 0 zoom 0.008) 10000
|
||||||
|
(< 0.008 zoom 0.015) 5000
|
||||||
|
(< 0.015 zoom 0.04) 2500
|
||||||
|
(< 0.04 zoom 0.07) 1000
|
||||||
|
(< 0.07 zoom 0.2) 500
|
||||||
|
(< 0.2 zoom 0.5) 250
|
||||||
|
(< 0.5 zoom 1) 100
|
||||||
|
(<= 1 zoom 2) 50
|
||||||
|
(< 2 zoom 4) 25
|
||||||
|
(< 4 zoom 6) 10
|
||||||
|
(< 6 zoom 15) 5
|
||||||
|
(< 15 zoom 25) 2
|
||||||
|
(< 25 zoom) 1
|
||||||
|
:else 1))
|
||||||
|
|
||||||
|
(defn get-clip-area
|
||||||
|
[vbox zoom axis]
|
||||||
|
(if (= axis :x)
|
||||||
|
(let [x (+ (:x vbox) (/ 25 zoom))
|
||||||
|
y (:y vbox)
|
||||||
|
width (- (:width vbox) (/ 21 zoom))
|
||||||
|
height (/ 25 zoom)]
|
||||||
|
{:x x :y y :width width :height height})
|
||||||
|
|
||||||
|
(let [x (:x vbox)
|
||||||
|
y (+ (:y vbox) (/ 25 zoom))
|
||||||
|
width (/ 25 zoom)
|
||||||
|
height (- (:height vbox) (/ 21 zoom))]
|
||||||
|
{:x x :y y :width width :height height})))
|
||||||
|
|
||||||
|
(defn get-rule-params
|
||||||
|
[vbox axis]
|
||||||
|
(if (= axis :x)
|
||||||
|
(let [start (:x vbox)
|
||||||
|
end (+ start (:width vbox))]
|
||||||
|
{:start start :end end})
|
||||||
|
|
||||||
|
(let [start (:y vbox)
|
||||||
|
end (+ start (:height vbox))]
|
||||||
|
{:start start :end end})))
|
||||||
|
|
||||||
|
(defn get-rule-axis
|
||||||
|
[val vbox zoom axis]
|
||||||
|
(let [rules-pos (/ rules-pos zoom)
|
||||||
|
rules-size (/ rules-size zoom)]
|
||||||
|
(if (= axis :x)
|
||||||
|
{:text-x val
|
||||||
|
:text-y (+ (:y vbox) (- rules-pos (/ 4 zoom)))
|
||||||
|
:line-x1 val
|
||||||
|
:line-y1 (+ (:y vbox) rules-pos (/ 2 zoom))
|
||||||
|
:line-x2 val
|
||||||
|
:line-y2 (+ (:y vbox) rules-pos (/ 2 zoom) rules-size)}
|
||||||
|
|
||||||
|
{:text-x (+ (:x vbox) (- rules-pos (/ 4 zoom)))
|
||||||
|
:text-y val
|
||||||
|
:line-x1 (+ (:x vbox) rules-pos (/ 2 zoom))
|
||||||
|
:line-y1 val
|
||||||
|
:line-x2 (+ (:x vbox) rules-pos (/ 2 zoom) rules-size)
|
||||||
|
:line-y2 val})))
|
||||||
|
|
||||||
|
(mf/defc rules-axis
|
||||||
|
[{:keys [zoom vbox axis]}]
|
||||||
|
(let [rules-width (/ rules-width zoom)
|
||||||
|
step (calculate-step-size zoom)
|
||||||
|
clip-id (str "clip-rule-" (d/name axis))]
|
||||||
|
|
||||||
|
[:g.rules {:clipPath (str "url(#" clip-id ")")}
|
||||||
|
|
||||||
|
[:defs
|
||||||
|
[:clipPath {:id clip-id}
|
||||||
|
(let [{:keys [x y width height]} (get-clip-area vbox zoom axis)]
|
||||||
|
[:rect {:x x :y y :width width :height height}])]]
|
||||||
|
|
||||||
|
(let [{:keys [start end]} (get-rule-params vbox axis)
|
||||||
|
minv (max (mth/round start) -100000)
|
||||||
|
minv (* (mth/ceil (/ minv step)) step)
|
||||||
|
maxv (min (mth/round end) 100000)
|
||||||
|
maxv (* (mth/floor (/ maxv step)) step)]
|
||||||
|
|
||||||
|
(for [step-val (range minv (inc maxv) step)]
|
||||||
|
(let [{:keys [text-x text-y line-x1 line-y1 line-x2 line-y2]}
|
||||||
|
(get-rule-axis step-val vbox zoom axis)]
|
||||||
|
[:*
|
||||||
|
[:text {:key (str "text-" (d/name axis) "-" step-val)
|
||||||
|
:x text-x
|
||||||
|
:y text-y
|
||||||
|
:text-anchor "middle"
|
||||||
|
:dominant-baseline "middle"
|
||||||
|
:transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")"))
|
||||||
|
:style {:font-size (/ 13 zoom)
|
||||||
|
:font-family "sourcesanspro"
|
||||||
|
:fill colors/gray-30}}
|
||||||
|
(str (mth/round step-val))]
|
||||||
|
|
||||||
|
[:line {:key (str "line-" (d/name axis) "-" step-val)
|
||||||
|
:x1 line-x1
|
||||||
|
:y1 line-y1
|
||||||
|
:x2 line-x2
|
||||||
|
:y2 line-y2
|
||||||
|
:style {:stroke colors/gray-30
|
||||||
|
:stroke-width rules-width}}]])))]))
|
||||||
|
|
||||||
|
(mf/defc rules
|
||||||
|
{::mf/wrap-props false
|
||||||
|
::mf/wrap [mf/memo]}
|
||||||
|
[props]
|
||||||
|
(let [zoom (obj/get props "zoom")
|
||||||
|
vbox (obj/get props "vbox")]
|
||||||
|
(when (some? vbox)
|
||||||
|
[:g.rules {:pointer-events "none"}
|
||||||
|
[:& rules-axis {:zoom zoom :vbox vbox :axis :x}]
|
||||||
|
[:& rules-axis {:zoom zoom :vbox vbox :axis :y}]])))
|
|
@ -52,7 +52,7 @@
|
||||||
:opacity line-opacity}])
|
:opacity line-opacity}])
|
||||||
|
|
||||||
(defn get-snap
|
(defn get-snap
|
||||||
[coord {:keys [shapes page-id filter-shapes modifiers]}]
|
[coord {:keys [shapes page-id remove-snap? modifiers]}]
|
||||||
(let [shape (if (> (count shapes) 1)
|
(let [shape (if (> (count shapes) 1)
|
||||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
||||||
(->> shapes (first)))
|
(->> shapes (first)))
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
(->> (sp/shape-snap-points shape)
|
(->> (sp/shape-snap-points shape)
|
||||||
(map #(vector frame-id %)))))
|
(map #(vector frame-id %)))))
|
||||||
(rx/flat-map (fn [[frame-id point]]
|
(rx/flat-map (fn [[frame-id point]]
|
||||||
(->> (snap/get-snap-points page-id frame-id filter-shapes point coord)
|
(->> (snap/get-snap-points page-id frame-id remove-snap? point coord)
|
||||||
(rx/map #(vector point % coord)))))
|
(rx/map #(vector point % coord)))))
|
||||||
(rx/reduce conj []))))
|
(rx/reduce conj []))))
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
(hash-map coord fixedv (flip coord) maxv)]))))
|
(hash-map coord fixedv (flip coord) maxv)]))))
|
||||||
|
|
||||||
(mf/defc snap-feedback
|
(mf/defc snap-feedback
|
||||||
[{:keys [shapes filter-shapes zoom modifiers] :as props}]
|
[{:keys [shapes remove-snap? zoom modifiers] :as props}]
|
||||||
(let [state (mf/use-state [])
|
(let [state (mf/use-state [])
|
||||||
subject (mf/use-memo #(rx/subject))
|
subject (mf/use-memo #(rx/subject))
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@
|
||||||
#(rx/dispose! sub))))
|
#(rx/dispose! sub))))
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps shapes filter-shapes modifiers)
|
(mf/deps shapes remove-snap? modifiers)
|
||||||
(fn []
|
(fn []
|
||||||
(rx/push! subject props)))
|
(rx/push! subject props)))
|
||||||
|
|
||||||
|
@ -152,29 +152,23 @@
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [layout zoom objects selected page-id drawing transform modifiers] :as props}]
|
[{:keys [layout zoom objects selected page-id drawing transform modifiers] :as props}]
|
||||||
|
|
||||||
(let [;; shapes (mf/deref (refs/objects-by-id selected))
|
(let [shapes (into [] (keep (d/getf objects)) selected)
|
||||||
;; filter-shapes (mf/deref refs/selected-shapes-with-children)
|
|
||||||
|
|
||||||
shapes (->> selected
|
filter-shapes
|
||||||
(map #(get objects %))
|
(into #{}
|
||||||
(filterv (comp not nil?)))
|
|
||||||
filter-shapes (into #{}
|
|
||||||
(comp (mapcat #(cp/get-object-with-children % objects))
|
(comp (mapcat #(cp/get-object-with-children % objects))
|
||||||
(map :id))
|
(map :id))
|
||||||
selected)
|
selected)
|
||||||
|
|
||||||
filter-shapes (fn [id]
|
remove-snap? (mf/use-memo
|
||||||
(if (= id :layout)
|
(mf/deps layout filter-shapes)
|
||||||
(or (not (contains? layout :display-grid))
|
#(snap/make-remove-snap layout filter-shapes))
|
||||||
(not (contains? layout :snap-grid)))
|
|
||||||
(or (filter-shapes id)
|
|
||||||
(not (contains? layout :dynamic-alignment)))))
|
|
||||||
|
|
||||||
shapes (if drawing [drawing] shapes)]
|
shapes (if drawing [drawing] shapes)]
|
||||||
(when (or drawing transform)
|
(when (or drawing transform)
|
||||||
[:& snap-feedback {:shapes shapes
|
[:& snap-feedback {:shapes shapes
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:filter-shapes filter-shapes
|
:remove-snap? remove-snap?
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:modifiers modifiers}])))
|
:modifiers modifiers}])))
|
||||||
|
|
||||||
|
|
|
@ -213,9 +213,20 @@
|
||||||
(.-innerText el)))
|
(.-innerText el)))
|
||||||
|
|
||||||
(defn query
|
(defn query
|
||||||
[^js el ^string query]
|
([^string query]
|
||||||
|
(query globals/document query))
|
||||||
|
|
||||||
|
([^js el ^string query]
|
||||||
(when (some? el)
|
(when (some? el)
|
||||||
(.querySelector el query)))
|
(.querySelector el query))))
|
||||||
|
|
||||||
|
(defn query-all
|
||||||
|
([^string query]
|
||||||
|
(query-all globals/document query))
|
||||||
|
|
||||||
|
([^js el ^string query]
|
||||||
|
(when (some? el)
|
||||||
|
(.querySelectorAll el query))))
|
||||||
|
|
||||||
(defn get-client-position
|
(defn get-client-position
|
||||||
[^js event]
|
[^js event]
|
||||||
|
|
|
@ -28,3 +28,9 @@
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
:frame (-> shape :selrect frame-snap-points)
|
:frame (-> shape :selrect frame-snap-points)
|
||||||
(into #{(gsh/center-shape shape)} (:points shape)))))
|
(into #{(gsh/center-shape shape)} (:points shape)))))
|
||||||
|
|
||||||
|
(defn guide-snap-points
|
||||||
|
[guide]
|
||||||
|
(if (= :x (:axis guide))
|
||||||
|
#{(gpt/point (:position guide) 0)}
|
||||||
|
#{(gpt/point 0 (:position guide))}))
|
||||||
|
|
|
@ -515,6 +515,20 @@
|
||||||
(let [flows-node (get-data node :penpot:flows)]
|
(let [flows-node (get-data node :penpot:flows)]
|
||||||
(->> flows-node :content (mapv parse-flow-node))))
|
(->> flows-node :content (mapv parse-flow-node))))
|
||||||
|
|
||||||
|
(defn parse-guide-node [node]
|
||||||
|
(let [attrs (-> node :attrs remove-penpot-prefix)]
|
||||||
|
(println attrs)
|
||||||
|
(let [id (uuid/next)]
|
||||||
|
[id
|
||||||
|
{:id id
|
||||||
|
:frame-id (when (:frame-id attrs) (-> attrs :frame-id uuid))
|
||||||
|
:axis (-> attrs :axis keyword)
|
||||||
|
:position (-> attrs :position d/parse-double)}])))
|
||||||
|
|
||||||
|
(defn parse-guides [node]
|
||||||
|
(let [guides-node (get-data node :penpot:guides)]
|
||||||
|
(->> guides-node :content (map parse-guide-node) (into {}))))
|
||||||
|
|
||||||
(defn extract-from-data
|
(defn extract-from-data
|
||||||
([node tag]
|
([node tag]
|
||||||
(extract-from-data node tag identity))
|
(extract-from-data node tag identity))
|
||||||
|
@ -764,7 +778,8 @@
|
||||||
grids (->> (parse-grids node)
|
grids (->> (parse-grids node)
|
||||||
(group-by :type)
|
(group-by :type)
|
||||||
(d/mapm (fn [_ v] (-> v first :params))))
|
(d/mapm (fn [_ v] (-> v first :params))))
|
||||||
flows (parse-flows node)]
|
flows (parse-flows node)
|
||||||
|
guides (parse-guides node)]
|
||||||
(cond-> {}
|
(cond-> {}
|
||||||
(some? background)
|
(some? background)
|
||||||
(assoc-in [:options :background] background)
|
(assoc-in [:options :background] background)
|
||||||
|
@ -773,7 +788,10 @@
|
||||||
(assoc-in [:options :saved-grids] grids)
|
(assoc-in [:options :saved-grids] grids)
|
||||||
|
|
||||||
(d/not-empty? flows)
|
(d/not-empty? flows)
|
||||||
(assoc-in [:options :flows] flows))))
|
(assoc-in [:options :flows] flows)
|
||||||
|
|
||||||
|
(d/not-empty? guides)
|
||||||
|
(assoc-in [:options :guides] guides))))
|
||||||
|
|
||||||
(defn parse-interactions
|
(defn parse-interactions
|
||||||
[node]
|
[node]
|
||||||
|
|
251
frontend/src/app/util/snap_data.cljs
Normal file
251
frontend/src/app/util/snap_data.cljs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
;; 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
|
||||||
|
"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- add-frame
|
||||||
|
[page-data frame]
|
||||||
|
(let [frame-id (:id frame)
|
||||||
|
parent-id (:parent-id frame)
|
||||||
|
frame-data (->> (snap/shape-snap-points frame)
|
||||||
|
(mapv #(array-map :type :shape
|
||||||
|
:id frame-id
|
||||||
|
:pt %)))
|
||||||
|
|
||||||
|
grid-x-data (->> (gg/grid-snap-points frame :x)
|
||||||
|
(mapv #(array-map :type :grid-x
|
||||||
|
:id frame-id
|
||||||
|
:pt %)))
|
||||||
|
|
||||||
|
grid-y-data (->> (gg/grid-snap-points frame :y)
|
||||||
|
(mapv #(array-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 #(array-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 #(array-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
|
||||||
|
"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))))
|
|
@ -40,14 +40,13 @@
|
||||||
(defmethod handler :update-page-indices
|
(defmethod handler :update-page-indices
|
||||||
[{:keys [page-id changes] :as message}]
|
[{: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)
|
(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
|
message (assoc message
|
||||||
:objects new-objects
|
:old-page old-page
|
||||||
:new-objects new-objects
|
:new-page new-page)]
|
||||||
:old-objects old-objects)]
|
|
||||||
(handler (-> message
|
(handler (-> message
|
||||||
(assoc :cmd :selection/update-index)))
|
(assoc :cmd :selection/update-index)))
|
||||||
(handler (-> message
|
(handler (-> message
|
||||||
|
|
|
@ -283,7 +283,6 @@
|
||||||
|
|
||||||
(defn setup-interactions
|
(defn setup-interactions
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
(letfn [(add-interactions
|
(letfn [(add-interactions
|
||||||
[file [id interactions]]
|
[file [id interactions]]
|
||||||
(->> interactions
|
(->> interactions
|
||||||
|
@ -294,7 +293,6 @@
|
||||||
(let [interactions (:interactions file)
|
(let [interactions (:interactions file)
|
||||||
file (dissoc file :interactions)]
|
file (dissoc file :interactions)]
|
||||||
(->> interactions (reduce add-interactions file))))]
|
(->> interactions (reduce add-interactions file))))]
|
||||||
|
|
||||||
(-> file process-interactions)))
|
(-> file process-interactions)))
|
||||||
|
|
||||||
(defn resolve-media
|
(defn resolve-media
|
||||||
|
@ -328,7 +326,12 @@
|
||||||
(assoc :id (resolve page-id)))
|
(assoc :id (resolve page-id)))
|
||||||
flows (->> (get-in page-data [:options :flows])
|
flows (->> (get-in page-data [:options :flows])
|
||||||
(mapv #(update % :starting-frame resolve)))
|
(mapv #(update % :starting-frame resolve)))
|
||||||
page-data (d/assoc-in-when page-data [:options :flows] flows)
|
guides (->> (get-in page-data [:options :guides])
|
||||||
|
(d/mapm #(update %2 :frame-id resolve)))
|
||||||
|
|
||||||
|
page-data (-> page-data
|
||||||
|
(d/assoc-in-when [:options :flows] flows)
|
||||||
|
(d/assoc-in-when [:options :guides] guides))
|
||||||
file (-> file (fb/add-page page-data))]
|
file (-> file (fb/add-page page-data))]
|
||||||
(->> (rx/from nodes)
|
(->> (rx/from nodes)
|
||||||
(rx/filter cip/shape?)
|
(rx/filter cip/shape?)
|
||||||
|
|
|
@ -170,8 +170,10 @@
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defmethod impl/handler :selection/update-index
|
(defmethod impl/handler :selection/update-index
|
||||||
[{:keys [page-id old-objects new-objects] :as message}]
|
[{:keys [page-id old-page new-page] :as message}]
|
||||||
(let [update-page-index
|
(let [old-objects (:objects old-page)
|
||||||
|
new-objects (:objects new-page)
|
||||||
|
update-page-index
|
||||||
(fn [index]
|
(fn [index]
|
||||||
(let [old-bounds (:bounds index)
|
(let [old-bounds (:bounds index)
|
||||||
new-bounds (objects-bounds new-objects)]
|
new-bounds (objects-bounds new-objects)]
|
||||||
|
|
|
@ -6,179 +6,32 @@
|
||||||
|
|
||||||
(ns app.worker.snaps
|
(ns app.worker.snaps
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.util.snap-data :as sd]
|
||||||
[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.worker.impl :as impl]
|
[app.worker.impl :as impl]
|
||||||
[clojure.set :as set]
|
|
||||||
[okulary.core :as l]))
|
[okulary.core :as l]))
|
||||||
|
|
||||||
(defonce state (l/atom {}))
|
(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
|
;; Public API
|
||||||
(defmethod impl/handler :snaps/initialize-index
|
(defmethod impl/handler :snaps/initialize-index
|
||||||
[{:keys [data] :as message}]
|
[{:keys [data] :as message}]
|
||||||
;; Create the index
|
|
||||||
(letfn [(process-page [state page]
|
(let [pages (vals (:pages-index data))]
|
||||||
(let [id (:id page)
|
(reset! state (reduce sd/add-page (sd/make-snap-data) pages)))
|
||||||
objects (:objects page)]
|
|
||||||
(index-page state id objects)))]
|
nil)
|
||||||
(swap! state #(reduce process-page % (vals (:pages-index data))))
|
|
||||||
;; (log-state)
|
|
||||||
;; Return nil so the worker will not answer anything back
|
|
||||||
nil))
|
|
||||||
|
|
||||||
(defmethod impl/handler :snaps/update-index
|
(defmethod impl/handler :snaps/update-index
|
||||||
[{:keys [page-id old-objects new-objects] :as message}]
|
[{:keys [old-page new-page] :as message}]
|
||||||
(swap! state update-page page-id old-objects new-objects)
|
(swap! state sd/update-page old-page new-page)
|
||||||
|
|
||||||
;; Uncomment this to regenerate the index everytime
|
|
||||||
#_(swap! state index-page page-id new-objects)
|
|
||||||
;; (log-state)
|
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defmethod impl/handler :snaps/range-query
|
(defmethod impl/handler :snaps/range-query
|
||||||
[{:keys [page-id frame-id coord ranges] :as message}]
|
[{:keys [page-id frame-id axis ranges] :as message}]
|
||||||
(letfn [(calculate-range [[from to]]
|
|
||||||
(-> @state
|
(into []
|
||||||
(get-in [page-id frame-id coord])
|
(comp (mapcat #(sd/query @state page-id frame-id axis %))
|
||||||
(rt/range-query from to)))]
|
(distinct))
|
||||||
(->> ranges
|
ranges))
|
||||||
(mapcat calculate-range)
|
|
||||||
set ;; unique
|
|
||||||
(into []))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
431
frontend/test/app/util/snap_data_test.cljs
Normal file
431
frontend/test/app/util/snap_data_test.cljs
Normal file
|
@ -0,0 +1,431 @@
|
||||||
|
;; 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)
|
||||||
|
|
||||||
|
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 (fb/delete-object file 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"
|
||||||
|
(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 (fb/create-rect file {:x 25 :y 25 :width 50 :height 50})
|
||||||
|
shape-id (:last-id file)
|
||||||
|
|
||||||
|
file (fb/close-artboard file)
|
||||||
|
|
||||||
|
page (fb/get-current-page file)
|
||||||
|
data (-> (sd/make-snap-data)
|
||||||
|
(sd/add-page page))
|
||||||
|
|
||||||
|
file (fb/delete-object file shape-id)
|
||||||
|
new-page (fb/get-current-page file)
|
||||||
|
|
||||||
|
data (sd/update-page data page new-page)
|
||||||
|
|
||||||
|
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||||
|
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
|
||||||
|
|
||||||
|
(t/is (some? data))
|
||||||
|
(t/is (= (count result-zero-x) 3))
|
||||||
|
(t/is (= (count result-frame-x) 3))))
|
||||||
|
|
||||||
|
(t/testing "Create global guide then remove it"
|
||||||
|
(let [file (-> (fb/create-file "Test")
|
||||||
|
(fb/add-page {:name "Page-1"})
|
||||||
|
(fb/add-guide {:position 50 :axis :x}))
|
||||||
|
|
||||||
|
guide-id (:last-id file)
|
||||||
|
|
||||||
|
file (-> (fb/add-artboard file {:x 200 :y 200 :width 100 :height 100})
|
||||||
|
(fb/close-artboard))
|
||||||
|
|
||||||
|
frame-id (:last-id file)
|
||||||
|
page (fb/get-current-page file)
|
||||||
|
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||||
|
|
||||||
|
new-page (-> (fb/delete-guide file guide-id)
|
||||||
|
(fb/get-current-page))
|
||||||
|
|
||||||
|
data (sd/update-page data page new-page)
|
||||||
|
|
||||||
|
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||||
|
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
|
||||||
|
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
|
||||||
|
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
|
||||||
|
|
||||||
|
(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) 0))
|
||||||
|
(t/is (= (count result-frame-y) 0))))
|
||||||
|
|
||||||
|
(t/testing "Create frame guide then remove it"
|
||||||
|
(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 (fb/add-guide file {:position 50 :axis :x :frame-id frame-id})
|
||||||
|
guide-id (:last-id file)
|
||||||
|
|
||||||
|
page (fb/get-current-page file)
|
||||||
|
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||||
|
|
||||||
|
new-page (-> (fb/delete-guide file guide-id)
|
||||||
|
(fb/get-current-page))
|
||||||
|
|
||||||
|
data (sd/update-page data page new-page)
|
||||||
|
|
||||||
|
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
|
||||||
|
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
|
||||||
|
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
|
||||||
|
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
|
||||||
|
(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) 0))
|
||||||
|
(t/is (= (count result-frame-y) 0))))
|
||||||
|
|
||||||
|
(t/testing "Update frame coordinates"
|
||||||
|
(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)
|
||||||
|
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||||
|
|
||||||
|
frame (fb/lookup-shape file frame-id)
|
||||||
|
new-frame (-> frame
|
||||||
|
(assoc :x 200 :y 200))
|
||||||
|
|
||||||
|
file (fb/update-object file frame new-frame)
|
||||||
|
new-page (fb/get-current-page file)
|
||||||
|
|
||||||
|
data (sd/update-page data page new-page)
|
||||||
|
|
||||||
|
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
|
||||||
|
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100])
|
||||||
|
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])
|
||||||
|
result-frame-x-2 (sd/query data (:id page) frame-id :x [200 300])]
|
||||||
|
|
||||||
|
(t/is (some? data))
|
||||||
|
(t/is (= (count result-zero-x-1) 0))
|
||||||
|
(t/is (= (count result-frame-x-1) 0))
|
||||||
|
(t/is (= (count result-zero-x-2) 3))
|
||||||
|
(t/is (= (count result-frame-x-2) 3))))
|
||||||
|
|
||||||
|
(t/testing "Update shape coordinates"
|
||||||
|
(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)
|
||||||
|
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||||
|
|
||||||
|
shape (fb/lookup-shape file shape-id)
|
||||||
|
new-shape (-> shape
|
||||||
|
(assoc :x 200 :y 200))
|
||||||
|
|
||||||
|
file (fb/update-object file shape new-shape)
|
||||||
|
new-page (fb/get-current-page file)
|
||||||
|
|
||||||
|
data (sd/update-page data page new-page)
|
||||||
|
|
||||||
|
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
|
||||||
|
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])]
|
||||||
|
|
||||||
|
(t/is (some? data))
|
||||||
|
(t/is (= (count result-zero-x-1) 0))
|
||||||
|
(t/is (= (count result-zero-x-2) 3))))
|
||||||
|
|
||||||
|
(t/testing "Update global guide"
|
||||||
|
(let [guide {:position 50 :axis :x}
|
||||||
|
file (-> (fb/create-file "Test")
|
||||||
|
(fb/add-page {:name "Page-1"})
|
||||||
|
(fb/add-guide guide))
|
||||||
|
|
||||||
|
guide-id (:last-id file)
|
||||||
|
guide (assoc guide :id guide-id)
|
||||||
|
|
||||||
|
file (-> (fb/add-artboard file {:x 500 :y 500 :width 100 :height 100})
|
||||||
|
(fb/close-artboard))
|
||||||
|
|
||||||
|
frame-id (:last-id file)
|
||||||
|
page (fb/get-current-page file)
|
||||||
|
data (-> (sd/make-snap-data) (sd/add-page page))
|
||||||
|
|
||||||
|
new-page (-> (fb/update-guide file (assoc guide :position 150))
|
||||||
|
(fb/get-current-page))
|
||||||
|
|
||||||
|
data (sd/update-page data page new-page)
|
||||||
|
|
||||||
|
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
|
||||||
|
result-zero-y-1 (sd/query data (:id page) uuid/zero :y [0 100])
|
||||||
|
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100])
|
||||||
|
result-frame-y-1 (sd/query data (:id page) frame-id :y [0 100])
|
||||||
|
|
||||||
|
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [0 200])
|
||||||
|
result-zero-y-2 (sd/query data (:id page) uuid/zero :y [0 200])
|
||||||
|
result-frame-x-2 (sd/query data (:id page) frame-id :x [0 200])
|
||||||
|
result-frame-y-2 (sd/query data (:id page) frame-id :y [0 200])
|
||||||
|
]
|
||||||
|
|
||||||
|
(t/is (some? data))
|
||||||
|
|
||||||
|
(t/is (= (count result-zero-x-1) 0))
|
||||||
|
(t/is (= (count result-zero-y-1) 0))
|
||||||
|
(t/is (= (count result-frame-x-1) 0))
|
||||||
|
(t/is (= (count result-frame-y-1) 0))
|
||||||
|
|
||||||
|
(t/is (= (count result-zero-x-2) 1))
|
||||||
|
(t/is (= (count result-zero-y-2) 0))
|
||||||
|
(t/is (= (count result-frame-x-2) 1))
|
||||||
|
(t/is (= (count result-frame-y-2) 0)))))
|
|
@ -1 +1 @@
|
||||||
1.11.0-beta
|
1.12.0-beta
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue