mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 16:30:37 -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.data.pages :as udp]
|
||||
[uxbox.data.shapes :as uds]
|
||||
[uxbox.util.datetime :as dt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.data :refer (index-of)]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Events (explicit)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Events (concrete)
|
||||
|
||||
(defn initialize
|
||||
"Initialize the workspace state."
|
||||
|
@ -93,9 +92,7 @@
|
|||
(->> (into #{} xf (vals (:shapes-by-id state)))
|
||||
(assoc-in state [:workspace :selected]))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Events (for selected)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Events (implicit) (for selected)
|
||||
|
||||
(defn deselect-all
|
||||
"Mark a shape selected for drawing in the canvas."
|
||||
|
@ -207,3 +204,41 @@
|
|||
(->> (get-in state [:workspace :selected])
|
||||
(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 ""}
|
||||
:route nil
|
||||
:auth (:uxbox/auth local-storage)
|
||||
:clipboard #queue []
|
||||
:profile nil
|
||||
:workspace nil
|
||||
:shapes-by-id {}
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
(:require [uxbox.shapes :as sh]
|
||||
[uxbox.util.data :refer (index-of)]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shape Creation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Shape Creation
|
||||
|
||||
(defn assoc-shape-to-page
|
||||
[state shape page]
|
||||
|
@ -48,30 +46,30 @@
|
|||
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
|
||||
|
||||
(defn duplicate-shapes
|
||||
[state shapes]
|
||||
(letfn [(all-toplevel? [coll]
|
||||
(every? #(nil? (:group %)) coll))
|
||||
(all-same-group? [coll]
|
||||
(let [group (:group (first coll))]
|
||||
(every? #(= group (:group %)) coll)))]
|
||||
(let [shapes (mapv #(get-in state [:shapes-by-id %]) shapes)]
|
||||
(cond
|
||||
(all-toplevel? shapes)
|
||||
(let [page (:page (first shapes))]
|
||||
(duplicate-shapes' state shapes page))
|
||||
([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 (mapv #(get-in state [:shapes-by-id %]) shapes)]
|
||||
(cond
|
||||
(all-toplevel? shapes)
|
||||
(let [page (or page (:page (first shapes)))]
|
||||
(duplicate-shapes' state shapes page))
|
||||
|
||||
(all-same-group? shapes)
|
||||
(let [page (:page (first shapes))
|
||||
group (:group (first shapes))]
|
||||
(duplicate-shapes' state shapes page group))
|
||||
(all-same-group? shapes)
|
||||
(let [page (or page (:page (first shapes)))
|
||||
group (:group (first shapes))]
|
||||
(duplicate-shapes' state shapes page group))
|
||||
|
||||
:else
|
||||
(let [page (:page (first shapes))]
|
||||
(duplicate-shapes' state shapes page))))))
|
||||
:else
|
||||
(let [page (or page (:page (first shapes)))]
|
||||
(duplicate-shapes' state shapes page)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Delete Shapes
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Delete Shapes
|
||||
|
||||
(defn dissoc-from-index
|
||||
"A function that dissoc shape from the indexed
|
||||
|
@ -127,9 +125,7 @@
|
|||
(dissoc-from-index $ shape)
|
||||
(clear-empty-groups $ shape)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shape Movements
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Shape Movements
|
||||
|
||||
(defn- drop-at-index
|
||||
[index coll v]
|
||||
|
@ -193,3 +189,26 @@
|
|||
:before (drop-before state tid sid)
|
||||
:after (drop-after state tid sid)
|
||||
(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
|
||||
(: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.mixins :as mx]
|
||||
[uxbox.ui.lightbox :as lightbox]
|
||||
[uxbox.data.workspace :as udw]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.ui.lightbox :as lightbox]))
|
||||
[uxbox.util.datetime :as dt]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Component
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; --- Lenses
|
||||
|
||||
(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
|
||||
[own]
|
||||
(html
|
||||
[:div.lightbox-body.clipboard
|
||||
[:div.clipboard-list
|
||||
(for [i (range 5)]
|
||||
[:div.clipboard-item {:key i}
|
||||
[:span.clipboard-icon i/box]
|
||||
[:span (str "shape " i)]])]
|
||||
[:a.close {:href "#"
|
||||
:on-click #(do (dom/prevent-default %)
|
||||
(lightbox/close!))} i/close]]))
|
||||
(let [clipboard (rum/react clipboard-l)]
|
||||
(html
|
||||
[:div.lightbox-body.clipboard
|
||||
[:div.clipboard-list
|
||||
(for [item clipboard]
|
||||
[:div.clipboard-item
|
||||
{:key (str (:id item))
|
||||
:on-click (partial on-paste item)}
|
||||
[:span.clipboard-icon i/box]
|
||||
[:span (str "Copied (" (dt/timeago (:created-at item)) ")")]])]
|
||||
[:a.close {:href "#"
|
||||
:on-click #(do (dom/prevent-default %)
|
||||
(lightbox/close!))} i/close]])))
|
||||
|
||||
(def clipboard-dialog
|
||||
(mx/component
|
||||
{:render clipboard-dialog-render
|
||||
:name "clipboard-dialog"
|
||||
:mixins []}))
|
||||
:mixins [mx/static rum/reactive]}))
|
||||
|
||||
(defmethod lightbox/render-lightbox :clipboard
|
||||
[_]
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
(:require [goog.events :as events]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.rstore :as rs]
|
||||
[uxbox.ui.lightbox :as lightbox]
|
||||
[uxbox.data.workspace :as dw])
|
||||
(:import goog.events.EventType
|
||||
goog.events.KeyCodes
|
||||
|
@ -33,6 +34,9 @@
|
|||
:ctrl+shift+l #(rs/emit! (dw/toggle-flag :layers))
|
||||
:ctrl+r #(rs/emit! (dw/toggle-flag :ruler))
|
||||
: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))
|
||||
:backspace #(rs/emit! (dw/delete-selected))
|
||||
:delete #(rs/emit! (dw/delete-selected))
|
||||
|
|
|
@ -69,7 +69,6 @@
|
|||
|
||||
(on-load-more [event]
|
||||
(dom/prevent-default event)
|
||||
(println "kaka")
|
||||
(let [since (:min-version history)
|
||||
params {:since since}]
|
||||
(rs/emit! (udh/fetch-page-history (:id page) params))))]
|
||||
|
|
|
@ -101,7 +101,6 @@
|
|||
:builtin/text i/text
|
||||
:builtin/group i/folder))
|
||||
|
||||
|
||||
(defn- get-hover-position
|
||||
[event group?]
|
||||
(let [target (.-currentTarget event)
|
||||
|
|
Loading…
Reference in a new issue