mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 00:10:11 -05:00
🐛 Safari compatibility fixes
This commit is contained in:
parent
9260c59afb
commit
001f90a540
12 changed files with 164 additions and 75 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
.debug-preview {
|
||||
max-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: scroll;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)))))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}])
|
||||
|
|
|
@ -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}]]))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue