mirror of
https://github.com/penpot/penpot.git
synced 2025-01-10 08:50:57 -05:00
Add clipboard management.
This commit is contained in:
parent
4519d6e508
commit
b5155eebcf
7 changed files with 125 additions and 50 deletions
|
@ -18,12 +18,11 @@
|
||||||
[uxbox.shapes :as sh]
|
[uxbox.shapes :as sh]
|
||||||
[uxbox.data.pages :as udp]
|
[uxbox.data.pages :as udp]
|
||||||
[uxbox.data.shapes :as uds]
|
[uxbox.data.shapes :as uds]
|
||||||
|
[uxbox.util.datetime :as dt]
|
||||||
[uxbox.util.geom.point :as gpt]
|
[uxbox.util.geom.point :as gpt]
|
||||||
[uxbox.util.data :refer (index-of)]))
|
[uxbox.util.data :refer (index-of)]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; --- Events (concrete)
|
||||||
;; Events (explicit)
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn initialize
|
(defn initialize
|
||||||
"Initialize the workspace state."
|
"Initialize the workspace state."
|
||||||
|
@ -93,9 +92,7 @@
|
||||||
(->> (into #{} xf (vals (:shapes-by-id state)))
|
(->> (into #{} xf (vals (:shapes-by-id state)))
|
||||||
(assoc-in state [:workspace :selected]))))))
|
(assoc-in state [:workspace :selected]))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; --- Events (implicit) (for selected)
|
||||||
;; Events (for selected)
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn deselect-all
|
(defn deselect-all
|
||||||
"Mark a shape selected for drawing in the canvas."
|
"Mark a shape selected for drawing in the canvas."
|
||||||
|
@ -207,3 +204,41 @@
|
||||||
(->> (get-in state [:workspace :selected])
|
(->> (get-in state [:workspace :selected])
|
||||||
(map #(uds/update-stroke-attrs % opts)))))))
|
(map #(uds/update-stroke-attrs % opts)))))))
|
||||||
|
|
||||||
|
;; --- Copy to Clipboard
|
||||||
|
|
||||||
|
(defrecord CopyToClipboard []
|
||||||
|
rs/UpdateEvent
|
||||||
|
(-apply-update [_ state]
|
||||||
|
(let [selected (get-in state [:workspace :selected])
|
||||||
|
item {:id (random-uuid)
|
||||||
|
:created-at (dt/now)
|
||||||
|
:items selected}
|
||||||
|
clipboard (-> (:clipboard state)
|
||||||
|
(conj item))]
|
||||||
|
(assoc state :clipboard
|
||||||
|
(if (> (count clipboard) 5)
|
||||||
|
(pop clipboard)
|
||||||
|
clipboard)))))
|
||||||
|
|
||||||
|
(defn copy-to-clipboard
|
||||||
|
"Copy selected shapes to clipboard."
|
||||||
|
[]
|
||||||
|
(CopyToClipboard.))
|
||||||
|
|
||||||
|
;; --- Paste from Clipboard
|
||||||
|
|
||||||
|
(defrecord PasteFromClipboard [id]
|
||||||
|
rs/UpdateEvent
|
||||||
|
(-apply-update [_ state]
|
||||||
|
(let [page (get-in state [:workspace :page])
|
||||||
|
selected (if (nil? id)
|
||||||
|
(first (:clipboard state))
|
||||||
|
(->> (:clipboard state)
|
||||||
|
(filter #(= id (:id %)))
|
||||||
|
(first)))]
|
||||||
|
(stsh/duplicate-shapes state (:items selected) page))))
|
||||||
|
|
||||||
|
(defn paste-from-clipboard
|
||||||
|
"Copy selected shapes to clipboard."
|
||||||
|
([] (PasteFromClipboard. nil))
|
||||||
|
([id] (PasteFromClipboard. id)))
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
:project-filter ""}
|
:project-filter ""}
|
||||||
:route nil
|
:route nil
|
||||||
:auth (:uxbox/auth local-storage)
|
:auth (:uxbox/auth local-storage)
|
||||||
|
:clipboard #queue []
|
||||||
:profile nil
|
:profile nil
|
||||||
:workspace nil
|
:workspace nil
|
||||||
:shapes-by-id {}
|
:shapes-by-id {}
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
(:require [uxbox.shapes :as sh]
|
(:require [uxbox.shapes :as sh]
|
||||||
[uxbox.util.data :refer (index-of)]))
|
[uxbox.util.data :refer (index-of)]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; --- Shape Creation
|
||||||
;; Shape Creation
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn assoc-shape-to-page
|
(defn assoc-shape-to-page
|
||||||
[state shape page]
|
[state shape page]
|
||||||
|
@ -48,7 +46,9 @@
|
||||||
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
||||||
|
|
||||||
(defn duplicate-shapes
|
(defn duplicate-shapes
|
||||||
[state shapes]
|
([state shapes]
|
||||||
|
(duplicate-shapes state shapes nil))
|
||||||
|
([state shapes page]
|
||||||
(letfn [(all-toplevel? [coll]
|
(letfn [(all-toplevel? [coll]
|
||||||
(every? #(nil? (:group %)) coll))
|
(every? #(nil? (:group %)) coll))
|
||||||
(all-same-group? [coll]
|
(all-same-group? [coll]
|
||||||
|
@ -57,21 +57,19 @@
|
||||||
(let [shapes (mapv #(get-in state [:shapes-by-id %]) shapes)]
|
(let [shapes (mapv #(get-in state [:shapes-by-id %]) shapes)]
|
||||||
(cond
|
(cond
|
||||||
(all-toplevel? shapes)
|
(all-toplevel? shapes)
|
||||||
(let [page (:page (first shapes))]
|
(let [page (or page (:page (first shapes)))]
|
||||||
(duplicate-shapes' state shapes page))
|
(duplicate-shapes' state shapes page))
|
||||||
|
|
||||||
(all-same-group? shapes)
|
(all-same-group? shapes)
|
||||||
(let [page (:page (first shapes))
|
(let [page (or page (:page (first shapes)))
|
||||||
group (:group (first shapes))]
|
group (:group (first shapes))]
|
||||||
(duplicate-shapes' state shapes page group))
|
(duplicate-shapes' state shapes page group))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(let [page (:page (first shapes))]
|
(let [page (or page (:page (first shapes)))]
|
||||||
(duplicate-shapes' state shapes page))))))
|
(duplicate-shapes' state shapes page)))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; --- Delete Shapes
|
||||||
;; Delete Shapes
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn dissoc-from-index
|
(defn dissoc-from-index
|
||||||
"A function that dissoc shape from the indexed
|
"A function that dissoc shape from the indexed
|
||||||
|
@ -127,9 +125,7 @@
|
||||||
(dissoc-from-index $ shape)
|
(dissoc-from-index $ shape)
|
||||||
(clear-empty-groups $ shape)))
|
(clear-empty-groups $ shape)))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; --- Shape Movements
|
||||||
;; Shape Movements
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn- drop-at-index
|
(defn- drop-at-index
|
||||||
[index coll v]
|
[index coll v]
|
||||||
|
@ -193,3 +189,26 @@
|
||||||
:before (drop-before state tid sid)
|
:before (drop-before state tid sid)
|
||||||
:after (drop-after state tid sid)
|
:after (drop-after state tid sid)
|
||||||
(throw (ex-info "Invalid data" {})))))
|
(throw (ex-info "Invalid data" {})))))
|
||||||
|
|
||||||
|
;; --- Shape Packing
|
||||||
|
|
||||||
|
;; (defn- deep-scan-shape-ids
|
||||||
|
;; [state acc id]
|
||||||
|
;; (let [shape (get-in state [:shapes-by-id id])]
|
||||||
|
;; (if (= (:type shape) :builtin/group)
|
||||||
|
;; (reduce (partial deep-scan-shape-ids state)
|
||||||
|
;; (conj acc id)
|
||||||
|
;; (:items shape))
|
||||||
|
;; (conj acc id))))
|
||||||
|
|
||||||
|
;; (defn pack-shape
|
||||||
|
;; [state id]
|
||||||
|
;; (let [ids (deep-scan-shape-ids state #{} id)
|
||||||
|
;; index (reduce (fn [acc id]
|
||||||
|
;; (let [shape (get-in state [:shapes-by-id id])]
|
||||||
|
;; (assoc acc id shape)))
|
||||||
|
;; {} ids)]
|
||||||
|
;; {:type :builtin/packed-shape
|
||||||
|
;; :index index
|
||||||
|
;; :id id}))
|
||||||
|
|
||||||
|
|
|
@ -7,33 +7,51 @@
|
||||||
|
|
||||||
(ns uxbox.ui.workspace.clipboard
|
(ns uxbox.ui.workspace.clipboard
|
||||||
(:require [sablono.core :as html :refer-macros [html]]
|
(:require [sablono.core :as html :refer-macros [html]]
|
||||||
|
[rum.core :as rum]
|
||||||
|
[lentes.core :as l]
|
||||||
|
[uxbox.rstore :as rs]
|
||||||
|
[uxbox.state :as st]
|
||||||
[uxbox.ui.icons :as i]
|
[uxbox.ui.icons :as i]
|
||||||
[uxbox.ui.mixins :as mx]
|
[uxbox.ui.mixins :as mx]
|
||||||
|
[uxbox.ui.lightbox :as lightbox]
|
||||||
|
[uxbox.data.workspace :as udw]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.ui.lightbox :as lightbox]))
|
[uxbox.util.datetime :as dt]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; --- Lenses
|
||||||
;; Component
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
(def ^:const ^:private clipboard-l
|
||||||
|
(-> (l/key :clipboard)
|
||||||
|
(l/focus-atom st/state)))
|
||||||
|
|
||||||
|
;; --- Clipboard Dialog Component
|
||||||
|
|
||||||
|
(defn- on-paste
|
||||||
|
[item]
|
||||||
|
(rs/emit! (udw/paste-from-clipboard (:id item)))
|
||||||
|
(lightbox/close!))
|
||||||
|
|
||||||
(defn- clipboard-dialog-render
|
(defn- clipboard-dialog-render
|
||||||
[own]
|
[own]
|
||||||
|
(let [clipboard (rum/react clipboard-l)]
|
||||||
(html
|
(html
|
||||||
[:div.lightbox-body.clipboard
|
[:div.lightbox-body.clipboard
|
||||||
[:div.clipboard-list
|
[:div.clipboard-list
|
||||||
(for [i (range 5)]
|
(for [item clipboard]
|
||||||
[:div.clipboard-item {:key i}
|
[:div.clipboard-item
|
||||||
|
{:key (str (:id item))
|
||||||
|
:on-click (partial on-paste item)}
|
||||||
[:span.clipboard-icon i/box]
|
[:span.clipboard-icon i/box]
|
||||||
[:span (str "shape " i)]])]
|
[:span (str "Copied (" (dt/timeago (:created-at item)) ")")]])]
|
||||||
[:a.close {:href "#"
|
[:a.close {:href "#"
|
||||||
:on-click #(do (dom/prevent-default %)
|
:on-click #(do (dom/prevent-default %)
|
||||||
(lightbox/close!))} i/close]]))
|
(lightbox/close!))} i/close]])))
|
||||||
|
|
||||||
(def clipboard-dialog
|
(def clipboard-dialog
|
||||||
(mx/component
|
(mx/component
|
||||||
{:render clipboard-dialog-render
|
{:render clipboard-dialog-render
|
||||||
:name "clipboard-dialog"
|
:name "clipboard-dialog"
|
||||||
:mixins []}))
|
:mixins [mx/static rum/reactive]}))
|
||||||
|
|
||||||
(defmethod lightbox/render-lightbox :clipboard
|
(defmethod lightbox/render-lightbox :clipboard
|
||||||
[_]
|
[_]
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
(:require [goog.events :as events]
|
(:require [goog.events :as events]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[uxbox.rstore :as rs]
|
[uxbox.rstore :as rs]
|
||||||
|
[uxbox.ui.lightbox :as lightbox]
|
||||||
[uxbox.data.workspace :as dw])
|
[uxbox.data.workspace :as dw])
|
||||||
(:import goog.events.EventType
|
(:import goog.events.EventType
|
||||||
goog.events.KeyCodes
|
goog.events.KeyCodes
|
||||||
|
@ -33,6 +34,9 @@
|
||||||
:ctrl+shift+l #(rs/emit! (dw/toggle-flag :layers))
|
:ctrl+shift+l #(rs/emit! (dw/toggle-flag :layers))
|
||||||
:ctrl+r #(rs/emit! (dw/toggle-flag :ruler))
|
:ctrl+r #(rs/emit! (dw/toggle-flag :ruler))
|
||||||
:ctrl+d #(rs/emit! (dw/duplicate-selected))
|
:ctrl+d #(rs/emit! (dw/duplicate-selected))
|
||||||
|
:ctrl+c #(rs/emit! (dw/copy-to-clipboard))
|
||||||
|
:ctrl+v #(rs/emit! (dw/paste-from-clipboard))
|
||||||
|
:ctrl+shift+v #(lightbox/open! :clipboard)
|
||||||
:esc #(rs/emit! (dw/deselect-all))
|
:esc #(rs/emit! (dw/deselect-all))
|
||||||
:backspace #(rs/emit! (dw/delete-selected))
|
:backspace #(rs/emit! (dw/delete-selected))
|
||||||
:delete #(rs/emit! (dw/delete-selected))
|
:delete #(rs/emit! (dw/delete-selected))
|
||||||
|
|
|
@ -69,7 +69,6 @@
|
||||||
|
|
||||||
(on-load-more [event]
|
(on-load-more [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(println "kaka")
|
|
||||||
(let [since (:min-version history)
|
(let [since (:min-version history)
|
||||||
params {:since since}]
|
params {:since since}]
|
||||||
(rs/emit! (udh/fetch-page-history (:id page) params))))]
|
(rs/emit! (udh/fetch-page-history (:id page) params))))]
|
||||||
|
|
|
@ -101,7 +101,6 @@
|
||||||
:builtin/text i/text
|
:builtin/text i/text
|
||||||
:builtin/group i/folder))
|
:builtin/group i/folder))
|
||||||
|
|
||||||
|
|
||||||
(defn- get-hover-position
|
(defn- get-hover-position
|
||||||
[event group?]
|
[event group?]
|
||||||
(let [target (.-currentTarget event)
|
(let [target (.-currentTarget event)
|
||||||
|
|
Loading…
Reference in a new issue