0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-06 14:50:20 -05:00

♻️ Initial refactor of page data structure (wip).

Still work in progress but is a necessary step for a future
(re)introduction of groups.
This commit is contained in:
Andrey Antukh 2020-03-08 12:46:09 +01:00
parent cbad98b783
commit ba373573e0
29 changed files with 1116 additions and 787 deletions

View file

@ -12,6 +12,7 @@
[mount.core :as mount]
[promesa.core :as p]
[uxbox.config :as cfg]
[uxbox.common.pages :as cp]
[uxbox.common.data :as d]
[uxbox.core]
[uxbox.db :as db]
@ -149,12 +150,7 @@
create-page
(fn [conn owner-id project-id file-id index]
(p/let [id (mk-uuid "page" project-id file-id index)
data {:version 1
:shapes []
:canvas []
:options {}
:shapes-by-id {}}
data cp/default-page-data
name (str "page " index)
version 0
ordering index

View file

@ -86,3 +86,80 @@
'(promesa.core/let)]}}}})
(kondo/print!))))
(comment
{:version 1
:options {}
:shapes [:id1 :id2]
:canvas [:id3]
:shapes-by-id {:id1 {:canvas :id3} :id2 {} :id3 {}}})
(comment
{:version 2
:options {}
:objects
{:root
{:type :frame
:shapes [:sid0 :frame-0]}
:frame0
{:type :frame
:parent :root
:shapes [:sid1 :sid2]}
:sid0
{:type :rect
:parent :root}
:sid1
{:type :rect
:parent :frame0}
:sid2
{:type :group
:shapes [:sid3 :sid4]
:parent :frame0}
:sid3
{:type :elipse
:parent :sid2}
:sid4
{:type :elipse
:parent :sid2}}})
(comment
{:version 3
:options {}
:rmap
{:id1 :root-frame
:id2 :root-frame
:id3 :frame-id-1
:id4 :frame-id-2
:id5 :frame-id-2
:id6 :frame-id-2}
:frames
{:root-frame
{:type :frame
:shapes [:id1 :id2]
:objects
{:id1 {:type :rect}
:id2 {:type :elipse}}}
:frame-id-1
{:type :frame
:shapes [:id3]
:objects
{:id3 {:type :path}}}
:frame-id-2
{:type :frame
:shapes [:id4]
:objects
{:id4 {:type :group
:shapes [:id5 :id6]}
:id5 {:type :path :parent :id4}
:id6 {:type :elipse :parent :id4}}}}})

View file

@ -57,27 +57,6 @@
not-found))
not-found coll)))
(defn diff-maps
[ma mb]
(let [ma-keys (set (keys ma))
mb-keys (set (keys mb))
added (set/difference mb-keys ma-keys)
removed (set/difference ma-keys mb-keys)
both (set/intersection ma-keys mb-keys)]
(concat
(mapv #(vector :set % (get mb %)) added)
(mapv #(vector :set % nil) removed)
(loop [k (first both)
r (rest both)
rs []]
(if k
(let [vma (get ma k)
vmb (get mb k)]
(if (= vma vmb)
(recur (first r) (rest r) rs)
(recur (first r) (rest r) (conj rs [:set k vmb]))))
rs)))))
(defn index-by
"Return a indexed map of the collection keyed by the result of
executing the getter over each element of the collection."

View file

@ -1,9 +1,18 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2019-2020 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.common.pages
"A common (clj/cljs) functions and specs for pages."
(:require
[uxbox.common.spec :as us]
[clojure.spec.alpha :as s]
[uxbox.common.data :as d]))
[uxbox.common.data :as d]
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]))
;; --- Specs
@ -46,7 +55,7 @@
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
(s/def ::stroke-width number?)
(s/def ::text-align #{"left" "right" "center" "justify"})
(s/def ::type #{:rect :path :circle :image :text :canvas :curve :icon})
(s/def ::type #{:rect :path :circle :image :text :canvas :curve :icon :frame})
(s/def ::x number?)
(s/def ::y number?)
(s/def ::cx number?)
@ -92,162 +101,243 @@
(s/def ::shapes (s/coll-of uuid? :kind vector?))
(s/def ::canvas (s/coll-of uuid? :kind vector?))
(s/def ::shapes-by-id
(s/def ::objects
(s/map-of uuid? ::shape))
(s/def ::data
(s/keys :req-un [::shapes
::canvas
::options
::shapes-by-id]))
(s/keys :req-un [::options
::version
::objects]))
;; Changes related
(s/def ::operation (s/tuple #{:set} keyword? any?))
(s/def ::attr keyword?)
(s/def ::val any?)
(s/def ::parent-id uuid?)
(s/def ::frame-id uuid?)
(s/def ::operations
(s/coll-of ::operation :kind vector?))
(defmulti operation-spec-impl :type)
(defmethod operation-spec-impl :set [_]
(s/keys :req-un [::attr ::val]))
(defmethod operation-spec-impl :mov [_]
(s/keys :req-un [::id ::index]))
(s/def ::operation (s/multi-spec operation-spec-impl :type))
(s/def ::operations (s/coll-of ::operation))
(defmulti change-spec-impl :type)
(defmethod change-spec-impl :add-shape [_]
(s/keys :req-un [::shape ::id ::session-id]
:opt-un [::index]))
(defmethod change-spec-impl :add-obj [_]
(s/keys :req-un [::id ::frame-id ::obj]
:opt-un [::session-id]))
(defmethod change-spec-impl :add-canvas [_]
(s/keys :req-un [::shape ::id ::session-id]
:opt-un [::index]))
(defmethod change-spec-impl :mod-obj [_]
(s/keys :req-un [::id ::operations]
:opt-un [::session-id]))
(defmethod change-spec-impl :mod-shape [_]
(s/keys :req-un [::id ::operations ::session-id]))
(defmethod change-spec-impl :del-obj [_]
(s/keys :req-un [::id]
:opt-un [::session-id]))
(defmethod change-spec-impl :mov-shape [_]
(s/keys :req-un [::id ::index ::session-id]))
(defmethod change-spec-impl :mov-obj [_]
(s/keys :req-un [::id ::frame-id]
:opt-un [::session-id]))
(defmethod change-spec-impl :mod-opts [_]
(s/keys :req-un [::operations ::session-id]))
;; (defmethod change-spec-impl :mod-shape [_]
;; (s/keys :req-un [::id ::operations ::session-id]))
(defmethod change-spec-impl :del-shape [_]
(s/keys :req-un [::id ::session-id]))
;; (defmethod change-spec-impl :mov-shape [_]
;; (s/keys :req-un [::id ::index ::session-id]))
(defmethod change-spec-impl :del-canvas [_]
(s/keys :req-un [::id ::session-id]))
;; (defmethod change-spec-impl :mod-opts [_]
;; (s/keys :req-un [::operations ::session-id]))
;; (defmethod change-spec-impl :del-shape [_]
;; (s/keys :req-un [::id ::session-id]))
;; (defmethod change-spec-impl :del-canvas [_]
;; (s/keys :req-un [::id ::session-id]))
(s/def ::change (s/multi-spec change-spec-impl :type))
(s/def ::changes (s/coll-of ::change))
(def root #uuid "00000000-0000-0000-0000-000000000000")
(def default-page-data
"A reference value of the empty page data."
{:version 1
:shapes []
:canvas []
{:version 3
:options {}
:shapes-by-id {}})
:objects
{root
{:id root
:type :frame
:name "root"
:shapes []}}})
;; --- Changes Processing Impl
(declare process-change)
(declare process-mod-shape)
(declare process-mod-opts)
(declare process-mov-shape)
(declare process-add-shape)
(declare process-add-canvas)
(declare process-del-shape)
(declare process-del-canvas)
(defmulti process-change
(fn [data change] (:type change)))
(defn process-changes
[data items]
(->> (us/verify ::changes items)
(reduce process-change data)))
(reduce #(or (process-change %1 %2) %1) data)))
(defn- process-change
[data {:keys [type] :as change}]
(case type
:add-shape (process-add-shape data change)
:add-canvas (process-add-canvas data change)
:mod-shape (process-mod-shape data change)
:mov-shape (process-mov-shape data change)
:del-shape (process-del-shape data change)
:del-canvas (process-del-canvas data change)
:mod-opts (process-mod-opts data change)))
(defmethod process-change :add-obj
[data {:keys [id obj frame-id index] :as change}]
(us/assert! (contains? (:objects data) frame-id) "process-change/add-obj")
(let [obj (assoc obj
:frame-id frame-id
:id id)]
(-> data
(update :objects assoc id obj)
(update-in [:objects frame-id :shapes]
(fn [shapes]
(cond
(some #{id} shapes)
shapes
(defn- process-add-shape
[data {:keys [id index shape] :as change}]
(-> data
(update :shapes (fn [shapes]
(cond
(some #{id} shapes)
shapes
(nil? index)
(conj shapes id)
(nil? index)
(conj shapes id)
:else
(let [[before after] (split-at index shapes)]
(d/concat [] before [id] after))))))))
:else
(let [[before after] (split-at index shapes)]
(d/concat [] before [id] after)))))
(update :shapes-by-id assoc id shape)))
(defn- process-obj-operation
[shape op]
(case (:type op)
:set
(let [attr (:attr op)
val (:val op)]
(if (nil? val)
(dissoc shape attr)
(assoc shape attr val)))
(defn- process-add-canvas
[data {:keys [id shape index] :as change}]
(-> data
(update :canvas (fn [shapes]
(cond
(some #{id} shapes)
shapes
(ex/raise :type :not-implemented
:hint "TODO")))
(nil? index)
(conj shapes id)
:else
(let [[before after] (split-at index shapes)]
(d/concat [] before [id] after)))))
(update :shapes-by-id assoc id shape)))
(defn- process-mod-shape
(defmethod process-change :mod-obj
[data {:keys [id operations] :as change}]
(if (get-in data [:shapes-by-id id])
(update-in data [:shapes-by-id id]
#(reduce (fn [shape [_ att val]]
(if (nil? val)
(dissoc shape att)
(assoc shape att val)))
% operations))
data))
(us/assert! (contains? (:objects data) id) "process-change/mod-obj")
(update-in data [:objects id]
#(reduce process-obj-operation % operations)))
(defn- process-mod-opts
[data {:keys [operations]}]
(update data :options
#(reduce (fn [options [_ att val]]
(if (nil? val)
(dissoc options att)
(assoc options att val)))
% operations)))
(defmethod process-change :mov-obj
[data {:keys [id frame-id] :as change}]
(us/assert! (contains? (:objects data) frame-id))
(let [frame-id' (get-in data [:objects id :frame-id])]
(when (not= frame-id frame-id')
(-> data
(update-in [:objects frame-id' :shapes] (fn [s] (filterv #(not= % id) s)))
(update-in [:objects id] assoc :frame-id frame-id)
(update-in [:objects frame-id :shapes] conj id)))))
(defn- process-mov-shape
[data {:keys [id index]}]
(let [shapes (:shapes data)
current-index (d/index-of shapes id)
shapes' (into [] (remove #(= % id) shapes))]
(cond
(= index current-index)
data
(nil? current-index)
(assoc data :shapes (d/concat [id] shapes'))
:else
(let [[before after] (split-at index shapes')]
(assoc data :shapes (d/concat [] before [id] after))))))
(defn- process-del-shape
(defmethod process-change :del-obj
[data {:keys [id] :as change}]
(-> data
(update :shapes (fn [s] (filterv #(not= % id) s)))
(update :shapes-by-id dissoc id)))
(when-let [{:keys [frame-id] :as obj} (get-in data [:objects id])]
(-> data
(update :objects dissoc id)
(update-in [:objects frame-id :shapes]
(fn [s] (filterv #(not= % id) s))))))
(defn- process-del-canvas
[data {:keys [id] :as change}]
(-> data
(update :canvas (fn [s] (filterv #(not= % id) s)))
(update :shapes-by-id dissoc id)))
;; (defn- process-change
;; [data {:keys [type] :as change}]
;; (case type
;; :add-obj (process-add-obj data change)
;; :mod-obj (process-mod-obj data change)
;; ;; :add-shape (process-add-shape data change)
;; ;; :add-canvas (process-add-canvas data change)
;; ;; :mod-shape (process-mod-shape data change)
;; ;; :mov-shape (process-mov-shape data change)
;; ;; :del-shape (process-del-shape data change)
;; ;; :del-canvas (process-del-canvas data change)
;; ;; :mod-opts (process-mod-opts data change)
;; ))
;; (defn- process-add-obj
;; (defn- process-add-shape
;; [data {:keys [id index shape] :as change}]
;; (-> data
;; (update :shapes (fn [shapes]
;; (cond
;; (some #{id} shapes)
;; shapes
;; (nil? index)
;; (conj shapes id)
;; :else
;; (let [[before after] (split-at index shapes)]
;; (d/concat [] before [id] after)))))
;; (update :shapes-by-id assoc id shape)))
;; (defn- process-add-canvas
;; [data {:keys [id shape index] :as change}]
;; (-> data
;; (update :canvas (fn [shapes]
;; (cond
;; (some #{id} shapes)
;; shapes
;; (nil? index)
;; (conj shapes id)
;; :else
;; (let [[before after] (split-at index shapes)]
;; (d/concat [] before [id] after)))))
;; (update :shapes-by-id assoc id shape)))
;; (defn- process-mod-shape
;; [data {:keys [id operations] :as change}]
;; (if (get-in data [:shapes-by-id id])
;; (update-in data [:shapes-by-id id]
;; #(reduce (fn [shape [_ att val]]
;; (if (nil? val)
;; (dissoc shape att)
;; (assoc shape att val)))
;; % operations))
;; data))
;; (defn- process-mod-opts
;; [data {:keys [operations]}]
;; (update data :options
;; #(reduce (fn [options [_ att val]]
;; (if (nil? val)
;; (dissoc options att)
;; (assoc options att val)))
;; % operations)))
;; (defn- process-mov-shape
;; [data {:keys [id index]}]
;; (let [shapes (:shapes data)
;; current-index (d/index-of shapes id)
;; shapes' (into [] (remove #(= % id) shapes))]
;; (cond
;; (= index current-index)
;; data
;; (nil? current-index)
;; (assoc data :shapes (d/concat [id] shapes'))
;; :else
;; (let [[before after] (split-at index shapes')]
;; (assoc data :shapes (d/concat [] before [id] after))))))
;; (defn- process-del-shape
;; [data {:keys [id] :as change}]
;; (-> data
;; (update :shapes (fn [s] (filterv #(not= % id) s)))
;; (update :shapes-by-id dissoc id)))
;; (defn- process-del-canvas
;; [data {:keys [id] :as change}]
;; (-> data
;; (update :canvas (fn [s] (filterv #(not= % id) s)))
;; (update :shapes-by-id dissoc id)))

View file

@ -110,6 +110,21 @@
;; --- Macros
(defmacro assert!
"Evaluates expr and throws an exception if it does not evaluate to
logical true."
([x]
(when *assert*
`(when-not ~x
(throw (ex/error :type :assertion-error
:hint (str "Assert failed: " (pr-str '~x)))))))
([x message]
(when *assert*
`(when-not ~x
(throw (ex/error :type :assertion-error
:hint (str "Assert failed: " (pr-str '~x))
:message ~message))))))
(defn spec-assert
[spec x]
(s/assert* spec x))

View file

@ -9,35 +9,64 @@ and persistence protocol.
This is a page data structure:
```
{:version 1
{:version 2
:options {}
:shapes [<id>, ...]
:canvas [<id>, ...]
:shapes-by-id {<id> <object>, ...}}
:rmap
{:id1 :default
:id2 :default
:id3 :id1}
:objects
{:root
{:type :root
:shapes [:id1 :id2]}
:id1
{:type :canvas
:shapes [:id3]}
:id2 {:type :rect}
:id3 {:type :circle}}}
```
This is a potential list of persistent ops:
```
;; Generic (Shapes & Canvas)
[:mod-shape <id> [:set <attr> <val?>], ...]
{:type :mod-opts
:operations [<op>, ...]
;; Example:
;; [:mod-shape 1 [:set :x 2] [:set :y 3]]
{:type :add-obj
:id <uuid>
:parent <uuid>
:obj <shape-object>}
;; Specific
[:add-shape <id> <object>]
[:add-canvas <id> <object>]
{:type :mod-obj
:id <uuid>
:operations [<op>, ...]}
[:del-shape <id>]
[:del-canvas <id>]
{:type :mov-obj
:id <uuid>
:dest <uuid>}
[:mov-canvas <id> :after <id|null>] ;; null implies at first position
[:mov-shape <id> :after <id|null>]
[:mod-opts [:set <attr> <val>], [:del <attr> nil], ...]
{:type :del-obj
:id <uuid>}
```
This is a potential list of operations:
```
{:type :set
:attr <any>
:val <any>}
{:type :mov
:id <uuid>
:index <int>}
```
## Ephemeral communication (Websocket protocol)

View file

@ -3,9 +3,9 @@
org.clojure/clojure {:mvn/version "1.10.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.256"}
cljsjs/react {:mvn/version "16.12.0-1"}
cljsjs/react-dom {:mvn/version "16.12.0-1"}
cljsjs/react-dom-server {:mvn/version "16.12.0-1"}
cljsjs/react {:mvn/version "16.13.0-0"}
cljsjs/react-dom {:mvn/version "16.13.0-0"}
cljsjs/react-dom-server {:mvn/version "16.13.0-0"}
environ/environ {:mvn/version "1.1.0"}
metosin/reitit-core {:mvn/version "0.3.10"}

View file

@ -9,8 +9,8 @@
(def viewport-width 4000)
(def viewport-height 4000)
(def canvas-start-x 1200)
(def canvas-start-y 1200)
(def frame-start-x 1200)
(def frame-start-y 1200)
(def grid-x-axis 10)
(def grid-y-axis 10)

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
[rumext.alpha :as mf]
[uxbox.util.math :as mth]
[uxbox.main.geom :as geom]
[uxbox.main.ui.shapes.canvas :as canvas]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.shapes.image :as image]
@ -40,7 +40,7 @@
[{:keys [shape] :as props}]
(when (and shape (not (:hidden shape)))
(case (:type shape)
:canvas [:& rect/rect-shape {:shape shape}]
:frame [:& rect/rect-shape {:shape shape}]
:curve [:& path/path-shape {:shape shape}]
:text [:& text/text-shape {:shape shape}]
:icon [:& icon/icon-shape {:shape shape}]
@ -53,7 +53,7 @@
[{:keys [data] :as props}]
(let [shapes-by-id (:shapes-by-id data)
shapes (map #(get shapes-by-id %) (:shapes data []))
canvas (map #(get shapes-by-id %) (:canvas data []))
frame (map #(get shapes-by-id %) (:frame data []))
dim (calculate-dimensions data)]
[:svg {:view-box (str "0 0 " (:width dim 0) " " (:height dim 0))
:version "1.1"
@ -61,7 +61,7 @@
:xmlns "http://www.w3.org/2000/svg"}
(background)
[:*
(for [item canvas]
(for [item frame]
[:& shape-wrapper {:shape item :key (:id item)}])
(for [item shapes]
[:& shape-wrapper {:shape item :key (:id item)}])]]))

View file

@ -27,7 +27,7 @@
:icon (move-rect shape dpoint)
:image (move-rect shape dpoint)
:rect (move-rect shape dpoint)
:canvas (move-rect shape dpoint)
:frame (move-rect shape dpoint)
:text (move-rect shape dpoint)
:curve (move-path shape dpoint)
:path (move-path shape dpoint)
@ -69,7 +69,7 @@
[shape position]
(case (:type shape)
:icon (absolute-move-rect shape position)
:canvas (absolute-move-rect shape position)
:frame (absolute-move-rect shape position)
:image (absolute-move-rect shape position)
:rect (absolute-move-rect shape position)
:circle (absolute-move-circle shape position)))
@ -482,6 +482,25 @@
:width width
:height height)))
;; --- Resolve Shape
(declare resolve-rect-shape)
(declare translate-from-frame)
(declare translate-to-frame)
(defn resolve-shape
[objects shape]
(case (:type shape)
:rect (resolve-rect-shape objects shape)
:frame (resolve-rect-shape objects shape)))
(defn- resolve-rect-shape
[objects {:keys [parent] :as shape}]
(loop [pobj (get objects parent)]
(if (= :frame (:type pobj))
(translate-from-frame shape pobj)
(recur (get objects (:parent pobj))))))
;; --- Transform Shape
(declare transform-rect)
@ -492,7 +511,7 @@
"Apply the matrix transformation to shape."
[{:keys [type] :as shape} xfmt]
(case type
:canvas (transform-rect shape xfmt)
:frame (transform-rect shape xfmt)
:rect (transform-rect shape xfmt)
:icon (transform-rect shape xfmt)
:text (transform-rect shape xfmt)
@ -599,6 +618,14 @@
:height (- maxy miny)
:type :rect}))
(defn translate-to-frame
[shape {:keys [x y] :as frame}]
(move shape (gpt/point (- x) (- y))))
(defn translate-from-frame
[shape {:keys [x y] :as frame}]
(move shape (gpt/point (+ x) (+ y))))
;; --- Helpers
(defn contained-in?

View file

@ -59,8 +59,8 @@
(-> (l/lens #(contains? % id))
(l/derive selected-shapes)))
(def selected-canvas
(-> (l/key :selected-canvas)
(def selected-frame
(-> (l/key :selected-frame)
(l/derive workspace-local)))
(def toolboxes

View file

@ -13,7 +13,7 @@
[rumext.alpha :as mf]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.canvas :as canvas]))
[uxbox.main.ui.shapes.frame :as frame]))
(def shape-wrapper canvas/shape-wrapper)
(def canvas-wrapper canvas/canvas-wrapper)
(def shape-wrapper frame/shape-wrapper)
(def frame-wrapper frame/frame-wrapper)

View file

@ -5,7 +5,9 @@
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.shapes.attrs
(:require [cuerdas.core :as str]))
(:require
[cuerdas.core :as str]
[uxbox.util.interop :as interop]))
;; (defn camel-case
@ -58,7 +60,8 @@
(case style
:mixed "5,5,1,5"
:dotted "5,5"
:dashed "10,10"))
:dashed "10,10"
nil))
(defn- transform-stroke-attrs
[{:keys [stroke-style] :or {stroke-style :none} :as attrs}]
@ -75,3 +78,21 @@
(-> (select-keys shape shape-style-attrs)
(transform-stroke-attrs)
(process-attrs)))
;; TODO: migrate all the code to use this function and then, rename.
(defn extract-style-attrs2
[shape]
(let [stroke-style (:stroke-style shape :none)
attrs #js {:fill (:fill-color shape nil)
:opacity (:opacity shape nil)
:rx (:rx shape nil)
:ry (:ry shape nil)}]
(when (not= :none stroke-style)
(interop/obj-assign! attrs
#js {:stroke (:stroke-color shape nil)
:strokeWidth (:stroke-width shape nil)
:strokeOpacity (:stroke-opacity shape nil)
:strokeDasharray (stroke-type->dasharray stroke-style)}))
attrs))

View file

@ -43,21 +43,21 @@
(rx/of (dw/materialize-displacement-in-bulk selected)
::dw/page-data-update))))))
(def start-move-canvas
(def start-move-frame
(ptk/reify ::start-move-selected
ptk/WatchEvent
(watch [_ state stream]
(let [flags (get-in state [:workspace-local :flags])
selected (get-in state [:workspace-local :selected])
stoper (rx/filter uws/mouse-up? stream)
canvas-id (first selected)
frame-id (first selected)
position @uws/mouse-position]
(rx/concat
(->> (uws/mouse-position-deltas position)
(rx/map #(dw/apply-canvas-displacement canvas-id %))
(rx/map #(dw/apply-frame-displacement frame-id %))
(rx/take-until stoper))
(rx/of (dw/materialize-canvas-displacement canvas-id)))))))
(rx/of (dw/materialize-frame-displacement frame-id)))))))
(defn on-mouse-down
([event shape] (on-mouse-down event shape nil))
@ -70,10 +70,10 @@
drawing?
nil
(= type :canvas)
(= type :frame)
(when selected?
(dom/stop-propagation event)
(st/emit! start-move-canvas))
(st/emit! start-move-frame))
(and (not selected?) (empty? selected))
(do

View file

@ -8,7 +8,7 @@
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.shapes.canvas
(ns uxbox.main.ui.shapes.frame
(:require
[lentes.core :as l]
[rumext.alpha :as mf]
@ -30,14 +30,24 @@
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]))
(declare canvas-wrapper)
(declare frame-wrapper)
(defn wrap-memo-shape
([component]
(js/React.memo
component
(fn [np op]
(let [n-shape (aget np "shape")
o-shape (aget op "shape")]
(= n-shape o-shape))))))
(mf/defc shape-wrapper
{:wrap [#(mf/wrap-memo % =)]}
{:wrap [wrap-memo-shape]}
[{:keys [shape] :as props}]
(when (and shape (not (:hidden shape)))
(case (:type shape)
:canvas [:& canvas-wrapper {:shape shape :childs []}]
:frame [:& frame-wrapper {:shape shape :childs []}]
:curve [:& path/path-wrapper {:shape shape}]
:text [:& text/text-wrapper {:shape shape}]
:icon [:& icon/icon-wrapper {:shape shape}]
@ -46,22 +56,48 @@
:image [:& image/image-wrapper {:shape shape}]
:circle [:& circle/circle-wrapper {:shape shape}])))
(def canvas-default-props
(def frame-default-props
{:fill-color "#ffffff"})
(declare canvas-shape)
(declare translate-to-canvas)
(declare frame-shape)
(declare translate-to-frame)
(mf/defc canvas-wrapper
{:wrap [#(mf/wrap-memo % =)]}
[{:keys [shape childs] :as props}]
(def kaka [1 2 3])
(defn wrap-memo-frame
([component]
(js/React.memo
component
(fn [np op]
(let [n-shape (aget np "shape")
o-shape (aget op "shape")
n-objs (aget np "objects")
o-objs (aget op "objects")
ids (:shapes n-shape)]
(and (identical? n-shape o-shape)
(loop [id (first ids)
ids (rest ids)]
(if (nil? id)
true
(if (identical? (get n-objs id)
(get o-objs id))
(recur (first ids) (rest ids))
false)))))))))
(mf/defc frame-wrapper
{:wrap [wrap-memo-frame]}
[{:keys [shape objects] :as props}]
(when (and shape (not (:hidden shape)))
(let [selected-iref (mf/use-memo
{:fn #(refs/make-selected (:id shape))
:deps (mf/deps (:id shape))})
selected? (mf/deref selected-iref)
on-mouse-down #(common/on-mouse-down % shape)
shape (merge canvas-default-props shape)
shape (merge frame-default-props shape)
childs (mapv #(get objects %) (:shapes shape))
on-double-click
(fn [event]
@ -71,9 +107,9 @@
[:g {:class (when selected? "selected")
:on-double-click on-double-click
:on-mouse-down on-mouse-down}
[:& canvas-shape {:shape shape :childs childs}]])))
[:& frame-shape {:shape shape :childs childs}]])))
(mf/defc canvas-shape
(mf/defc frame-shape
[{:keys [shape childs] :as props}]
(let [rotation (:rotation shape)
ds-modifier (:displacement-modifier shape)
@ -93,7 +129,7 @@
:height height
))
translate #(translate-to-canvas % ds-modifier (gpt/point (- x) (- y)))
translate #(translate-to-frame % ds-modifier (gpt/point (- x) (- y)))
]
[:svg {:x x :y y :width width :height height}
@ -101,12 +137,12 @@
(for [item childs]
[:& shape-wrapper {:shape (translate item) :key (:id item)}])]))
(defn- translate-to-canvas
[shape canvas-ds-modifier pt]
(defn- translate-to-frame
[shape frame-ds-modifier pt]
(let [rz-modifier (:resize-modifier shape)
shape (cond-> shape
(gmt/matrix? canvas-ds-modifier)
(geom/transform canvas-ds-modifier)
(gmt/matrix? frame-ds-modifier)
(geom/transform frame-ds-modifier)
(gmt/matrix? rz-modifier)
(-> (geom/transform rz-modifier)

View file

@ -12,8 +12,7 @@
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.data :refer [classnames normalize-props]]
[uxbox.util.dom :as dom]
[uxbox.util.interop :as interop]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]))
@ -21,23 +20,19 @@
(declare rect-shape)
(mf/defc rect-wrapper
{:wrap [#(mf/wrap-memo % =)]}
[{:keys [shape] :as props}]
(let [selected-iref (mf/use-memo
{:fn #(refs/make-selected (:id shape))
:deps (mf/deps (:id shape))})
selected? (mf/deref selected-iref)
(mf/defrc rect-wrapper
[props]
(let [shape (unchecked-get props "shape")
on-mouse-down #(common/on-mouse-down % shape)]
[:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down}
[:g.shape {:on-mouse-down on-mouse-down}
[:& rect-shape {:shape shape}]]))
;; --- Rect Shape
(mf/defc rect-shape
[{:keys [shape] :as props}]
(let [ds-modifier (:displacement-modifier shape)
(mf/defrc rect-shape
[props]
(let [shape (unchecked-get props "shape")
ds-modifier (:displacement-modifier shape)
rz-modifier (:resize-modifier shape)
shape (cond-> shape
@ -52,12 +47,13 @@
(+ x (/ width 2))
(+ y (/ height 2))))
props (-> (attrs/extract-style-attrs shape)
(assoc :x x
:y y
:transform transform
:id (str "shape-" id)
:width width
:height height
))]
[:& "rect" props]))
props (-> (attrs/extract-style-attrs2 shape)
(interop/obj-assign!
#js {:x x
:y y
:transform transform
:id (str "shape-" id)
:width width
:height height}))]
[:> "rect" props]))

View file

@ -45,10 +45,10 @@
(st/emit! (ms/->ScrollEvent (gpt/point left top)))))
(defn- on-wheel
[event canvas]
[event frame]
(when (kbd/ctrl? event)
(let [prev-zoom @refs/selected-zoom
dom (mf/ref-node canvas)
dom (mf/ref-node frame)
scroll-position (scroll/get-current-position-absolute dom)
mouse-point @ms/mouse-position]
(dom/prevent-default event)
@ -60,7 +60,7 @@
(mf/defc workspace-content
[{:keys [page file flags] :as params}]
(let [canvas (mf/use-ref nil)
(let [frame (mf/use-ref nil)
layout (mf/deref refs/workspace-layout)
left-sidebar? (not (empty? (keep layout [:layers :sitemap
:document-history])))
@ -77,7 +77,7 @@
[:section.workspace-content
{:class classes
:on-scroll on-scroll
:on-wheel #(on-wheel % canvas)}
:on-wheel #(on-wheel % frame)}
[:& history-dialog]
@ -87,7 +87,7 @@
[:& horizontal-rule]
[:& vertical-rule]])
[:section.workspace-viewport {:id "workspace-viewport" :ref canvas}
[:section.workspace-viewport {:id "workspace-viewport" :ref frame}
[:& viewport {:page page}]]]
;; Aside

View file

@ -52,7 +52,7 @@
:fill-color "#000000"
:fill-opacity 0
:segments []}
{:type :canvas
{:type :frame
:name "Canvas"}
{:type :curve
:name "Path"
@ -281,8 +281,8 @@
shape (dissoc shape ::initialized? :resize-modifier)]
;; Add & select the created shape to the workspace
(rx/of dw/deselect-all
(if (= :canvas (:type shape))
(dw/add-canvas shape)
(if (= :frame (:type shape))
(dw/add-frame shape)
(dw/add-shape shape))))))))))
(def close-drawing-path

View file

@ -94,9 +94,9 @@
[:div.workspace-options
[:ul.options-btn
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.canvas")
:class (when (= selected-drawtool :canvas) "selected")
:on-click (partial select-drawtool :canvas)}
{:alt (tr "workspace.header.frame")
:class (when (= selected-drawtool :frame) "selected")
:on-click (partial select-drawtool :frame)}
i/artboard]
[:li.tooltip.tooltip-bottom
{:alt (tr "workspace.header.rect")

View file

@ -133,7 +133,7 @@
:stroke-opacity "1"}}]
(when (and (fn? on-rotate)
(not= :canvas (:type shape)))
(not= :frame (:type shape)))
[:*
[:path {:stroke "#31EFB8"
:stroke-opacity "1"
@ -255,7 +255,7 @@
:on-resize on-resize}]))
(mf/defc single-selection-handlers
[{:keys [shape zoom] :as props}]
[{:keys [shape zoom objects] :as props}]
(let [on-resize #(do (dom/stop-propagation %2)
(st/emit! (start-resize %1 #{(:id shape)} shape)))
on-rotate #(do (dom/stop-propagation %)
@ -263,6 +263,7 @@
ds-modifier (:displacement-modifier shape)
rz-modifier (:resize-modifier shape)
;; shape (geom/resolve-shape objects shape)
shape (cond-> (geom/shape->rect-shape shape)
(gmt/matrix? rz-modifier) (geom/transform rz-modifier)
(gmt/matrix? ds-modifier) (geom/transform ds-modifier))]
@ -274,11 +275,13 @@
(mf/defc selection-handlers
[{:keys [selected edition zoom] :as props}]
(let [data (mf/deref refs/workspace-data)
(let [data (mf/deref refs/workspace-data)
objects (:objects data)
;; We need remove posible nil values because on shape
;; deletion many shape will reamin selected and deleted
;; in the same time for small instant of time
shapes (->> (map #(get-in data [:shapes-by-id %]) selected)
shapes (->> (map #(get objects %) selected)
(remove nil?))
num (count shapes)
{:keys [id type] :as shape} (first shapes)]
@ -303,4 +306,5 @@
:else
[:& single-selection-handlers {:shape shape
:objects objects
:zoom zoom}])))

View file

@ -21,6 +21,7 @@
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
[uxbox.util.dom :as dom]
[uxbox.util.uuid :as uuid]
[uxbox.util.i18n :as i18n :refer [t]]))
(def ^:private shapes-iref
@ -76,34 +77,35 @@
{:on-double-click on-click}
(:name shape "")])))
;; --- Layer Item
(def strip-attrs
#(select-keys % [:id :frame :name :type :hidden :blocked]))
(mf/defc layer-item
{:wrap [#(mf/wrap-memo % =)]}
[{:keys [shape selected index] :as props}]
(let [selected? (contains? selected (:id shape))
{:wrap [mf/wrap-memo]}
[{:keys [index item selected] :as props}]
(let [selected? (contains? selected (:id item))
toggle-blocking
(fn [event]
(dom/stop-propagation event)
(if (:blocked shape)
(st/emit! (dw/unblock-shape (:id shape)))
(st/emit! (dw/block-shape (:id shape)))))
(if (:blocked item)
(st/emit! (dw/unblock-shape (:id item)))
(st/emit! (dw/block-shape (:id item)))))
toggle-visibility
(fn [event]
(dom/stop-propagation event)
(if (:hidden shape)
(st/emit! (dw/show-shape (:id shape)))
(st/emit! (dw/hide-shape (:id shape)))))
(if (:hidden item)
(st/emit! (dw/show-shape (:id item)))
(st/emit! (dw/hide-shape (:id item)))))
select-shape
(fn [event]
(dom/prevent-default event)
(let [id (:id shape)]
(let [id (:id item)]
(cond
(or (:blocked shape)
(:hidden shape))
(or (:blocked item)
(:hidden item))
nil
(.-ctrlKey event)
@ -118,7 +120,7 @@
on-drop
(fn [item monitor]
(st/emit! (dw/commit-shape-order-change (:shape-id item))))
#_(st/emit! (dw/commit-shape-order-change (:shape-id item))))
on-hover
(fn [item monitor]
@ -126,8 +128,8 @@
[dprops dnd-ref] (use-sortable
{:type "layer-item"
:data {:shape-id (:id shape)
:page-id (:page shape)
:data {:shape-id (:id item)
:page-id (:page item)
:index index}
:on-hover on-hover
:on-drop on-drop})]
@ -139,24 +141,22 @@
:on-click select-shape
:on-double-click #(dom/stop-propagation %)}
[:div.element-actions
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
[:div.toggle-element {:class (when-not (:hidden item) "selected")
:on-click toggle-visibility}
i/eye]
[:div.block-element {:class (when (:blocked shape) "selected")
[:div.block-element {:class (when (:blocked item) "selected")
:on-click toggle-blocking}
i/lock]]
[:& element-icon {:shape shape}]
[:& layer-name {:shape shape}]]]))
[:& element-icon {:shape item}]
[:& layer-name {:shape item}]]]))
(mf/defc canvas-item
(mf/defc layer-frame-item
{:wrap [#(mf/wrap-memo % =)]}
[{:keys [canvas shapes selected index] :as props}]
(let [selected? (contains? selected (:id canvas))
[{:keys [item selected index objects] :as props}]
(let [selected? (contains? selected (:id item))
local (mf/use-state {:collapsed false})
collapsed? (:collapsed @local)
shapes (filter #(= (:canvas (second %)) (:id canvas)) shapes)
toggle-collapse
(fn [event]
(dom/stop-propagation event)
@ -165,24 +165,24 @@
toggle-blocking
(fn [event]
(dom/stop-propagation event)
(if (:blocked canvas)
(st/emit! (dw/unblock-shape (:id canvas)))
(st/emit! (dw/block-shape (:id canvas)))))
(if (:blocked item)
(st/emit! (dw/unblock-shape (:id item)))
(st/emit! (dw/block-shape (:id item)))))
toggle-visibility
(fn [event]
(dom/stop-propagation event)
(if (:hidden canvas)
(st/emit! (dw/show-canvas (:id canvas)))
(st/emit! (dw/hide-canvas (:id canvas)))))
(if (:hidden item)
(st/emit! (dw/show-frame (:id item)))
(st/emit! (dw/hide-frame (:id item)))))
select-shape
(fn [event]
(dom/prevent-default event)
(let [id (:id canvas)]
(let [id (:id item)]
(cond
(or (:blocked canvas)
(:hidden canvas))
(or (:blocked item)
(:hidden item))
nil
(.-ctrlKey event)
@ -201,13 +201,13 @@
on-hover
(fn [item monitor]
(st/emit! (dw/change-canvas-order {:id (:canvas-id item)
(st/emit! (dw/change-frame-order {:id (:frame-id item)
:index index})))
[dprops dnd-ref] (use-sortable
{:type "canvas-item"
:data {:canvas-id (:id canvas)
:page-id (:page canvas)
{:type "frame-item"
:data {:frame-id (:id item)
:page-id (:page item)
:index index}
:on-hover on-hover
:on-drop on-drop})]
@ -219,84 +219,68 @@
:on-click select-shape
:on-double-click #(dom/stop-propagation %)}
[:div.element-actions
[:div.toggle-element {:class (when-not (:hidden canvas) "selected")
[:div.toggle-element {:class (when-not (:hidden item) "selected")
:on-click toggle-visibility}
i/eye]
#_[:div.block-element {:class (when (:blocked canvas) "selected")
#_[:div.block-element {:class (when (:blocked item) "selected")
:on-click toggle-blocking}
i/lock]]
[:div.element-icon i/folder]
[:& layer-name {:shape canvas}]
[:& layer-name {:shape item}]
[:span.toggle-content
{:on-click toggle-collapse
:class (when-not collapsed? "inverse")}
i/arrow-slide]]
(when-not collapsed?
[:ul
(for [[index shape] shapes]
[:& layer-item {:shape shape
:selected selected
:index index
:key (:id shape)}])])]))
(for [[index id] (d/enumerate (:shapes item))]
(let [item (get objects id)]
(if (= (:type item) :frame)
[:& layer-frame-item
{:item item
:key (:id item)
:objects objects
:index index}]
[:& layer-item
{:item item
:index index
:key (:id item)}])))])]))
;; --- Layers List
(mf/defc layers-list
{:wrap [#(mf/wrap-memo % =)]}
[{:keys [shapes selected] :as props}]
[:ul.element-list
(for [[index shape] shapes]
[:& layer-item {:shape shape
:selected selected
:index index
:key (:id shape)}])])
(mf/defc canvas-list
{:wrap [#(mf/wrap-memo % =)]}
[{:keys [shapes canvas selected] :as props}]
[:ul.element-list
(for [[index item] canvas]
[:& canvas-item {:canvas item
:shapes shapes
:selected selected
:index index
:key (:id item)}])])
(mf/defc layers-tree
{:wrap [mf/wrap-memo]}
[props]
(let [selected (mf/deref refs/selected-shapes)
data (mf/deref refs/workspace-data)
objects (:objects data)
root (get objects uuid/zero)]
[:ul.element-list
(for [[index id] (d/enumerate (:shapes root))]
(let [item (get objects id)]
(if (= (:type item) :frame)
[:& layer-frame-item
{:item item
:key (:id item)
:objects objects
:index index}]
[:& layer-item
{:item item
:index index
:key (:id item)}])))]))
;; --- Layers Toolbox
;; NOTE: we need to consider using something like react window for
;; only render visible items instead of all.
(mf/defc layers-toolbox
{:wrap [mf/wrap-memo]}
[{:keys [page] :as props}]
(let [locale (i18n/use-locale)
on-click #(st/emit! (dw/toggle-layout-flag :layers))
selected (mf/deref refs/selected-shapes)
data (mf/deref refs/workspace-data)
shapes-map (:shapes-by-id data)
strip #(select-keys % [:id :canvas :name :type :hidden :blocked])
canvas (->> (:canvas data)
(map #(get shapes-map %))
(map strip)
(d/enumerate))
shapes (->> (:shapes data)
(map #(get shapes-map %))
(map strip))
all-shapes (d/enumerate shapes)
unc-shapes (->> shapes
(filter #(nil? (:canvas %)))
(d/enumerate))]
on-click #(st/emit! (dw/toggle-layout-flag :layers))]
[:div#layers.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/layers]
[:span (t locale "workspace.sidebar.layers")]
#_[:div.tool-window-close {:on-click on-click} i/close]]
[:div.tool-window-content
[:& canvas-list {:canvas canvas
:shapes all-shapes
:selected selected}]
[:& layers-list {:shapes unc-shapes
:selected selected}]]]))
[:& layers-tree]]]))

View file

@ -13,7 +13,7 @@
[uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.ui.workspace.sidebar.options.canvas :as canvas]
[uxbox.main.ui.workspace.sidebar.options.frame :as frame]
[uxbox.main.ui.workspace.sidebar.options.rect :as rect]
[uxbox.main.ui.workspace.sidebar.options.icon :as icon]
[uxbox.main.ui.workspace.sidebar.options.circle :as circle]
@ -29,7 +29,7 @@
[{:keys [shape] :as props}]
[:div
(case (:type shape)
:canvas [:& canvas/options {:shape shape}]
:frame [:& frame/options {:shape shape}]
:text [:& text/options {:shape shape}]
:rect [:& rect/options {:shape shape}]
:icon [:& icon/options {:shape shape}]
@ -43,7 +43,7 @@
[{:keys [shape-id] :as props}]
(let [shape-iref (mf/use-memo
{:deps (mf/deps shape-id)
:fn #(-> (l/in [:workspace-data :shapes-by-id shape-id])
:fn #(-> (l/in [:workspace-data :objects shape-id])
(l/derive st/state))})
shape (mf/deref shape-iref)]
[:& shape-options {:shape shape}]))

View file

@ -8,7 +8,7 @@
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.options.canvas
(ns uxbox.main.ui.workspace.sidebar.options.frame
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
@ -43,8 +43,8 @@
delta (if (= attr :x)
(gpt/point (math/neg (- pval cval)) 0)
(gpt/point 0 (math/neg (- pval cval))))]
(st/emit! (udw/apply-canvas-displacement (:id shape) delta)
(udw/materialize-canvas-displacement (:id shape)))))
(st/emit! (udw/apply-frame-displacement (:id shape) delta)
(udw/materialize-frame-displacement (:id shape)))))
on-width-change #(on-size-change % :width)
on-height-change #(on-size-change % :height)

View file

@ -22,13 +22,13 @@
[uxbox.main.ui.workspace.grid :refer [grid]]
[uxbox.main.ui.workspace.ruler :refer [ruler]]
[uxbox.main.ui.workspace.drawarea :refer [start-drawing]]
[uxbox.main.ui.shapes :refer [shape-wrapper canvas-wrapper]]
[uxbox.main.ui.shapes :refer [shape-wrapper frame-wrapper]]
[uxbox.main.ui.workspace.drawarea :refer [draw-area]]
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
[uxbox.util.data :refer [parse-int]]
[uxbox.util.perf :as perf]
[uxbox.util.components :refer [use-rxsub]]
[uxbox.util.uuid :as uuid]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt])
(:import goog.events.EventType))
@ -142,22 +142,23 @@
(declare remote-user-cursors)
(mf/defc canvas-and-shapes
(mf/defc frame-and-shapes
{:wrap [mf/wrap-memo]}
[props]
(let [data (mf/deref refs/workspace-data)
shapes-map (:shapes-by-id data)
shapes (->> (map #(get shapes-map %) (:shapes data []))
(group-by :canvas))
canvas (map #(get shapes-map %) (:canvas data []))]
objects (:objects data)
root (get objects uuid/zero)
shapes (->> (:shapes root)
(map #(get objects %)))]
[:g.shapes
(for [item canvas]
[:& canvas-wrapper {:shape item
:key (:id item)
:childs (reverse (get shapes (:id item)))}])
(for [item (reverse (get shapes nil))]
[:& shape-wrapper {:shape item
:key (:id item)}])]))
(for [item shapes]
(if (= (:type item) :frame)
[:& frame-wrapper {:shape item
:key (:id item)
:objects objects}]
[:& shape-wrapper {:shape item
:key (:id item)}]))]))
(mf/defc viewport
[{:keys [page] :as props}]
@ -169,139 +170,157 @@
selected]
:as local} (mf/deref refs/workspace-local)
viewport-ref (mf/use-ref nil)
zoom (or zoom 1)]
(letfn [(on-mouse-down [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :down ctrl? shift?)))
(when (not edition)
(if drawing-tool
(st/emit! (start-drawing drawing-tool))
(st/emit! handle-selrect))))
zoom (or zoom 1)
(on-context-menu [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :context-menu ctrl? shift?))))
on-mouse-down
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :down ctrl? shift?)))
(when (not edition)
(if drawing-tool
(st/emit! (start-drawing drawing-tool))
(st/emit! handle-selrect))))
(on-mouse-up [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :up ctrl? shift?))))
on-context-menu
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :context-menu ctrl? shift?))))
(on-click [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :click ctrl? shift?))))
on-mouse-up
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :up ctrl? shift?))))
(on-double-click [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift?))))
on-click
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :click ctrl? shift?))))
(translate-point-to-viewport [pt]
(let [viewport (mf/ref-node viewport-ref)
brect (.getBoundingClientRect viewport)
brect (gpt/point (parse-int (.-left brect))
(parse-int (.-top brect)))]
(gpt/subtract pt brect)))
on-double-click
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift?))))
(on-key-down [event]
(let [bevent (.getBrowserEvent event)
key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(when-not (.-repeat bevent)
(st/emit! (ms/->KeyboardEvent :down key ctrl? shift?))
(when (kbd/space? event)
(st/emit! handle-viewport-positioning)
#_(st/emit! (dw/start-viewport-positioning))))))
on-key-down
(fn [event]
(let [bevent (.getBrowserEvent event)
key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(when-not (.-repeat bevent)
(st/emit! (ms/->KeyboardEvent :down key ctrl? shift?))
(when (kbd/space? event)
(st/emit! handle-viewport-positioning)
#_(st/emit! (dw/start-viewport-positioning))))))
(on-key-up [event]
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(when (kbd/space? event)
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift?))))
on-key-up
(fn [event]
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
(when (kbd/space? event)
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift?))))
(on-mouse-move [event]
(let [pt (gpt/point (.-clientX event)
(.-clientY event))
pt (translate-point-to-viewport pt)]
(st/emit! (ms/->PointerEvent :viewport pt
(kbd/ctrl? event)
(kbd/shift? event)))))
;; translate-point-to-viewport
;; (fn [pt]
;; (let [viewport (mf/ref-node viewport-ref)
;; brect (.getBoundingClientRect viewport)
;; brect (gpt/point (parse-int (.-left brect))
;; (parse-int (.-top brect)))]
;; (gpt/subtract pt brect)))
(on-mount []
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
key2 (events/listen js/document EventType.KEYUP on-key-up)]
(fn []
(events/unlistenByKey key1)
(events/unlistenByKey key2))))]
(mf/use-effect on-mount)
[:*
[:& coordinates {:zoom zoom}]
[:svg.viewport {:width (* c/viewport-width zoom)
:height (* c/viewport-height zoom)
:ref viewport-ref
:class (when drawing-tool "drawing")
:on-context-menu on-context-menu
:on-click on-click
:on-double-click on-double-click
:on-mouse-move on-mouse-move
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
[:*
[:& canvas-and-shapes]
on-mouse-move
(fn [event]
;; NOTE: offsetX and offsetY are marked as "experimental" on
;; MDN site but seems like they are supported on all
;; browsers so we can avoid translation opetation just using
;; this attributes.
(let [;; pt (gpt/point (.-clientX event)
;; (.-clientY event))
;; pt (translate-point-to-viewport pt)
pt (gpt/point (.-offsetX (.-nativeEvent event))
(.-offsetY (.-nativeEvent event)))]
(st/emit! (ms/->PointerEvent :viewport pt
(kbd/ctrl? event)
(kbd/shift? event)))))
(when (seq selected)
[:& selection-handlers {:selected selected
:zoom zoom
:edition edition}])
on-mount
(fn []
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
key2 (events/listen js/document EventType.KEYUP on-key-up)]
(fn []
(events/unlistenByKey key1)
(events/unlistenByKey key2))))]
(when-let [drawing-shape (:drawing local)]
[:& draw-area {:shape drawing-shape
:zoom zoom
:modifiers (:modifiers local)}])]
(mf/use-effect on-mount)
[:*
[:& coordinates {:zoom zoom}]
[:svg.viewport {:width (* c/viewport-width zoom)
:height (* c/viewport-height zoom)
:ref viewport-ref
:class (when drawing-tool "drawing")
:on-context-menu on-context-menu
:on-click on-click
:on-double-click on-double-click
:on-mouse-move on-mouse-move
:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
;; [:> js/React.Profiler
;; {:id "foobar"
;; :on-render (perf/react-on-profile)}
;; [:& frame-and-shapes]]
[:& frame-and-shapes]
(if (contains? flags :grid)
[:& grid])]
(when (seq selected)
[:& selection-handlers {:selected selected
:zoom zoom
:edition edition}])
(when tooltip
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])
(when-let [drawing-shape (:drawing local)]
[:& draw-area {:shape drawing-shape
:zoom zoom
:modifiers (:modifiers local)}])
(when (contains? flags :ruler)
[:& ruler {:zoom zoom :ruler (:ruler local)}])
(if (contains? flags :grid)
[:& grid])]
(when tooltip
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])
;; -- METER CURSOR MULTIUSUARIO
[:& remote-user-cursors {:page page}]
(when (contains? flags :ruler)
[:& ruler {:zoom zoom :ruler (:ruler local)}])
[:& selrect {:data (:selrect local)}]]])))
[:& remote-user-cursors {:page page}]
[:& selrect {:data (:selrect local)}]]]))
(mf/defc remote-user-cursor

View file

@ -11,3 +11,7 @@
"Convert an es6 iterable into cljs Seq."
[v]
(seq (js/Array.from v)))
(defn obj-assign!
[obj1 obj2]
(js/Object.assign obj1 obj2))

View file

@ -6,7 +6,8 @@
(ns uxbox.util.perf
"Performance and debugging tools."
#?(:cljs (:require-macros [uxbox.util.perf])))
#?(:cljs (:require-macros [uxbox.util.perf]))
#?(:cljs (:require [uxbox.util.math :as math])))
#?(:clj
(defmacro with-measure
@ -17,3 +18,25 @@
time# (.toFixed (- end# start#) 2)]
(println (str "[perf|" ~name "] => " time#))
res#)))
;; id, // the "id" prop of the Profiler tree that has just committed
;; phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
;; actualDuration, // time spent rendering the committed update
;; baseDuration, // estimated time to render the entire subtree without memoization
;; startTime, // when React began rendering this update
;; commitTime, // when React committed this update
;; interactions // the Set of interactions belonging to this update
#?(:cljs
(defn react-on-profile
[]
(let [sum (volatile! 0)
ctr (volatile! 0)]
(fn [id phase adur, bdur, st, ct, itx]
(vswap! sum (fn [prev] (+ prev adur)))
(vswap! ctr inc)
(js/console.log (str "[profile:" id ":" phase "]")
""
(str "time=" (math/precision adur 4))
(str "avg=" (math/precision (/ @sum @ctr) 4)))))))

View file

@ -13,7 +13,7 @@
[uxbox.util.data :refer [seek]]
[uxbox.view.data.viewer :as dv]
[uxbox.view.store :as st]
[uxbox.view.ui.viewer.canvas :refer [canvas]]
[uxbox.view.ui.viewer.frame :refer [frame]]
[uxbox.view.ui.viewer.nav :refer [nav]]
[uxbox.view.ui.viewer.sitemap :refer [sitemap]]
[lentes.core :as l]))
@ -45,4 +45,4 @@
:pages pages
:selected id}])
[:& nav {:flags flags}]
[:& canvas {:page (seek #(= id (:id %)) pages)}]])))
[:& frame {:page (seek #(= id (:id %)) pages)}]])))

View file

@ -5,7 +5,7 @@
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.view.ui.viewer.canvas
(ns uxbox.view.ui.viewer.frame
(:require
[rumext.alpha :as mf]
[uxbox.view.ui.viewer.shapes :as shapes]))
@ -25,12 +25,12 @@
(declare shape)
(mf/defc canvas
(mf/defc frame
{:wrap [mf/wrap-memo]}
[{:keys [page] :as props}]
#_(let [{:keys [metadata id]} page
{:keys [width height]} metadata]
[:div.view-canvas
[:div.view-frame
[:svg.page-layout {:width width
:height height}
[:& background metadata]