mirror of
https://github.com/penpot/penpot.git
synced 2025-06-11 17:21:38 +02:00
♻️ Change paste implementation to work with more browsers
This commit is contained in:
parent
264811c5ee
commit
6186d82151
4 changed files with 149 additions and 83 deletions
|
@ -769,6 +769,14 @@
|
||||||
"es" : "Actualizado: %s"
|
"es" : "Actualizado: %s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"errors.clipboard-not-implemented" : {
|
||||||
|
"translations" : {
|
||||||
|
"en" : "Your browser cannot do this operation, please use Ctrl-V",
|
||||||
|
"fr" : "",
|
||||||
|
"ru" : "",
|
||||||
|
"es" : "Tu navegador no puede realizar esta operación, por favor usa Ctrl-V."
|
||||||
|
}
|
||||||
|
},
|
||||||
"errors.auth.unauthorized" : {
|
"errors.auth.unauthorized" : {
|
||||||
"used-in" : [ "src/app/main/ui/auth/login.cljs:82" ],
|
"used-in" : [ "src/app/main/ui/auth/login.cljs:82" ],
|
||||||
"translations" : {
|
"translations" : {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.main.constants :as c]
|
[app.main.constants :as c]
|
||||||
[app.main.data.colors :as mdc]
|
[app.main.data.colors :as mdc]
|
||||||
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.main.data.workspace.notifications :as dwn]
|
[app.main.data.workspace.notifications :as dwn]
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[app.util.transit :as t]
|
[app.util.transit :as t]
|
||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
|
[app.util.i18n :refer [tr] :as i18n]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
|
@ -1016,7 +1018,6 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(rx/of (dwc/update-shapes ids #(gsh/resize-rect % attr value) {:reg-objects? true})))))
|
(rx/of (dwc/update-shapes ids #(gsh/resize-rect % attr value) {:reg-objects? true})))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Shape Proportions
|
;; --- Shape Proportions
|
||||||
|
|
||||||
(defn set-shape-proportion-lock
|
(defn set-shape-proportion-lock
|
||||||
|
@ -1029,6 +1030,7 @@
|
||||||
(assoc shape :proportion-lock false)
|
(assoc shape :proportion-lock false)
|
||||||
(-> (assoc shape :proportion-lock true)
|
(-> (assoc shape :proportion-lock true)
|
||||||
(gpr/assign-proportions)))))))))
|
(gpr/assign-proportions)))))))))
|
||||||
|
|
||||||
;; --- Update Shape Position
|
;; --- Update Shape Position
|
||||||
|
|
||||||
(s/def ::x number?)
|
(s/def ::x number?)
|
||||||
|
@ -1053,6 +1055,21 @@
|
||||||
(rx/of (dwt/set-modifiers [id] {:displacement displ})
|
(rx/of (dwt/set-modifiers [id] {:displacement displ})
|
||||||
(dwt/apply-modifiers [id]))))))
|
(dwt/apply-modifiers [id]))))))
|
||||||
|
|
||||||
|
;; --- Update Shape Flags
|
||||||
|
|
||||||
|
(defn update-shape-flags
|
||||||
|
[id {:keys [blocked hidden] :as flags}]
|
||||||
|
(s/assert ::us/uuid id)
|
||||||
|
(s/assert ::shape-attrs flags)
|
||||||
|
(ptk/reify ::update-shape-flags
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(letfn [(update-fn [obj]
|
||||||
|
(cond-> obj
|
||||||
|
(boolean? blocked) (assoc :blocked blocked)
|
||||||
|
(boolean? hidden) (assoc :hidden hidden)))]
|
||||||
|
(rx/of (dwc/update-shapes-recursive [id] update-fn))))))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Navigation
|
;; Navigation
|
||||||
|
@ -1195,6 +1212,68 @@
|
||||||
(rx/catch on-copy-error)
|
(rx/catch on-copy-error)
|
||||||
(rx/ignore)))))))
|
(rx/ignore)))))))
|
||||||
|
|
||||||
|
(declare paste-shape)
|
||||||
|
(declare paste-text)
|
||||||
|
(declare paste-image)
|
||||||
|
|
||||||
|
(def paste
|
||||||
|
(ptk/reify ::paste
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(try
|
||||||
|
(let [clipboard-str (wapi/read-from-clipboard)
|
||||||
|
|
||||||
|
paste-transit-str
|
||||||
|
(->> clipboard-str
|
||||||
|
(rx/filter t/transit?)
|
||||||
|
(rx/map t/decode)
|
||||||
|
(rx/filter #(= :copied-shapes (:type %)))
|
||||||
|
(rx/map #(select-keys % [:selected :objects]))
|
||||||
|
(rx/map paste-shape))
|
||||||
|
|
||||||
|
paste-plain-text-str
|
||||||
|
(->> clipboard-str
|
||||||
|
(rx/filter (comp not empty?))
|
||||||
|
(rx/map paste-text))
|
||||||
|
|
||||||
|
paste-image-str
|
||||||
|
(->> (wapi/read-image-from-clipboard)
|
||||||
|
(rx/map paste-image))]
|
||||||
|
|
||||||
|
(->> (rx/concat paste-transit-str
|
||||||
|
paste-plain-text-str
|
||||||
|
paste-image-str)
|
||||||
|
(rx/first)
|
||||||
|
(rx/catch
|
||||||
|
(fn [err]
|
||||||
|
(js/console.error "Clipboard error:" err)
|
||||||
|
(rx/empty)))))
|
||||||
|
(catch :default e
|
||||||
|
(let [data (ex-data e)]
|
||||||
|
(if (:not-implemented data)
|
||||||
|
(rx/of (dm/warn (tr "errors.clipboard-not-implemented")))
|
||||||
|
(js/console.error "ERROR" e))))))))
|
||||||
|
|
||||||
|
(defn paste-from-event
|
||||||
|
[event]
|
||||||
|
(ptk/reify ::paste-from-event
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(try
|
||||||
|
(let [paste-data (wapi/read-from-paste-event event)]
|
||||||
|
(when paste-data
|
||||||
|
(let [text-data (wapi/extract-text paste-data)
|
||||||
|
decoded-data (when (and paste-data (t/transit? text-data))
|
||||||
|
(t/decode text-data))]
|
||||||
|
(if (and decoded-data (= (:type decoded-data) :copied-shapes))
|
||||||
|
(rx/of (paste-shape decoded-data))
|
||||||
|
(if-not (empty? text-data)
|
||||||
|
(rx/of (paste-text text-data))
|
||||||
|
(let [images (wapi/extract-images paste-data)]
|
||||||
|
(rx/from (map paste-image images))))))))
|
||||||
|
(catch :default err
|
||||||
|
(js/console.error "Clipboard error:" err))))))
|
||||||
|
|
||||||
(defn selected-frame? [state]
|
(defn selected-frame? [state]
|
||||||
(let [selected (get-in state [:workspace-local :selected])
|
(let [selected (get-in state [:workspace-local :selected])
|
||||||
page-id (:current-page-id state)
|
page-id (:current-page-id state)
|
||||||
|
@ -1202,9 +1281,9 @@
|
||||||
(and (and (= 1 (count selected))
|
(and (and (= 1 (count selected))
|
||||||
(= :frame (get-in objects [(first selected) :type]))))))
|
(= :frame (get-in objects [(first selected) :type]))))))
|
||||||
|
|
||||||
(defn- paste-impl
|
(defn- paste-shape
|
||||||
[{:keys [selected objects] :as data}]
|
[{:keys [selected objects] :as data}]
|
||||||
(ptk/reify ::paste-impl
|
(ptk/reify ::paste-shape
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [selected-objs (map #(get objects %) selected)
|
(let [selected-objs (map #(get objects %) selected)
|
||||||
|
@ -1240,71 +1319,6 @@
|
||||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||||
(dwc/select-shapes selected))))))
|
(dwc/select-shapes selected))))))
|
||||||
|
|
||||||
(defn- image-uploaded
|
|
||||||
[image]
|
|
||||||
(let [{:keys [x y]} @ms/mouse-position
|
|
||||||
{:keys [width height]} image
|
|
||||||
shape {:name (:name image)
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:x (- x (/ width 2))
|
|
||||||
:y (- y (/ height 2))
|
|
||||||
:metadata {:width width
|
|
||||||
:height height
|
|
||||||
:id (:id image)
|
|
||||||
:path (:path image)}}]
|
|
||||||
(st/emit! (create-and-add-shape :image x y shape))))
|
|
||||||
|
|
||||||
(defn- paste-image-impl
|
|
||||||
[image]
|
|
||||||
(ptk/reify ::paste-bin-impl
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [file-id (get-in state [:workspace-file :id])
|
|
||||||
params {:file-id file-id
|
|
||||||
:local? true
|
|
||||||
:js-files [image]}]
|
|
||||||
(rx/of (dwp/upload-media-objects
|
|
||||||
(with-meta params
|
|
||||||
{:on-success image-uploaded})))))))
|
|
||||||
|
|
||||||
(declare paste-text)
|
|
||||||
|
|
||||||
(def paste
|
|
||||||
(ptk/reify ::paste
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(try
|
|
||||||
(let [clipboard-str (wapi/read-from-clipboard)
|
|
||||||
|
|
||||||
paste-transit-str
|
|
||||||
(->> clipboard-str
|
|
||||||
(rx/filter t/transit?)
|
|
||||||
(rx/map t/decode)
|
|
||||||
(rx/filter #(= :copied-shapes (:type %)))
|
|
||||||
(rx/map #(select-keys % [:selected :objects]))
|
|
||||||
(rx/map paste-impl))
|
|
||||||
|
|
||||||
paste-plain-text-str
|
|
||||||
(->> clipboard-str
|
|
||||||
(rx/filter (comp not empty?))
|
|
||||||
(rx/map paste-text))
|
|
||||||
|
|
||||||
paste-image-str
|
|
||||||
(->> (wapi/read-image-from-clipboard)
|
|
||||||
(rx/map paste-image-impl))]
|
|
||||||
|
|
||||||
(->> (rx/concat paste-transit-str
|
|
||||||
paste-plain-text-str
|
|
||||||
paste-image-str)
|
|
||||||
(rx/first)
|
|
||||||
(rx/catch
|
|
||||||
(fn [err]
|
|
||||||
(js/console.error "Clipboard error:" err)
|
|
||||||
(rx/empty)))))
|
|
||||||
(catch :default e
|
|
||||||
(.error js/console "ERROR" e))))))
|
|
||||||
|
|
||||||
(defn as-content [text]
|
(defn as-content [text]
|
||||||
(let [paragraphs (->> (str/lines text)
|
(let [paragraphs (->> (str/lines text)
|
||||||
(map str/trim)
|
(map str/trim)
|
||||||
|
@ -1341,18 +1355,33 @@
|
||||||
(dwc/add-shape shape)
|
(dwc/add-shape shape)
|
||||||
dwc/commit-undo-transaction)))))
|
dwc/commit-undo-transaction)))))
|
||||||
|
|
||||||
(defn update-shape-flags
|
(defn- image-uploaded
|
||||||
[id {:keys [blocked hidden] :as flags}]
|
[image]
|
||||||
(s/assert ::us/uuid id)
|
(let [{:keys [x y]} @ms/mouse-position
|
||||||
(s/assert ::shape-attrs flags)
|
{:keys [width height]} image
|
||||||
(ptk/reify ::update-shape-flags
|
shape {:name (:name image)
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:x (- x (/ width 2))
|
||||||
|
:y (- y (/ height 2))
|
||||||
|
:metadata {:width width
|
||||||
|
:height height
|
||||||
|
:id (:id image)
|
||||||
|
:path (:path image)}}]
|
||||||
|
(st/emit! (create-and-add-shape :image x y shape))))
|
||||||
|
|
||||||
|
(defn- paste-image
|
||||||
|
[image]
|
||||||
|
(ptk/reify ::paste-bin-impl
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(letfn [(update-fn [obj]
|
(let [file-id (get-in state [:workspace-file :id])
|
||||||
(cond-> obj
|
params {:file-id file-id
|
||||||
(boolean? blocked) (assoc :blocked blocked)
|
:local? true
|
||||||
(boolean? hidden) (assoc :hidden hidden)))]
|
:js-files [image]}]
|
||||||
(rx/of (dwc/update-shapes-recursive [id] update-fn))))))
|
(rx/of (dwp/upload-media-objects
|
||||||
|
(with-meta params
|
||||||
|
{:on-success image-uploaded})))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; GROUPS
|
;; GROUPS
|
||||||
|
@ -1662,7 +1691,6 @@
|
||||||
(dwd/select-for-drawing :text))
|
(dwd/select-for-drawing :text))
|
||||||
"p" #(st/emit! (dwd/select-for-drawing :path))
|
"p" #(st/emit! (dwd/select-for-drawing :path))
|
||||||
(c-mod "c") #(st/emit! copy-selected)
|
(c-mod "c") #(st/emit! copy-selected)
|
||||||
(c-mod "v") #(st/emit! paste)
|
|
||||||
(c-mod "x") #(st/emit! copy-selected delete-selected)
|
(c-mod "x") #(st/emit! copy-selected delete-selected)
|
||||||
"escape" #(st/emit! (esc-pressed))
|
"escape" #(st/emit! (esc-pressed))
|
||||||
"del" #(st/emit! delete-selected)
|
"del" #(st/emit! delete-selected)
|
||||||
|
|
|
@ -475,6 +475,10 @@
|
||||||
(with-meta params
|
(with-meta params
|
||||||
{:on-success #(on-uploaded % viewport-coord)})))))))
|
{:on-success #(on-uploaded % viewport-coord)})))))))
|
||||||
|
|
||||||
|
on-paste
|
||||||
|
(fn [event]
|
||||||
|
(st/emit! (dw/paste-from-event event)))
|
||||||
|
|
||||||
on-resize
|
on-resize
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [node (mf/ref-val viewport-ref)
|
(let [node (mf/ref-val viewport-ref)
|
||||||
|
@ -496,7 +500,8 @@
|
||||||
;; bind with passive=false to allow the event to be cancelled
|
;; bind with passive=false to allow the event to be cancelled
|
||||||
;; https://stackoverflow.com/a/57582286/3219895
|
;; https://stackoverflow.com/a/57582286/3219895
|
||||||
(events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false})
|
(events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false})
|
||||||
(events/listen js/window EventType.RESIZE on-resize)]]
|
(events/listen js/window EventType.RESIZE on-resize)
|
||||||
|
(events/listen js/window EventType.PASTE on-paste)]]
|
||||||
|
|
||||||
(fn []
|
(fn []
|
||||||
(doseq [key keys]
|
(doseq [key keys]
|
||||||
|
|
|
@ -79,12 +79,15 @@
|
||||||
(let [cboard (unchecked-get js/navigator "clipboard")]
|
(let [cboard (unchecked-get js/navigator "clipboard")]
|
||||||
(.writeText ^js cboard data)))
|
(.writeText ^js cboard data)))
|
||||||
|
|
||||||
(defn- read-from-clipboard
|
(defn read-from-clipboard
|
||||||
[]
|
[]
|
||||||
(let [cboard (unchecked-get js/navigator "clipboard")]
|
(let [cboard (unchecked-get js/navigator "clipboard")]
|
||||||
(rx/from (.readText ^js cboard))))
|
(if (.-readText ^js cboard)
|
||||||
|
(rx/from (.readText ^js cboard))
|
||||||
|
(throw (ex-info "This browser does not implement read from clipboard protocol"
|
||||||
|
{:not-implemented true})))))
|
||||||
|
|
||||||
(defn- read-image-from-clipboard
|
(defn read-image-from-clipboard
|
||||||
[]
|
[]
|
||||||
(let [cboard (unchecked-get js/navigator "clipboard")
|
(let [cboard (unchecked-get js/navigator "clipboard")
|
||||||
read-item (fn [item]
|
read-item (fn [item]
|
||||||
|
@ -97,6 +100,28 @@
|
||||||
(rx/mapcat identity) ;; Convert each item into an emission
|
(rx/mapcat identity) ;; Convert each item into an emission
|
||||||
(rx/switch-map read-item))))
|
(rx/switch-map read-item))))
|
||||||
|
|
||||||
|
(defn read-from-paste-event
|
||||||
|
[event]
|
||||||
|
(let [target (.-target ^js event)]
|
||||||
|
(when (and (not (.-isContentEditable target)) ;; ignore when pasting into
|
||||||
|
(not= (.-tagName target) "INPUT")) ;; an editable control
|
||||||
|
(-> ^js event
|
||||||
|
(.getBrowserEvent)
|
||||||
|
(.-clipboardData)))))
|
||||||
|
|
||||||
|
(defn extract-text
|
||||||
|
[clipboard-data]
|
||||||
|
(when clipboard-data
|
||||||
|
(.getData clipboard-data "text")))
|
||||||
|
|
||||||
|
(defn extract-images
|
||||||
|
[clipboard-data]
|
||||||
|
(when clipboard-data
|
||||||
|
(let [file-list (-> (.-files ^js clipboard-data))]
|
||||||
|
(->> (range (.-length file-list))
|
||||||
|
(map #(.item file-list %))
|
||||||
|
(filter #(str/starts-with? (.-type %) "image/"))))))
|
||||||
|
|
||||||
(defn request-fullscreen
|
(defn request-fullscreen
|
||||||
[el]
|
[el]
|
||||||
(cond
|
(cond
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue