From 001f90a54009cfa5ec5e6ad823baacf879136342 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 30 Nov 2020 17:59:39 +0100 Subject: [PATCH] :bug: Safari compatibility fixes --- frontend/resources/styles/common/base.scss | 5 ++ .../main/partials/debug-icons-preview.scss | 1 - frontend/src/app/config.cljs | 44 +++++++++++++++- frontend/src/app/main/data/workspace.cljs | 52 +++++++++++-------- frontend/src/app/main/ui/cursors.clj | 10 ++-- frontend/src/app/main/ui/shapes/text.cljs | 16 +++--- .../src/app/main/ui/shapes/text/styles.cljs | 22 ++++---- .../src/app/main/ui/workspace/selection.cljs | 7 +-- .../app/main/ui/workspace/shapes/text.cljs | 12 +++-- .../main/ui/workspace/shapes/text/editor.cljs | 51 +++++++++++------- .../src/app/main/ui/workspace/viewport.cljs | 16 +++--- frontend/src/app/util/dom.cljs | 3 ++ 12 files changed, 164 insertions(+), 75 deletions(-) diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss index a9fcacdee..ddd623792 100644 --- a/frontend/resources/styles/common/base.scss +++ b/frontend/resources/styles/common/base.scss @@ -224,3 +224,8 @@ input[type=number]::-webkit-inner-spin-button, input[type=number] { -moz-appearance: textfield; } + +[contenteditable] { + -webkit-user-select: text; + user-select: text; +} diff --git a/frontend/resources/styles/main/partials/debug-icons-preview.scss b/frontend/resources/styles/main/partials/debug-icons-preview.scss index 5e56fc0a4..7a4940eec 100644 --- a/frontend/resources/styles/main/partials/debug-icons-preview.scss +++ b/frontend/resources/styles/main/partials/debug-icons-preview.scss @@ -1,5 +1,4 @@ .debug-preview { - max-height: 100vh; display: flex; flex-direction: column; overflow: scroll; diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 6281a10f5..bff44964c 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -9,9 +9,41 @@ (ns app.config (:require + [clojure.spec.alpha :as s] + [app.common.spec :as us] [app.util.object :as obj] + [app.util.dom :as dom] [cuerdas.core :as str])) +(s/def ::platform #{:windows :linux :macos :other}) +(s/def ::browser #{:chrome :mozilla :safari :edge :other}) + +(defn parse-browser + [] + (let [user-agent (-> (dom/get-user-agent) str/lower) + check-chrome? (fn [] (str/includes? user-agent "chrom")) + check-firefox? (fn [] (str/includes? user-agent "firefox")) + check-edge? (fn [] (str/includes? user-agent "edg")) + check-safari? (fn [] (str/includes? user-agent "safari"))] + (cond + (check-edge?) :edge + (check-chrome?) :chrome + (check-firefox?) :firefox + (check-safari?) :safari + :else :other))) + +(defn parse-platform + [] + (let [user-agent (-> (dom/get-user-agent) str/lower) + check-windows? (fn [] (str/includes? user-agent "windows")) + check-linux? (fn [] (str/includes? user-agent "linux")) + check-macos? (fn [] (str/includes? user-agent "mac os"))] + (cond + (check-windows?) :windows + (check-linux?) :linux + (check-macos?) :macos + :else :other))) + (this-as global (def default-language "en") (def demo-warning (obj/get global "appDemoWarning" false)) @@ -22,7 +54,17 @@ (def public-uri (or (obj/get global "appPublicURI") (.-origin ^js js/location))) (def media-uri (str public-uri "/media")) - (def default-theme "default")) + (def default-theme "default") + (def browser (parse-browser)) + (def platform (parse-platform))) + +(defn ^boolean check-browser? [candidate] + (us/verify ::browser candidate) + (= candidate browser)) + +(defn ^boolean check-platform? [candidate] + (us/verify ::platform candidate) + (= candidate platform)) (defn resolve-media-path [path] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f85f2da3d..bd4251ffe 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1625,45 +1625,53 @@ (deselect-all true)) (rx/empty)))))) +(defn c-mod + "Adds the control/command modifier to a shortcuts depending on the + operating system for the user" + [shortcut] + (if (cfg/check-platform? :macos) + (str "command+" shortcut) + (str "ctrl+" shortcut))) + (def shortcuts - {"ctrl+i" #(st/emit! (toggle-layout-flags :assets)) - "ctrl+l" #(st/emit! (toggle-layout-flags :sitemap :layers)) - "ctrl+shift+r" #(st/emit! (toggle-layout-flags :rules)) - "ctrl+a" #(st/emit! (select-all)) - "ctrl+p" #(st/emit! (toggle-layout-flags :colorpalette)) - "ctrl+'" #(st/emit! (toggle-layout-flags :display-grid)) - "ctrl+shift+'" #(st/emit! (toggle-layout-flags :snap-grid)) + {(c-mod "i") #(st/emit! (toggle-layout-flags :assets)) + (c-mod "l") #(st/emit! (toggle-layout-flags :sitemap :layers)) + (c-mod "shift+r") #(st/emit! (toggle-layout-flags :rules)) + (c-mod "a") #(st/emit! (select-all)) + (c-mod "p") #(st/emit! (toggle-layout-flags :colorpalette)) + (c-mod "'") #(st/emit! (toggle-layout-flags :display-grid)) + (c-mod "shift+'") #(st/emit! (toggle-layout-flags :snap-grid)) "+" #(st/emit! (increase-zoom nil)) "-" #(st/emit! (decrease-zoom nil)) - "ctrl+g" #(st/emit! group-selected) + (c-mod "g") #(st/emit! group-selected) "shift+g" #(st/emit! ungroup-selected) - "ctrl+m" #(st/emit! mask-group) + (c-mod "m") #(st/emit! mask-group) "shift+m" #(st/emit! unmask-group) - "ctrl+k" #(st/emit! dwl/add-component) + (c-mod "k") #(st/emit! dwl/add-component) "shift+0" #(st/emit! reset-zoom) "shift+1" #(st/emit! zoom-to-fit-all) "shift+2" #(st/emit! zoom-to-selected-shape) - "ctrl+d" #(st/emit! duplicate-selected) - "ctrl+z" #(st/emit! dwc/undo) - "ctrl+shift+z" #(st/emit! dwc/redo) - "ctrl+y" #(st/emit! dwc/redo) - "ctrl+q" #(st/emit! dwc/reinitialize-undo) + (c-mod "d") #(st/emit! duplicate-selected) + (c-mod "z") #(st/emit! dwc/undo) + (c-mod "shift+z") #(st/emit! dwc/redo) + (c-mod "y") #(st/emit! dwc/redo) + (c-mod "q") #(st/emit! dwc/reinitialize-undo) "a" #(st/emit! (dwd/select-for-drawing :frame)) "r" #(st/emit! (dwd/select-for-drawing :rect)) "e" #(st/emit! (dwd/select-for-drawing :circle)) "t" #(st/emit! dwtxt/start-edit-if-selected (dwd/select-for-drawing :text)) "p" #(st/emit! (dwd/select-for-drawing :path)) - "ctrl+c" #(st/emit! copy-selected) - "ctrl+v" #(st/emit! paste) - "ctrl+x" #(st/emit! copy-selected delete-selected) + (c-mod "c") #(st/emit! copy-selected) + (c-mod "v") #(st/emit! paste) + (c-mod "x") #(st/emit! copy-selected delete-selected) "escape" #(st/emit! (esc-pressed)) "del" #(st/emit! delete-selected) "backspace" #(st/emit! delete-selected) - "ctrl+up" #(st/emit! (vertical-order-selected :up)) - "ctrl+down" #(st/emit! (vertical-order-selected :down)) - "ctrl+shift+up" #(st/emit! (vertical-order-selected :top)) - "ctrl+shift+down" #(st/emit! (vertical-order-selected :bottom)) + (c-mod "up") #(st/emit! (vertical-order-selected :up)) + (c-mod "down") #(st/emit! (vertical-order-selected :down)) + (c-mod "shift+up") #(st/emit! (vertical-order-selected :top)) + (c-mod "shift+down") #(st/emit! (vertical-order-selected :bottom)) "shift+up" #(st/emit! (dwt/move-selected :up true)) "shift+down" #(st/emit! (dwt/move-selected :down true)) "shift+right" #(st/emit! (dwt/move-selected :right true)) diff --git a/frontend/src/app/main/ui/cursors.clj b/frontend/src/app/main/ui/cursors.clj index e816aed0f..5360356e2 100644 --- a/frontend/src/app/main/ui/cursors.clj +++ b/frontend/src/app/main/ui/cursors.clj @@ -55,10 +55,14 @@ (defn encode-svg-cursor [id rotation x y height] (let [svg-path (str cursor-folder "/" (name id) ".svg") - data (-> svg-path io/resource slurp parse-svg uri/percent-encode) - transform (if rotation (str " transform='rotate(" rotation ")'") "")] + data (-> svg-path io/resource slurp parse-svg) + data (uri/percent-encode data) + + data (if rotation + (str/fmt "%3Cg transform='rotate(%s 8,8)'%3E%s%3C/g%3E" rotation data) + data)] (str "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='20px' " - "height='" height "px' " transform "%3E" data "%3C/svg%3E\") " x " " y ", auto"))) + "height='" height "px' %3E" data "%3C/svg%3E\") " x " " y ", auto"))) (defmacro cursor-ref "Creates a static cursor given its name, rotation and x/y hotspot" diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index 2d36fe781..266dc6601 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -26,7 +26,7 @@ [{:keys [node index shape] :as props}] (let [embed-resources? (mf/use-ctx muc/embed-ctx) {:keys [type text children]} node - + props #js {:shape shape} render-node (fn [index node] (mf/element text-node {:index index @@ -35,29 +35,31 @@ :shape shape}))] (if (string? text) - (let [style (sts/generate-text-styles (clj->js node))] - [:span.text-node {:style style} (if (= text "") "\u00A0" text)]) + (let [style (sts/generate-text-styles (clj->js node) props)] + [:span {:style style + :className (when (:fill-color-gradient node) "gradient")} + (if (= text "") "\u00A0" text)]) (let [children (map-indexed render-node children)] (case type "root" - (let [style (sts/generate-root-styles (clj->js node) #js {:shape shape})] + (let [style (sts/generate-root-styles (clj->js node) props)] [:div.root.rich-text {:key index :style style :xmlns "http://www.w3.org/1999/xhtml"} [:* - [:style ".text-node { background: var(--text-color); -webkit-text-fill-color: transparent; -webkit-background-clip: text;"] + [:style ".gradient { background: var(--text-color); -webkit-text-fill-color: transparent; -webkit-background-clip: text;"] (when embed-resources? [ste/embed-fontfaces-style {:node node}])] children]) "paragraph-set" - (let [style (sts/generate-paragraph-set-styles (clj->js node))] + (let [style (sts/generate-paragraph-set-styles (clj->js node) props)] [:div.paragraph-set {:key index :style style} children]) "paragraph" - (let [style (sts/generate-paragraph-styles (clj->js node))] + (let [style (sts/generate-paragraph-styles (clj->js node) props)] [:p.paragraph {:key index :style style} children]) nil))))) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 41e2e29e2..02926dc78 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -35,27 +35,29 @@ (= talign "justify") (obj/set! "justifyContent" "stretch")))) (defn generate-paragraph-set-styles - [data] + [data props] ;; The position absolute is used so the paragraph is "outside" ;; the normal layout and can grow outside its parent ;; We use this element to measure the size of the text - (let [base #js {:display "inline-block" - :position "absolute"}] + (let [base #js {:display "inline-block"}] base)) (defn generate-paragraph-styles - [data] - (let [base #js {:fontSize "14px" + [data props] + (let [shape (obj/get props "shape") + grow-type (:grow-type shape) + base #js {:fontSize "14px" :margin "inherit" :lineHeight "1.2"} lh (obj/get data "line-height") ta (obj/get data "text-align")] (cond-> base ta (obj/set! "textAlign" ta) - lh (obj/set! "lineHeight" lh)))) + lh (obj/set! "lineHeight" lh) + (= grow-type :auto-width) (obj/set! "whiteSpace" "pre")))) (defn generate-text-styles - [data] + [data props] (let [letter-spacing (obj/get data "letter-spacing") text-decoration (obj/get data "text-decoration") text-transform (obj/get data "text-transform") @@ -82,7 +84,7 @@ fill-color-ref-file (obj/get data "fill-color-ref-file") [r g b a] (uc/hex->rgba fill-color fill-opacity) - background (if fill-color-gradient + text-color (if fill-color-gradient (uc/gradient->css (js->clj fill-color-gradient)) (str/format "rgba(%s, %s, %s, %s)" r g b a)) @@ -91,7 +93,8 @@ base #js {:textDecoration text-decoration :textTransform text-transform :lineHeight (or line-height "inherit") - "--text-color" background}] + :color text-color + "--text-color" text-color}] (when (and (string? letter-spacing) (pos? (alength letter-spacing))) @@ -117,4 +120,5 @@ (obj/set! base "fontStyle" font-style) (obj/set! base "fontWeight" font-weight)))) + base)) diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs index f14e5d956..4d53b6802 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -156,10 +156,11 @@ (let [res-point (if (#{:top :bottom} position) {:y y} {:x x}) - width length #_(max 0 (- length (/ (* resize-point-rect-size 2) zoom))) + target-length (max 0 (- length (/ (* resize-point-rect-size 2) zoom))) + width (if (< target-length 6) length target-length) height (/ resize-side-height zoom)] - [:rect {:x x - :y (- y (/ resize-side-height 2 zoom)) + [:rect {:x (+ x (/ (- length width) 2)) + :y (- y (/ height 2)) :width width :height height :transform (gmt/multiply transform diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 895de130e..ea30d0282 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -115,11 +115,13 @@ (resize-observer shape text-node ".paragraph-set") [:> shape-container {:shape shape} - [:& text/text-shape {:key "text-shape" - :ref text-ref - :shape shape - :selected? selected? - :style {:display (when edition? "none")}}] + ;; We keep hidden the shape when we're editing so it keeps track of the size + ;; and updates the selrect acordingly + [:g.text-shape {:opacity (when edition? 0)} + [:& text/text-shape {:key "text-shape" + :ref text-ref + :shape shape + :selected? selected?}]] (when edition? [:& editor/text-shape-edit {:key "editor" :shape shape}]) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 30277d568..0e55d242c 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -82,7 +82,7 @@ data (obj/get props "element") type (obj/get data "type") shape (obj/get props "shape") - style (sts/generate-paragraph-set-styles data) + style (sts/generate-paragraph-set-styles data props) attrs (-> (obj/get props "attributes") (obj/set! "style" style) (obj/set! "className" type))] @@ -95,7 +95,7 @@ childs (obj/get props "children") data (obj/get props "element") type (obj/get data "type") - style (sts/generate-paragraph-styles data) + style (sts/generate-paragraph-styles data props) attrs (-> (obj/get props "attributes") (obj/set! "style" style) (obj/set! "className" type))] @@ -106,10 +106,14 @@ [props] (let [childs (obj/get props "children") data (obj/get props "leaf") - style (sts/generate-text-styles data) + type (obj/get data "type") + style (sts/generate-text-styles data props) attrs (-> (obj/get props "attributes") - (obj/set! "style" style) - (obj/set! "className" "text-node"))] + (obj/set! "style" style)) + gradient (obj/get data "fill-color-gradient" nil)] + (if gradient + (obj/set! attrs "className" (str type " gradient")) + (obj/set! attrs "className" type)) [:> :span attrs childs])) (defn- render-element @@ -135,7 +139,7 @@ ;; --- Text Shape Edit -(mf/defc text-shape-edit +(mf/defc text-shape-edit-html {::mf/wrap [mf/memo] ::mf/wrap-props false ::mf/forward-ref true} @@ -161,9 +165,6 @@ on-click-outside (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (let [sidebar (dom/get-element "settings-bar") assets (dom/get-element-by-class "assets-bar") cpicker (dom/get-element-by-class "colorpicker-tooltip") @@ -174,9 +175,13 @@ (and assets (.contains assets target)) (and self (.contains self target)) (and cpicker (.contains cpicker target))) - (if selecting? - (mf/set-ref-val! selecting-ref false) - (on-close))))) + (do + (dom/prevent-default event) + (dom/stop-propagation event) + + (if selecting? + (mf/set-ref-val! selecting-ref false) + (on-close)))))) on-mouse-down (fn [event] @@ -230,13 +235,9 @@ (reset! state (parse-content content)) (reset! content-var content))) - [:foreignObject {:ref self-ref - :transform (gsh/transform-matrix shape) - :x x :y y - :width (if (#{:auto-width} grow-type) 10000 width) - :height (if (#{:auto-height :auto-width} grow-type) 10000 height)} + [:div.text-editor {:ref self-ref} [:style "span { line-height: inherit; } - .text-node { background: var(--text-color); -webkit-text-fill-color: transparent; -webkit-background-clip: text;"] + .gradient { background: var(--text-color); -webkit-text-fill-color: transparent; -webkit-background-clip: text;"] [:> rslate/Slate {:editor editor :value @state :on-change on-change} @@ -257,3 +258,17 @@ ;; WARN: monky patch (obj/set! slate/Transforms "deselect" (constantly nil))) :placeholder (when (= :fixed grow-type) "Type some text here...")}]]])) + +(mf/defc text-shape-edit + {::mf/wrap [mf/memo] + ::mf/wrap-props false + ::mf/forward-ref true} + [props ref] + (let [shape (unchecked-get props "shape") + {:keys [x y width height grow-type]} shape] + [:foreignObject {:transform (gsh/transform-matrix shape) + :x x :y y + :width (if (#{:auto-width} grow-type) 10000 width) + :height (if (#{:auto-height :auto-width} grow-type) 10000 height)} + + [:& text-shape-edit-html {:shape shape}]])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 20d384a60..adbb0b053 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -116,8 +116,7 @@ (st/emit! dw/start-pan) (rx/subscribe stream (fn [delta] - (let [vbox (.. ^js node -viewBox -baseVal) - zoom (gpt/point @refs/selected-zoom) + (let [zoom (gpt/point @refs/selected-zoom) delta (gpt/divide delta zoom)] (st/emit! (dw/update-viewport-position {:x #(- % (:x delta)) @@ -352,10 +351,15 @@ on-mouse-move (fn [event] (let [event (.getBrowserEvent ^js event) - pt (dom/get-client-position ^js event) - pt (translate-point-to-viewport pt) - delta (gpt/point (.-movementX ^js event) - (.-movementY ^js event))] + raw-pt (dom/get-client-position ^js event) + pt (translate-point-to-viewport raw-pt) + + ;; We calculate the delta because Safari's MouseEvent.movementX/Y drop + ;; events + delta (if @last-position + (gpt/subtract raw-pt @last-position) + (gpt/point 0 0))] + (reset! last-position raw-pt) (st/emit! (ms/->PointerEvent :delta delta (kbd/ctrl? event) (kbd/shift? event) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 81cc977a1..2095bae52 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -245,3 +245,6 @@ (defn ^boolean class? [node class-name] (let [class-list (.-classList ^js node)] (.contains ^js class-list class-name))) + +(defn get-user-agent [] + (.-userAgent js/navigator))