0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-26 08:29:42 -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.persistence :as dwp]
[uxbox.main.data.workspace.texts :as dwtxt] [uxbox.main.data.workspace.texts :as dwtxt]
[uxbox.main.data.workspace.transforms :as dwt] [uxbox.main.data.workspace.transforms :as dwt]
[uxbox.main.data.workspace.selection :as dws]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.streams :as ms] [uxbox.main.streams :as ms]
@ -438,101 +439,6 @@
(assoc :zoom zoom) (assoc :zoom zoom)
(update :vbox merge srect))))))))))) (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 ;; --- Add shape to Workspace
@ -598,161 +504,12 @@
:id id}] :id id}]
(rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true}) (rx/of (dwc/commit-changes [rchange] [uchange] {:commit-local? true})
(select-shapes #{id}) (dws/select-shapes #{id})
(when (= :text (:type attrs)) (when (= :text (:type attrs))
(start-edition-mode id))))))) (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 ;; --- Update Shape Attrs
@ -906,7 +663,7 @@
shapes (map lookup selected) shapes (map lookup selected)
shape? #(not= (:type %) :frame)] shape? #(not= (:type %) :frame)]
(rx/of (delete-shapes selected) (rx/of (delete-shapes selected)
deselect-all))))) dws/deselect-all)))))
;; --- Rename Shape ;; --- Rename Shape
@ -1186,21 +943,6 @@
(update [_ state] (update [_ state]
(update state :workspace-local dissoc :expanded)))) (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 (defn recursive-assign
"A helper for assign recursively a shape attr." "A helper for assign recursively a shape attr."
[id attr value] [id attr value]
@ -1284,7 +1026,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(rx/of (select-shape (:id shape)))))) (rx/of (dws/select-shape (:id shape))))))
(def hide-context-menu (def hide-context-menu
(ptk/reify ::hide-context-menu (ptk/reify ::hide-context-menu
@ -1342,7 +1084,7 @@
unames (-> (get-in state [:workspace-data page-id :objects]) unames (-> (get-in state [:workspace-data page-id :objects])
(retrieve-used-names)) (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 %)) uchanges (mapv #(array-map :type :del-obj :id (:id %))
(reverse rchanges)) (reverse rchanges))
@ -1351,7 +1093,7 @@
(map #(get-in % [:obj :id])) (map #(get-in % [:obj :id]))
(into #{}))] (into #{}))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes selected)))))) (dws/select-shapes selected))))))
(def paste (def paste
(ptk/reify ::paste (ptk/reify ::paste
@ -1432,7 +1174,7 @@
{:type :del-obj {:type :del-obj
:id id}]] :id id}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(select-shapes #{id})))))))) (dws/select-shapes #{id}))))))))
(def remove-group (def remove-group
(ptk/reify ::remove-group (ptk/reify ::remove-group
@ -1551,6 +1293,14 @@
(def delete-page dwp/delete-page) (def delete-page dwp/delete-page)
(def create-empty-page dwp/create-empty-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 ;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -298,3 +298,17 @@
(update state :workspace-local dissoc :undo-index :undo)))) (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.spec :as us]
[uxbox.common.pages :as cp] [uxbox.common.pages :as cp]
[uxbox.main.data.workspace.common :as dwc] [uxbox.main.data.workspace.common :as dwc]
[uxbox.main.data.workspace.selection :as dws]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.streams :as ms] [uxbox.main.streams :as ms]
@ -191,6 +192,7 @@
;; -- MOVE ;; -- MOVE
(declare start-move) (declare start-move)
(declare start-move-duplicate)
(defn start-move-selected (defn start-move-selected
[] []
@ -206,33 +208,52 @@
(rx/map #(gpt/length %)) (rx/map #(gpt/length %))
(rx/filter #(> % 1)) (rx/filter #(> % 1))
(rx/take 1) (rx/take 1)
(rx/map #(start-move initial selected))))))) (rx/with-latest vector ms/mouse-position-alt)
(rx/flat-map
(defn start-move (fn [[_ alt?]]
[from-position ids] (if alt?
(ptk/reify ::start-move ;; When alt is down we start a duplicate+move
ptk/UpdateEvent (rx/of (start-move-duplicate initial)
(update [_ state] dws/duplicate-selected)
(-> state ;; Otherwise just plain old move
(assoc-in [:workspace-local :transform] :move))) (rx/of (start-move initial selected))))))))))
(defn start-move-duplicate [from-position]
(ptk/reify ::start-move-selected
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [page-id (get state :current-page-id) (->> stream
objects (get-in state [:workspace-data page-id :objects]) (rx/filter (ptk/type? ::dws/duplicate-selected))
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids) (rx/first)
stopper (rx/filter ms/mouse-up? stream) (rx/map #(start-move from-position))))))
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) (defn start-move
finish-transform)))))) ([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 (defn- get-displacement-with-grid
"Retrieve the correct displacement delta point for the "Retrieve the correct displacement delta point for the

View file

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

View file

@ -59,7 +59,7 @@
transform (if rotation (str " transform='rotate(" rotation ")'") "") transform (if rotation (str " transform='rotate(" rotation ")'") "")
data (clojure.pprint/cl-format data (clojure.pprint/cl-format
nil 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)] transform data x y)]
data)) data))

View file

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

View file

@ -227,3 +227,10 @@
[(deref state) ref])) [(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." "A workspace specific shapes wrappers."
(:require (:require
[rumext.alpha :as mf] [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.rect :as rect]
[uxbox.main.ui.shapes.circle :as circle] [uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.icon :as icon] [uxbox.main.ui.shapes.icon :as icon]
@ -55,9 +59,13 @@
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame") frame (unchecked-get props "frame")
opts #js {:shape (->> shape (geom/transform-shape 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))) (when (and shape (not (:hidden shape)))
[:* [:g.shape {:style {:cursor (if @alt? cur/duplicate nil)}}
(case (:type shape) (case (:type shape)
:curve [:> path/path-wrapper opts] :curve [:> path/path-wrapper opts]
:path [:> path/path-wrapper opts] :path [:> path/path-wrapper opts]

View file

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