0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 07:58:49 -05:00

🎉 Duplicate move

This commit is contained in:
alonso.torres 2020-06-02 15:49:18 +02:00
parent d6c97f9d19
commit 174b9db1d2
10 changed files with 428 additions and 320 deletions

View file

@ -25,6 +25,7 @@
[uxbox.main.data.workspace.persistence :as dwp]
[uxbox.main.data.workspace.texts :as dwtxt]
[uxbox.main.data.workspace.transforms :as dwt]
[uxbox.main.data.workspace.selection :as dws]
[uxbox.main.repo :as rp]
[uxbox.main.store :as st]
[uxbox.main.streams :as ms]
@ -438,101 +439,6 @@
(assoc :zoom zoom)
(update :vbox merge srect)))))))))))
;; --- Selection Rect
(declare select-shapes-by-current-selrect)
(declare deselect-all)
(defn update-selrect
[selrect]
(ptk/reify ::update-selrect
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selrect] selrect))))
(def handle-selection
(letfn [(data->selrect [data]
(let [start (:start data)
stop (:stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
end-x (max (:x start) (:x stop))
end-y (max (:y start) (:y stop))]
{:type :rect
:x start-x
:y start-y
:width (mth/abs (- end-x start-x))
:height (mth/abs (- end-y start-y))}))]
(ptk/reify ::handle-selection
ptk/WatchEvent
(watch [_ state stream]
(let [stoper (rx/filter #(or (interrupt? %)
(ms/mouse-up? %))
stream)]
(rx/concat
(rx/of deselect-all)
(->> ms/mouse-position
(rx/scan (fn [data pos]
(if data
(assoc data :stop pos)
{:start pos :stop pos}))
nil)
(rx/map data->selrect)
(rx/filter #(or (> (:width %) 10)
(> (:height %) 10)))
(rx/map update-selrect)
(rx/take-until stoper))
(rx/of select-shapes-by-current-selrect)))))))
;; --- Toggle shape's selection status (selected or deselected)
(declare expand-all-parents)
(defn select-shape
([id] (select-shape id false))
([id toggle?]
(us/verify ::us/uuid id)
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected]
(fn [selected]
(if-not toggle?
(conj selected id)
(if (contains? selected id)
(disj selected id)
(conj selected id))))))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])]
(rx/of (expand-all-parents [id] objects)))))))
(defn select-shapes
[ids]
(us/verify ::set-of-uuid ids)
(ptk/reify ::select-shapes
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected] ids))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])]
(rx/of (expand-all-parents ids objects))))))
(def deselect-all
"Clear all possible state of drawing, edition
or any similar action taken by the user."
(ptk/reify ::deselect-all
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local #(-> %
(assoc :selected #{})
(dissoc :selected-frame))))))
;; --- Add shape to Workspace
@ -598,161 +504,12 @@
:id id}]
(rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true})
(select-shapes #{id})
(dws/select-shapes #{id})
(when (= :text (:type attrs))
(start-edition-mode id)))))))
;; --- Duplicate Shapes
(declare prepare-duplicate-changes)
(declare prepare-duplicate-change)
(declare prepare-duplicate-frame-change)
(declare prepare-duplicate-shape-change)
(def ^:private change->name #(get-in % [:obj :name]))
(defn- prepare-duplicate-changes
"Prepare objects to paste: generate new id, give them unique names,
move to the position of mouse pointer, and find in what frame they
fit."
[objects names ids delta]
(loop [names names
chgs []
id (first ids)
ids (rest ids)]
(if (nil? id)
chgs
(let [result (prepare-duplicate-change objects names id delta)
result (if (vector? result) result [result])]
(recur
(into names (map change->name) result)
(into chgs result)
(first ids)
(rest ids))))))
(defn- prepare-duplicate-change
[objects names id delta]
(let [obj (get objects id)]
(if (= :frame (:type obj))
(prepare-duplicate-frame-change objects names obj delta)
(prepare-duplicate-shape-change objects names obj delta nil nil))))
(defn- prepare-duplicate-shape-change
[objects names obj delta frame-id parent-id]
(let [id (uuid/next)
name (generate-unique-name names (:name obj))
renamed-obj (assoc obj :id id :name name)
moved-obj (geom/move renamed-obj delta)
frames (cp/select-frames objects)
frame-id (if frame-id
frame-id
(dwc/calculate-frame-overlap frames moved-obj))
parent-id (or parent-id frame-id)
children-changes
(loop [names names
result []
cid (first (:shapes obj))
cids (rest (:shapes obj))]
(if (nil? cid)
result
(let [obj (get objects cid)
changes (prepare-duplicate-shape-change objects names obj delta frame-id id)]
(recur
(into names (map change->name changes))
(into result changes)
(first cids)
(rest cids)))))
reframed-obj (-> moved-obj
(assoc :frame-id frame-id)
(dissoc :shapes))]
(into [{:type :add-obj
:id id
:old-id (:id obj)
:frame-id frame-id
:parent-id parent-id
:obj (dissoc reframed-obj :shapes)}]
children-changes)))
(defn- prepare-duplicate-frame-change
[objects names obj delta]
(let [frame-id (uuid/next)
frame-name (generate-unique-name names (:name obj))
sch (->> (map #(get objects %) (:shapes obj))
(mapcat #(prepare-duplicate-shape-change objects names % delta frame-id frame-id)))
renamed-frame (-> obj
(assoc :id frame-id)
(assoc :name frame-name)
(assoc :frame-id uuid/zero)
(dissoc :shapes))
moved-frame (geom/move renamed-frame delta)
fch {:type :add-obj
:old-id (:id obj)
:id frame-id
:frame-id uuid/zero
:obj moved-frame}]
(into [fch] sch)))
(declare select-shapes)
(def duplicate-selected
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
selected (get-in state [:workspace-local :selected])
objects (get-in state [:workspace-data page-id :objects])
delta (gpt/point 0 0)
unames (retrieve-used-names objects)
rchanges (prepare-duplicate-changes objects unames selected delta)
uchanges (mapv #(array-map :type :del-obj :id (:id %))
(reverse rchanges))
selected (->> rchanges
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into #{}))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))
;; --- Select Shapes (By selrect)
(def select-shapes-by-current-selrect
(ptk/reify ::select-shapes-by-current-selrect
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace-page :id])
selrect (get-in state [:workspace-local :selrect])]
(rx/merge
(rx/of (update-selrect nil))
(when selrect
(->> (uw/ask! {:cmd :selection/query
:page-id page-id
:rect selrect})
(rx/map select-shapes))))))))
(defn select-inside-group
[group-id position]
(ptk/reify ::select-inside-group
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data page-id :objects])
group (get objects group-id)
children (map #(get objects %) (:shapes group))
selected (->> children (filter #(geom/has-point? % position)) first)]
(when selected
(rx/of deselect-all (select-shape (:id selected))))))))
;; --- Update Shape Attrs
@ -906,7 +663,7 @@
shapes (map lookup selected)
shape? #(not= (:type %) :frame)]
(rx/of (delete-shapes selected)
deselect-all)))))
dws/deselect-all)))))
;; --- Rename Shape
@ -1186,21 +943,6 @@
(update [_ state]
(update state :workspace-local dissoc :expanded))))
(defn expand-all-parents
[ids objects]
(ptk/reify ::expand-all-parents
ptk/UpdateEvent
(update [_ state]
(let [expand-fn (fn [expanded]
(merge expanded
(->> ids
(map #(cp/get-all-parents % objects))
flatten
(filter #(not= % uuid/zero))
(map (fn [id] {id true}))
(into {}))))]
(update-in state [:workspace-local :expanded] expand-fn)))))
(defn recursive-assign
"A helper for assign recursively a shape attr."
[id attr value]
@ -1284,7 +1026,7 @@
ptk/WatchEvent
(watch [_ state stream]
(rx/of (select-shape (:id shape))))))
(rx/of (dws/select-shape (:id shape))))))
(def hide-context-menu
(ptk/reify ::hide-context-menu
@ -1342,7 +1084,7 @@
unames (-> (get-in state [:workspace-data page-id :objects])
(retrieve-used-names))
rchanges (prepare-duplicate-changes objects unames selected delta)
rchanges (dws/prepare-duplicate-changes objects unames selected delta)
uchanges (mapv #(array-map :type :del-obj :id (:id %))
(reverse rchanges))
@ -1351,7 +1093,7 @@
(map #(get-in % [:obj :id]))
(into #{}))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))
(dws/select-shapes selected))))))
(def paste
(ptk/reify ::paste
@ -1432,7 +1174,7 @@
{:type :del-obj
:id id}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes #{id}))))))))
(dws/select-shapes #{id}))))))))
(def remove-group
(ptk/reify ::remove-group
@ -1551,6 +1293,14 @@
(def delete-page dwp/delete-page)
(def create-empty-page dwp/create-empty-page)
;; Selection
(def select-shape dws/select-shape)
(def deselect-all dws/deselect-all)
(def select-shapes dws/select-shapes)
(def duplicate-selected dws/duplicate-selected)
(def handle-selection dws/handle-selection)
(def select-inside-group dws/select-inside-group)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -298,3 +298,17 @@
(update state :workspace-local dissoc :undo-index :undo))))
(defn expand-all-parents
[ids objects]
(ptk/reify ::expand-all-parents
ptk/UpdateEvent
(update [_ state]
(let [expand-fn (fn [expanded]
(merge expanded
(->> ids
(map #(cp/get-all-parents % objects))
flatten
(filter #(not= % uuid/zero))
(map (fn [id] {id true}))
(into {}))))]
(update-in state [:workspace-local :expanded] expand-fn)))))

View file

@ -0,0 +1,299 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.data.workspace.selection
(:require
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]
[uxbox.main.data.workspace.common :as dwc]
[uxbox.main.worker :as uw]
[uxbox.main.streams :as ms]
[uxbox.common.pages :as cp]
[uxbox.common.spec :as us]
[uxbox.common.uuid :as uuid]
[uxbox.common.data :as d]
[uxbox.common.geom.shapes :as geom]
[uxbox.common.geom.point :as gpt]
[uxbox.common.math :as mth]))
(s/def ::set-of-uuid
(s/every uuid? :kind set?))
(s/def ::set-of-string
(s/every string? :kind set?))
;; Duplicate from workspace.
;; FIXME: Move these functions to a common place
(defn interrupt? [e] (= e :interrupt))
(defn- retrieve-used-names
[objects]
(into #{} (map :name) (vals objects)))
(defn- extract-numeric-suffix
[basename]
(if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)]
[p1 (+ 1 (d/parse-integer p2))]
[basename 1]))
(defn- generate-unique-name
"A unique name generator"
[used basename]
(s/assert ::set-of-string used)
(s/assert ::us/string basename)
(let [[prefix initial] (extract-numeric-suffix basename)]
(loop [counter initial]
(let [candidate (str prefix "-" counter)]
(if (contains? used candidate)
(recur (inc counter))
candidate)))))
;; --- Selection Rect
(declare select-shapes-by-current-selrect)
(declare deselect-all)
(defn update-selrect
[selrect]
(ptk/reify ::update-selrect
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selrect] selrect))))
(def handle-selection
(letfn [(data->selrect [data]
(let [start (:start data)
stop (:stop data)
start-x (min (:x start) (:x stop))
start-y (min (:y start) (:y stop))
end-x (max (:x start) (:x stop))
end-y (max (:y start) (:y stop))]
{:type :rect
:x start-x
:y start-y
:width (mth/abs (- end-x start-x))
:height (mth/abs (- end-y start-y))}))]
(ptk/reify ::handle-selection
ptk/WatchEvent
(watch [_ state stream]
(let [stoper (rx/filter #(or (interrupt? %)
(ms/mouse-up? %))
stream)]
(rx/concat
(rx/of deselect-all)
(->> ms/mouse-position
(rx/scan (fn [data pos]
(if data
(assoc data :stop pos)
{:start pos :stop pos}))
nil)
(rx/map data->selrect)
(rx/filter #(or (> (:width %) 10)
(> (:height %) 10)))
(rx/map update-selrect)
(rx/take-until stoper))
(rx/of select-shapes-by-current-selrect)))))))
;; --- Toggle shape's selection status (selected or deselected)
(defn select-shape
([id] (select-shape id false))
([id toggle?]
(us/verify ::us/uuid id)
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected]
(fn [selected]
(if-not toggle?
(conj selected id)
(if (contains? selected id)
(disj selected id)
(conj selected id))))))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])]
(rx/of (dwc/expand-all-parents [id] objects)))))))
(defn select-shapes
[ids]
(us/verify ::set-of-uuid ids)
(ptk/reify ::select-shapes
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :selected] ids))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace-page :id])
objects (get-in state [:workspace-data page-id :objects])]
(rx/of (dwc/expand-all-parents ids objects))))))
(def deselect-all
"Clear all possible state of drawing, edition
or any similar action taken by the user."
(ptk/reify ::deselect-all
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local #(-> %
(assoc :selected #{})
(dissoc :selected-frame))))))
;; --- Select Shapes (By selrect)
(def select-shapes-by-current-selrect
(ptk/reify ::select-shapes-by-current-selrect
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:workspace-page :id])
selrect (get-in state [:workspace-local :selrect])]
(rx/merge
(rx/of (update-selrect nil))
(when selrect
(->> (uw/ask! {:cmd :selection/query
:page-id page-id
:rect selrect})
(rx/map select-shapes))))))))
(defn select-inside-group
[group-id position]
(ptk/reify ::select-inside-group
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (get-in state [:workspace-data page-id :objects])
group (get objects group-id)
children (map #(get objects %) (:shapes group))
selected (->> children (filter #(geom/has-point? % position)) first)]
(when selected
(rx/of deselect-all (select-shape (:id selected))))))))
;; --- Duplicate Shapes
(declare prepare-duplicate-changes)
(declare prepare-duplicate-change)
(declare prepare-duplicate-frame-change)
(declare prepare-duplicate-shape-change)
(def ^:private change->name #(get-in % [:obj :name]))
(defn- prepare-duplicate-changes
"Prepare objects to paste: generate new id, give them unique names,
move to the position of mouse pointer, and find in what frame they
fit."
[objects names ids delta]
(loop [names names
chgs []
id (first ids)
ids (rest ids)]
(if (nil? id)
chgs
(let [result (prepare-duplicate-change objects names id delta)
result (if (vector? result) result [result])]
(recur
(into names (map change->name) result)
(into chgs result)
(first ids)
(rest ids))))))
(defn- prepare-duplicate-change
[objects names id delta]
(let [obj (get objects id)]
(if (= :frame (:type obj))
(prepare-duplicate-frame-change objects names obj delta)
(prepare-duplicate-shape-change objects names obj delta nil nil))))
(defn- prepare-duplicate-shape-change
[objects names obj delta frame-id parent-id]
(let [id (uuid/next)
name (generate-unique-name names (:name obj))
renamed-obj (assoc obj :id id :name name)
moved-obj (geom/move renamed-obj delta)
frames (cp/select-frames objects)
frame-id (if frame-id
frame-id
(dwc/calculate-frame-overlap frames moved-obj))
parent-id (or parent-id frame-id)
children-changes
(loop [names names
result []
cid (first (:shapes obj))
cids (rest (:shapes obj))]
(if (nil? cid)
result
(let [obj (get objects cid)
changes (prepare-duplicate-shape-change objects names obj delta frame-id id)]
(recur
(into names (map change->name changes))
(into result changes)
(first cids)
(rest cids)))))
reframed-obj (-> moved-obj
(assoc :frame-id frame-id)
(dissoc :shapes))]
(into [{:type :add-obj
:id id
:old-id (:id obj)
:frame-id frame-id
:parent-id parent-id
:obj (dissoc reframed-obj :shapes)}]
children-changes)))
(defn- prepare-duplicate-frame-change
[objects names obj delta]
(let [frame-id (uuid/next)
frame-name (generate-unique-name names (:name obj))
sch (->> (map #(get objects %) (:shapes obj))
(mapcat #(prepare-duplicate-shape-change objects names % delta frame-id frame-id)))
renamed-frame (-> obj
(assoc :id frame-id)
(assoc :name frame-name)
(assoc :frame-id uuid/zero)
(dissoc :shapes))
moved-frame (geom/move renamed-frame delta)
fch {:type :add-obj
:old-id (:id obj)
:id frame-id
:frame-id uuid/zero
:obj moved-frame}]
(into [fch] sch)))
(def duplicate-selected
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (:current-page-id state)
selected (get-in state [:workspace-local :selected])
objects (get-in state [:workspace-data page-id :objects])
delta (gpt/point 0 0)
unames (retrieve-used-names objects)
rchanges (prepare-duplicate-changes objects unames selected delta)
uchanges (mapv #(array-map :type :del-obj :id (:id %))
(reverse rchanges))
selected (->> rchanges
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into #{}))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected))))))

View file

@ -18,6 +18,7 @@
[uxbox.common.spec :as us]
[uxbox.common.pages :as cp]
[uxbox.main.data.workspace.common :as dwc]
[uxbox.main.data.workspace.selection :as dws]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.streams :as ms]
@ -191,6 +192,7 @@
;; -- MOVE
(declare start-move)
(declare start-move-duplicate)
(defn start-move-selected
[]
@ -206,33 +208,52 @@
(rx/map #(gpt/length %))
(rx/filter #(> % 1))
(rx/take 1)
(rx/map #(start-move initial selected)))))))
(defn start-move
[from-position ids]
(ptk/reify ::start-move
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :transform] :move)))
(rx/with-latest vector ms/mouse-position-alt)
(rx/flat-map
(fn [[_ alt?]]
(if alt?
;; When alt is down we start a duplicate+move
(rx/of (start-move-duplicate initial)
dws/duplicate-selected)
;; Otherwise just plain old move
(rx/of (start-move initial selected))))))))))
(defn start-move-duplicate [from-position]
(ptk/reify ::start-move-selected
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get state :current-page-id)
objects (get-in state [:workspace-data page-id :objects])
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids)
stopper (rx/filter ms/mouse-up? stream)
layout (get state :workspace-layout)]
(rx/concat
(->> ms/mouse-position
(rx/take-until stopper)
(rx/map #(gpt/to-vec from-position %))
(rx/switch-map #(snap/closest-snap-move page-id shapes objects layout %))
(rx/map gmt/translate-matrix)
(rx/map #(set-modifiers ids {:displacement %})))
(->> stream
(rx/filter (ptk/type? ::dws/duplicate-selected))
(rx/first)
(rx/map #(start-move from-position))))))
(rx/of (apply-modifiers ids)
finish-transform))))))
(defn start-move
([from-position] (start-move from-position nil))
([from-position ids]
(ptk/reify ::start-move
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :transform] :move)))
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get state :current-page-id)
objects (get-in state [:workspace-data page-id :objects])
ids (if (nil? ids) (get-in state [:workspace-local :selected]) ids)
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids)
stopper (rx/filter ms/mouse-up? stream)
layout (get state :workspace-layout)]
(rx/concat
(->> ms/mouse-position
(rx/take-until stopper)
(rx/map #(gpt/to-vec from-position %))
(rx/switch-map #(snap/closest-snap-move page-id shapes objects layout %))
(rx/map gmt/translate-matrix)
(rx/map #(set-modifiers ids {:displacement %})))
(rx/of (apply-modifiers ids)
finish-transform)))))))
(defn- get-displacement-with-grid
"Retrieve the correct displacement delta point for the

View file

@ -14,13 +14,13 @@
;; --- User Events
(defrecord KeyboardEvent [type key shift ctrl])
(defrecord KeyboardEvent [type key shift ctrl alt])
(defn keyboard-event?
[v]
(instance? KeyboardEvent v))
(defrecord MouseEvent [type ctrl shift])
(defrecord MouseEvent [type ctrl shift alt])
(defn mouse-event?
[v]
@ -36,7 +36,7 @@
(and (mouse-event? v)
(= :click (:type v))))
(defrecord PointerEvent [source pt ctrl shift])
(defrecord PointerEvent [source pt ctrl shift alt])
(defn pointer-event?
[v]
@ -73,6 +73,24 @@
(rx/subscribe-with ob sub)
sub))
(defonce mouse-position-alt
(let [sub (rx/behavior-subject nil)
ob (->> st/stream
(rx/filter pointer-event?)
(rx/map :alt)
(rx/dedupe))]
(rx/subscribe-with ob sub)
sub))
(defonce keyboard-alt
(let [sub (rx/behavior-subject nil)
ob (->> st/stream
(rx/filter keyboard-event?)
(rx/map :alt)
(rx/dedupe))]
(rx/subscribe-with ob sub)
sub))
(defn mouse-position-deltas
[current]
(->> (rx/concat (rx/of current)
@ -97,4 +115,3 @@
(rx/map :point))]
(rx/subscribe-with sob sub)
sub))

View file

@ -59,7 +59,7 @@
transform (if rotation (str " transform='rotate(" rotation ")'") "")
data (clojure.pprint/cl-format
nil
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='24px' height='24px'~A%3E~A%3C/svg%3E\") ~A ~A, auto"
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='20px' height='20px'~A%3E~A%3C/svg%3E\") ~A ~A, auto"
transform data x y)]
data))

View file

@ -53,4 +53,3 @@
[:span {:style {:white-space "nowrap"
:margin-right "1rem"}} (pr-str key)]])))]))

View file

@ -227,3 +227,10 @@
[(deref state) ref]))
(defn use-stream
"Wraps the subscription to a strem into a `use-effect` call"
[stream on-subscribe]
(mf/use-effect (fn []
(let [sub (->> stream (rx/subs on-subscribe))]
#(rx/dispose! sub)))))

View file

@ -11,6 +11,10 @@
"A workspace specific shapes wrappers."
(:require
[rumext.alpha :as mf]
[beicon.core :as rx]
[uxbox.main.streams :as ms]
[uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.cursors :as cur]
[uxbox.main.ui.shapes.rect :as rect]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.icon :as icon]
@ -55,9 +59,13 @@
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
opts #js {:shape (->> shape (geom/transform-shape frame))
:frame frame}]
:frame frame}
alt? (mf/use-state false)]
(hooks/use-stream ms/keyboard-alt #(reset! alt? %))
(when (and shape (not (:hidden shape)))
[:*
[:g.shape {:style {:cursor (if @alt? cur/duplicate nil)}}
(case (:type shape)
:curve [:> path/path-wrapper opts]
:path [:> path/path-wrapper opts]

View file

@ -154,9 +154,8 @@
(let [event (.-nativeEvent event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :down ctrl? shift?))
alt? (kbd/alt? event)]
(st/emit! (ms/->MouseEvent :down ctrl? shift? alt?))
(cond
(and (not edition) (= 1 (.-which event)))
@ -183,9 +182,8 @@
(let [event (.-nativeEvent event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :up ctrl? shift?))
alt? (kbd/alt? event)]
(st/emit! (ms/->MouseEvent :up ctrl? shift? alt?))
(when (= 2 (.-which event))
(st/emit! dw/finish-pan
@ -213,9 +211,8 @@
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :click ctrl? shift?)))))
alt? (kbd/alt? event)]
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt?)))))
on-double-click
(mf/use-callback
@ -223,9 +220,8 @@
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:shift? shift?
:ctrl? ctrl?}]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift?)))))
alt? (kbd/alt? event)]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?)))))
on-key-down
(mf/use-callback
@ -234,13 +230,11 @@
key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}
alt? (kbd/alt? event)
target (dom/get-target event)]
(when-not (.-repeat bevent)
(st/emit! (ms/->KeyboardEvent :down key ctrl? shift?))
(st/emit! (ms/->KeyboardEvent :down key ctrl? shift? alt?))
(when (and (kbd/space? event)
(not= "rich-text" (obj/get target "className")))
(handle-viewport-positioning viewport-ref))))))
@ -251,13 +245,10 @@
(let [key (.-keyCode event)
ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
opts {:key key
:shift? shift?
:ctrl? ctrl?}]
alt? (kbd/alt? event)]
(when (kbd/space? event)
(st/emit! dw/finish-pan
::finish-positioning))
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift?)))))
(st/emit! dw/finish-pan ::finish-positioning))
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift? alt?)))))
translate-point-to-viewport
(fn [pt]
@ -282,10 +273,12 @@
(.-movementY event))]
(st/emit! (ms/->PointerEvent :delta delta
(kbd/ctrl? event)
(kbd/shift? event)))
(kbd/shift? event)
(kbd/alt? event)))
(st/emit! (ms/->PointerEvent :viewport pt
(kbd/ctrl? event)
(kbd/shift? event)))))
(kbd/shift? event)
(kbd/alt? event)))))
on-mouse-wheel
(mf/use-callback