diff --git a/CHANGES.md b/CHANGES.md index b0bf82c882..2f1fde89e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -58,6 +58,11 @@ - Fix problem when importing SVG's with uses with overriding properties [#Taiga 2884](https://tree.taiga.io/project/penpot/issue/2884) - Fix inconsistency with radius in SVG an CSS [#1587](https://github.com/penpot/penpot/issues/1587) - Fix clickable area in layers [#1680](https://github.com/penpot/penpot/issues/1680) +- Fix problems with trackpad zoom and scroll in MacOS [#1161](https://github.com/penpot/penpot/issues/1161) +- Fix problem with copy/paste in Safari [#1209](https://github.com/penpot/penpot/issues/1209) +- Fix paste ordering for frames not being respected [Taiga #3097](https://tree.taiga.io/project/penpot/issue/3097) +- Improved command support for MacOS [Taiga #2789](https://tree.taiga.io/project/penpot/issue/2789) +- Fix shift+2 shortcut in MacOS with non-english keyboards [Taiga #3038](https://tree.taiga.io/project/penpot/issue/3038) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index e950bc7ccb..242f5b9e3e 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -81,9 +81,7 @@ shapes (nil? index) - (if (= :frame (:type obj)) - (into [id] shapes) - (conj shapes id)) + (conj shapes id) :else (cph/insert-at-index shapes index [id])))) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index b0cf90e841..e30f19dcde 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -198,8 +198,7 @@ (assert-page-id changes) (let [obj (cond-> obj (not= index ::undefined) - (assoc :index index)) - + (assoc ::index index)) add-change {:type :add-obj :id (:id obj) diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index b86f2cbbc7..fcb5a03510 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -52,7 +52,7 @@ services: - PENPOT_SMTP_PASSWORD= - PENPOT_SMTP_SSL=false - PENPOT_SMTP_TLS=false - - PENPOT_FLAGS="enable-cors enable-insecure-register enable-audit-log" + - PENPOT_FLAGS="enable-cors enable-insecure-register enable-audit-log disable-secure-session-cookies" # LDAP setup - PENPOT_LDAP_HOST=ldap diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index eda428e300..25de91e04b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1198,13 +1198,10 @@ [] (letfn [;; Sort objects so they have the same relative ordering ;; when pasted later. - (sort-selected [state data] - (let [selected (:selected data) - page-id (:current-page-id state) - objects (get-in state [:workspace-data - :pages-index - page-id - :objects])] + (sort-selected-async [state data] + (let [selected (wsh/lookup-selected state) + objects (wsh/lookup-page-objects state) + page-id (:current-page-id state)] (->> (uw/ask! {:cmd :selection/query-z-index :page-id page-id :objects objects @@ -1216,6 +1213,24 @@ (map first) (into (d/ordered-set))))))))) + ;; We cannot call to a remote procedure in Safari (for the copy) so we need + ;; to calculate it here instead of on the worker + (sort-selected-sync [state data] + (let [selected (wsh/lookup-selected state) + objects (wsh/lookup-page-objects state) + z-index (cp/calculate-z-index objects) + z-values (->> selected + (map #(vector % + (+ (get z-index %) + (get z-index (get-in objects [% :frame-id])))))) + selected + (->> z-values + (sort-by second) + (map first) + (into (d/ordered-set)))] + + (assoc data :selected selected))) + ;; Retrieve all ids of selected shapes with corresponding ;; children; this is needed because each shape should be ;; processed one by one because of async events (data url @@ -1277,11 +1292,18 @@ :file-id (:current-file-id state) :selected selected :objects {} - :images #{}}] + :images #{}} + + sort-results + (fn [obs] + ;; Safari doesn't allow asynchronous sorting on the copy + (if (cfg/check-browser? :safari) + (rx/map (partial sort-selected-sync state) obs) + (rx/mapcat (partial sort-selected-async state) obs)))] (->> (rx/from (seq (vals pdata))) (rx/merge-map (partial prepare-object objects selected)) (rx/reduce collect-data initial) - (rx/mapcat (partial sort-selected state)) + (sort-results) (rx/map t/encode-str) (rx/map wapi/write-to-clipboard) (rx/catch on-copy-error) @@ -1415,7 +1437,7 @@ (calculate-paste-position [state mouse-pos in-viewport?] (let [page-objects (wsh/lookup-page-objects state) selected-objs (map #(get paste-objects %) selected) - has-frame? (d/seek #(= (:type %) :frame) selected-objs) + has-frame? (d/seek #(= (:type %) :frame) selected-objs) page-selected (wsh/lookup-selected state) wrapper (gsh/selection-rect selected-objs) orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))] @@ -1502,21 +1524,18 @@ ;; Calculate position for the pasted elements [frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?) - paste-objects (->> paste-objects - (d/mapm (fn [_ shape] - (-> shape - (assoc :frame-id frame-id) - (assoc :parent-id parent-id) + process-shape + (fn [_ shape] + (-> shape + (assoc :frame-id frame-id) + (assoc :parent-id parent-id) - (cond-> - ;; if foreign instance, detach the shape - (foreign-instance? shape paste-objects state) - (dissoc :component-id - :component-file - :component-root? - :remote-synced? - :shape-ref - :touched)))))) + ;; if foreign instance, detach the shape + (cond-> (foreign-instance? shape paste-objects state) + (dissoc :component-id :component-file :component-root? + :remote-synced? :shape-ref :touched)))) + + paste-objects (->> paste-objects (d/mapm process-shape)) all-objects (merge (:objects page) paste-objects) @@ -1750,3 +1769,4 @@ (dm/export dwz/zoom-to-fit-all) (dm/export dwz/decrease-zoom) (dm/export dwz/increase-zoom) +(dm/export dwz/set-zoom) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 98d6dc0c37..dfeb3a043b 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -325,7 +325,7 @@ selected) changes (-> (pcb/empty-changes it page-id) - (pcb/add-object shape))] + (pcb/add-object shape {:index (when (= :frame (:type shape)) 0)}))] (rx/concat (rx/of (dch/commit-changes changes) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index fa02d13903..762397938a 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -284,15 +284,27 @@ (let [shapes (map (d/getf all-objects) ids) unames (volatile! (dwc/retrieve-used-names (:objects page))) update-unames! (fn [new-name] (vswap! unames conj new-name)) - all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) #{} ids) - ids-map (into {} (map #(vector % (uuid/next))) all-ids)] - (-> (reduce (fn [changes shape] - (prepare-duplicate-change changes all-objects page unames update-unames! ids-map shape delta)) - (-> (pcb/empty-changes it) - (pcb/with-page page) - (pcb/with-objects all-objects)) - shapes) - (prepare-duplicate-flows shapes page ids-map)))) + all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) (d/ordered-set) ids) + ids-map (into {} (map #(vector % (uuid/next))) all-ids) + + init-changes + (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/with-objects all-objects)) + + changes + (->> shapes + (reduce #(prepare-duplicate-change %1 + all-objects + page + unames + update-unames! + ids-map + %2 + delta) + init-changes))] + + (prepare-duplicate-flows changes shapes page ids-map))) (defn- prepare-duplicate-change [changes objects page unames update-unames! ids-map shape delta] @@ -349,7 +361,6 @@ (geom/move delta) (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) - changes (pcb/add-object changes new-obj {:ignore-touched true}) changes (-> (pcb/add-object changes new-obj {:ignore-touched true}) (pcb/amend-last-change #(assoc % :old-id (:id obj))))] diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index e02bdce160..2d4a8b9e86 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -140,7 +140,7 @@ :fn #(st/emit! dw/zoom-to-fit-all)} :zoom-selected {:tooltip (ds/shift "2") - :command "shift+2" + :command ["shift+2" "@" "\""] :fn #(st/emit! dw/zoom-to-selected-shape)} :duplicate {:tooltip (ds/meta "D") diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index f7b7d88ba1..63dc4ac7c1 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -541,13 +541,13 @@ initial-angle (gpt/angle @ms/mouse-position group-center) calculate-angle - (fn [pos ctrl? shift?] + (fn [pos mod? shift?] (let [angle (- (gpt/angle pos group-center) initial-angle) angle (if (neg? angle) (+ 360 angle) angle) angle (if (= angle 360) 0 angle) - angle (if ctrl? + angle (if mod? (* (mth/floor (/ angle 45)) 45) angle) angle (if shift? @@ -556,11 +556,11 @@ angle))] (rx/concat (->> ms/mouse-position - (rx/with-latest vector ms/mouse-position-ctrl) + (rx/with-latest vector ms/mouse-position-mod) (rx/with-latest vector ms/mouse-position-shift) (rx/map - (fn [[[pos ctrl?] shift?]] - (let [delta-angle (calculate-angle pos ctrl? shift?)] + (fn [[[pos mod?] shift?]] + (let [delta-angle (calculate-angle pos mod? shift?)] (set-rotation-modifiers delta-angle shapes group-center)))) (rx/take-until stoper)) (rx/of (apply-modifiers (map :id shapes)) diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs index 7b5201ffa7..759fddd233 100644 --- a/frontend/src/app/main/streams.cljs +++ b/frontend/src/app/main/streams.cljs @@ -7,6 +7,7 @@ (ns app.main.streams "User interaction events and streams." (:require + [app.config :as cfg] [app.main.store :as st] [app.util.globals :as globals] [app.util.keyboard :as kbd] @@ -20,7 +21,7 @@ [v] (instance? KeyboardEvent v)) -(defrecord MouseEvent [type ctrl shift alt]) +(defrecord MouseEvent [type ctrl shift alt meta]) (defn mouse-event? [v] @@ -46,7 +47,7 @@ (and (mouse-event? v) (= :double-click (:type v)))) -(defrecord PointerEvent [source pt ctrl shift alt]) +(defrecord PointerEvent [source pt ctrl shift alt meta]) (defn pointer-event? [v] @@ -83,6 +84,20 @@ (rx/subscribe-with ob sub) sub)) +(defonce mouse-position-meta + (let [sub (rx/behavior-subject nil) + ob (->> st/stream + (rx/filter pointer-event?) + (rx/map :meta) + (rx/dedupe))] + (rx/subscribe-with ob sub) + sub)) + +(defonce mouse-position-mod + (if (cfg/check-platform? :macos) + mouse-position-meta + mouse-position-ctrl)) + (defonce mouse-position-shift (let [sub (rx/behavior-subject nil) ob (->> st/stream @@ -111,7 +126,7 @@ ob (->> (rx/merge (->> st/stream (rx/filter keyboard-event?) - (rx/filter kbd/altKey?) + (rx/filter kbd/alt-key?) (rx/map #(= :down (:type %)))) ;; Fix a situation caused by using `ctrl+alt` kind of shortcuts, ;; that makes keyboard-alt stream registering the key pressed but @@ -119,15 +134,15 @@ (->> window-blur (rx/map (constantly false)))) (rx/dedupe))] - (rx/subscribe-with ob sub) - sub)) + (rx/subscribe-with ob sub) + sub)) (defonce keyboard-ctrl (let [sub (rx/behavior-subject nil) ob (->> (rx/merge (->> st/stream (rx/filter keyboard-event?) - (rx/filter kbd/ctrlKey?) + (rx/filter kbd/ctrl-key?) (rx/map #(= :down (:type %)))) ;; Fix a situation caused by using `ctrl+alt` kind of shortcuts, ;; that makes keyboard-alt stream registering the key pressed but @@ -135,9 +150,30 @@ (->> window-blur (rx/map (constantly false)))) (rx/dedupe))] - (rx/subscribe-with ob sub) + (rx/subscribe-with ob sub) sub)) +(defonce keyboard-meta + (let [sub (rx/behavior-subject nil) + ob (->> (rx/merge + (->> st/stream + (rx/filter keyboard-event?) + (rx/filter kbd/meta-key?) + (rx/map #(= :down (:type %)))) + ;; Fix a situation caused by using `ctrl+alt` kind of shortcuts, + ;; that makes keyboard-alt stream registering the key pressed but + ;; on blurring the window (unfocus) the key down is never arrived. + (->> window-blur + (rx/map (constantly false)))) + (rx/dedupe))] + (rx/subscribe-with ob sub) + sub)) + +(defonce keyboard-mod + (if (cfg/check-platform? :macos) + keyboard-meta + keyboard-ctrl)) + (defonce keyboard-space (let [sub (rx/behavior-subject nil) ob (->> st/stream diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index 0b9cce30de..3a5504fe93 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -14,6 +14,7 @@ [app.main.ui.dashboard.sidebar :refer [profile-section]] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [tr]] + [app.util.keyboard :as kbd] [app.util.router :as rt] [potok.core :as ptk] [rumext.alpha :as mf])) @@ -55,8 +56,7 @@ (fn [event] (let [version (:main @cf/version)] (st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version})) - (if (and (.-ctrlKey ^js event) - (.-altKey ^js event)) + (if (and (kbd/alt? event) (kbd/mod? event)) (st/emit! (modal/show {:type :onboarding})) (st/emit! (modal/show {:type :release-notes :version version}))))))] diff --git a/frontend/src/app/main/ui/shapes/text/svg_text.cljs b/frontend/src/app/main/ui/shapes/text/svg_text.cljs index 75648bbfeb..b2747ab37a 100644 --- a/frontend/src/app/main/ui/shapes/text/svg_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/svg_text.cljs @@ -51,8 +51,8 @@ [:> :g group-props (for [[index data] (d/enumerate position-data)] (let [props (-> #js {:x (mth/round (:x data)) - :y (mth/round (:y data)) - :dominantBaseline "ideographic" + :y (mth/round (- (:y data) (:height data))) + :alignmentBaseline "text-before-edge" :style (-> #js {:fontFamily (:font-family data) :fontSize (:font-size data) :fontWeight (:font-weight data) diff --git a/frontend/src/app/main/ui/viewer/handoff.cljs b/frontend/src/app/main/ui/viewer/handoff.cljs index f3cbeaa7be..4a00631ea6 100644 --- a/frontend/src/app/main/ui/viewer/handoff.cljs +++ b/frontend/src/app/main/ui/viewer/handoff.cljs @@ -28,7 +28,7 @@ [{:keys [local file page frame]}] (let [on-mouse-wheel (fn [event] - (when (or (kbd/ctrl? event) (kbd/meta? event)) + (when (kbd/mod? event) (dom/prevent-default event) (let [event (.getBrowserEvent ^js event) delta (+ (.-deltaY ^js event) diff --git a/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs index 4319c262d1..4e05ac4092 100644 --- a/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs @@ -45,7 +45,7 @@ (dom/prevent-default event) (let [id (:id item)] (cond - (or (kbd/ctrl? event) (kbd/meta? event)) + (kbd/mod? event) (st/emit! (dv/toggle-selection id)) (kbd/shift? event) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index e3e0b55b40..587b41ac92 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -61,7 +61,7 @@ on-mouse-wheel (fn [event] - (when (or (kbd/ctrl? event) (kbd/meta? event)) + (when (kbd/mod? event) (dom/prevent-default event) (let [event (.getBrowserEvent ^js event) delta (+ (.-deltaY ^js event) (.-deltaX ^js event))] diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index f89dd55f7f..1b78424549 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -47,15 +47,15 @@ (st/emit! (drp/create-node-at-position (meta position)))) (let [shift? (kbd/shift? event) - ctrl? (kbd/ctrl? event)] + mod? (kbd/mod? event)] (cond last-p? (st/emit! (drp/reset-last-handler)) - (and (= edit-mode :move) ctrl? (not curve?)) + (and (= edit-mode :move) mod? (not curve?)) (st/emit! (drp/make-curve position)) - (and (= edit-mode :move) ctrl? curve?) + (and (= edit-mode :move) mod? curve?) (st/emit! (drp/make-corner position)) (= edit-mode :move) diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 18bf8fc093..85e635161e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -27,6 +27,7 @@ [app.util.timers :as timers] [app.util.webapi :as wapi] [beicon.core :as rx] + [debug :refer [debug?]] [okulary.core :as l] [rumext.alpha :as mf])) @@ -224,4 +225,22 @@ (when show-svg-text? [:g.text-svg {:pointer-events "none"} - [:& svg/text-shape {:shape shape}]])]])) + [:& svg/text-shape {:shape shape}]]) + + (when (debug? :text-outline) + (for [data (:position-data shape)] + (let [{:keys [x y width height]} data] + [:* + ;; Text fragment bounding box + [:rect {:x x + :y (- y height) + :width width + :height height + :style {:fill "none" :stroke "red"}}] + + ;; Text baselineazo + [:line {:x1 (mth/round x) + :y1 (mth/round (- (:y data) (:height data))) + :x2 (mth/round (+ x width)) + :y2 (mth/round (- (:y data) (:height data))) + :style {:stroke "blue"}}]])))]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 6c08fa5eee..9f8032f392 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -1491,7 +1491,7 @@ (mf/deps extend-selected-assets selected-assets) (fn [asset-type asset-groups event asset-id default-click] (cond - (kbd/ctrl? event) + (kbd/mod? event) (do (dom/stop-propagation event) (st/emit! (dw/toggle-selected-assets asset-id asset-type))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index de27f21ede..e9f619f516 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -134,7 +134,7 @@ (kbd/shift? event) (st/emit! (dw/shift-select-shapes id)) - (or (kbd/ctrl? event) (kbd/meta? event)) + (kbd/mod? event) (st/emit! (dw/select-shape id true)) (> (count selected) 1) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index c43b88c149..4f058a79f1 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -77,7 +77,7 @@ ;; STATE alt? (mf/use-state false) - ctrl? (mf/use-state false) + mod? (mf/use-state false) space? (mf/use-state false) cursor (mf/use-state (utils/get-cursor :pointer-inner)) hover-ids (mf/use-state nil) @@ -171,9 +171,9 @@ (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?) (hooks/setup-viewport-size viewport-ref) - (hooks/setup-cursor cursor alt? ctrl? space? panning drawing-tool drawing-path? node-editing?) - (hooks/setup-keyboard alt? ctrl? space?) - (hooks/setup-hover-shapes page-id move-stream base-objects transform selected ctrl? hover hover-ids @hover-disabled? focus zoom) + (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?) + (hooks/setup-keyboard alt? mod? space?) + (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom) (hooks/setup-viewport-modifiers modifiers base-objects) (hooks/setup-shortcuts node-editing? drawing-path?) (hooks/setup-active-frames base-objects vbox hover active-frames) @@ -258,7 +258,7 @@ {:objects base-objects :selected selected :hover (cond - (and @hover (or @ctrl? (not= :frame (:type @hover)))) + (and @hover (or @mod? (not= :frame (:type @hover)))) #{(:id @hover)} @frame-hover @@ -271,7 +271,7 @@ {:shapes selected-shapes :zoom zoom :edition edition - :disable-handlers (or drawing-tool edition @space? @ctrl?) + :disable-handlers (or drawing-tool edition @space? @mod?) :on-move-selected on-move-selected :on-context-menu on-menu-selected}]) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 871ebf04e4..e844451ca9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.viewport.actions (:require [app.common.geom.point :as gpt] + [app.common.math :as mth] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.workspace :as dw] @@ -40,8 +41,10 @@ (let [event (.-nativeEvent bevent) ctrl? (kbd/ctrl? event) + meta? (kbd/meta? event) shift? (kbd/shift? event) alt? (kbd/alt? event) + mod? (kbd/mod? event) left-click? (and (not panning) (= 1 (.-which event))) middle-click? (and (not panning) (= 2 (.-which event))) @@ -53,7 +56,7 @@ middle-click? (do (dom/prevent-default bevent) - (if ctrl? + (if mod? (let [raw-pt (dom/get-client-position event) viewport (mf/ref-val viewport-ref) pt (utils/translate-point-to-viewport viewport zoom raw-pt)] @@ -63,7 +66,7 @@ left-click? (do - (st/emit! (ms/->MouseEvent :down ctrl? shift? alt?)) + (st/emit! (ms/->MouseEvent :down ctrl? shift? alt? meta?)) (when (and (not= edition id) text-editing?) (st/emit! dw/clear-edition-mode)) @@ -78,7 +81,7 @@ ;; Handle path node area selection (st/emit! (dwdp/handle-area-selection shift?)) - (and @space? ctrl?) + (and @space? mod?) (let [raw-pt (dom/get-client-position event) viewport (mf/ref-val viewport-ref) pt (utils/translate-point-to-viewport viewport zoom raw-pt)] @@ -90,8 +93,8 @@ drawing-tool (st/emit! (dd/start-drawing drawing-tool)) - (or (not id) (and frame? (not selected?)) ctrl?) - (st/emit! (dw/handle-area-selection shift? ctrl?)) + (or (not id) (and frame? (not selected?)) mod?) + (st/emit! (dw/handle-area-selection shift? mod?)) (not drawing-tool) (st/emit! (when (or shift? (not selected?)) @@ -105,11 +108,11 @@ (fn [bevent] (let [event (.-nativeEvent bevent) shift? (kbd/shift? event) - ctrl? (kbd/ctrl? event) + mod? (kbd/mod? event) left-click? (= 1 (.-which event))] (when (and left-click? - (not ctrl?) + (not mod?) (not shift?) (not @space?) (or (not @hover) @@ -153,14 +156,16 @@ (let [ctrl? (kbd/ctrl? event) shift? (kbd/shift? event) alt? (kbd/alt? event) + meta? (kbd/meta? event) + mod? (kbd/mod? event) hovering? (some? @hover) frame? (= :frame (:type @hover)) selected? (contains? selected (:id @hover))] - (st/emit! (ms/->MouseEvent :click ctrl? shift? alt?)) + (st/emit! (ms/->MouseEvent :click ctrl? shift? alt? meta?)) (when (and hovering? - (or (not frame?) ctrl?) + (or (not frame?) mod?) (not @space?) (not selected?) (not edition) @@ -177,13 +182,14 @@ (let [ctrl? (kbd/ctrl? event) shift? (kbd/shift? event) alt? (kbd/alt? event) + meta? (kbd/meta? event) {:keys [id type] :as shape} @hover frame? (= :frame type) group? (= :group type)] - (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?)) + (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?)) ;; Emit asynchronously so the double click to exit shapes won't break (timers/schedule @@ -242,12 +248,13 @@ ctrl? (kbd/ctrl? event) shift? (kbd/shift? event) alt? (kbd/alt? event) + meta? (kbd/meta? event) left-click? (= 1 (.-which event)) middle-click? (= 2 (.-which event))] (when left-click? - (st/emit! (ms/->MouseEvent :up ctrl? shift? alt?))) + (st/emit! (ms/->MouseEvent :up ctrl? shift? alt? meta?))) (when middle-click? (dom/prevent-default event) @@ -343,11 +350,13 @@ (st/emit! (ms/->PointerEvent :delta delta (kbd/ctrl? event) (kbd/shift? event) - (kbd/alt? event))) + (kbd/alt? event) + (kbd/meta? event))) (st/emit! (ms/->PointerEvent :viewport pt (kbd/ctrl? event) (kbd/shift? event) - (kbd/alt? event)))))))) + (kbd/alt? event) + (kbd/meta? event)))))))) (defn on-pointer-move [viewport-ref zoom move-stream] (mf/use-callback @@ -362,27 +371,18 @@ (mf/use-callback (mf/deps zoom) (fn [event] - (let [event (.getBrowserEvent ^js event) - raw-pt (dom/get-client-position event) - viewport (mf/ref-val viewport-ref) - pt (utils/translate-point-to-viewport viewport zoom raw-pt) - - ctrl? (kbd/ctrl? event) - meta? (kbd/meta? event) + (let [viewport (mf/ref-val viewport-ref) + event (.getBrowserEvent ^js event) target (dom/get-target event)] - (cond - (or ctrl? meta?) - (do - (dom/prevent-default event) - (dom/stop-propagation event) - (let [delta (+ (.-deltaY ^js event) - (.-deltaX ^js event))] - (if (pos? delta) - (st/emit! (dw/decrease-zoom pt)) - (st/emit! (dw/increase-zoom pt))))) + (when (.contains ^js viewport target) + (dom/prevent-default event) + (dom/stop-propagation event) + (let [pt (->> (dom/get-client-position event) + (utils/translate-point-to-viewport viewport zoom)) - (.contains ^js viewport target) - (let [delta-mode (.-deltaMode ^js event) + mod? (kbd/mod? event) + + delta-mode (.-deltaMode ^js event) unit (cond (= delta-mode WheelEvent.DeltaMode.PIXEL) 1 @@ -396,13 +396,16 @@ delta-x (-> (.-deltaX ^js event) (* unit) (/ zoom))] - (dom/prevent-default event) - (dom/stop-propagation event) - (if (and (not (cfg/check-platform? :macos)) ;; macos sends delta-x automatically, don't need to do it - (kbd/shift? event)) - (st/emit! (dw/update-viewport-position {:x #(+ % delta-y)})) - (st/emit! (dw/update-viewport-position {:x #(+ % delta-x) - :y #(+ % delta-y)}))))))))) + (if mod? + (let [delta (* -1 (+ (.-deltaY ^js event) (.-deltaX ^js event))) + scale (-> (+ 1 (/ delta 100)) (mth/clamp 0.77 1.3))] + (st/emit! (dw/set-zoom pt scale))) + (if (and (not (cfg/check-platform? :macos)) + ;; macos sends delta-x automatically, don't need to do it + (kbd/shift? event)) + (st/emit! (dw/update-viewport-position {:x #(+ % delta-y)})) + (st/emit! (dw/update-viewport-position {:x #(+ % delta-x) + :y #(+ % delta-y)})))))))))) (defn on-drag-enter [] (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 28239e3120..897d31d500 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -58,16 +58,16 @@ ;; We schedule the event so it fires after `initialize-page` event (timers/schedule #(st/emit! (dw/initialize-viewport size))))))) -(defn setup-cursor [cursor alt? ctrl? space? panning drawing-tool drawing-path? path-editing?] +(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing?] (mf/use-effect - (mf/deps @cursor @alt? @ctrl? @space? panning drawing-tool drawing-path? path-editing?) + (mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing?) (fn [] (let [show-pen? (or (= drawing-tool :path) (and drawing-path? (not= drawing-tool :curve))) new-cursor (cond - (and @ctrl? @space?) (utils/get-cursor :zoom) + (and @mod? @space?) (utils/get-cursor :zoom) (or panning @space?) (utils/get-cursor :hand) (= drawing-tool :comments) (utils/get-cursor :comments) (= drawing-tool :frame) (utils/get-cursor :create-artboard) @@ -82,9 +82,9 @@ (when (not= @cursor new-cursor) (reset! cursor new-cursor)))))) -(defn setup-keyboard [alt? ctrl? space?] +(defn setup-keyboard [alt? mod? space?] (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) - (hooks/use-stream ms/keyboard-ctrl #(reset! ctrl? %)) + (hooks/use-stream ms/keyboard-mod #(reset! mod? %)) (hooks/use-stream ms/keyboard-space #(reset! space? %))) (defn group-empty-space? @@ -100,10 +100,10 @@ (some #(cph/is-parent? objects % group-id)) (not)))) -(defn setup-hover-shapes [page-id move-stream objects transform selected ctrl? hover hover-ids hover-disabled? focus zoom] +(defn setup-hover-shapes [page-id move-stream objects transform selected mod? hover hover-ids hover-disabled? focus zoom] (let [;; We use ref so we don't recreate the stream on a change zoom-ref (mf/use-ref zoom) - ctrl-ref (mf/use-ref @ctrl?) + mod-ref (mf/use-ref @mod?) transform-ref (mf/use-ref nil) selected-ref (mf/use-ref selected) hover-disabled-ref (mf/use-ref hover-disabled?) @@ -114,7 +114,7 @@ (mf/deps page-id) (fn [point] (let [zoom (mf/ref-val zoom-ref) - ctrl? (mf/ref-val ctrl-ref) + mod? (mf/ref-val mod-ref) rect (gsh/center->rect point (/ 5 zoom) (/ 5 zoom))] (if (mf/ref-val hover-disabled-ref) (rx/of nil) @@ -123,7 +123,7 @@ :page-id page-id :rect rect :include-frames? true - :clip-children? (not ctrl?) + :clip-children? (not mod?) :reverse? true}))))) ;; we want the topmost shape to be selected first over-shapes-stream @@ -150,8 +150,8 @@ #(mf/set-ref-val! zoom-ref zoom)) (mf/use-effect - (mf/deps @ctrl?) - #(mf/set-ref-val! ctrl-ref @ctrl?)) + (mf/deps @mod?) + #(mf/set-ref-val! mod-ref @mod?)) (mf/use-effect (mf/deps selected) @@ -176,14 +176,14 @@ selected (mf/ref-val selected-ref) focus (mf/ref-val focus-ref) - ctrl? (mf/ref-val ctrl-ref) + mod? (mf/ref-val mod-ref) remove-xfm (mapcat #(cph/get-parent-ids objects %)) remove-id? (cond-> (into #{} remove-xfm selected) - (not ctrl?) + (not mod?) (into (filter #(group-empty-space? % objects ids)) ids) - ctrl? + mod? (into (filter is-group?) ids)) hover-shape (->> ids diff --git a/frontend/src/app/util/keyboard.cljs b/frontend/src/app/util/keyboard.cljs index eea236f83d..52077fb30e 100644 --- a/frontend/src/app/util/keyboard.cljs +++ b/frontend/src/app/util/keyboard.cljs @@ -4,37 +4,45 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.util.keyboard) +(ns app.util.keyboard + (:require + [app.config :as cfg])) (defn is-key? - [key] - (fn [e] + [^string key] + (fn [^js e] (= (.-key e) key))) (defn ^boolean alt? - [event] + [^js event] (.-altKey event)) (defn ^boolean ctrl? - [event] + [^js event] (.-ctrlKey event)) (defn ^boolean meta? - [event] + [^js event] (.-metaKey event)) (defn ^boolean shift? - [event] + [^js event] (.-shiftKey event)) +(defn ^boolean mod? + [^js event] + (if (cfg/check-platform? :macos) + (meta? event) + (ctrl? event))) + (def esc? (is-key? "Escape")) (def enter? (is-key? "Enter")) (def space? (is-key? " ")) (def up-arrow? (is-key? "ArrowUp")) (def down-arrow? (is-key? "ArrowDown")) -(def altKey? (is-key? "Alt")) -(def ctrlKey? (or (is-key? "Control") - (is-key? "Meta"))) +(def alt-key? (is-key? "Alt")) +(def ctrl-key? (is-key? "Control")) +(def meta-key? (is-key? "Meta")) (def comma? (is-key? ",")) (def backspace? (is-key? "Backspace")) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 867df64558..21b6881a08 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -46,6 +46,9 @@ ;; When active we can check in the browser the export values :show-export-metadata + + ;; Show text fragments outlines + :text-outline }) ;; These events are excluded when we activate the :events flag