mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 21:26:37 +02:00
🚧 Initial work on multicanvas feature.
This commit is contained in:
parent
176ca590e1
commit
807555d478
32 changed files with 1408 additions and 1495 deletions
|
@ -12,6 +12,8 @@
|
|||
[uxbox.util.data :refer [replace-by-id
|
||||
index-by]]))
|
||||
|
||||
;; TODO: this need refactor (completely broken)
|
||||
|
||||
;; --- Initialize History State
|
||||
|
||||
(declare fetch-history)
|
||||
|
@ -52,7 +54,7 @@
|
|||
(deftype PinnedPageHistoryFetched [items]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [items-map (index-by items :version)
|
||||
(let [items-map (index-by :version items)
|
||||
items-set (into #{} items)]
|
||||
(update-in state [:workspace :history]
|
||||
(fn [history]
|
||||
|
@ -164,7 +166,7 @@
|
|||
(assoc :history true
|
||||
:data (:data item)))]
|
||||
(-> state
|
||||
(udp/assoc-page page)
|
||||
(udp/unpack-page page)
|
||||
(assoc-in [:workspace :history :selected] version)))))
|
||||
|
||||
(defn select-page-history
|
||||
|
@ -203,7 +205,7 @@
|
|||
(set! noop true)
|
||||
state)
|
||||
(let [packed (get-in state [:packed-pages page-id])]
|
||||
(-> (udp/assoc-page state packed)
|
||||
(-> (udp/unpack-page state packed)
|
||||
(assoc-in [:workspace :history :deselecting] true)
|
||||
(assoc-in [:workspace :history :selected] nil))))))
|
||||
|
||||
|
|
|
@ -5,18 +5,17 @@
|
|||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.data.pages
|
||||
(:require [cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.time :as dt]))
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.data :refer [index-by-id]]))
|
||||
|
||||
;; --- Specs
|
||||
|
||||
|
@ -77,50 +76,43 @@
|
|||
|
||||
;; --- Helpers
|
||||
|
||||
;; TODO: make sure remove all :tmp-* related attrs from shape
|
||||
|
||||
(defn pack-page-shapes
|
||||
"Create a hash-map of shapes indexed by their id that belongs
|
||||
to the provided page."
|
||||
[state page]
|
||||
(let [lookup-shape-xf (map #(get-in state [:shapes %]))]
|
||||
(reduce (fn reducer [acc {:keys [id type items] :as shape}]
|
||||
(let [shape (assoc shape :page (:id page))]
|
||||
(cond
|
||||
(= type :group)
|
||||
(reduce reducer
|
||||
(assoc acc id shape)
|
||||
(sequence lookup-shape-xf items))
|
||||
|
||||
(uuid? id)
|
||||
(assoc acc id shape)
|
||||
|
||||
:else acc)))
|
||||
{}
|
||||
(sequence lookup-shape-xf (:shapes page)))))
|
||||
|
||||
(defn pack-page
|
||||
"Return a packed version of page object ready
|
||||
for send to remore storage service."
|
||||
[state id]
|
||||
(let [page (get-in state [:pages id])
|
||||
shapes (pack-page-shapes state page)]
|
||||
(-> page
|
||||
(assoc-in [:data :shapes] (vec (:shapes page)))
|
||||
(assoc-in [:data :shapes-map] shapes)
|
||||
(dissoc :shapes))))
|
||||
(letfn [(get-shape [id]
|
||||
(get-in state [:shapes id]))
|
||||
(pack-shapes [ids]
|
||||
(reduce #(assoc %1 %2 (get-shape %2)) {} ids))
|
||||
(pack-canvas [ids]
|
||||
(mapv #(get-in state [:canvas %]) ids))]
|
||||
(let [page (get-in state [:pages id])
|
||||
data {:canvas (pack-canvas (:canvas page))
|
||||
:shapes (vec (:shapes page))
|
||||
:shapes-map (pack-shapes (:shapes page))}]
|
||||
(-> page
|
||||
(assoc :data data)
|
||||
(dissoc :shapes :canvas)))))
|
||||
|
||||
(defn assoc-page
|
||||
(defn unpack-page
|
||||
"Unpacks packed page object and assocs it to the
|
||||
provided state."
|
||||
[state {:keys [id data] :as page}]
|
||||
(let [shapes (:shapes data)
|
||||
shapes-map (:shapes-map data)
|
||||
|
||||
canvas-data (:canvas data [])
|
||||
|
||||
canvas (mapv :id canvas-data)
|
||||
canvas-map (index-by-id canvas-data)
|
||||
|
||||
page (-> page
|
||||
(dissoc :data)
|
||||
(assoc :canvas canvas)
|
||||
(assoc :shapes shapes))]
|
||||
(-> state
|
||||
(update :shapes merge shapes-map)
|
||||
(update :canvas merge canvas-map)
|
||||
(update :pages assoc id page))))
|
||||
|
||||
(defn purge-page
|
||||
|
@ -160,7 +152,7 @@
|
|||
(assoc-in $ [:projects id :pages] page-ids)
|
||||
;; TODO: this is a workaround
|
||||
(assoc-in $ [:projects id :page-id] (first page-ids))
|
||||
(reduce assoc-page $ pages)
|
||||
(reduce unpack-page $ pages)
|
||||
(reduce assoc-packed-page $ pages)))))
|
||||
|
||||
(defn pages-fetched
|
||||
|
@ -194,7 +186,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-page data)
|
||||
(unpack-page data)
|
||||
(assoc-packed-page data)))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
|
|
@ -2,527 +2,537 @@
|
|||
;; 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) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.data.shapes
|
||||
(:require [cljs.spec.alpha :as s]
|
||||
[lentes.core :as l]
|
||||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.shapes-impl :as impl]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.util.data :refer [dissoc-in]]
|
||||
[uxbox.util.forms :as sc]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.data :refer [index-of]]))
|
||||
|
||||
;; --- Specs
|
||||
|
||||
(s/def ::blocked boolean?)
|
||||
(s/def ::collapsed boolean?)
|
||||
(s/def ::content string?)
|
||||
(s/def ::fill-color string?)
|
||||
(s/def ::fill-opacity number?)
|
||||
(s/def ::line-height number?)
|
||||
(s/def ::letter-spacing number?)
|
||||
(s/def ::text-align #{"left" "right" "center" "justify"})
|
||||
(s/def ::font-family string?)
|
||||
(s/def ::font-size number?)
|
||||
(s/def ::font-style string?)
|
||||
(s/def ::font-weight string?)
|
||||
(s/def ::font-size number?)
|
||||
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
|
||||
(s/def ::stroke-width number?)
|
||||
(s/def ::stroke-color string?)
|
||||
(s/def ::stroke-opacity number?)
|
||||
(s/def ::rx number?)
|
||||
(s/def ::ry number?)
|
||||
(s/def ::height number?)
|
||||
(s/def ::hidden boolean?)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::letter-spacing number?)
|
||||
(s/def ::line-height number?)
|
||||
(s/def ::locked boolean?)
|
||||
(s/def ::name string?)
|
||||
(s/def ::page uuid?)
|
||||
(s/def ::proportion number?)
|
||||
(s/def ::proportion-lock boolean?)
|
||||
(s/def ::collapsed boolean?)
|
||||
(s/def ::hidden boolean?)
|
||||
(s/def ::blocked boolean?)
|
||||
(s/def ::locked boolean?)
|
||||
(s/def ::rx number?)
|
||||
(s/def ::ry number?)
|
||||
(s/def ::stroke-color string?)
|
||||
(s/def ::stroke-opacity number?)
|
||||
(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})
|
||||
(s/def ::width number?)
|
||||
(s/def ::height number?)
|
||||
(s/def ::x1 number?)
|
||||
(s/def ::y1 number?)
|
||||
(s/def ::x2 number?)
|
||||
(s/def ::y1 number?)
|
||||
(s/def ::y2 number?)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::page uuid?)
|
||||
(s/def ::type #{:rect
|
||||
:group
|
||||
:path
|
||||
:circle
|
||||
:image
|
||||
:text})
|
||||
|
||||
(s/def ::attributes
|
||||
(s/keys :opt-un [::fill-color
|
||||
(s/keys :opt-un [::blocked
|
||||
::collapsed
|
||||
::conent
|
||||
::fill-color
|
||||
::fill-opacity
|
||||
::line-height
|
||||
::letter-spacing
|
||||
::text-align
|
||||
::font-family
|
||||
::font-size
|
||||
::font-style
|
||||
::font-weight
|
||||
::font-size
|
||||
::stroke-style
|
||||
::stroke-width
|
||||
::hidden
|
||||
::letter-spacing
|
||||
::line-height
|
||||
::locked
|
||||
::proportion
|
||||
::proportion-lock
|
||||
::rx ::ry
|
||||
::stroke-color
|
||||
::stroke-opacity
|
||||
::rx ::ry
|
||||
::stroke-style
|
||||
::stroke-width
|
||||
::text-align
|
||||
::x1 ::x2
|
||||
::y1 ::y2
|
||||
::proportion-lock
|
||||
::proportion
|
||||
::collapsed
|
||||
::hidden
|
||||
::blocked
|
||||
::locked]))
|
||||
::y1 ::y2]))
|
||||
|
||||
(s/def ::minimal-shape
|
||||
(s/keys ::req-un [::id ::page ::type ::name]))
|
||||
|
||||
(s/def ::shape
|
||||
(s/merge (s/keys ::req-un [::id ::page ::type]) ::attributes))
|
||||
(s/merge ::minimal-shape ::attributes))
|
||||
|
||||
(s/def ::rect-like-shape
|
||||
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))
|
||||
|
||||
;; --- Shapes CRUD
|
||||
;; --- Shape Creation
|
||||
|
||||
(deftype AddShape [data]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (geom/setup-proportions data)
|
||||
page-id (get-in state [:workspace :current])]
|
||||
(impl/assoc-shape-to-page state shape page-id))))
|
||||
(defn retrieve-used-names
|
||||
"Returns a set of already used names by shapes
|
||||
in the current page."
|
||||
[{:keys [shapes] :as state}]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
xf (comp (filter #(= pid (:page %)))
|
||||
(map :name))]
|
||||
(into #{} xf (vals shapes))))
|
||||
|
||||
(defn add-shape
|
||||
[data]
|
||||
{:pre [(us/valid? ::shape data)]}
|
||||
(AddShape. data))
|
||||
(defn generate-unique-name
|
||||
"A unique name generator based on the previous
|
||||
state of the used names."
|
||||
[state basename]
|
||||
(let [used (retrieve-used-names state)]
|
||||
(loop [counter 1]
|
||||
(let [candidate (str basename "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate)))))
|
||||
|
||||
;; --- Delete Shape
|
||||
(defn assoc-shape-to-page
|
||||
[state shape page]
|
||||
(let [shape-id (uuid/random)
|
||||
shape-name (generate-unique-name state (:name shape))
|
||||
shape (assoc shape
|
||||
:page page
|
||||
:id shape-id
|
||||
:name shape-name)]
|
||||
(-> state
|
||||
(update-in [:pages page :shapes] #(into [] (cons shape-id %)))
|
||||
(assoc-in [:shapes shape-id] shape))))
|
||||
|
||||
(deftype DeleteShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(impl/dissoc-shape state shape))))
|
||||
(defn duplicate-shapes'
|
||||
([state shapes page]
|
||||
(duplicate-shapes' state shapes page nil))
|
||||
([state shapes page group]
|
||||
(letfn [(duplicate-shape [state shape page group]
|
||||
(if (= (:type shape) :group)
|
||||
(let [id (uuid/random)
|
||||
items (:items shape)
|
||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||
shape (assoc shape
|
||||
:id id
|
||||
:page page
|
||||
:items []
|
||||
:name name)
|
||||
state (if (nil? group)
|
||||
(-> state
|
||||
(update-in [:pages page :shapes]
|
||||
#(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))
|
||||
(-> state
|
||||
(update-in [:shapes group :items]
|
||||
#(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape)))]
|
||||
(->> (map #(get-in state [:shapes %]) items)
|
||||
(reverse)
|
||||
(reduce #(duplicate-shape %1 %2 page id) state)))
|
||||
(let [id (uuid/random)
|
||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||
shape (-> (dissoc shape :group)
|
||||
(assoc :id id :page page :name name)
|
||||
(merge (when group {:group group})))]
|
||||
(if (nil? group)
|
||||
(-> state
|
||||
(update-in [:pages page :shapes] #(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))
|
||||
(-> state
|
||||
(update-in [:shapes group :items] #(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))))))]
|
||||
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
||||
|
||||
(defn delete-shape
|
||||
"Remove the shape using its id."
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(DeleteShape. id))
|
||||
(defn duplicate-shapes
|
||||
([state shapes]
|
||||
(duplicate-shapes state shapes nil))
|
||||
([state shapes page]
|
||||
(letfn [(all-toplevel? [coll]
|
||||
(every? #(nil? (:group %)) coll))
|
||||
(all-same-group? [coll]
|
||||
(let [group (:group (first coll))]
|
||||
(every? #(= group (:group %)) coll)))]
|
||||
(let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))]
|
||||
(cond
|
||||
(all-toplevel? shapes)
|
||||
(let [page (or page (:page (first shapes)))]
|
||||
(duplicate-shapes' state shapes page))
|
||||
|
||||
;; --- Rename Shape
|
||||
(all-same-group? shapes)
|
||||
(let [page (or page (:page (first shapes)))
|
||||
group (:group (first shapes))]
|
||||
(duplicate-shapes' state shapes page group))
|
||||
|
||||
(deftype RenameShape [id name]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :name] name)))
|
||||
:else
|
||||
(let [page (or page (:page (first shapes)))]
|
||||
(duplicate-shapes' state shapes page)))))))
|
||||
|
||||
(defn rename-shape
|
||||
[id name]
|
||||
{:pre [(uuid? id) (string? name)]}
|
||||
(RenameShape. id name))
|
||||
;; --- Delete Shapes
|
||||
|
||||
;; --- Update Rotation
|
||||
(defn dissoc-from-index
|
||||
"A function that dissoc shape from the indexed
|
||||
data structure of shapes from the state."
|
||||
[state {:keys [id type] :as shape}]
|
||||
(if (= :group type)
|
||||
(let [items (map #(get-in state [:shapes %]) (:items shape))]
|
||||
(as-> state $
|
||||
(update-in $ [:shapes] dissoc id)
|
||||
(reduce dissoc-from-index $ items)))
|
||||
(update-in state [:shapes] dissoc id)))
|
||||
|
||||
(deftype UpdateShapeRotation [id rotation]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :rotation] rotation)))
|
||||
(defn dissoc-from-page
|
||||
"Given a shape, try to remove its reference from the
|
||||
corresponding page."
|
||||
[state {:keys [id page] :as shape}]
|
||||
(as-> (get-in state [:pages page :shapes]) $
|
||||
(into [] (remove #(= % id) $))
|
||||
(assoc-in state [:pages page :shapes] $)))
|
||||
|
||||
(defn update-rotation
|
||||
[id rotation]
|
||||
{:pre [(uuid? id)
|
||||
(number? rotation)
|
||||
(>= rotation 0)
|
||||
(>= 360 rotation)]}
|
||||
(UpdateShapeRotation. id rotation))
|
||||
(defn dissoc-from-group
|
||||
"Given a shape, try to remove its reference from the
|
||||
corresponding group (only if it belongs to one group)."
|
||||
[state {:keys [id group] :as shape}]
|
||||
(if-let [group' (get-in state [:shapes group])]
|
||||
(as-> (:items group') $
|
||||
(into [] (remove #(= % id) $))
|
||||
(assoc-in state [:shapes group :items] $))
|
||||
state))
|
||||
|
||||
;; --- Update Dimensions
|
||||
(declare dissoc-shape)
|
||||
|
||||
(deftype UpdateDimensions [id dimensions]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id] geom/resize-dim dimensions)))
|
||||
(defn clear-empty-groups
|
||||
"Given the shape, try to clean all empty groups
|
||||
that this shape belongs to.
|
||||
|
||||
(s/def ::update-dimensions-opts
|
||||
(s/keys :opt-un [::width ::height]))
|
||||
The main purpose of this function is remove the
|
||||
all empty parent groups of recently removed
|
||||
shape."
|
||||
[state {:keys [group] :as shape}]
|
||||
(if-let [group' (get-in state [:shapes group])]
|
||||
(if (empty? (:items group'))
|
||||
(-> (dissoc-shape state group')
|
||||
(update-in [:workspace :selected] disj (:id group')))
|
||||
state)
|
||||
state))
|
||||
|
||||
(defn update-dimensions
|
||||
"A helper event just for update the position
|
||||
of the shape using the width and height attrs
|
||||
instread final point of coordinates."
|
||||
[id opts]
|
||||
{:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]}
|
||||
(UpdateDimensions. id opts))
|
||||
(defn dissoc-shape
|
||||
"Given a shape, removes it from the state."
|
||||
[state shape]
|
||||
(as-> state $
|
||||
(dissoc-from-page $ shape)
|
||||
(dissoc-from-group $ shape)
|
||||
(dissoc-from-index $ shape)
|
||||
(clear-empty-groups $ shape)))
|
||||
|
||||
;; --- Update Shape Position
|
||||
;; --- Shape Movements
|
||||
|
||||
(deftype UpdateShapePosition [id point]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id] geom/absolute-move point)))
|
||||
(defn- drop-at-index
|
||||
[index coll v]
|
||||
(let [[fst snd] (split-at index coll)]
|
||||
(into [] (concat fst [v] snd))))
|
||||
|
||||
(defn update-position
|
||||
"Update the start position coordenate of the shape."
|
||||
[id point]
|
||||
{:pre [(uuid? id) (gpt/point? point)]}
|
||||
(UpdateShapePosition. id point))
|
||||
(defn drop-relative
|
||||
[state loc sid]
|
||||
{:pre [(not (nil? sid))]}
|
||||
(let [shape (get-in state [:shapes (first sid)])
|
||||
{:keys [page group]} shape
|
||||
sid (:id shape)
|
||||
|
||||
;; --- Update Shape Text
|
||||
shapes (if group
|
||||
(get-in state [:shapes group :items])
|
||||
(get-in state [:pages page :shapes]))
|
||||
|
||||
(deftype UpdateShapeTextContent [id text]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :content] text)))
|
||||
index (case loc
|
||||
:first 0
|
||||
:after (min (- (count shapes) 1) (inc (index-of shapes sid)))
|
||||
:before (max 0 (- (index-of shapes sid) 1))
|
||||
:last (- (count shapes) 1))
|
||||
|
||||
(defn update-text
|
||||
[id text]
|
||||
{:pre [(uuid? id) (string? text)]}
|
||||
(UpdateShapeTextContent. id text))
|
||||
state (-> state
|
||||
(dissoc-from-page shape)
|
||||
(dissoc-from-group shape))
|
||||
|
||||
;; --- Update Shape Attrs
|
||||
shapes (if group
|
||||
(get-in state [:shapes group :items])
|
||||
(get-in state [:pages page :shapes]))
|
||||
|
||||
(declare UpdateAttrs)
|
||||
;; TODO: moved
|
||||
(deftype UpdateAttrs [id attrs]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [type] :as shape} (get-in state [:shapes id])]
|
||||
(if (= type :group)
|
||||
(rx/from-coll (map #(UpdateAttrs. % attrs) (:items shape)))
|
||||
(rx/of #(update-in % [:shapes id] merge attrs))))))
|
||||
shapes (drop-at-index index shapes sid)]
|
||||
|
||||
(defn update-attrs
|
||||
[id attrs]
|
||||
{:pre [(uuid? id) (us/valid? ::attributes attrs)]}
|
||||
(let [atts (us/extract attrs ::attributes)]
|
||||
(UpdateAttrs. id attrs)))
|
||||
(if group
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes group :items] shapes)
|
||||
(update-in $ [:shapes sid] assoc :group group)
|
||||
(clear-empty-groups $ shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:pages page :shapes] shapes)
|
||||
(update-in $ [:shapes sid] dissoc :group)
|
||||
(clear-empty-groups $ shape)))))
|
||||
|
||||
;; --- Shape Proportions
|
||||
(defn drop-aside
|
||||
[state loc tid sid]
|
||||
{:pre [(not= tid sid)
|
||||
(not (nil? tid))
|
||||
(not (nil? sid))]}
|
||||
(let [{:keys [page group]} (get-in state [:shapes tid])
|
||||
source (get-in state [:shapes sid])
|
||||
|
||||
(deftype LockShapeProportions [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [[width height] (-> (get-in state [:shapes id])
|
||||
(geom/size)
|
||||
(keep [:width :height]))
|
||||
proportion (/ width height)]
|
||||
(update-in state [:shapes id] assoc
|
||||
:proportion proportion
|
||||
:proportion-lock true))))
|
||||
state (-> state
|
||||
(dissoc-from-page source)
|
||||
(dissoc-from-group source))
|
||||
|
||||
(defn lock-proportions
|
||||
"Mark proportions of the shape locked and save the current
|
||||
proportion as additional precalculated property."
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(LockShapeProportions. id))
|
||||
shapes (if group
|
||||
(get-in state [:shapes group :items])
|
||||
(get-in state [:pages page :shapes]))
|
||||
|
||||
(deftype UnlockShapeProportions [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :proportion-lock] false)))
|
||||
index (case loc
|
||||
:after (inc (index-of shapes tid))
|
||||
:before (index-of shapes tid))
|
||||
|
||||
(defn unlock-proportions
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UnlockShapeProportions. id))
|
||||
shapes (drop-at-index index shapes sid)]
|
||||
(if group
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes group :items] shapes)
|
||||
(update-in $ [:shapes sid] assoc :group group)
|
||||
(clear-empty-groups $ source))
|
||||
(as-> state $
|
||||
(assoc-in $ [:pages page :shapes] shapes)
|
||||
(update-in $ [:shapes sid] dissoc :group)
|
||||
(clear-empty-groups $ source)))))
|
||||
|
||||
;; --- Group Collapsing
|
||||
(def drop-after #(drop-aside %1 :after %2 %3))
|
||||
(def drop-before #(drop-aside %1 :before %2 %3))
|
||||
|
||||
(deftype CollapseGroupShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id] assoc :collapsed true)))
|
||||
|
||||
(defn collapse-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(CollapseGroupShape. id))
|
||||
|
||||
(deftype UncollapseGroupShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id] assoc :collapsed false)))
|
||||
|
||||
(defn uncollapse-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UncollapseGroupShape. id))
|
||||
|
||||
;; --- Shape Visibility
|
||||
|
||||
(deftype HideShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-hidden [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :hidden] true)
|
||||
(reduce mark-hidden $ (:items shape)))
|
||||
(assoc-in state [:shapes id :hidden] true))))]
|
||||
(mark-hidden state id))))
|
||||
|
||||
(defn hide-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(HideShape. id))
|
||||
|
||||
(deftype ShowShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-visible [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :hidden] false)
|
||||
(reduce mark-visible $ (:items shape)))
|
||||
(assoc-in state [:shapes id :hidden] false))))]
|
||||
(mark-visible state id))))
|
||||
|
||||
(defn show-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(ShowShape. id))
|
||||
|
||||
;; --- Shape Blocking
|
||||
|
||||
(deftype BlockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-blocked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :blocked] true)
|
||||
(reduce mark-blocked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :blocked] true))))]
|
||||
(mark-blocked state id))))
|
||||
|
||||
(defn block-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(BlockShape. id))
|
||||
|
||||
(deftype UnblockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-unblocked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :blocked] false)
|
||||
(reduce mark-unblocked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :blocked] false))))]
|
||||
(mark-unblocked state id))))
|
||||
|
||||
(defn unblock-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UnblockShape. id))
|
||||
|
||||
;; --- Shape Locking
|
||||
|
||||
(deftype LockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-locked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :locked] true)
|
||||
(reduce mark-locked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :locked] true))))]
|
||||
(mark-locked state id))))
|
||||
|
||||
(defn lock-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(LockShape. id))
|
||||
|
||||
(deftype UnlockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-unlocked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :locked] false)
|
||||
(reduce mark-unlocked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :locked] false))))]
|
||||
(mark-unlocked state id))))
|
||||
|
||||
(defn unlock-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UnlockShape. id))
|
||||
|
||||
;; --- Drop Shape
|
||||
|
||||
(deftype DropShape [sid tid loc]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(impl/drop-shape state sid tid loc)))
|
||||
(defn drop-inside
|
||||
[state tid sid]
|
||||
{:pre [(not= tid sid)]}
|
||||
(let [source (get-in state [:shapes sid])
|
||||
state (-> state
|
||||
(dissoc-from-page source)
|
||||
(dissoc-from-group source))
|
||||
shapes (get-in state [:shapes tid :items])]
|
||||
(if (seq shapes)
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes tid :items] (conj shapes sid))
|
||||
(update-in $ [:shapes sid] assoc :group tid))
|
||||
state)))
|
||||
|
||||
(defn drop-shape
|
||||
"Event used in drag and drop for transfer shape
|
||||
from one position to an other."
|
||||
[sid tid loc]
|
||||
{:pre [(uuid? sid)
|
||||
(uuid? tid)
|
||||
(keyword? loc)]}
|
||||
(DropShape. sid tid loc))
|
||||
[state sid tid loc]
|
||||
(if (= tid sid)
|
||||
state
|
||||
(case loc
|
||||
:inside (drop-inside state tid sid)
|
||||
:before (drop-before state tid sid)
|
||||
:after (drop-after state tid sid)
|
||||
(throw (ex-info "Invalid data" {})))))
|
||||
|
||||
;; --- Update Interaction
|
||||
(defn move-layer
|
||||
[state shape loc]
|
||||
(case loc
|
||||
:up (drop-relative state :before shape)
|
||||
:down (drop-relative state :after shape)
|
||||
:top (drop-relative state :first shape)
|
||||
:bottom (drop-relative state :last shape)
|
||||
(throw (ex-info "Invalid data" {}))))
|
||||
|
||||
(deftype UpdateInteraction [shape interaction]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (or (:id interaction)
|
||||
(uuid/random))
|
||||
data (assoc interaction :id id)]
|
||||
(assoc-in state [:shapes shape :interactions id] data))))
|
||||
;; --- Shape Selection
|
||||
|
||||
(defn update-interaction
|
||||
[shape interaction]
|
||||
(UpdateInteraction. shape interaction))
|
||||
(defn- try-match-shape
|
||||
[xf selrect acc {:keys [type id items] :as shape}]
|
||||
(cond
|
||||
(geom/contained-in? shape selrect)
|
||||
(conj acc id)
|
||||
|
||||
;; --- Delete Interaction
|
||||
(geom/overlaps? shape selrect)
|
||||
(conj acc id)
|
||||
|
||||
(deftype DeleteInteracton [shape id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes shape :interactions] dissoc id)))
|
||||
(:locked shape)
|
||||
acc
|
||||
|
||||
(defn delete-interaction
|
||||
[shape id]
|
||||
{:pre [(uuid? id) (uuid? shape)]}
|
||||
(DeleteInteracton. shape id))
|
||||
(= type :group)
|
||||
(reduce (partial try-match-shape xf selrect)
|
||||
acc (sequence xf items))
|
||||
|
||||
;; --- Path Modifications
|
||||
:else
|
||||
acc))
|
||||
|
||||
(deftype UpdatePath [id index delta]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id :segments index] gpt/add delta)))
|
||||
(defn match-by-selrect
|
||||
[state page-id selrect]
|
||||
(let [xf (comp (map #(get-in state [:shapes %]))
|
||||
(remove :hidden)
|
||||
(remove :blocked)
|
||||
(map geom/selection-rect))
|
||||
match (partial try-match-shape xf selrect)
|
||||
shapes (get-in state [:pages page-id :shapes])]
|
||||
(reduce match #{} (sequence xf shapes))))
|
||||
|
||||
(defn update-path
|
||||
"Update a concrete point in the path shape."
|
||||
[id index delta]
|
||||
{:pre [(uuid? id) (number? index) (gpt/point? delta)]}
|
||||
(UpdatePath. id index delta))
|
||||
(defn group-shapes
|
||||
[state shapes page]
|
||||
(letfn [(replace-first-item [pred coll replacement]
|
||||
(into []
|
||||
(concat
|
||||
(take-while #(not (pred %)) coll)
|
||||
[replacement]
|
||||
(drop 1 (drop-while #(not (pred %)) coll)))))
|
||||
|
||||
(deftype InitialPathPointAlign [id index]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [shape (get-in state [:shapes id])
|
||||
point (get-in shape [:segments index])]
|
||||
(->> (uwrk/align-point point)
|
||||
(rx/map #(update-path id index %))))))
|
||||
(move-shapes-to-new-group [state page shapes new-group]
|
||||
(reduce (fn [state {:keys [id group] :as shape}]
|
||||
(-> state
|
||||
(update-in [:shapes group :items] #(remove (set [id]) %))
|
||||
(update-in [:pages page :shapes] #(remove (set [id]) %))
|
||||
(clear-empty-groups shape)
|
||||
(assoc-in [:shapes id :group] new-group)
|
||||
))
|
||||
state
|
||||
shapes))
|
||||
|
||||
(defn initial-path-point-align
|
||||
"Event responsible of align a specified point of the
|
||||
shape by `index` with the grid."
|
||||
[id index]
|
||||
{:pre [(uuid? id)
|
||||
(number? index)
|
||||
(not (neg? index))]}
|
||||
(InitialPathPointAlign. id index))
|
||||
(update-shapes-on-page [state page shapes group]
|
||||
(as-> (get-in state [:pages page :shapes]) $
|
||||
(replace-first-item (set shapes) $ group)
|
||||
(remove (set shapes) $)
|
||||
(into [] $)
|
||||
(assoc-in state [:pages page :shapes] $)))
|
||||
|
||||
;; --- Events (implicit) (for selected)
|
||||
(update-shapes-on-group [state parent-group shapes group]
|
||||
(as-> (get-in state [:shapes parent-group :items]) $
|
||||
(replace-first-item (set shapes) $ group)
|
||||
(remove (set shapes) $)
|
||||
(into [] $)
|
||||
(assoc-in state [:shapes parent-group :items] $)))
|
||||
|
||||
;; NOTE: moved to workspace
|
||||
(deftype DeselectAll []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace :selected] #{}))
|
||||
(update-shapes-on-index [state shapes group]
|
||||
(reduce (fn [state {:keys [id] :as shape}]
|
||||
(as-> shape $
|
||||
(assoc $ :group group)
|
||||
(assoc-in state [:shapes id] $)))
|
||||
state
|
||||
shapes))]
|
||||
(let [sid (uuid/random)
|
||||
shapes' (map #(get-in state [:shapes %]) shapes)
|
||||
distinct-groups (distinct (map :group shapes'))
|
||||
parent-group (cond
|
||||
(not= 1 (count distinct-groups)) :multi
|
||||
(nil? (first distinct-groups)) :page
|
||||
:else (first distinct-groups))
|
||||
name (generate-unique-name state "Group")
|
||||
group {:type :group
|
||||
:name name
|
||||
:items (into [] shapes)
|
||||
:id sid
|
||||
:page page}]
|
||||
(as-> state $
|
||||
(update-shapes-on-index $ shapes' sid)
|
||||
(cond
|
||||
(= :multi parent-group)
|
||||
(-> $
|
||||
(move-shapes-to-new-group page shapes' sid)
|
||||
(update-in [:pages page :shapes] #(into [] (cons sid %))))
|
||||
(= :page parent-group)
|
||||
(update-shapes-on-page $ page shapes sid)
|
||||
:else
|
||||
(update-shapes-on-group $ parent-group shapes sid))
|
||||
(update $ :shapes assoc sid group)
|
||||
(cond
|
||||
(= :multi parent-group) $
|
||||
(= :page parent-group) $
|
||||
:else (assoc-in $ [:shapes sid :group] parent-group))
|
||||
(update $ :workspace assoc :selected #{sid})))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/just ::uev/interrupt)))
|
||||
(defn degroup-shapes
|
||||
[state shapes page-id]
|
||||
(letfn [(get-relocation-position [state {id :id parent-id :group}]
|
||||
(if (nil? parent-id)
|
||||
(index-of (get-in state [:pages page-id :shapes]) id)
|
||||
(index-of (get-in state [:shapes parent-id :items]) id)))
|
||||
|
||||
(defn deselect-all
|
||||
"Clear all possible state of drawing, edition
|
||||
or any similar action taken by the user."
|
||||
[]
|
||||
(DeselectAll.))
|
||||
(relocate-shape [state shape-id parent-id position]
|
||||
(if (nil? parent-id)
|
||||
(-> state
|
||||
(update-in [:pages page-id :shapes] #(drop-at-index position % shape-id))
|
||||
(update-in [:shapes shape-id] dissoc :group))
|
||||
(-> state
|
||||
(update-in [:shapes parent-id :items] #(drop-at-index position % shape-id))
|
||||
(assoc-in [:shapes shape-id :group] parent-id))))
|
||||
|
||||
;; --- Group Selected Shapes
|
||||
(remove-group [state {id :id parent-id :group}]
|
||||
(let [xform (remove #{id})]
|
||||
(as-> state $
|
||||
(update $ :shapes dissoc id)
|
||||
(if (nil? parent-id)
|
||||
(update-in $ [:pages page-id :shapes] #(into [] xform %))
|
||||
(update-in $ [:shapes parent-id :items] #(into [] xform %))))))
|
||||
|
||||
(deftype GroupSelectedShapes []
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace :page])
|
||||
selected (get-in state [:workspace :selected])]
|
||||
(assert (not (empty? selected)) "selected set is empty")
|
||||
(assert (uuid? id) "selected page is not an uuid")
|
||||
(impl/group-shapes state selected id))))
|
||||
(relocate-group-items [state {id :id parent-id :group items :items :as group}]
|
||||
(let [position (get-relocation-position state group)]
|
||||
(as-> state $
|
||||
(reduce #(relocate-shape %1 %2 parent-id position) $ (reverse items))
|
||||
(remove-group $ group))))
|
||||
|
||||
(defn group-selected
|
||||
[]
|
||||
(GroupSelectedShapes.))
|
||||
(select-degrouped [state groups]
|
||||
(let [items (into #{} (mapcat :items groups))]
|
||||
(assoc-in state [:workspace :selected] items)))
|
||||
|
||||
;; --- Ungroup Selected Shapes
|
||||
(remove-from-parent [state id parent-id]
|
||||
(assert (not (nil? parent-id)) "parent-id should never be nil here")
|
||||
(update-in state [:shapes parent-id :items] #(into [] (remove #{id}) %)))
|
||||
|
||||
(deftype UngroupSelectedShapes []
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace :page])
|
||||
selected (get-in state [:workspace :selected])]
|
||||
(assert (not (empty? selected)) "selected set is empty")
|
||||
(assert (uuid? id) "selected page is not an uuid")
|
||||
(impl/degroup-shapes state selected id))))
|
||||
(strip-empty-groups [state parent-id]
|
||||
(if (nil? parent-id)
|
||||
state
|
||||
(let [group (get-in state [:shapes parent-id])]
|
||||
(if (empty? (:items group))
|
||||
(-> state
|
||||
(remove-group group)
|
||||
(strip-empty-groups (:group group)))
|
||||
state))))
|
||||
|
||||
(defn ungroup-selected
|
||||
[]
|
||||
(UngroupSelectedShapes.))
|
||||
(selective-degroup [state [shape & rest :as shapes]]
|
||||
(let [group (get-in state [:shapes (:group shape)])
|
||||
position (get-relocation-position state group)
|
||||
parent-id (:group group)]
|
||||
(as-> state $
|
||||
(assoc-in $ [:workspace :selected] (into #{} (map :id shapes)))
|
||||
(reduce (fn [state {shape-id :id}]
|
||||
(-> state
|
||||
(relocate-shape shape-id parent-id position)
|
||||
(remove-from-parent shape-id (:id group))))
|
||||
$ (reverse shapes))
|
||||
(strip-empty-groups $ (:id group)))))]
|
||||
|
||||
;; --- Duplicate Selected
|
||||
(let [shapes (into #{} (map #(get-in state [:shapes %])) shapes)
|
||||
groups (into #{} (filter #(= (:type %) :group)) shapes)
|
||||
parents (into #{} (map :group) shapes)]
|
||||
(cond
|
||||
(and (= (count shapes) (count groups))
|
||||
(= 1 (count parents))
|
||||
(not (empty? groups)))
|
||||
(as-> state $
|
||||
(reduce relocate-group-items $ groups)
|
||||
(reduce remove-group $ groups)
|
||||
(select-degrouped $ groups))
|
||||
|
||||
(deftype DuplicateSelected []
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected (get-in state [:workspace :selected])]
|
||||
(impl/duplicate-shapes state selected))))
|
||||
|
||||
(defn duplicate-selected
|
||||
[]
|
||||
(DuplicateSelected.))
|
||||
(and (empty? groups)
|
||||
(= 1 (count parents))
|
||||
(not (nil? (first parents))))
|
||||
(selective-degroup state shapes)
|
||||
|
||||
:else
|
||||
(throw (ex-info "invalid condition for degrouping" {}))))))
|
||||
|
||||
(defn materialize-xfmt
|
||||
[state id xfmt]
|
||||
(let [{:keys [type items] :as shape} (get-in state [:shapes id])]
|
||||
(if (= type :group)
|
||||
(reduce #(materialize-xfmt %1 %2 xfmt) state items)
|
||||
(update-in state [:shapes id] geom/transform xfmt))))
|
||||
|
|
|
@ -1,470 +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) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.data.shapes-impl
|
||||
(:require [lentes.core :as l]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.data :refer (index-of)]))
|
||||
|
||||
;; --- Shape Creation
|
||||
|
||||
(defn retrieve-used-names
|
||||
"Returns a set of already used names by shapes
|
||||
in the current page."
|
||||
[{:keys [shapes] :as state}]
|
||||
(let [page (l/focus ul/selected-page state)
|
||||
xform (comp (map second)
|
||||
(filter #(= page (:page %)))
|
||||
(map :name))]
|
||||
(into #{} xform shapes)))
|
||||
|
||||
(defn generate-unique-name
|
||||
"A unique name generator based on the previous
|
||||
state of the used names."
|
||||
[state basename]
|
||||
(let [used (retrieve-used-names state)]
|
||||
(loop [counter 1]
|
||||
(let [candidate (str basename "-" counter)]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc counter))
|
||||
candidate)))))
|
||||
|
||||
(defn assoc-shape-to-page
|
||||
[state shape page]
|
||||
(let [shape-id (uuid/random)
|
||||
shape-name (generate-unique-name state (:name shape))
|
||||
shape (assoc shape
|
||||
:page page
|
||||
:id shape-id
|
||||
:name shape-name)]
|
||||
(-> state
|
||||
(update-in [:pages page :shapes] #(into [] (cons shape-id %)))
|
||||
(assoc-in [:shapes shape-id] shape))))
|
||||
|
||||
(defn duplicate-shapes'
|
||||
([state shapes page]
|
||||
(duplicate-shapes' state shapes page nil))
|
||||
([state shapes page group]
|
||||
(letfn [(duplicate-shape [state shape page group]
|
||||
(if (= (:type shape) :group)
|
||||
(let [id (uuid/random)
|
||||
items (:items shape)
|
||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||
shape (assoc shape
|
||||
:id id
|
||||
:page page
|
||||
:items []
|
||||
:name name)
|
||||
state (if (nil? group)
|
||||
(-> state
|
||||
(update-in [:pages page :shapes]
|
||||
#(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))
|
||||
(-> state
|
||||
(update-in [:shapes group :items]
|
||||
#(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape)))]
|
||||
(->> (map #(get-in state [:shapes %]) items)
|
||||
(reverse)
|
||||
(reduce #(duplicate-shape %1 %2 page id) state)))
|
||||
(let [id (uuid/random)
|
||||
name (generate-unique-name state (str (:name shape) "-copy"))
|
||||
shape (-> (dissoc shape :group)
|
||||
(assoc :id id :page page :name name)
|
||||
(merge (when group {:group group})))]
|
||||
(if (nil? group)
|
||||
(-> state
|
||||
(update-in [:pages page :shapes] #(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))
|
||||
(-> state
|
||||
(update-in [:shapes group :items] #(into [] (cons id %)))
|
||||
(assoc-in [:shapes id] shape))))))]
|
||||
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
||||
|
||||
(defn duplicate-shapes
|
||||
([state shapes]
|
||||
(duplicate-shapes state shapes nil))
|
||||
([state shapes page]
|
||||
(letfn [(all-toplevel? [coll]
|
||||
(every? #(nil? (:group %)) coll))
|
||||
(all-same-group? [coll]
|
||||
(let [group (:group (first coll))]
|
||||
(every? #(= group (:group %)) coll)))]
|
||||
(let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))]
|
||||
(cond
|
||||
(all-toplevel? shapes)
|
||||
(let [page (or page (:page (first shapes)))]
|
||||
(duplicate-shapes' state shapes page))
|
||||
|
||||
(all-same-group? shapes)
|
||||
(let [page (or page (:page (first shapes)))
|
||||
group (:group (first shapes))]
|
||||
(duplicate-shapes' state shapes page group))
|
||||
|
||||
:else
|
||||
(let [page (or page (:page (first shapes)))]
|
||||
(duplicate-shapes' state shapes page)))))))
|
||||
|
||||
;; --- Delete Shapes
|
||||
|
||||
(defn dissoc-from-index
|
||||
"A function that dissoc shape from the indexed
|
||||
data structure of shapes from the state."
|
||||
[state {:keys [id type] :as shape}]
|
||||
(if (= :group type)
|
||||
(let [items (map #(get-in state [:shapes %]) (:items shape))]
|
||||
(as-> state $
|
||||
(update-in $ [:shapes] dissoc id)
|
||||
(reduce dissoc-from-index $ items)))
|
||||
(update-in state [:shapes] dissoc id)))
|
||||
|
||||
(defn dissoc-from-page
|
||||
"Given a shape, try to remove its reference from the
|
||||
corresponding page."
|
||||
[state {:keys [id page] :as shape}]
|
||||
(as-> (get-in state [:pages page :shapes]) $
|
||||
(into [] (remove #(= % id) $))
|
||||
(assoc-in state [:pages page :shapes] $)))
|
||||
|
||||
(defn dissoc-from-group
|
||||
"Given a shape, try to remove its reference from the
|
||||
corresponding group (only if it belongs to one group)."
|
||||
[state {:keys [id group] :as shape}]
|
||||
(if-let [group' (get-in state [:shapes group])]
|
||||
(as-> (:items group') $
|
||||
(into [] (remove #(= % id) $))
|
||||
(assoc-in state [:shapes group :items] $))
|
||||
state))
|
||||
|
||||
(declare dissoc-shape)
|
||||
|
||||
(defn clear-empty-groups
|
||||
"Given the shape, try to clean all empty groups
|
||||
that this shape belongs to.
|
||||
|
||||
The main purpose of this function is remove the
|
||||
all empty parent groups of recently removed
|
||||
shape."
|
||||
[state {:keys [group] :as shape}]
|
||||
(if-let [group' (get-in state [:shapes group])]
|
||||
(if (empty? (:items group'))
|
||||
(-> (dissoc-shape state group')
|
||||
(update-in [:workspace :selected] disj (:id group')))
|
||||
state)
|
||||
state))
|
||||
|
||||
(defn dissoc-shape
|
||||
"Given a shape, removes it from the state."
|
||||
[state shape]
|
||||
(as-> state $
|
||||
(dissoc-from-page $ shape)
|
||||
(dissoc-from-group $ shape)
|
||||
(dissoc-from-index $ shape)
|
||||
(clear-empty-groups $ shape)))
|
||||
|
||||
;; --- Shape Movements
|
||||
|
||||
(defn- drop-at-index
|
||||
[index coll v]
|
||||
(let [[fst snd] (split-at index coll)]
|
||||
(into [] (concat fst [v] snd))))
|
||||
|
||||
(defn drop-relative
|
||||
[state loc sid]
|
||||
{:pre [(not (nil? sid))]}
|
||||
(let [shape (get-in state [:shapes (first sid)])
|
||||
{:keys [page group]} shape
|
||||
sid (:id shape)
|
||||
|
||||
shapes (if group
|
||||
(get-in state [:shapes group :items])
|
||||
(get-in state [:pages page :shapes]))
|
||||
|
||||
index (case loc
|
||||
:first 0
|
||||
:after (min (- (count shapes) 1) (inc (index-of shapes sid)))
|
||||
:before (max 0 (- (index-of shapes sid) 1))
|
||||
:last (- (count shapes) 1))
|
||||
|
||||
state (-> state
|
||||
(dissoc-from-page shape)
|
||||
(dissoc-from-group shape))
|
||||
|
||||
shapes (if group
|
||||
(get-in state [:shapes group :items])
|
||||
(get-in state [:pages page :shapes]))
|
||||
|
||||
shapes (drop-at-index index shapes sid)]
|
||||
|
||||
(if group
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes group :items] shapes)
|
||||
(update-in $ [:shapes sid] assoc :group group)
|
||||
(clear-empty-groups $ shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:pages page :shapes] shapes)
|
||||
(update-in $ [:shapes sid] dissoc :group)
|
||||
(clear-empty-groups $ shape)))))
|
||||
|
||||
(defn drop-aside
|
||||
[state loc tid sid]
|
||||
{:pre [(not= tid sid)
|
||||
(not (nil? tid))
|
||||
(not (nil? sid))]}
|
||||
(let [{:keys [page group]} (get-in state [:shapes tid])
|
||||
source (get-in state [:shapes sid])
|
||||
|
||||
state (-> state
|
||||
(dissoc-from-page source)
|
||||
(dissoc-from-group source))
|
||||
|
||||
shapes (if group
|
||||
(get-in state [:shapes group :items])
|
||||
(get-in state [:pages page :shapes]))
|
||||
|
||||
index (case loc
|
||||
:after (inc (index-of shapes tid))
|
||||
:before (index-of shapes tid))
|
||||
|
||||
shapes (drop-at-index index shapes sid)]
|
||||
(if group
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes group :items] shapes)
|
||||
(update-in $ [:shapes sid] assoc :group group)
|
||||
(clear-empty-groups $ source))
|
||||
(as-> state $
|
||||
(assoc-in $ [:pages page :shapes] shapes)
|
||||
(update-in $ [:shapes sid] dissoc :group)
|
||||
(clear-empty-groups $ source)))))
|
||||
|
||||
(def drop-after #(drop-aside %1 :after %2 %3))
|
||||
(def drop-before #(drop-aside %1 :before %2 %3))
|
||||
|
||||
(defn drop-inside
|
||||
[state tid sid]
|
||||
{:pre [(not= tid sid)]}
|
||||
(let [source (get-in state [:shapes sid])
|
||||
state (-> state
|
||||
(dissoc-from-page source)
|
||||
(dissoc-from-group source))
|
||||
shapes (get-in state [:shapes tid :items])]
|
||||
(if (seq shapes)
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes tid :items] (conj shapes sid))
|
||||
(update-in $ [:shapes sid] assoc :group tid))
|
||||
state)))
|
||||
|
||||
(defn drop-shape
|
||||
[state sid tid loc]
|
||||
(if (= tid sid)
|
||||
state
|
||||
(case loc
|
||||
:inside (drop-inside state tid sid)
|
||||
:before (drop-before state tid sid)
|
||||
:after (drop-after state tid sid)
|
||||
(throw (ex-info "Invalid data" {})))))
|
||||
|
||||
(defn move-layer
|
||||
[state shape loc]
|
||||
(case loc
|
||||
:up (drop-relative state :before shape)
|
||||
:down (drop-relative state :after shape)
|
||||
:top (drop-relative state :first shape)
|
||||
:bottom (drop-relative state :last shape)
|
||||
(throw (ex-info "Invalid data" {}))))
|
||||
|
||||
;; --- Shape Selection
|
||||
|
||||
(defn- try-match-shape
|
||||
[xf selrect acc {:keys [type id items] :as shape}]
|
||||
(cond
|
||||
(geom/contained-in? shape selrect)
|
||||
(conj acc id)
|
||||
|
||||
(geom/overlaps? shape selrect)
|
||||
(conj acc id)
|
||||
|
||||
(:locked shape)
|
||||
acc
|
||||
|
||||
(= type :group)
|
||||
(reduce (partial try-match-shape xf selrect)
|
||||
acc (sequence xf items))
|
||||
|
||||
:else
|
||||
acc))
|
||||
|
||||
(defn match-by-selrect
|
||||
[state page-id selrect]
|
||||
(let [xf (comp (map #(get-in state [:shapes %]))
|
||||
(remove :hidden)
|
||||
(remove :blocked)
|
||||
(map geom/selection-rect))
|
||||
match (partial try-match-shape xf selrect)
|
||||
shapes (get-in state [:pages page-id :shapes])]
|
||||
(reduce match #{} (sequence xf shapes))))
|
||||
|
||||
(defn group-shapes
|
||||
[state shapes page]
|
||||
(letfn [(replace-first-item [pred coll replacement]
|
||||
(into []
|
||||
(concat
|
||||
(take-while #(not (pred %)) coll)
|
||||
[replacement]
|
||||
(drop 1 (drop-while #(not (pred %)) coll)))))
|
||||
|
||||
(move-shapes-to-new-group [state page shapes new-group]
|
||||
(reduce (fn [state {:keys [id group] :as shape}]
|
||||
(-> state
|
||||
(update-in [:shapes group :items] #(remove (set [id]) %))
|
||||
(update-in [:pages page :shapes] #(remove (set [id]) %))
|
||||
(clear-empty-groups shape)
|
||||
(assoc-in [:shapes id :group] new-group)
|
||||
))
|
||||
state
|
||||
shapes))
|
||||
|
||||
(update-shapes-on-page [state page shapes group]
|
||||
(as-> (get-in state [:pages page :shapes]) $
|
||||
(replace-first-item (set shapes) $ group)
|
||||
(remove (set shapes) $)
|
||||
(into [] $)
|
||||
(assoc-in state [:pages page :shapes] $)))
|
||||
|
||||
(update-shapes-on-group [state parent-group shapes group]
|
||||
(as-> (get-in state [:shapes parent-group :items]) $
|
||||
(replace-first-item (set shapes) $ group)
|
||||
(remove (set shapes) $)
|
||||
(into [] $)
|
||||
(assoc-in state [:shapes parent-group :items] $)))
|
||||
|
||||
(update-shapes-on-index [state shapes group]
|
||||
(reduce (fn [state {:keys [id] :as shape}]
|
||||
(as-> shape $
|
||||
(assoc $ :group group)
|
||||
(assoc-in state [:shapes id] $)))
|
||||
state
|
||||
shapes))]
|
||||
(let [sid (uuid/random)
|
||||
shapes' (map #(get-in state [:shapes %]) shapes)
|
||||
distinct-groups (distinct (map :group shapes'))
|
||||
parent-group (cond
|
||||
(not= 1 (count distinct-groups)) :multi
|
||||
(nil? (first distinct-groups)) :page
|
||||
:else (first distinct-groups))
|
||||
name (generate-unique-name state "Group")
|
||||
group {:type :group
|
||||
:name name
|
||||
:items (into [] shapes)
|
||||
:id sid
|
||||
:page page}]
|
||||
(as-> state $
|
||||
(update-shapes-on-index $ shapes' sid)
|
||||
(cond
|
||||
(= :multi parent-group)
|
||||
(-> $
|
||||
(move-shapes-to-new-group page shapes' sid)
|
||||
(update-in [:pages page :shapes] #(into [] (cons sid %))))
|
||||
(= :page parent-group)
|
||||
(update-shapes-on-page $ page shapes sid)
|
||||
:else
|
||||
(update-shapes-on-group $ parent-group shapes sid))
|
||||
(update $ :shapes assoc sid group)
|
||||
(cond
|
||||
(= :multi parent-group) $
|
||||
(= :page parent-group) $
|
||||
:else (assoc-in $ [:shapes sid :group] parent-group))
|
||||
(update $ :workspace assoc :selected #{sid})))))
|
||||
|
||||
(defn degroup-shapes
|
||||
[state shapes page-id]
|
||||
(letfn [(get-relocation-position [state {id :id parent-id :group}]
|
||||
(if (nil? parent-id)
|
||||
(index-of (get-in state [:pages page-id :shapes]) id)
|
||||
(index-of (get-in state [:shapes parent-id :items]) id)))
|
||||
|
||||
(relocate-shape [state shape-id parent-id position]
|
||||
(if (nil? parent-id)
|
||||
(-> state
|
||||
(update-in [:pages page-id :shapes] #(drop-at-index position % shape-id))
|
||||
(update-in [:shapes shape-id] dissoc :group))
|
||||
(-> state
|
||||
(update-in [:shapes parent-id :items] #(drop-at-index position % shape-id))
|
||||
(assoc-in [:shapes shape-id :group] parent-id))))
|
||||
|
||||
(remove-group [state {id :id parent-id :group}]
|
||||
(let [xform (remove #{id})]
|
||||
(as-> state $
|
||||
(update $ :shapes dissoc id)
|
||||
(if (nil? parent-id)
|
||||
(update-in $ [:pages page-id :shapes] #(into [] xform %))
|
||||
(update-in $ [:shapes parent-id :items] #(into [] xform %))))))
|
||||
|
||||
(relocate-group-items [state {id :id parent-id :group items :items :as group}]
|
||||
(let [position (get-relocation-position state group)]
|
||||
(as-> state $
|
||||
(reduce #(relocate-shape %1 %2 parent-id position) $ (reverse items))
|
||||
(remove-group $ group))))
|
||||
|
||||
(select-degrouped [state groups]
|
||||
(let [items (into #{} (mapcat :items groups))]
|
||||
(assoc-in state [:workspace :selected] items)))
|
||||
|
||||
(remove-from-parent [state id parent-id]
|
||||
(assert (not (nil? parent-id)) "parent-id should never be nil here")
|
||||
(update-in state [:shapes parent-id :items] #(into [] (remove #{id}) %)))
|
||||
|
||||
(strip-empty-groups [state parent-id]
|
||||
(if (nil? parent-id)
|
||||
state
|
||||
(let [group (get-in state [:shapes parent-id])]
|
||||
(if (empty? (:items group))
|
||||
(-> state
|
||||
(remove-group group)
|
||||
(strip-empty-groups (:group group)))
|
||||
state))))
|
||||
|
||||
(selective-degroup [state [shape & rest :as shapes]]
|
||||
(let [group (get-in state [:shapes (:group shape)])
|
||||
position (get-relocation-position state group)
|
||||
parent-id (:group group)]
|
||||
(as-> state $
|
||||
(assoc-in $ [:workspace :selected] (into #{} (map :id shapes)))
|
||||
(reduce (fn [state {shape-id :id}]
|
||||
(-> state
|
||||
(relocate-shape shape-id parent-id position)
|
||||
(remove-from-parent shape-id (:id group))))
|
||||
$ (reverse shapes))
|
||||
(strip-empty-groups $ (:id group)))))]
|
||||
(let [shapes (into #{} (map #(get-in state [:shapes %])) shapes)
|
||||
groups (into #{} (filter #(= (:type %) :group)) shapes)
|
||||
parents (into #{} (map :group) shapes)]
|
||||
(cond
|
||||
(and (= (count shapes) (count groups))
|
||||
(= 1 (count parents))
|
||||
(not (empty? groups)))
|
||||
(as-> state $
|
||||
(reduce relocate-group-items $ groups)
|
||||
(reduce remove-group $ groups)
|
||||
(select-degrouped $ groups))
|
||||
|
||||
(and (empty? groups)
|
||||
(= 1 (count parents))
|
||||
(not (nil? (first parents))))
|
||||
(selective-degroup state shapes)
|
||||
|
||||
:else
|
||||
(throw (ex-info "invalid condition for degrouping" {}))))))
|
||||
|
||||
(defn materialize-xfmt
|
||||
[state id xfmt]
|
||||
(let [{:keys [type items] :as shape} (get-in state [:shapes id])]
|
||||
(if (= type :group)
|
||||
(reduce #(materialize-xfmt %1 %2 xfmt) state items)
|
||||
(update-in state [:shapes id] geom/transform xfmt))))
|
|
@ -7,24 +7,21 @@
|
|||
(ns uxbox.main.data.workspace
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
;; [uxbox.main.data.workspace.ruler :as wruler]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.projects :as dp]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.shapes-impl :as simpl]
|
||||
[uxbox.main.data.workspace.ruler :as wruler]
|
||||
[uxbox.main.data.shapes :as ds]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.util.data :refer [dissoc-in index-of]]
|
||||
[uxbox.util.data :refer [dissoc-in index-of seek]]
|
||||
[uxbox.util.forms :as sc]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
|
@ -35,8 +32,10 @@
|
|||
|
||||
;; --- Expose inner functions
|
||||
|
||||
(def start-ruler wruler/start-ruler)
|
||||
(def clear-ruler wruler/clear-ruler)
|
||||
(def start-ruler nil)
|
||||
(def clear-ruler nil)
|
||||
|
||||
(defn interrupt? [e] (= e :interrupt))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; General workspace events
|
||||
|
@ -258,7 +257,7 @@
|
|||
(->> (:clipboard state)
|
||||
(filter #(= id (:id %)))
|
||||
(first)))]
|
||||
(simpl/duplicate-shapes state (:items selected) page-id))))
|
||||
(ds/duplicate-shapes state (:items selected) page-id))))
|
||||
|
||||
(defn paste-from-clipboard
|
||||
"Copy selected shapes to clipboard."
|
||||
|
@ -327,18 +326,51 @@
|
|||
{:pre [(uuid? id)]}
|
||||
(InitializeAlignment. id))
|
||||
|
||||
;; --- Duplicate Selected
|
||||
|
||||
(def duplicate-selected
|
||||
(reify
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected (get-in state [:workspace :selected])]
|
||||
(ds/duplicate-shapes state selected)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shapes on Workspace events
|
||||
;; Shapes events
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn add-shape
|
||||
[data]
|
||||
{:pre [(us/valid? ::ds/shape data)]}
|
||||
(reify
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; TODO: revisit the `setup-proportions` seems unnecesary
|
||||
(let [shape (assoc (geom/setup-proportions data)
|
||||
:id (uuid/random))
|
||||
pid (get-in state [:workspace :current])]
|
||||
(ds/assoc-shape-to-page state shape pid)))))
|
||||
|
||||
(defn delete-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(reify
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(ds/dissoc-shape state shape)))))
|
||||
|
||||
(defrecord SelectShape [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (get-in state [:workspace :current])
|
||||
selected (get-in state [:workspace page-id :selected])]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
selected (get-in state [:workspace pid :selected])]
|
||||
(if (contains? selected id)
|
||||
(update-in state [:workspace page-id :selected] disj id)
|
||||
(update-in state [:workspace page-id :selected] conj id))))
|
||||
(update-in state [:workspace pid :selected] disj id)
|
||||
(update-in state [:workspace pid :selected] conj id))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
|
@ -353,9 +385,10 @@
|
|||
(defrecord DeselectAll []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (get-in state [:workspace :current])]
|
||||
(assoc-in state [:workspace page-id :selected] #{})))
|
||||
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid] #(-> %
|
||||
(assoc :selected #{})
|
||||
(dissoc :selected-canvas)))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/just :interrupt)))
|
||||
|
@ -388,7 +421,7 @@
|
|||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
selrect (get-in state [:workspace pid :selrect])
|
||||
shapes (simpl/match-by-selrect state pid selrect)]
|
||||
shapes (ds/match-by-selrect state pid selrect)]
|
||||
(assoc-in state [:workspace pid :selected] shapes)))))
|
||||
|
||||
;; --- Update Shape Attrs
|
||||
|
@ -400,8 +433,8 @@
|
|||
|
||||
(defn update-shape-attrs
|
||||
[id attrs]
|
||||
{:pre [(uuid? id) (us/valid? ::uds/attributes attrs)]}
|
||||
(let [atts (us/extract attrs ::uds/attributes)]
|
||||
{:pre [(uuid? id) (us/valid? ::ds/attributes attrs)]}
|
||||
(let [atts (us/extract attrs ::ds/attributes)]
|
||||
(UpdateShapeAttrs. id attrs)))
|
||||
|
||||
;; --- Update Selected Shapes attrs
|
||||
|
@ -416,7 +449,7 @@
|
|||
|
||||
(defn update-selected-shapes-attrs
|
||||
[attrs]
|
||||
{:pre [(us/valid? ::uds/attributes attrs)]}
|
||||
{:pre [(us/valid? ::ds/attributes attrs)]}
|
||||
(UpdateSelectedShapesAttrs. attrs))
|
||||
|
||||
|
||||
|
@ -486,27 +519,49 @@
|
|||
(update [_ state]
|
||||
(let [id (get-in state [:workspace :current])
|
||||
selected (get-in state [:workspace id :selected])]
|
||||
(simpl/move-layer state selected loc))))
|
||||
(ds/move-layer state selected loc))))
|
||||
|
||||
(defn move-selected-layer
|
||||
[loc]
|
||||
{:pre [(us/valid? ::direction loc)]}
|
||||
(MoveSelectedLayer. loc))
|
||||
|
||||
;; --- Update Shape Position
|
||||
|
||||
(deftype UpdateShapePosition [id point]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id] geom/absolute-move point)))
|
||||
|
||||
(defn update-position
|
||||
"Update the start position coordenate of the shape."
|
||||
[id point]
|
||||
{:pre [(uuid? id) (gpt/point? point)]}
|
||||
(UpdateShapePosition. id point))
|
||||
|
||||
;; --- Delete Selected
|
||||
|
||||
(defrecord DeleteSelected []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:workspace :current])
|
||||
selected (get-in state [:workspace id :selected])]
|
||||
(rx/from-coll
|
||||
(into [(deselect-all)] (map #(uds/delete-shape %) selected))))))
|
||||
|
||||
(defn delete-selected
|
||||
(def delete-selected
|
||||
"Deselect all and remove all selected shapes."
|
||||
[]
|
||||
(DeleteSelected.))
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [id (get-in state [:workspace :current])
|
||||
selected (get-in state [:workspace id :selected])]
|
||||
(rx/from-coll
|
||||
(into [(deselect-all)] (map #(delete-shape %) selected)))))))
|
||||
|
||||
;; --- Rename Shape
|
||||
|
||||
(defn rename-shape
|
||||
[id name]
|
||||
{:pre [(uuid? id) (string? name)]}
|
||||
(reify
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :name] name))))
|
||||
|
||||
;; --- Change Shape Order (Ordering)
|
||||
|
||||
|
@ -563,7 +618,7 @@
|
|||
(let [pid (get-in state [:workspace :current])
|
||||
displacement (get-in state [:workspace pid :modifiers id :displacement])]
|
||||
(if (gmt/matrix? displacement)
|
||||
(rx/of #(simpl/materialize-xfmt % id displacement)
|
||||
(rx/of #(ds/materialize-xfmt % id displacement)
|
||||
#(update-in % [:workspace pid :modifiers id] dissoc :displacement)
|
||||
::udp/page-update)
|
||||
(rx/empty)))))
|
||||
|
@ -595,7 +650,7 @@
|
|||
(let [pid (get-in state [:workspace :current])
|
||||
resize (get-in state [:workspace pid :modifiers id :resize])]
|
||||
(if (gmt/matrix? resize)
|
||||
(rx/of #(simpl/materialize-xfmt % id resize)
|
||||
(rx/of #(ds/materialize-xfmt % id resize)
|
||||
#(update-in % [:workspace pid :modifiers id] dissoc :resize)
|
||||
::udp/page-update)
|
||||
(rx/empty)))))
|
||||
|
@ -627,19 +682,252 @@
|
|||
|
||||
;; --- Select for Drawing
|
||||
|
||||
(defn select-for-drawing
|
||||
[shape]
|
||||
(def clear-drawing
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
current (get-in state [:workspace pid :drawing-tool])]
|
||||
(if (or (nil? shape)
|
||||
(= shape current))
|
||||
(update-in state [:workspace pid] dissoc :drawing :drawing-tool)
|
||||
(update-in state [:workspace pid] assoc
|
||||
:drawing shape
|
||||
:drawing-tool shape))))))
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid] dissoc :drawing-tool :drawing)))))
|
||||
|
||||
(defn select-for-drawing?
|
||||
[e]
|
||||
(= (::type (meta e)) ::select-for-drawing))
|
||||
|
||||
(defn select-for-drawing
|
||||
[tool]
|
||||
(reify
|
||||
IMeta
|
||||
(-meta [_] {::type ::select-for-drawing})
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(prn "select-for-drawing" tool)
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid] assoc :drawing-tool tool)))))
|
||||
|
||||
;; --- Shape Proportions
|
||||
|
||||
(deftype LockShapeProportions [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [[width height] (-> (get-in state [:shapes id])
|
||||
(geom/size)
|
||||
(keep [:width :height]))
|
||||
proportion (/ width height)]
|
||||
(update-in state [:shapes id] assoc
|
||||
:proportion proportion
|
||||
:proportion-lock true))))
|
||||
|
||||
(defn lock-proportions
|
||||
"Mark proportions of the shape locked and save the current
|
||||
proportion as additional precalculated property."
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(LockShapeProportions. id))
|
||||
|
||||
(deftype UnlockShapeProportions [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:shapes id :proportion-lock] false)))
|
||||
|
||||
(defn unlock-proportions
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UnlockShapeProportions. id))
|
||||
|
||||
;; --- Update Dimensions
|
||||
|
||||
(deftype UpdateDimensions [id dimensions]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id] geom/resize-dim dimensions)))
|
||||
|
||||
(s/def ::update-dimensions-opts
|
||||
(s/keys :opt-un [::width ::height]))
|
||||
|
||||
(defn update-dimensions
|
||||
"A helper event just for update the position
|
||||
of the shape using the width and height attrs
|
||||
instread final point of coordinates."
|
||||
[id opts]
|
||||
{:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]}
|
||||
(UpdateDimensions. id opts))
|
||||
|
||||
;; --- Update Interaction
|
||||
|
||||
(deftype UpdateInteraction [shape interaction]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (or (:id interaction)
|
||||
(uuid/random))
|
||||
data (assoc interaction :id id)]
|
||||
(assoc-in state [:shapes shape :interactions id] data))))
|
||||
|
||||
(defn update-interaction
|
||||
[shape interaction]
|
||||
(UpdateInteraction. shape interaction))
|
||||
|
||||
;; --- Delete Interaction
|
||||
|
||||
(deftype DeleteInteracton [shape id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes shape :interactions] dissoc id)))
|
||||
|
||||
(defn delete-interaction
|
||||
[shape id]
|
||||
{:pre [(uuid? id) (uuid? shape)]}
|
||||
(DeleteInteracton. shape id))
|
||||
|
||||
;; --- Path Modifications
|
||||
|
||||
(deftype UpdatePath [id index delta]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:shapes id :segments index] gpt/add delta)))
|
||||
|
||||
(defn update-path
|
||||
"Update a concrete point in the path shape."
|
||||
[id index delta]
|
||||
{:pre [(uuid? id) (number? index) (gpt/point? delta)]}
|
||||
(UpdatePath. id index delta))
|
||||
|
||||
;; --- Initial Path Point Alignment
|
||||
|
||||
(deftype InitialPathPointAlign [id index]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [shape (get-in state [:shapes id])
|
||||
point (get-in shape [:segments index])]
|
||||
(->> (uwrk/align-point point)
|
||||
(rx/map #(update-path id index %))))))
|
||||
|
||||
(defn initial-path-point-align
|
||||
"Event responsible of align a specified point of the
|
||||
shape by `index` with the grid."
|
||||
[id index]
|
||||
{:pre [(uuid? id)
|
||||
(number? index)
|
||||
(not (neg? index))]}
|
||||
(InitialPathPointAlign. id index))
|
||||
|
||||
;; --- Shape Visibility
|
||||
|
||||
(deftype HideShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-hidden [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :hidden] true)
|
||||
(reduce mark-hidden $ (:items shape)))
|
||||
(assoc-in state [:shapes id :hidden] true))))]
|
||||
(mark-hidden state id))))
|
||||
|
||||
(defn hide-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(HideShape. id))
|
||||
|
||||
(deftype ShowShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-visible [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :hidden] false)
|
||||
(reduce mark-visible $ (:items shape)))
|
||||
(assoc-in state [:shapes id :hidden] false))))]
|
||||
(mark-visible state id))))
|
||||
|
||||
(defn show-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(ShowShape. id))
|
||||
|
||||
;; --- Shape Blocking
|
||||
|
||||
(deftype BlockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-blocked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :blocked] true)
|
||||
(reduce mark-blocked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :blocked] true))))]
|
||||
(mark-blocked state id))))
|
||||
|
||||
(defn block-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(BlockShape. id))
|
||||
|
||||
(deftype UnblockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-unblocked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :blocked] false)
|
||||
(reduce mark-unblocked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :blocked] false))))]
|
||||
(mark-unblocked state id))))
|
||||
|
||||
(defn unblock-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UnblockShape. id))
|
||||
|
||||
;; --- Shape Locking
|
||||
|
||||
(deftype LockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-locked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :locked] true)
|
||||
(reduce mark-locked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :locked] true))))]
|
||||
(mark-locked state id))))
|
||||
|
||||
(defn lock-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(LockShape. id))
|
||||
|
||||
(deftype UnlockShape [id]
|
||||
udp/IPageUpdate
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(letfn [(mark-unlocked [state id]
|
||||
(let [shape (get-in state [:shapes id])]
|
||||
(if (= :group (:type shape))
|
||||
(as-> state $
|
||||
(assoc-in $ [:shapes id :locked] false)
|
||||
(reduce mark-unlocked $ (:items shape)))
|
||||
(assoc-in state [:shapes id :locked] false))))]
|
||||
(mark-unlocked state id))))
|
||||
|
||||
(defn unlock-shape
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(UnlockShape. id))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Selection Rect IMPL
|
||||
|
@ -662,6 +950,42 @@
|
|||
:y2 end-y
|
||||
:type :rect)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Canvas Interactions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; ;; --- Group Collapsing
|
||||
|
||||
;; (deftype CollapseGroupShape [id]
|
||||
;; udp/IPageUpdate
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (update-in state [:shapes id] assoc :collapsed true)))
|
||||
|
||||
;; (defn collapse-shape
|
||||
;; [id]
|
||||
;; {:pre [(uuid? id)]}
|
||||
;; (CollapseGroupShape. id))
|
||||
|
||||
;; (deftype UncollapseGroupShape [id]
|
||||
;; udp/IPageUpdate
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (update-in state [:shapes id] assoc :collapsed false)))
|
||||
|
||||
;; (defn uncollapse-shape
|
||||
;; [id]
|
||||
;; {:pre [(uuid? id)]}
|
||||
;; (UncollapseGroupShape. id))
|
||||
|
||||
(defn select-canvas
|
||||
[id]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid] assoc :selected-canvas id)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Server Interactions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
(ns uxbox.main.data.workspace.ruler
|
||||
"Workspace ruler related events. Mostly or all events
|
||||
are related to UI logic."
|
||||
(:require [beicon.core :as rx]
|
||||
#_(:require [beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
|
@ -17,79 +17,79 @@
|
|||
|
||||
;; --- Constants
|
||||
|
||||
(declare stop-ruler?)
|
||||
(declare clear-ruler)
|
||||
(declare update-ruler)
|
||||
;; (declare stop-ruler?)
|
||||
;; (declare clear-ruler)
|
||||
;; (declare update-ruler)
|
||||
|
||||
(def ^:private immanted-zones
|
||||
(let [transform #(vector (- % 7) (+ % 7) %)
|
||||
right (map transform (range 0 181 15))
|
||||
left (map (comp transform -) (range 0 181 15))]
|
||||
(vec (concat right left))))
|
||||
;; (def ^:private immanted-zones
|
||||
;; (let [transform #(vector (- % 7) (+ % 7) %)
|
||||
;; right (map transform (range 0 181 15))
|
||||
;; left (map (comp transform -) (range 0 181 15))]
|
||||
;; (vec (concat right left))))
|
||||
|
||||
(defn- align-position
|
||||
[pos]
|
||||
(let [angle (gpt/angle pos)]
|
||||
(reduce (fn [pos [a1 a2 v]]
|
||||
(if (< a1 angle a2)
|
||||
(reduced (gpt/update-angle pos v))
|
||||
pos))
|
||||
pos
|
||||
immanted-zones)))
|
||||
;; (defn- align-position
|
||||
;; [pos]
|
||||
;; (let [angle (gpt/angle pos)]
|
||||
;; (reduce (fn [pos [a1 a2 v]]
|
||||
;; (if (< a1 angle a2)
|
||||
;; (reduced (gpt/update-angle pos v))
|
||||
;; pos))
|
||||
;; pos
|
||||
;; immanted-zones)))
|
||||
|
||||
;; --- Start Ruler
|
||||
;; ;; --- Start Ruler
|
||||
|
||||
(deftype StartRuler []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
pos (get-in state [:workspace :pointer :viewport])]
|
||||
(assoc-in state [:workspace pid :ruler] {:start pos :end pos})))
|
||||
;; (deftype StartRuler []
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (let [pid (get-in state [:workspace :current])
|
||||
;; pos (get-in state [:workspace :pointer :viewport])]
|
||||
;; (assoc-in state [:workspace pid :ruler] {:start pos :end pos})))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (->> (rx/filter #(= ::uev/interrupt %) stream)
|
||||
(rx/take 1))]
|
||||
(->> streams/mouse-position
|
||||
(rx/take-until stoper)
|
||||
(rx/map (juxt :viewport :ctrl))
|
||||
(rx/map (fn [[pt ctrl?]]
|
||||
(update-ruler pt ctrl?)))))))
|
||||
;; ptk/WatchEvent
|
||||
;; (watch [_ state stream]
|
||||
;; (let [stoper (->> (rx/filter #(= ::uev/interrupt %) stream)
|
||||
;; (rx/take 1))]
|
||||
;; (->> streams/mouse-position
|
||||
;; (rx/take-until stoper)
|
||||
;; (rx/map (juxt :viewport :ctrl))
|
||||
;; (rx/map (fn [[pt ctrl?]]
|
||||
;; (update-ruler pt ctrl?)))))))
|
||||
|
||||
(defn start-ruler
|
||||
[]
|
||||
(StartRuler.))
|
||||
;; (defn start-ruler
|
||||
;; []
|
||||
;; (StartRuler.))
|
||||
|
||||
;; --- Update Ruler
|
||||
;; ;; --- Update Ruler
|
||||
|
||||
(deftype UpdateRuler [point ctrl?]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
ruler (get-in state [:workspace pid :ruler])]
|
||||
(if-not ctrl?
|
||||
(assoc-in state [:workspace pid :ruler :end] point)
|
||||
(let [start (get-in state [:workspace pid :ruler :start])
|
||||
end (-> (gpt/subtract point start)
|
||||
(align-position)
|
||||
(gpt/add start))]
|
||||
(assoc-in state [:workspace pid :ruler :end] end))))))
|
||||
;; (deftype UpdateRuler [point ctrl?]
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (let [pid (get-in state [:workspace :current])
|
||||
;; ruler (get-in state [:workspace pid :ruler])]
|
||||
;; (if-not ctrl?
|
||||
;; (assoc-in state [:workspace pid :ruler :end] point)
|
||||
;; (let [start (get-in state [:workspace pid :ruler :start])
|
||||
;; end (-> (gpt/subtract point start)
|
||||
;; (align-position)
|
||||
;; (gpt/add start))]
|
||||
;; (assoc-in state [:workspace pid :ruler :end] end))))))
|
||||
|
||||
(defn update-ruler
|
||||
[point ctrl?]
|
||||
{:pre [(gpt/point? point)
|
||||
(boolean? ctrl?)]}
|
||||
(UpdateRuler. point ctrl?))
|
||||
;; (defn update-ruler
|
||||
;; [point ctrl?]
|
||||
;; {:pre [(gpt/point? point)
|
||||
;; (boolean? ctrl?)]}
|
||||
;; (UpdateRuler. point ctrl?))
|
||||
|
||||
;; --- Clear Ruler
|
||||
;; ;; --- Clear Ruler
|
||||
|
||||
(deftype ClearRuler []
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])]
|
||||
(update-in state [:workspace pid] dissoc :ruler))))
|
||||
;; (deftype ClearRuler []
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (let [pid (get-in state [:workspace :current])]
|
||||
;; (update-in state [:workspace pid] dissoc :ruler))))
|
||||
|
||||
(defn clear-ruler
|
||||
[]
|
||||
(ClearRuler.))
|
||||
;; (defn clear-ruler
|
||||
;; []
|
||||
;; (ClearRuler.))
|
||||
|
||||
|
|
|
@ -6,23 +6,7 @@
|
|||
|
||||
;; TODO: DEPRECTATED, maintained just for temporal documentation, delete on near future
|
||||
|
||||
(ns uxbox.main.data.workspace-drawing
|
||||
"Workspace drawing data events and impl."
|
||||
(:require [beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.streams :as streams]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.main.lenses :as ul]
|
||||
[uxbox.util.geom.path :as pth]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
(ns uxbox.main.data.workspace-drawing)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Events
|
||||
|
@ -234,7 +218,7 @@
|
|||
;; (rx/filter uev/mouse-up?)
|
||||
;; (rx/take 1)))
|
||||
;; start? (volatile! true)
|
||||
;; mouse (->> streams/viewport-mouse-position
|
||||
;; mouse (->> streams/mouse-position
|
||||
;; (rx/take-until stoper)
|
||||
;; (rx/mapcat conditional-align)
|
||||
;; (rx/map translate-to-canvas)
|
||||
|
@ -325,7 +309,7 @@
|
|||
;; (defn- on-init-draw-free-path
|
||||
;; [shape stoper]
|
||||
;; (let [stoper (get-path-stoper-stream stoper true)
|
||||
;; mouse (->> streams/viewport-mouse-position
|
||||
;; mouse (->> streams/mouse-position
|
||||
;; (rx/mapcat conditional-align)
|
||||
;; (rx/map translate-to-canvas))
|
||||
|
||||
|
@ -341,7 +325,7 @@
|
|||
;; [shape stoper]
|
||||
;; (let [last-point (volatile! @refs/canvas-mouse-position)
|
||||
;; stoper (get-path-stoper-stream stoper)
|
||||
;; mouse (->> (rx/sample 10 streams/viewport-mouse-position)
|
||||
;; mouse (->> (rx/sample 10 streams/mouse-position)
|
||||
;; (rx/mapcat conditional-align)
|
||||
;; (rx/map translate-to-canvas))
|
||||
;; points (->> (get-path-point-stream)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
:image (move-rect shape dpoint)
|
||||
:rect (move-rect shape dpoint)
|
||||
:text (move-rect shape dpoint)
|
||||
:curve (move-path shape dpoint)
|
||||
:path (move-path shape dpoint)
|
||||
:circle (move-circle shape dpoint)
|
||||
:group (move-group shape dpoint)))
|
||||
|
@ -125,12 +126,12 @@
|
|||
"Calculate the size of the shape."
|
||||
[shape]
|
||||
(case (:type shape)
|
||||
:group (assoc shape :width 100 :height 100)
|
||||
:circle (size-circle shape)
|
||||
:text (size-rect shape)
|
||||
:rect (size-rect shape)
|
||||
:icon (size-rect shape)
|
||||
:image (size-rect shape)
|
||||
:curve (size-path shape)
|
||||
:path (size-path shape)))
|
||||
|
||||
(defn- size-path
|
||||
|
@ -184,6 +185,7 @@
|
|||
:icon (setup-proportions-image shape)
|
||||
:image (setup-proportions-image shape)
|
||||
:text shape
|
||||
:curve (setup-proportions-rect shape)
|
||||
:path (setup-proportions-rect shape)))
|
||||
|
||||
(defn setup-proportions-image
|
||||
|
@ -461,6 +463,7 @@
|
|||
(case type
|
||||
:circle (circle->rect-shape state shape)
|
||||
:path (path->rect-shape state shape)
|
||||
:curve (path->rect-shape state shape)
|
||||
shape)))
|
||||
|
||||
(defn shapes->rect-shape
|
||||
|
@ -517,6 +520,7 @@
|
|||
:text (transform-rect shape xfmt)
|
||||
:image (transform-rect shape xfmt)
|
||||
:path (transform-path shape xfmt)
|
||||
:curve (transform-path shape xfmt)
|
||||
:circle (transform-circle shape xfmt)))
|
||||
|
||||
(defn- transform-rect
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
(ns uxbox.main.lenses
|
||||
(:require [lentes.core :as l]))
|
||||
|
||||
;; --- Workspace
|
||||
;; --- FIXME: remove this ns
|
||||
|
||||
(def workspace (l/key :workspace))
|
||||
(def workspace-flags (comp workspace (l/key :flags)))
|
||||
|
||||
(def selected-drawing (comp workspace (l/key :drawing)))
|
||||
(def selected-shapes (comp workspace (l/key :selected)))
|
||||
(def selected-page (comp workspace (l/key :page)))
|
||||
(def selected-project (comp workspace (l/key :project)))
|
|
@ -52,6 +52,10 @@
|
|||
(-> (l/key :selected)
|
||||
(l/derive workspace)))
|
||||
|
||||
(def selected-canvas
|
||||
(-> (l/key :selected-canvas)
|
||||
(l/derive workspace)))
|
||||
|
||||
(def toolboxes
|
||||
(-> (l/key :toolboxes)
|
||||
(l/derive workspace)))
|
||||
|
@ -100,28 +104,6 @@
|
|||
(l/lens alignment-activated?))
|
||||
(l/derive workspace)))
|
||||
|
||||
;; ...
|
||||
|
||||
(def mouse-position
|
||||
(-> (l/in [:workspace :pointer])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def canvas-mouse-position
|
||||
(-> (l/key :canvas)
|
||||
(l/derive mouse-position)))
|
||||
|
||||
(def viewport-mouse-position
|
||||
(-> (l/key :viewport)
|
||||
(l/derive mouse-position)))
|
||||
|
||||
(def window-mouse-position
|
||||
(-> (l/key :window)
|
||||
(l/derive mouse-position)))
|
||||
|
||||
(def workspace-scroll
|
||||
(-> (l/in [:workspace :scroll])
|
||||
(l/derive st/state)))
|
||||
|
||||
(def shapes-by-id
|
||||
(-> (l/key :shapes)
|
||||
(l/derive st/state)))
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.repo.pages
|
||||
"A main interface for access to remote resources."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.config :refer (url)]
|
||||
[uxbox.main.repo.impl :refer (request send!)]
|
||||
[uxbox.util.transit :as t]))
|
||||
(:require
|
||||
[uxbox.config :refer [url]]
|
||||
[uxbox.main.repo.impl :refer [request send!]]))
|
||||
|
||||
(defmethod request :fetch/pages
|
||||
[type data]
|
||||
|
|
|
@ -1,65 +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) 2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.streams
|
||||
"A collection of derived streams."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.user-events :as uev]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(defn- user-interaction-event?
|
||||
[event]
|
||||
(or (uev/keyboard-event? event)
|
||||
(uev/mouse-event? event)))
|
||||
|
||||
(defonce events
|
||||
(rx/filter user-interaction-event? st/stream))
|
||||
|
||||
;; --- Mouse Position Stream
|
||||
|
||||
(defonce mouse-position
|
||||
(rx/filter uev/pointer-event? st/stream))
|
||||
|
||||
(defonce canvas-mouse-position
|
||||
(->> mouse-position
|
||||
(rx/map :canvas)
|
||||
(rx/share)))
|
||||
|
||||
(defonce viewport-mouse-position
|
||||
(->> mouse-position
|
||||
(rx/map :viewport)
|
||||
(rx/share)))
|
||||
|
||||
(defonce window-mouse-position
|
||||
(->> mouse-position
|
||||
(rx/map :window)
|
||||
(rx/share)))
|
||||
|
||||
(defonce mouse-position-ctrl
|
||||
(->> mouse-position
|
||||
(rx/map :ctrl)
|
||||
(rx/share)))
|
||||
|
||||
(defn- coords-delta
|
||||
[[old new]]
|
||||
(gpt/subtract new old))
|
||||
|
||||
(defonce mouse-position-deltas
|
||||
(->> viewport-mouse-position
|
||||
(rx/sample 10)
|
||||
(rx/map #(gpt/divide % @refs/selected-zoom))
|
||||
(rx/mapcat (fn [point]
|
||||
(if @refs/selected-alignment
|
||||
(uwrk/align-point point)
|
||||
(rx/of point))))
|
||||
(rx/buffer 2 1)
|
||||
(rx/map coords-delta)
|
||||
(rx/share)))
|
|
@ -20,6 +20,7 @@
|
|||
[shape]
|
||||
(mf/html
|
||||
(case (:type shape)
|
||||
:curve [:& path/path-component {:shape shape}]
|
||||
:text [:& text/text-component {:shape shape}]
|
||||
:icon [:& icon/icon-component {:shape shape}]
|
||||
:rect [:& rect/rect-component {:shape shape}]
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
wst (get-in state [:workspace pid])
|
||||
stoper (->> ws/interaction-events
|
||||
stoper (->> stream
|
||||
(rx/filter ws/mouse-up?)
|
||||
(rx/take 1))
|
||||
stream (->> ws/mouse-position-deltas
|
||||
|
@ -58,7 +58,8 @@
|
|||
(and (not selected?) (empty? selected))
|
||||
(do
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dw/select-shape id)
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id)
|
||||
(start-move-selected)))
|
||||
|
||||
(and (not selected?) (not (empty? selected)))
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
style (make-style shape)
|
||||
on-input (fn [ev]
|
||||
(let [content (dom/event->inner-text ev)]
|
||||
(st/emit! (uds/update-text id content))))]
|
||||
(st/emit! (udw/update-shape-attrs id {:content content}))))]
|
||||
[:foreignObject {:x x1 :y y1 :width width :height height}
|
||||
[:div {:style (normalize-props style)
|
||||
:ref (::container own)
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
(let [prev-zoom @refs/selected-zoom
|
||||
dom (mf/ref-node canvas)
|
||||
scroll-position (scroll/get-current-position-absolute dom)
|
||||
mouse-point @uws/viewport-mouse-position]
|
||||
mouse-point @uws/mouse-position]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(if (pos? (.-deltaY event))
|
||||
|
@ -62,7 +62,7 @@
|
|||
|
||||
(defn- subscribe
|
||||
[canvas page]
|
||||
(scroll/scroll-to-page-center (mf/ref-node canvas) page)
|
||||
;; (scroll/scroll-to-page-center (mf/ref-node canvas) page)
|
||||
(st/emit! (udp/watch-page-changes (:id page))
|
||||
(udu/watch-page-changes (:id page)))
|
||||
(let [sub (shortcuts/init)]
|
||||
|
|
|
@ -8,45 +8,46 @@
|
|||
(ns uxbox.main.ui.workspace.canvas
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[lentes.core :as l]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.shapes :as uus]
|
||||
[uxbox.main.ui.workspace.drawarea :refer [draw-area]]
|
||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
[uxbox.main.ui.workspace.streams :as uws]
|
||||
[uxbox.util.data :refer [parse-int]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
||||
;; --- Background
|
||||
(def selected-canvas
|
||||
(-> (l/key :selected-canvas)
|
||||
(l/derive refs/workspace)))
|
||||
|
||||
(mf/def background
|
||||
:mixins [mf/memo]
|
||||
:render
|
||||
(fn [own {:keys [background] :as metadata}]
|
||||
[:rect
|
||||
{:x 0 :y 0
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:fill (or background "#ffffff")}]))
|
||||
|
||||
;; --- Canvas
|
||||
(defn- make-canvas-iref
|
||||
[id]
|
||||
(-> (l/in [:canvas id])
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc canvas
|
||||
[{:keys [page wst] :as props}]
|
||||
(let [{:keys [metadata id]} page
|
||||
zoom (:zoom wst 1) ;; NOTE: maybe forward wst to draw-area
|
||||
width (:width metadata)
|
||||
height (:height metadata)]
|
||||
[:svg.page-canvas {:x c/canvas-start-x
|
||||
:y c/canvas-start-y
|
||||
:width width
|
||||
:height height}
|
||||
[:& background metadata]
|
||||
#_[:svg.page-layout
|
||||
[:g.main
|
||||
(for [id (reverse (:shapes page))]
|
||||
[:& uus/shape-component {:id id :key id}])
|
||||
(when (seq (:selected wst))
|
||||
[:& selection-handlers {:wst wst}])
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])]]]))
|
||||
[{:keys [id] :as props}]
|
||||
(letfn [(on-double-click [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dw/select-canvas id)))]
|
||||
(let [canvas-iref (mf/use-memo #(make-canvas-iref id) #js [id])
|
||||
canvas (mf/deref canvas-iref)
|
||||
selected (mf/deref selected-canvas)
|
||||
selected? (= id selected)]
|
||||
[:rect.page-canvas
|
||||
{:x (:x canvas)
|
||||
:class (when selected? "selected")
|
||||
:y (:y canvas)
|
||||
:fill (:background canvas "#ffffff")
|
||||
:width (:width canvas)
|
||||
:height (:height canvas)
|
||||
:on-double-click on-double-click}])))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -21,19 +21,27 @@
|
|||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.util.math :as mth]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :refer [seek]]
|
||||
[uxbox.util.geom.path :as path]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
(defn- rxfinalize
|
||||
[f ob]
|
||||
(.pipe ob (.finalize js/rxjs.operators f)))
|
||||
|
||||
;; --- Events
|
||||
|
||||
(declare handle-drawing)
|
||||
(declare handle-drawing-generic)
|
||||
(declare handle-drawing-path)
|
||||
(declare handle-drawing-free-path)
|
||||
(declare handle-drawing-curve)
|
||||
(declare handle-finish-drawing)
|
||||
(declare conditional-align)
|
||||
|
||||
(defn start-drawing
|
||||
[object]
|
||||
[type]
|
||||
{:pre [(keyword? type)]}
|
||||
(let [id (gensym "drawing")]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
|
@ -42,35 +50,65 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [lock (get-in state [:workspace :drawing-lock])]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
lock (get-in state [:workspace :drawing-lock])]
|
||||
(if (= lock id)
|
||||
(rx/merge (->> stream
|
||||
(rx/filter #(= % handle-finish-drawing))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
||||
(rx/of (handle-drawing object)))
|
||||
(rx/merge
|
||||
(->> (rx/filter #(= % handle-finish-drawing) stream)
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] #(update % :workspace dissoc :drawing-lock))))
|
||||
(rx/of (handle-drawing type)))
|
||||
(rx/empty)))))))
|
||||
|
||||
(defn- conditional-align [point align?]
|
||||
(if align?
|
||||
(uwrk/align-point point)
|
||||
(rx/of point)))
|
||||
(def ^:private minimal-shapes
|
||||
[{:type :rect
|
||||
:name "Rect"
|
||||
:stroke-color "#000000"}
|
||||
{:type :circle
|
||||
:name "Circle"}
|
||||
{:type :path
|
||||
:name "Path"
|
||||
:stroke-style :solid
|
||||
:stroke-color "#000000"
|
||||
:stroke-width 2
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
:segments []}
|
||||
{:type :curve
|
||||
:name "Path"
|
||||
:stroke-style :solid
|
||||
:stroke-color "#000000"
|
||||
:stroke-width 2
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
:segments []}
|
||||
{:type :text
|
||||
:name "Text"
|
||||
:content "Type your text here"}])
|
||||
|
||||
(defn- make-minimal-shape
|
||||
[type]
|
||||
(let [tool (seek #(= type (:type %)) minimal-shapes)]
|
||||
(assert tool "unexpected drawing tool")
|
||||
(assoc tool :id (uuid/random))))
|
||||
|
||||
;; TODO: maybe this should be a simple function
|
||||
(defn handle-drawing
|
||||
[shape]
|
||||
[type]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
shape (make-minimal-shape type)]
|
||||
(assoc-in state [:workspace pid :drawing] shape)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of
|
||||
(if (= :path (:type shape))
|
||||
(if (:free shape)
|
||||
(handle-drawing-free-path shape)
|
||||
(handle-drawing-path shape))
|
||||
(handle-drawing-generic shape))))))
|
||||
(case type
|
||||
:path (rx/of handle-drawing-path)
|
||||
:curve (rx/of handle-drawing-curve)
|
||||
(rx/of handle-drawing-generic)))))
|
||||
|
||||
(defn- handle-drawing-generic
|
||||
[shape]
|
||||
(def handle-drawing-generic
|
||||
(letfn [(initialize-drawing [state point]
|
||||
(let [pid (get-in state [:workspace :current])
|
||||
shape (get-in state [:workspace pid :drawing])
|
||||
|
@ -114,11 +152,11 @@
|
|||
stoper (->> (rx/filter #(or (uws/mouse-up? %) (= % :interrupt)) stream)
|
||||
(rx/take 1))
|
||||
|
||||
mouse (->> uws/viewport-mouse-position
|
||||
mouse (->> uws/mouse-position
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/with-latest vector uws/mouse-position-ctrl))]
|
||||
(rx/concat
|
||||
(->> uws/viewport-mouse-position
|
||||
(->> uws/mouse-position
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(conditional-align % align?))
|
||||
(rx/map (fn [pt] #(initialize-drawing % pt))))
|
||||
|
@ -127,8 +165,7 @@
|
|||
(rx/take-until stoper))
|
||||
(rx/of handle-finish-drawing)))))))
|
||||
|
||||
(defn handle-drawing-path
|
||||
[shape]
|
||||
(def handle-drawing-path
|
||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||
(or (= event :interrupt)
|
||||
(and (uws/mouse-event? event)
|
||||
|
@ -166,12 +203,12 @@
|
|||
flags (get-in state [:workspace pid :flags])
|
||||
align? (refs/alignment-activated? flags)
|
||||
|
||||
last-point (volatile! @uws/viewport-mouse-position)
|
||||
last-point (volatile! @uws/mouse-position)
|
||||
|
||||
stoper (->> (rx/filter stoper-event? stream)
|
||||
(rx/take 1))
|
||||
(rx/share))
|
||||
|
||||
mouse (->> (rx/sample 10 uws/viewport-mouse-position)
|
||||
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?)))
|
||||
|
||||
points (->> stream
|
||||
|
@ -186,7 +223,6 @@
|
|||
(rx/with-latest vector counter)
|
||||
(rx/map flatten))
|
||||
|
||||
|
||||
imm-transform #(vector (- % 7) (+ % 7) %)
|
||||
immanted-zones (vec (concat
|
||||
(map imm-transform (range 0 181 15))
|
||||
|
@ -205,8 +241,8 @@
|
|||
|
||||
(->> points
|
||||
(rx/take-until stoper)
|
||||
(rx/map (fn [pt]
|
||||
#(insert-point-segment % pt))))
|
||||
(rx/map (fn [pt]#(insert-point-segment % pt))))
|
||||
|
||||
(rx/concat
|
||||
(->> stream'
|
||||
(rx/map (fn [[point ctrl? index :as xxx]]
|
||||
|
@ -221,8 +257,7 @@
|
|||
(rx/of remove-dangling-segmnet
|
||||
handle-finish-drawing))))))))
|
||||
|
||||
(defn- handle-drawing-free-path
|
||||
[shape]
|
||||
(def handle-drawing-curve
|
||||
(letfn [(stoper-event? [{:keys [type shift] :as event}]
|
||||
(or (= event :interrupt)
|
||||
(and (uws/mouse-event? event) (= type :up))))
|
||||
|
@ -249,7 +284,7 @@
|
|||
stoper (->> (rx/filter stoper-event? stream)
|
||||
(rx/take 1))
|
||||
|
||||
mouse (->> (rx/sample 10 uws/viewport-mouse-position)
|
||||
mouse (->> (rx/sample 10 uws/mouse-position)
|
||||
(rx/mapcat #(conditional-align % align?)))]
|
||||
(rx/concat
|
||||
(rx/of initialize-drawing)
|
||||
|
@ -275,10 +310,11 @@
|
|||
#(update-in % [:workspace pid :modifiers] dissoc (:id shape))
|
||||
|
||||
;; Unselect the drawing tool
|
||||
#(update-in % [:workspace pid] dissoc :drawing :drawing-tool)
|
||||
;; TODO; maybe a specific event for clear draw-tool
|
||||
dw/clear-drawing
|
||||
|
||||
;; Add & select the cred shape to the workspace
|
||||
(ds/add-shape shape)
|
||||
(dw/add-shape (dissoc shape ::initialized?))
|
||||
(dw/select-first-shape)))
|
||||
(rx/of #(update-in % [:workspace pid] dissoc :drawing :drawing-tool)))))))
|
||||
|
||||
|
@ -296,8 +332,8 @@
|
|||
|
||||
(mf/defc draw-area
|
||||
[{:keys [zoom shape modifiers] :as props}]
|
||||
(if (= (:type shape) :path)
|
||||
[:& path-draw-area {:shape shape}]
|
||||
(case (:type shape)
|
||||
(:path :curve) [:& path-draw-area {:shape shape}]
|
||||
[:& generic-draw-area {:shape (assoc shape :modifiers modifiers)
|
||||
:zoom zoom}]))
|
||||
|
||||
|
@ -328,7 +364,7 @@
|
|||
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
||||
[:g
|
||||
(shapes/render-shape shape)
|
||||
(when-not (:free shape)
|
||||
(when (not= :curve (:type shape))
|
||||
[:circle.close-bezier
|
||||
{:cx x
|
||||
:cy y
|
||||
|
@ -336,3 +372,8 @@
|
|||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}])])))
|
||||
|
||||
(defn- conditional-align [point align?]
|
||||
(if align?
|
||||
(uwrk/align-point point)
|
||||
(rx/of point)))
|
||||
|
|
|
@ -11,9 +11,7 @@
|
|||
[beicon.core :as rx]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -42,11 +40,11 @@
|
|||
(let [result (geom/resize-shape vid shape point lock?)
|
||||
scale (geom/calculate-scale-ratio shape result)
|
||||
mtx (geom/generate-resize-matrix vid shape scale)
|
||||
xfm (map #(udw/apply-temporal-resize % mtx))]
|
||||
xfm (map #(dw/apply-temporal-resize % mtx))]
|
||||
(apply st/emit! (sequence xfm ids))))
|
||||
|
||||
(on-end []
|
||||
(apply st/emit! (map udw/apply-resize ids)))
|
||||
(apply st/emit! (map dw/apply-resize ids)))
|
||||
|
||||
;; Unifies the instantaneous proportion lock modifier
|
||||
;; activated by Ctrl key and the shapes own proportion
|
||||
|
@ -68,10 +66,10 @@
|
|||
|
||||
(let [shape (->> (geom/shape->rect-shape shape)
|
||||
(geom/size))
|
||||
stoper (->> ws/interaction-events
|
||||
stoper (->> st/stream
|
||||
(rx/filter ws/mouse-up?)
|
||||
(rx/take 1))
|
||||
stream (->> ws/viewport-mouse-position
|
||||
stream (->> ws/mouse-position
|
||||
(rx/take-until stoper)
|
||||
(rx/map apply-zoom)
|
||||
(rx/mapcat apply-grid-alignment)
|
||||
|
@ -160,22 +158,23 @@
|
|||
(letfn [(on-mouse-down [event index]
|
||||
(dom/stop-propagation event)
|
||||
|
||||
(let [stoper (get-edition-stream-stoper ws/interaction-events)
|
||||
;; TODO: this need code ux refactor
|
||||
(let [stoper (get-edition-stream-stoper)
|
||||
stream (rx/take-until stoper ws/mouse-position-deltas)]
|
||||
(when @refs/selected-alignment
|
||||
(st/emit! (uds/initial-path-point-align (:id shape) index)))
|
||||
(st/emit! (dw/initial-path-point-align (:id shape) index)))
|
||||
(rx/subscribe stream #(on-handler-move % index))))
|
||||
|
||||
(get-edition-stream-stoper [stream]
|
||||
(get-edition-stream-stoper []
|
||||
(let [stoper? #(and (ws/mouse-event? %) (= (:type %) :up))]
|
||||
(rx/merge
|
||||
(rx/filter stoper? stream)
|
||||
(->> stream
|
||||
(rx/filter stoper? st/stream)
|
||||
(->> st/stream
|
||||
(rx/filter #(= % :interrupt))
|
||||
(rx/take 1)))))
|
||||
|
||||
(on-handler-move [delta index]
|
||||
(st/emit! (uds/update-path (:id shape) index delta)))]
|
||||
(st/emit! (dw/update-path (:id shape) index delta)))]
|
||||
|
||||
(let [displacement (:displacement modifiers)
|
||||
segments (cond->> (:segments shape)
|
||||
|
|
|
@ -12,10 +12,7 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.lightbox :as dl]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.undo :as udu]
|
||||
[uxbox.main.data.history :as udh]
|
||||
[uxbox.main.ui.workspace.sidebar.drawtools :as wsd])
|
||||
[uxbox.main.data.undo :as du])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes
|
||||
goog.ui.KeyboardShortcutHandler
|
||||
|
@ -27,26 +24,24 @@
|
|||
|
||||
(defonce +shortcuts+
|
||||
{:shift+g #(st/emit! (dw/toggle-flag :grid))
|
||||
:ctrl+g #(st/emit! (uds/group-selected))
|
||||
:ctrl+shift+g #(st/emit! (uds/ungroup-selected))
|
||||
:ctrl+shift+m #(st/emit! (dw/toggle-flag :sitemap))
|
||||
:ctrl+shift+f #(st/emit! (dw/toggle-flag :drawtools))
|
||||
:ctrl+shift+i #(st/emit! (dw/toggle-flag :icons))
|
||||
:ctrl+shift+l #(st/emit! (dw/toggle-flag :layers))
|
||||
:ctrl+0 #(st/emit! (dw/reset-zoom))
|
||||
:ctrl+r #(st/emit! (dw/toggle-flag :ruler))
|
||||
:ctrl+d #(st/emit! (uds/duplicate-selected))
|
||||
:ctrl+d #(st/emit! dw/duplicate-selected)
|
||||
:ctrl+c #(st/emit! (dw/copy-to-clipboard))
|
||||
:ctrl+v #(st/emit! (dw/paste-from-clipboard))
|
||||
:ctrl+shift+v #(dl/open! :clipboard)
|
||||
:ctrl+z #(st/emit! (udu/undo))
|
||||
:ctrl+shift+z #(st/emit! (udu/redo))
|
||||
:ctrl+y #(st/emit! (udu/redo))
|
||||
:ctrl+b #(st/emit! (dw/select-for-drawing wsd/+draw-tool-rect+))
|
||||
:ctrl+e #(st/emit! (dw/select-for-drawing wsd/+draw-tool-circle+))
|
||||
:ctrl+t #(st/emit! (dw/select-for-drawing wsd/+draw-tool-text+))
|
||||
:ctrl+z #(st/emit! (du/undo))
|
||||
:ctrl+shift+z #(st/emit! (du/redo))
|
||||
:ctrl+y #(st/emit! (du/redo))
|
||||
:ctrl+b #(st/emit! (dw/select-for-drawing :rect))
|
||||
:ctrl+e #(st/emit! (dw/select-for-drawing :circle))
|
||||
:ctrl+t #(st/emit! (dw/select-for-drawing :text))
|
||||
:esc #(st/emit! (dw/deselect-all))
|
||||
:delete #(st/emit! (dw/delete-selected))
|
||||
:delete #(st/emit! dw/delete-selected)
|
||||
:ctrl+up #(st/emit! (dw/move-selected-layer :up))
|
||||
:ctrl+down #(st/emit! (dw/move-selected-layer :down))
|
||||
:ctrl+shift+up #(st/emit! (dw/move-selected-layer :top))
|
||||
|
|
|
@ -21,17 +21,16 @@
|
|||
(mf/defc left-sidebar
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [flags page] :as props}]
|
||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||
[:> rdnd/provider {:backend rdnd/html5}
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:project-id (:project page)
|
||||
:current-page-id (:id page)
|
||||
:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page}])]]])
|
||||
[:aside.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:project-id (:project page)
|
||||
:current-page-id (:id page)
|
||||
:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page}])]])
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
|
@ -43,6 +42,5 @@
|
|||
[:& draw-toolbox {:flags flags}])
|
||||
(when (contains? flags :element-options)
|
||||
[:& options-toolbox {:page page}])
|
||||
(when (contains? flags :icons)
|
||||
#_(icons-toolbox))]])
|
||||
|
||||
#_(when (contains? flags :icons)
|
||||
(icons-toolbox))]])
|
||||
|
|
|
@ -17,96 +17,69 @@
|
|||
|
||||
;; --- Constants
|
||||
|
||||
(def +draw-tool-rect+
|
||||
{:type :rect
|
||||
:id (uuid/random)
|
||||
:name "Rect"
|
||||
:stroke-color "#000000"})
|
||||
|
||||
(def +draw-tool-circle+
|
||||
{:type :circle
|
||||
:id (uuid/random)
|
||||
:name "Circle"})
|
||||
|
||||
(def +draw-tool-path+
|
||||
{:type :path
|
||||
:id (uuid/random)
|
||||
:name "Path"
|
||||
:stroke-style :solid
|
||||
:stroke-color "#000000"
|
||||
:stroke-width 2
|
||||
:fill-color "#000000"
|
||||
:fill-opacity 0
|
||||
;; :close? true
|
||||
:points []})
|
||||
|
||||
(def +draw-tool-curve+
|
||||
(assoc +draw-tool-path+
|
||||
:id (uuid/random)
|
||||
:free true))
|
||||
|
||||
(def +draw-tool-text+
|
||||
{:type :text
|
||||
:id (uuid/random)
|
||||
:name "Text"
|
||||
:content "Hello world"})
|
||||
|
||||
(def +draw-tools+
|
||||
[{:icon i/box
|
||||
:help "ds.help.rect"
|
||||
:shape +draw-tool-rect+
|
||||
:type :rect
|
||||
:priority 1}
|
||||
{:icon i/circle
|
||||
:help "ds.help.circle"
|
||||
:shape +draw-tool-circle+
|
||||
:type :circle
|
||||
:priority 2}
|
||||
{:icon i/text
|
||||
:help "ds.help.text"
|
||||
:shape +draw-tool-text+
|
||||
:type :text
|
||||
:priority 4}
|
||||
{:icon i/curve
|
||||
:help "ds.help.path"
|
||||
:shape +draw-tool-path+
|
||||
:type :path
|
||||
:priority 5}
|
||||
{:icon i/pencil
|
||||
:help "ds.help.curve"
|
||||
:shape +draw-tool-curve+
|
||||
:priority 6}])
|
||||
:type :curve
|
||||
:priority 6}
|
||||
;; TODO: we need an icon for canvas creation
|
||||
{:icon i/box
|
||||
:help "ds.help.canvas"
|
||||
:type :canvas
|
||||
:priority 7}])
|
||||
|
||||
;; --- Draw Toolbox (Component)
|
||||
|
||||
(mf/defc draw-toolbox
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [flags] :as props}]
|
||||
(let [close #(st/emit! (dw/toggle-flag :drawtools))
|
||||
dtool (mf/deref refs/selected-drawing-tool)
|
||||
tools (->> (into [] +draw-tools+)
|
||||
(sort-by (comp :priority second)))
|
||||
(letfn [(close [event]
|
||||
(st/emit! (dw/deactivate-flag :drawtools)))
|
||||
(select [event tool]
|
||||
(st/emit! :interrupt
|
||||
(dw/deactivate-ruler)
|
||||
(dw/select-for-drawing tool)))
|
||||
(toggle-ruler [event]
|
||||
(st/emit! (dw/select-for-drawing nil)
|
||||
(dw/deselect-all)
|
||||
(dw/toggle-ruler)))]
|
||||
|
||||
select-drawtool #(st/emit! :interrupt
|
||||
(dw/deactivate-ruler)
|
||||
(dw/select-for-drawing %))
|
||||
toggle-ruler #(st/emit! (dw/select-for-drawing nil)
|
||||
(dw/deselect-all)
|
||||
(dw/toggle-ruler))]
|
||||
(let [selected (mf/deref refs/selected-drawing-tool)
|
||||
tools (sort-by (comp :priority second) +draw-tools+)]
|
||||
[:div.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
(for [item tools]
|
||||
(let [selected? (= (:type item) selected)]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr (:help item))
|
||||
:class (when selected? "selected")
|
||||
:key (:type item)
|
||||
:on-click #(select % (:type item))}
|
||||
(:icon item)]))
|
||||
|
||||
[:div#form-tools.tool-window.drawing-tools
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/window]
|
||||
[:span (tr "ds.draw-tools")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
(for [[i props] (map-indexed vector tools)]
|
||||
(let [selected? (= dtool (:shape props))]
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr (:help props))
|
||||
:class (when selected? "selected")
|
||||
:key i
|
||||
:on-click (partial select-drawtool (:shape props))}
|
||||
(:icon props)]))
|
||||
[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr "ds.help.ruler")
|
||||
:on-click toggle-ruler
|
||||
:class (when (contains? flags :ruler) "selected")}
|
||||
i/ruler-tool]]]))
|
||||
#_[:div.tool-btn.tooltip.tooltip-hover
|
||||
{:alt (tr "ds.help.ruler")
|
||||
:on-click toggle-ruler
|
||||
:class (when (contains? flags :ruler) "selected")}
|
||||
i/ruler-tool]]])))
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
|
@ -44,9 +43,10 @@
|
|||
on-blur (fn [event]
|
||||
(let [target (dom/event->target event)
|
||||
parent (.-parentNode target)
|
||||
parent (.-parentNode parent)
|
||||
name (dom/get-value target)]
|
||||
(set! (.-draggable parent) true)
|
||||
(st/emit! (uds/rename-shape (:id shape) name))
|
||||
(st/emit! (dw/rename-shape (:id shape) name))
|
||||
(swap! local assoc :edition false)))
|
||||
on-key-down (fn [event]
|
||||
(js/console.log event)
|
||||
|
@ -54,7 +54,8 @@
|
|||
(on-blur event)))
|
||||
on-click (fn [event]
|
||||
(dom/prevent-default event)
|
||||
(let [parent (.-parentNode (.-target event))]
|
||||
(let [parent (.-parentNode (.-target event))
|
||||
parent (.-parentNode parent)]
|
||||
(set! (.-draggable parent) false))
|
||||
(swap! local assoc :edition true))]
|
||||
(if (:edition @local)
|
||||
|
@ -77,18 +78,18 @@
|
|||
(let [id (:id shape)
|
||||
blocked? (:blocked shape)]
|
||||
(if blocked?
|
||||
(st/emit! (uds/unblock-shape id))
|
||||
(st/emit! (uds/block-shape id)))))
|
||||
(st/emit! (dw/unblock-shape id))
|
||||
(st/emit! (dw/block-shape id)))))
|
||||
|
||||
(toggle-visibility [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [id (:id shape)
|
||||
hidden? (:hidden shape)]
|
||||
(if hidden?
|
||||
(st/emit! (uds/show-shape id))
|
||||
(st/emit! (uds/hide-shape id)))
|
||||
(st/emit! (dw/show-shape id))
|
||||
(st/emit! (dw/hide-shape id)))
|
||||
(when (contains? selected id)
|
||||
(st/emit! (udw/select-shape id)))))
|
||||
(st/emit! (dw/select-shape id)))))
|
||||
|
||||
(select-shape [event]
|
||||
(dom/prevent-default event)
|
||||
|
@ -99,24 +100,20 @@
|
|||
nil
|
||||
|
||||
(.-ctrlKey event)
|
||||
(st/emit! (udw/select-shape id))
|
||||
(st/emit! (dw/select-shape id))
|
||||
|
||||
(> (count selected) 1)
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id))
|
||||
|
||||
(contains? selected id)
|
||||
(st/emit! (udw/select-shape id))
|
||||
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id))
|
||||
:else
|
||||
(st/emit! (udw/deselect-all)
|
||||
(udw/select-shape id)))))
|
||||
(st/emit! (dw/deselect-all)
|
||||
(dw/select-shape id)))))
|
||||
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/persist-page (:page shape))))
|
||||
|
||||
(on-hover [item monitor]
|
||||
(st/emit! (udw/change-shape-order {:id (:shape-id item)
|
||||
(st/emit! (dw/change-shape-order {:id (:shape-id item)
|
||||
:index index})))]
|
||||
(let [selected? (contains? selected (:id shape))
|
||||
[dprops dnd-ref] (use-sortable
|
||||
|
@ -132,8 +129,7 @@
|
|||
:dragging-TODO (:dragging? dprops))}
|
||||
[:div.element-list-body {:class (classnames :selected selected?)
|
||||
:on-click select-shape
|
||||
:on-double-click #(dom/stop-propagation %)
|
||||
:draggable true}
|
||||
:on-double-click #(dom/stop-propagation %)}
|
||||
[:div.element-actions
|
||||
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||
:on-click toggle-visibility}
|
||||
|
@ -144,20 +140,52 @@
|
|||
[:div.element-icon (element-icon shape)]
|
||||
[:& layer-name {:shape shape}]]])))
|
||||
|
||||
|
||||
;; --- Layer Canvas
|
||||
|
||||
;; (mf/defc layer-canvas
|
||||
;; [{:keys [canvas selected index] :as props}]
|
||||
;; (letfn [(select-shape [event]
|
||||
;; (dom/prevent-default event)
|
||||
;; (st/emit! (dw/select-canvas (:id canvas))))
|
||||
;; (let [selected? (contains? selected (:id shape))]
|
||||
;; [:li {:class (classnames
|
||||
;; :selected selected?)}
|
||||
;; [:div.element-list-body {:class (classnames :selected selected?)
|
||||
;; :on-click select-shape
|
||||
;; :on-double-click #(dom/stop-propagation %)
|
||||
;; :draggable true}
|
||||
;; [:div.element-actions
|
||||
;; [:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||
;; :on-click toggle-visibility}
|
||||
;; i/eye]
|
||||
;; [:div.block-element {:class (when (:blocked shape) "selected")
|
||||
;; :on-click toggle-blocking}
|
||||
;; i/lock]]
|
||||
;; [:div.element-icon (element-icon shape)]
|
||||
;; [:& layer-name {:shape shape}]]])))
|
||||
|
||||
;; --- Layers List
|
||||
|
||||
(def ^:private shapes-iref
|
||||
(-> (l/key :shapes)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private canvas-iref
|
||||
(-> (l/key :canvas)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc layers-list
|
||||
[{:keys [shapes selected] :as props}]
|
||||
(let [shapes-map (mf/deref shapes-iref)]
|
||||
(let [shapes-map (mf/deref shapes-iref)
|
||||
canvas-map (mf/deref canvas-iref)
|
||||
selected-shapes (mf/deref refs/selected-shapes)
|
||||
selected-canvas (mf/deref refs/selected-canvas)]
|
||||
[:div.tool-window-content
|
||||
[:ul.element-list
|
||||
(for [[index id] (map-indexed vector shapes)]
|
||||
[:& layer-item {:shape (get shapes-map id)
|
||||
:selected selected
|
||||
:selected selected-shapes
|
||||
:index index
|
||||
:key id}])]]))
|
||||
|
||||
|
@ -165,7 +193,7 @@
|
|||
|
||||
(mf/defc layers-toolbox
|
||||
[{:keys [page selected] :as props}]
|
||||
(let [on-click #(st/emit! (udw/toggle-flag :layers))
|
||||
(let [on-click #(st/emit! (dw/toggle-flag :layers))
|
||||
selected (mf/deref refs/selected-shapes)]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
|
|
@ -93,14 +93,14 @@
|
|||
value (parse-int value 0)
|
||||
sid (:id shape)
|
||||
props {attr value}]
|
||||
(st/emit! (uds/update-dimensions sid props))))
|
||||
(st/emit! (udw/update-dimensions sid props))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(st/emit! (uds/update-rotation sid value))))
|
||||
(st/emit! (udw/update-shape-attrs sid {:rotation value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
|
@ -108,11 +108,11 @@
|
|||
value (parse-int value nil)
|
||||
sid (:id shape)
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position sid point))))
|
||||
(st/emit! (udw/update-position sid point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||
|
||||
|
|
|
@ -90,14 +90,13 @@
|
|||
value (parse-int value 0)
|
||||
sid (:id shape)
|
||||
props {attr value}]
|
||||
(st/emit! (uds/update-dimensions sid props))))
|
||||
(st/emit! (udw/update-dimensions sid props))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
sid (:id shape)]
|
||||
(st/emit! (uds/update-rotation sid value))))
|
||||
value (parse-int value 0)]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
|
@ -105,11 +104,11 @@
|
|||
value (parse-int value nil)
|
||||
sid (:id shape)
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position sid point))))
|
||||
(st/emit! (udw/update-position sid point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer (parse-int parse-float read-string)]
|
||||
|
@ -106,30 +105,30 @@
|
|||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)
|
||||
props {attr value}]
|
||||
(st/emit! (uds/update-dimensions (:id shape) props))))
|
||||
(st/emit! (dw/update-dimensions (:id shape) props))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)]
|
||||
(st/emit! (uds/update-rotation (:id shape) value))))
|
||||
(st/emit! (dw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||
|
||||
(defn- on-opacity-change
|
||||
[event shape]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-float value 1)
|
||||
value (/ value 10000)]
|
||||
(st/emit! (uds/update-attrs (:id shape) {:opacity value}))))
|
||||
(st/emit! (dw/update-shape-attrs (:id shape) {:opacity value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value nil)
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position (:id shape) point))))
|
||||
(st/emit! (dw/update-position (:id shape) point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (dw/unlock-proportions (:id shape)))
|
||||
(st/emit! (dw/lock-proportions (:id shape)))))
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.colorpicker :as cp]
|
||||
|
@ -59,7 +59,7 @@
|
|||
(delete [item]
|
||||
(let [sid (:id shape)
|
||||
id (:id item)]
|
||||
(st/emit! (uds/delete-interaction sid id))))
|
||||
(st/emit! (dw/delete-interaction sid id))))
|
||||
(on-delete [item event]
|
||||
(dom/prevent-default event)
|
||||
(let [delete (partial delete item)]
|
||||
|
@ -455,7 +455,7 @@
|
|||
(dom/prevent-default event)
|
||||
(let [sid (:id shape)
|
||||
data (deref form)]
|
||||
(st/emit! (uds/update-interaction sid data))
|
||||
(st/emit! (dw/update-interaction sid data))
|
||||
(reset! form nil)))
|
||||
(on-cancel [event]
|
||||
(dom/prevent-default event)
|
||||
|
|
|
@ -87,23 +87,23 @@
|
|||
[event shape attr]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int 0))]
|
||||
(st/emit! (uds/update-dimensions (:id shape) {attr value}))))
|
||||
(st/emit! (udw/update-dimensions (:id shape) {attr value}))))
|
||||
|
||||
(defn- on-rotation-change
|
||||
[event shape]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int 0))]
|
||||
(st/emit! (uds/update-rotation (:id shape) value))))
|
||||
(let [value (dom/event->value event)
|
||||
value (parse-int value 0)]
|
||||
(st/emit! (udw/update-shape-attrs (:id shape) {:rotation value}))))
|
||||
|
||||
(defn- on-position-change
|
||||
[event shape attr]
|
||||
(let [value (-> (dom/event->value event)
|
||||
(parse-int nil))
|
||||
point (gpt/point {attr value})]
|
||||
(st/emit! (uds/update-position (:id shape) point))))
|
||||
(st/emit! (udw/update-position (:id shape) point))))
|
||||
|
||||
(defn- on-proportion-lock-change
|
||||
[event shape]
|
||||
(if (:proportion-lock shape)
|
||||
(st/emit! (uds/unlock-proportions (:id shape)))
|
||||
(st/emit! (uds/lock-proportions (:id shape)))))
|
||||
(st/emit! (udw/unlock-proportions (:id shape)))
|
||||
(st/emit! (udw/lock-proportions (:id shape)))))
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
{:mixins [mx/static]}
|
||||
[menu {:keys [id] :as shape}]
|
||||
(letfn [(update-attrs [attrs]
|
||||
(st/emit! (uds/update-attrs id attrs)))
|
||||
(st/emit! (udw/update-shape-attrs id attrs)))
|
||||
(on-font-family-change [event]
|
||||
(let [value (dom/event->value event)
|
||||
attrs {:font-family (read-string value)
|
||||
|
|
|
@ -52,21 +52,7 @@
|
|||
(and (mouse-event? v)
|
||||
(= :click (:type v))))
|
||||
|
||||
(defrecord PointerEvent [window
|
||||
viewport
|
||||
ctrl
|
||||
shift])
|
||||
|
||||
(defn pointer-event
|
||||
[window viewport ctrl shift]
|
||||
{:pre [(gpt/point? window)
|
||||
(gpt/point? viewport)
|
||||
(boolean? ctrl)
|
||||
(boolean? shift)]}
|
||||
(PointerEvent. window
|
||||
viewport
|
||||
ctrl
|
||||
shift))
|
||||
(defrecord PointerEvent [source pt ctrl shift])
|
||||
|
||||
(defn pointer-event?
|
||||
[v]
|
||||
|
@ -90,23 +76,14 @@
|
|||
|
||||
;; --- Derived streams
|
||||
|
||||
;; TODO: this shoul be DEPRECATED
|
||||
(defonce interaction-events
|
||||
(rx/filter interaction-event? st/stream))
|
||||
|
||||
(defonce mouse-position
|
||||
(rx/filter pointer-event? st/stream))
|
||||
|
||||
(defonce viewport-mouse-position
|
||||
(let [sub (rx/behavior-subject nil)]
|
||||
(-> (rx/map :viewport mouse-position)
|
||||
(rx/subscribe-with sub))
|
||||
sub))
|
||||
|
||||
(defonce window-mouse-position
|
||||
(let [sub (rx/behavior-subject nil)]
|
||||
(-> (rx/map :window mouse-position)
|
||||
(rx/subscribe-with sub))
|
||||
(let [sub (rx/behavior-subject nil)
|
||||
ob (->> st/stream
|
||||
(rx/filter pointer-event?)
|
||||
(rx/filter #(= :viewport (:source %)))
|
||||
(rx/map :pt)
|
||||
)]
|
||||
(rx/subscribe-with ob sub)
|
||||
sub))
|
||||
|
||||
(defonce mouse-position-ctrl
|
||||
|
@ -116,7 +93,7 @@
|
|||
sub))
|
||||
|
||||
(defonce mouse-position-deltas
|
||||
(->> viewport-mouse-position
|
||||
(->> mouse-position
|
||||
(rx/sample 10)
|
||||
(rx/map #(gpt/divide % @refs/selected-zoom))
|
||||
(rx/mapcat (fn [point]
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
(mf/defc coordinates
|
||||
[{:keys [zoom] :as props}]
|
||||
(let [coords (some-> (use-rxsub uws/viewport-mouse-position)
|
||||
(let [coords (some-> (use-rxsub uws/mouse-position)
|
||||
(gpt/divide zoom)
|
||||
(gpt/round 0))]
|
||||
[:ul.coordinates
|
||||
|
@ -60,16 +60,16 @@
|
|||
:circle "Drag to draw a Circle"
|
||||
nil))
|
||||
|
||||
(mf/defc cursor-tooltip
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [tooltip]}]
|
||||
(let [coords (mf/deref refs/window-mouse-position)]
|
||||
[:span.cursor-tooltip
|
||||
{:style
|
||||
{:position "fixed"
|
||||
:left (str (+ (:x coords) 5) "px")
|
||||
:top (str (- (:y coords) 25) "px")}}
|
||||
tooltip]))
|
||||
;; (mf/defc cursor-tooltip
|
||||
;; {:wrap [mf/wrap-memo]}
|
||||
;; [{:keys [tooltip]}]
|
||||
;; (let [coords (mf/deref refs/window-mouse-position)]
|
||||
;; [:span.cursor-tooltip
|
||||
;; {:style
|
||||
;; {:position "fixed"
|
||||
;; :left (str (+ (:x coords) 5) "px")
|
||||
;; :top (str (- (:y coords) 25) "px")}}
|
||||
;; tooltip]))
|
||||
|
||||
;; --- Selection Rect
|
||||
|
||||
|
@ -89,15 +89,13 @@
|
|||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (->> (rx/merge (rx/filter #(= % :interrupt) stream)
|
||||
(rx/filter uws/mouse-up? stream))
|
||||
(rx/take 1))]
|
||||
(let [stoper (rx/filter #(or (dw/interrupt? %) (uws/mouse-up? %)) stream)]
|
||||
(rx/concat
|
||||
(->> uws/viewport-mouse-position
|
||||
(rx/of (dw/deselect-all))
|
||||
(->> uws/mouse-position
|
||||
(rx/map (fn [pos] #(update-state % pos)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of (dw/deselect-all)
|
||||
dw/select-shapes-by-current-selrect
|
||||
(rx/of dw/select-shapes-by-current-selrect
|
||||
clear-state)))))))
|
||||
|
||||
(mf/defc selrect
|
||||
|
@ -115,33 +113,187 @@
|
|||
;; --- Viewport Positioning
|
||||
|
||||
(def handle-viewport-positioning
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper (->> (rx/filter #(= ::finish-positioning %) stream)
|
||||
(rx/take 1))
|
||||
reference @uws/viewport-mouse-position
|
||||
dom (dom/get-element "workspace-viewport")]
|
||||
(->> uws/viewport-mouse-position
|
||||
(rx/map (fn [point]
|
||||
(let [{:keys [x y]} (gpt/subtract point reference)
|
||||
cx (.-scrollLeft dom)
|
||||
cy (.-scrollTop dom)]
|
||||
(set! (.-scrollLeft dom) (- cx x))
|
||||
(set! (.-scrollTop dom) (- cy y)))))
|
||||
(rx/take-until stoper)
|
||||
(rx/ignore))))))
|
||||
(letfn [(on-point [dom reference point]
|
||||
(let [{:keys [x y]} (gpt/subtract point reference)
|
||||
cx (.-scrollLeft dom)
|
||||
cy (.-scrollTop dom)]
|
||||
(set! (.-scrollLeft dom) (- cx x))
|
||||
(set! (.-scrollTop dom) (- cy y))))]
|
||||
(reify
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [stoper (rx/filter #(= ::finish-positioning %) stream)
|
||||
reference @uws/mouse-position
|
||||
dom (dom/get-element "workspace-viewport")]
|
||||
(-> (rx/take-until stoper uws/mouse-position)
|
||||
(rx/subscribe #(on-point dom reference %))))))))
|
||||
|
||||
;; --- Viewport
|
||||
|
||||
(mf/def viewport
|
||||
(mf/defc viewport
|
||||
[{:keys [page] :as props}]
|
||||
(let [{:keys [drawing-tool tooltip zoom flags edition] :as wst} (mf/deref refs/workspace)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
tooltip (or tooltip (get-shape-tooltip drawing-tool))
|
||||
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! (uws/mouse-event :down ctrl? shift?)))
|
||||
(when (not edition)
|
||||
(if drawing-tool
|
||||
(st/emit! (start-drawing drawing-tool))
|
||||
(st/emit! :interrupt handle-selrect))))
|
||||
|
||||
(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! (uws/mouse-event :context-menu ctrl? shift?))))
|
||||
|
||||
(on-mouse-up [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
opts {:shift? shift?
|
||||
:ctrl? ctrl?}]
|
||||
(st/emit! (uws/mouse-event :up 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! (uws/mouse-event :click 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! (uws/mouse-event :double-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-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! (uws/keyboard-event :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! (uws/keyboard-event :up key ctrl? shift?))))
|
||||
|
||||
(on-mouse-move [event]
|
||||
(let [pt (gpt/point (.-clientX event)
|
||||
(.-clientY event))
|
||||
pt (translate-point-to-viewport pt)]
|
||||
;; (prn "viewport:on-mouse-move" pt)
|
||||
(st/emit! (uws/->PointerEvent :viewport pt (kbd/ctrl? event) (kbd/shift? event)))))
|
||||
|
||||
|
||||
;; ;; ctrl? (kbd/ctrl? event)
|
||||
;; ;; shift? (kbd/shift? event)
|
||||
;; ;; event {:ctrl ctrl?
|
||||
;; ;; :shift shift?
|
||||
;; ;; :window-coords wpt
|
||||
;; ;; :viewport-coords vpt}
|
||||
;; ]
|
||||
;; #_(st/emit! (uws/pointer-event wpt vpt ctrl? shift?))))
|
||||
|
||||
(on-mount []
|
||||
(prn "viewport.on-mount" (:id page))
|
||||
(let [
|
||||
;; key1 (events/listen js/document EventType.MOUSEMOVE on-mousemove)
|
||||
key2 (events/listen js/document EventType.KEYDOWN on-key-down)
|
||||
key3 (events/listen js/document EventType.KEYUP on-key-up)]
|
||||
(fn []
|
||||
;; (events/unlistenByKey key1)
|
||||
(events/unlistenByKey key2)
|
||||
(events/unlistenByKey key3))))]
|
||||
|
||||
(mf/use-effect on-mount)
|
||||
;; (prn "viewport.render" (:id page))
|
||||
|
||||
[:*
|
||||
[:& coordinates {:zoom zoom}]
|
||||
#_[:div.tooltip-container
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:tooltip tooltip}])]
|
||||
[: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 ")")}
|
||||
(when page
|
||||
[:*
|
||||
(for [id (:canvas page)]
|
||||
[:& canvas {:key id :id id}])
|
||||
|
||||
(for [id (reverse (:shapes page))]
|
||||
[:& uus/shape-component {:id id :key id}])
|
||||
|
||||
(when (seq (:selected wst))
|
||||
[:& selection-handlers {:wst wst}])
|
||||
|
||||
(when-let [dshape (:drawing wst)]
|
||||
[:& draw-area {:shape dshape
|
||||
:zoom (:zoom wst)
|
||||
:modifiers (:modifiers wst)}])])
|
||||
|
||||
|
||||
|
||||
(if (contains? flags :grid)
|
||||
[:& grid {:page page}])]
|
||||
(when (contains? flags :ruler)
|
||||
[:& ruler {:zoom zoom :ruler (:ruler wst)}])
|
||||
[:& selrect {:data (:selrect wst)}]]])))
|
||||
|
||||
|
||||
#_(mf/def viewport
|
||||
:init
|
||||
(fn [own props]
|
||||
(assoc own ::viewport (mf/create-ref)))
|
||||
|
||||
:did-mount
|
||||
(fn [own]
|
||||
(letfn [(translate-point-to-viewport [pt]
|
||||
(letfn [
|
||||
(translate-point-to-viewport [pt]
|
||||
(let [viewport (mf/ref-node (::viewport own))
|
||||
brect (.getBoundingClientRect viewport)
|
||||
brect (gpt/point (parse-int (.-left brect))
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
"Return a indexed map of the collection
|
||||
keyed by the result of executing the getter
|
||||
over each element of the collection."
|
||||
[coll getter]
|
||||
[getter coll]
|
||||
(persistent!
|
||||
(reduce #(assoc! %1 (getter %2) %2) (transient {}) coll)))
|
||||
|
||||
(def index-by-id #(index-by % :id))
|
||||
(def index-by-id #(index-by :id %))
|
||||
|
||||
(defn remove-nil-vals
|
||||
"Given a map, return a map removing key-value
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue