mirror of
https://github.com/penpot/penpot.git
synced 2025-06-02 12:51:38 +02:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
c9ddc83eef
39 changed files with 678 additions and 450 deletions
|
@ -3,6 +3,7 @@
|
|||
{penpot/common
|
||||
{:local/root "../common"}
|
||||
|
||||
org.clojure/clojure {:mvn/version "1.10.3"}
|
||||
binaryage/devtools {:mvn/version "RELEASE"}
|
||||
metosin/reitit-core {:mvn/version "0.5.15"}
|
||||
|
||||
|
|
|
@ -1209,7 +1209,8 @@
|
|||
}
|
||||
|
||||
.modal-container {
|
||||
background-image: url("../images/deco-left.png"), url("../images/deco-right.png");
|
||||
background-image: url("../images/deco-left.png"),
|
||||
url("../images/deco-right.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 10% 50px, 90% 50px;
|
||||
background-size: 65px;
|
||||
|
@ -1236,8 +1237,18 @@
|
|||
--checkbox-border-radius: 3px;
|
||||
--dropdown-option-background-color: rgba(0, 195, 139, 1);
|
||||
--dropdown-option-active-background-color: rgba(0, 138, 98, 1);
|
||||
--invalid-field-background-color: rgba(238.51780000000002, 205.7178, 204.11780000000002, 1);
|
||||
--message-fail-background-color: rgba(238.51780000000002, 205.7178, 204.11780000000002, 1);
|
||||
--invalid-field-background-color: rgba(
|
||||
238.51780000000002,
|
||||
205.7178,
|
||||
204.11780000000002,
|
||||
1
|
||||
);
|
||||
--message-fail-background-color: rgba(
|
||||
238.51780000000002,
|
||||
205.7178,
|
||||
204.11780000000002,
|
||||
1
|
||||
);
|
||||
--message-success-background-color: rgba(171, 232, 197, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -523,6 +523,12 @@
|
|||
right: 0.5rem;
|
||||
left: unset;
|
||||
top: 0;
|
||||
|
||||
.context-menu-action {
|
||||
overflow-wrap: break-word;
|
||||
min-width: 223px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,7 +194,8 @@
|
|||
(ptk/reify ::initialize-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-not (contains? (get-in state [:workspace-data :pages-index]) page-id)
|
||||
(if (contains? (get-in state [:workspace-data :pages-index]) page-id)
|
||||
(rx/of (dwp/preload-data-uris))
|
||||
(let [default-page-id (get-in state [:workspace-data :pages 0])]
|
||||
(rx/of (go-to-page default-page-id)))))
|
||||
|
||||
|
@ -1356,26 +1357,28 @@
|
|||
edit-id (get-in state [:workspace-local :edition])
|
||||
is-editing-text? (and edit-id (= :text (get-in objects [edit-id :type])))]
|
||||
|
||||
(cond
|
||||
(and (string? text-data)
|
||||
(str/includes? text-data "<svg"))
|
||||
(rx/of (paste-svg text-data))
|
||||
;; Some paste events can be fired while we're editing a text
|
||||
;; we forbid that scenario so the default behaviour is executed
|
||||
(when-not is-editing-text?
|
||||
(cond
|
||||
(and (string? text-data)
|
||||
(str/includes? text-data "<svg"))
|
||||
(rx/of (paste-svg text-data))
|
||||
|
||||
(seq image-data)
|
||||
(rx/from (map paste-image image-data))
|
||||
(seq image-data)
|
||||
(rx/from (map paste-image image-data))
|
||||
|
||||
(coll? decoded-data)
|
||||
(->> (rx/of decoded-data)
|
||||
(rx/filter #(= :copied-shapes (:type %)))
|
||||
(rx/map #(paste-shape % in-viewport?)))
|
||||
(coll? decoded-data)
|
||||
(->> (rx/of decoded-data)
|
||||
(rx/filter #(= :copied-shapes (:type %)))
|
||||
(rx/map #(paste-shape % in-viewport?)))
|
||||
|
||||
;; Some paste events can be fired while we're editing a text
|
||||
;; we forbid that scenario so the default behaviour is executed
|
||||
(and (string? text-data) (not is-editing-text?))
|
||||
(rx/of (paste-text text-data))
|
||||
(string? text-data)
|
||||
(rx/of (paste-text text-data))
|
||||
|
||||
:else
|
||||
(rx/empty))))
|
||||
|
||||
:else
|
||||
(rx/empty)))
|
||||
(catch :default err
|
||||
(js/console.error "Clipboard error:" err))))))
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[app.common.spec.change :as spec.change]
|
||||
[app.common.spec.file :as spec.file]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.fonts :as df]
|
||||
|
@ -653,6 +654,9 @@
|
|||
|
||||
frame-changes (->> stream
|
||||
(rx/filter dch/commit-changes?)
|
||||
|
||||
;; Async so we wait for additional side-effects of commit-changes
|
||||
(rx/observe-on :async)
|
||||
(rx/filter (comp not thumbnail-change?))
|
||||
(rx/with-latest-from objects-stream)
|
||||
(rx/map extract-frame-changes)
|
||||
|
@ -678,3 +682,26 @@
|
|||
(rx/buffer-until (->> frame-changes (rx/debounce 1000)))
|
||||
(rx/flat-map #(reduce set/union %))
|
||||
(rx/map #(update-frame-thumbnail %)))))))))
|
||||
|
||||
(defn preload-data-uris
|
||||
"Preloads the image data so it's ready when necesary"
|
||||
[]
|
||||
(ptk/reify ::preload-data-uris
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [extract-urls
|
||||
(fn [{:keys [metadata fill-image]}]
|
||||
(cond
|
||||
(some? metadata)
|
||||
[(cfg/resolve-file-media metadata)]
|
||||
|
||||
(some? fill-image)
|
||||
[(cfg/resolve-file-media fill-image)]))
|
||||
|
||||
uris (into #{}
|
||||
(comp (mapcat extract-urls)
|
||||
(filter some?))
|
||||
(vals (wsh/lookup-page-objects state)))]
|
||||
(->> (rx/from uris)
|
||||
(rx/merge-map #(http/fetch-data-uri % false))
|
||||
(rx/ignore))))))
|
||||
|
|
|
@ -110,7 +110,10 @@
|
|||
(rx/dedupe)
|
||||
(rx/map #(select-shapes-by-current-selrect preserve? ignore-groups?))))
|
||||
|
||||
(rx/of (update-selrect nil)))))))
|
||||
(->> (rx/of (update-selrect nil))
|
||||
;; We need the async so the current event finishes before updating the selrect
|
||||
;; otherwise the `on-click` event will trigger with a `nil` selrect
|
||||
(rx/observe-on :async)))))))
|
||||
|
||||
;; --- Toggle shape's selection status (selected or deselected)
|
||||
|
||||
|
@ -123,13 +126,7 @@
|
|||
(ptk/reify ::select-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-local :selected]
|
||||
(fn [selected]
|
||||
(if-not toggle?
|
||||
(conj (d/ordered-set) id)
|
||||
(if (contains? selected id)
|
||||
(disj selected id)
|
||||
(conj selected id))))))
|
||||
(update-in state [:workspace-local :selected] d/toggle-selection id toggle?))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -506,6 +503,7 @@
|
|||
|
||||
id-duplicated (when (= (count selected) 1) (first selected))]
|
||||
|
||||
;; Warning: This order is important for the focus mode.
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(select-shapes selected)
|
||||
(memorize-duplicated id-original id-duplicated)))))))
|
||||
|
|
|
@ -592,31 +592,46 @@
|
|||
|
||||
(defn start-move-selected
|
||||
"Enter mouse move mode, until mouse button is released."
|
||||
[]
|
||||
(ptk/reify ::start-move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [initial (deref ms/mouse-position)
|
||||
selected (wsh/lookup-selected state {:omit-blocked? true})
|
||||
stopper (rx/filter ms/mouse-up? stream)
|
||||
zoom (get-in state [:workspace-local :zoom] 1)]
|
||||
(when-not (empty? selected)
|
||||
(->> ms/mouse-position
|
||||
(rx/map #(gpt/to-vec initial %))
|
||||
(rx/map #(gpt/length %))
|
||||
(rx/filter #(> % (/ 10 zoom)))
|
||||
(rx/take 1)
|
||||
(rx/with-latest vector ms/mouse-position-alt)
|
||||
(rx/mapcat
|
||||
(fn [[_ alt?]]
|
||||
(if alt?
|
||||
;; When alt is down we start a duplicate+move
|
||||
(rx/of (start-move-duplicate initial)
|
||||
(dws/duplicate-selected false))
|
||||
;; Otherwise just plain old move
|
||||
(rx/of (start-move initial selected)))))
|
||||
(rx/take-until stopper)))))))
|
||||
([]
|
||||
(start-move-selected nil false))
|
||||
|
||||
([id shift?]
|
||||
(ptk/reify ::start-move-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [initial (deref ms/mouse-position)
|
||||
|
||||
stopper (rx/filter ms/mouse-up? stream)
|
||||
zoom (get-in state [:workspace-local :zoom] 1)
|
||||
|
||||
;; We toggle the selection so we don't have to wait for the event
|
||||
selected
|
||||
(cond-> (wsh/lookup-selected state {:omit-blocked? true})
|
||||
(some? id)
|
||||
(d/toggle-selection id shift?))]
|
||||
|
||||
(when (or (d/not-empty? selected) (some? id))
|
||||
(->> ms/mouse-position
|
||||
(rx/map #(gpt/to-vec initial %))
|
||||
(rx/map #(gpt/length %))
|
||||
(rx/filter #(> % (/ 10 zoom)))
|
||||
(rx/take 1)
|
||||
(rx/with-latest vector ms/mouse-position-alt)
|
||||
(rx/mapcat
|
||||
(fn [[_ alt?]]
|
||||
(rx/concat
|
||||
(if (some? id)
|
||||
(rx/of (dws/select-shape id shift?))
|
||||
(rx/empty))
|
||||
|
||||
(if alt?
|
||||
;; When alt is down we start a duplicate+move
|
||||
(rx/of (start-move-duplicate initial)
|
||||
(dws/duplicate-selected false))
|
||||
|
||||
;; Otherwise just plain old move
|
||||
(rx/of (start-move initial selected))))))
|
||||
(rx/take-until stopper))))))))
|
||||
(defn- start-move-duplicate
|
||||
[from-position]
|
||||
(ptk/reify ::start-move-duplicate
|
||||
|
|
|
@ -73,44 +73,44 @@
|
|||
|
||||
parse-value
|
||||
(mf/use-callback
|
||||
(mf/deps ref min-val max-val value nillable default-val)
|
||||
(fn []
|
||||
(let [input-node (mf/ref-val ref)
|
||||
new-value (-> (dom/get-value input-node)
|
||||
(str/strip-suffix ".")
|
||||
(sm/expr-eval value))]
|
||||
(cond
|
||||
(d/num? new-value)
|
||||
(-> new-value
|
||||
(cljs.core/max us/min-safe-int)
|
||||
(cljs.core/min us/max-safe-int)
|
||||
(cond->
|
||||
(d/num? min-val)
|
||||
(cljs.core/max min-val)
|
||||
(mf/deps ref min-val max-val value nillable default-val)
|
||||
(fn []
|
||||
(let [input-node (mf/ref-val ref)
|
||||
new-value (-> (dom/get-value input-node)
|
||||
(str/strip-suffix ".")
|
||||
(sm/expr-eval value))]
|
||||
(cond
|
||||
(d/num? new-value)
|
||||
(-> new-value
|
||||
(cljs.core/max (/ us/min-safe-int 2))
|
||||
(cljs.core/min (/ us/max-safe-int 2))
|
||||
(cond->
|
||||
(d/num? min-val)
|
||||
(cljs.core/max min-val)
|
||||
|
||||
(d/num? max-val)
|
||||
(cljs.core/min max-val)))
|
||||
(d/num? max-val)
|
||||
(cljs.core/min max-val)))
|
||||
|
||||
nillable
|
||||
default-val
|
||||
nillable
|
||||
default-val
|
||||
|
||||
:else value))))
|
||||
:else value))))
|
||||
|
||||
update-input
|
||||
(mf/use-callback
|
||||
(mf/deps ref)
|
||||
(fn [new-value]
|
||||
(let [input-node (mf/ref-val ref)]
|
||||
(dom/set-value! input-node (fmt/format-number new-value)))))
|
||||
(mf/deps ref)
|
||||
(fn [new-value]
|
||||
(let [input-node (mf/ref-val ref)]
|
||||
(dom/set-value! input-node (fmt/format-number new-value)))))
|
||||
|
||||
apply-value
|
||||
(mf/use-callback
|
||||
(mf/deps on-change update-input value)
|
||||
(fn [new-value]
|
||||
(mf/set-ref-val! dirty-ref false)
|
||||
(when (and (not= new-value value) (some? on-change))
|
||||
(on-change new-value))
|
||||
(update-input new-value)))
|
||||
(mf/deps on-change update-input value)
|
||||
(fn [new-value]
|
||||
(mf/set-ref-val! dirty-ref false)
|
||||
(when (and (not= new-value value) (some? on-change))
|
||||
(on-change new-value))
|
||||
(update-input new-value)))
|
||||
|
||||
set-delta
|
||||
(mf/use-callback
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
(let [xf-get-bounds (comp (map #(get objects %)) (map #(calc-bounds % objects)))
|
||||
padding (filters/calculate-padding object)
|
||||
obj-bounds (-> (filters/get-filters-bounds object)
|
||||
(update :x - padding)
|
||||
(update :y - padding)
|
||||
(update :width + (* 2 padding))
|
||||
(update :height + (* 2 padding)))]
|
||||
(update :x - (:horizontal padding))
|
||||
(update :y - (:vertical padding))
|
||||
(update :width + (* 2 (:horizontal padding)))
|
||||
(update :height + (* 2 (:vertical padding))))]
|
||||
|
||||
(cond
|
||||
(and (= :group (:type object))
|
||||
|
|
|
@ -175,12 +175,12 @@
|
|||
(obj/set! styles "fill" (str "url(#fill-0-" render-id ")"))
|
||||
|
||||
;; imported svgs can have fill and fill-opacity attributes
|
||||
(obj/contains? svg-styles "fill")
|
||||
(and (some? svg-styles) (obj/contains? svg-styles "fill"))
|
||||
(-> styles
|
||||
(obj/set! "fill" (obj/get svg-styles "fill"))
|
||||
(obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))
|
||||
|
||||
(obj/contains? svg-attrs "fill")
|
||||
(and (some? svg-attrs) (obj/contains? svg-attrs "fill"))
|
||||
(-> styles
|
||||
(obj/set! "fill" (obj/get svg-attrs "fill"))
|
||||
(obj/set! "fillOpacity" (obj/get svg-attrs "fillOpacity")))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.gradients :as grad]
|
||||
|
@ -327,8 +328,12 @@
|
|||
(obj/clone))
|
||||
|
||||
props (cond-> props
|
||||
(d/not-empty? (:shadow shape))
|
||||
(obj/set! "filter" (dm/fmt "url(#filter_%)" render-id)))
|
||||
(or
|
||||
;; There are any shadows
|
||||
(and (d/not-empty? (:shadow shape)) (not (cph/frame-shape? shape)))
|
||||
;; There are no strokes and a blur
|
||||
(and (some? (:blur shape)) (not (cph/frame-shape? shape)) (empty? (:strokes shape))))
|
||||
(obj/set! "filter" (dm/fmt "url(#filter_%)" render-id)))
|
||||
|
||||
svg-defs (:svg-defs shape {})
|
||||
svg-attrs (:svg-attrs shape {})
|
||||
|
@ -345,7 +350,7 @@
|
|||
(obj/without ["fill" "fillOpacity"])))]
|
||||
(obj/set! props "fill" (dm/fmt "url(#fill-0-%)" render-id)))
|
||||
|
||||
(obj/contains? svg-styles "fill")
|
||||
(and (some? svg-styles) (obj/contains? svg-styles "fill"))
|
||||
(let [style
|
||||
(-> (obj/get props "style")
|
||||
(obj/clone)
|
||||
|
@ -354,7 +359,7 @@
|
|||
(-> props
|
||||
(obj/set! "style" style)))
|
||||
|
||||
(obj/contains? svg-attrs "fill")
|
||||
(and (some? svg-attrs) (obj/contains? svg-attrs "fill"))
|
||||
(let [style
|
||||
(-> (obj/get props "style")
|
||||
(obj/clone)
|
||||
|
@ -374,10 +379,7 @@
|
|||
|
||||
(cond-> (obj/merge! props fill-props)
|
||||
(some? style)
|
||||
(obj/set! "style" style)))
|
||||
|
||||
:else
|
||||
props)))
|
||||
(obj/set! "style" style))))))
|
||||
|
||||
(defn build-stroke-props [position child value render-id]
|
||||
(let [props (-> (obj/get child "props")
|
||||
|
@ -391,7 +393,19 @@
|
|||
(obj/set! "fillOpacity" "none")))
|
||||
(add-style (obj/get (attrs/extract-stroke-attrs value position render-id) "style")))))
|
||||
|
||||
(mf/defc shape-custom-strokes
|
||||
|
||||
(mf/defc shape-fills
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")
|
||||
elem-name (obj/get child "type")
|
||||
render-id (mf/use-ctx muc/render-ctx)]
|
||||
|
||||
[:g {:id (dm/fmt "fills-%" (:id shape))}
|
||||
[:> elem-name (build-fill-props shape child render-id)]]))
|
||||
|
||||
(mf/defc shape-strokes
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [child (obj/get props "children")
|
||||
|
@ -401,13 +415,9 @@
|
|||
stroke-props (-> (obj/new)
|
||||
(obj/set! "id" (dm/fmt "strokes-%" (:id shape)))
|
||||
(cond->
|
||||
(some? (:blur shape))
|
||||
(and (some? (:blur shape)) (not (cph/frame-shape? shape)))
|
||||
(obj/set! "filter" (dm/fmt "url(#filter_blur_%)" render-id))))]
|
||||
|
||||
[:*
|
||||
[:g {:id (dm/fmt "fills-%" (:id shape))}
|
||||
[:> elem-name (build-fill-props shape child render-id)]]
|
||||
|
||||
(when
|
||||
(d/not-empty? (:strokes shape))
|
||||
[:> :g stroke-props
|
||||
|
@ -416,3 +426,16 @@
|
|||
shape (assoc value :points (:points shape))]
|
||||
[:& shape-custom-stroke {:shape shape :index index}
|
||||
[:> elem-name props]]))])]))
|
||||
|
||||
(mf/defc shape-custom-strokes
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")]
|
||||
|
||||
[:*
|
||||
[:& shape-fills {:shape shape}
|
||||
child]
|
||||
|
||||
[:& shape-strokes {:shape shape}
|
||||
child]]))
|
||||
|
|
|
@ -170,7 +170,6 @@
|
|||
([shape filters blur-value]
|
||||
|
||||
(let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag])))
|
||||
frame? (= :frame (:type shape))
|
||||
{:keys [x y width height]} (:selrect shape)]
|
||||
(if svg-root?
|
||||
;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum
|
||||
|
@ -183,6 +182,7 @@
|
|||
(map (partial filter-bounds shape)))
|
||||
;; We add the selrect so the minimum size will be the selrect
|
||||
filter-bounds (conj filter-bounds (-> shape :points gsh/points->selrect))
|
||||
|
||||
x1 (apply min (map :x1 filter-bounds))
|
||||
y1 (apply min (map :y1 filter-bounds))
|
||||
x2 (apply max (map :x2 filter-bounds))
|
||||
|
@ -195,18 +195,30 @@
|
|||
|
||||
;; We should move the frame filter coordinates because they should be
|
||||
;; relative with the frame. By default they come as absolute
|
||||
{:x (if frame? (- x1 x) x1)
|
||||
:y (if frame? (- y1 y) y1)
|
||||
{:x x1
|
||||
:y y1
|
||||
:width (- x2 x1)
|
||||
:height (- y2 y1)})))))
|
||||
|
||||
(defn calculate-padding [shape]
|
||||
(let [stroke-width (apply max 0 (map #(case (:stroke-alignment % :center)
|
||||
:center (/ (:stroke-width % 0) 2)
|
||||
:outer (:stroke-width % 0)
|
||||
0) (:strokes shape)))
|
||||
margin (apply max 0 (map #(gsh/shape-stroke-margin % stroke-width) (:strokes shape)))]
|
||||
(+ stroke-width margin)))
|
||||
:center (/ (:stroke-width % 0) 2)
|
||||
:outer (:stroke-width % 0)
|
||||
0) (:strokes shape)))
|
||||
|
||||
margin (apply max 0 (map #(gsh/shape-stroke-margin % stroke-width) (:strokes shape)))
|
||||
|
||||
|
||||
shadow-width (apply max 0 (map #(case (:style % :drop-shadow)
|
||||
:drop-shadow (+ (mth/abs (:offset-x %)) (* (:spread %) 2) (* (:blur %) 2) 10)
|
||||
0) (:shadow shape)))
|
||||
|
||||
shadow-height (apply max 0 (map #(case (:style % :drop-shadow)
|
||||
:drop-shadow (+ (mth/abs (:offset-y %)) (* (:spread %) 2) (* (:blur %) 2) 10)
|
||||
0) (:shadow shape)))]
|
||||
|
||||
{:horizontal (+ stroke-width margin shadow-width)
|
||||
:vertical (+ stroke-width margin shadow-height)}))
|
||||
|
||||
(defn change-filter-in
|
||||
"Adds the previous filter as `filter-in` parameter"
|
||||
|
@ -220,10 +232,10 @@
|
|||
bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
|
||||
padding (calculate-padding shape)
|
||||
selrect (:selrect shape)
|
||||
filter-x (/ (- (:x bounds) (:x selrect) padding) (:width selrect))
|
||||
filter-y (/ (- (:y bounds) (:y selrect) padding) (:height selrect))
|
||||
filter-width (/ (+ (:width bounds) (* 2 padding)) (:width selrect))
|
||||
filter-height (/ (+ (:height bounds) (* 2 padding)) (:height selrect))]
|
||||
filter-x (/ (- (:x bounds) (:x selrect) (:horizontal padding)) (:width selrect))
|
||||
filter-y (/ (- (:y bounds) (:y selrect) (:vertical padding)) (:height selrect))
|
||||
filter-width (/ (+ (:width bounds) (* 2 (:horizontal padding))) (:width selrect))
|
||||
filter-height (/ (+ (:height bounds) (* 2 (:vertical padding))) (:height selrect))]
|
||||
(when (> (count filters) 2)
|
||||
[:filter {:id filter-id
|
||||
:x filter-x
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
(ns app.main.ui.shapes.frame
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.custom-stroke :refer [shape-fills shape-strokes]]
|
||||
[app.util.object :as obj]
|
||||
[debug :refer [debug?]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -27,13 +28,12 @@
|
|||
[{:keys [shape render-id]}]
|
||||
(when (= :frame (:type shape))
|
||||
(let [{:keys [x y width height]} shape
|
||||
padding (filters/calculate-padding shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x (- x padding)
|
||||
:y (- y padding)
|
||||
:width (+ width (* 2 padding))
|
||||
:height (+ height (* 2 padding))}))
|
||||
#js {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height}))
|
||||
path? (some? (.-d props))]
|
||||
[:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||
(if path?
|
||||
|
@ -63,22 +63,32 @@
|
|||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
transform (gsh/transform-matrix shape)
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))
|
||||
path? (some? (.-d props))]
|
||||
|
||||
[:*
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])])))
|
||||
path? (some? (.-d props))
|
||||
render-id (mf/use-ctx muc/render-ctx)]
|
||||
|
||||
[:*
|
||||
[:g {:clip-path (frame-clip-url shape render-id)}
|
||||
[:*
|
||||
[:& shape-fills {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]
|
||||
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])
|
||||
[:& shape-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]]]])))
|
||||
|
||||
|
|
|
@ -50,14 +50,11 @@
|
|||
|
||||
wrapper-props
|
||||
(cond-> wrapper-props
|
||||
(some #(= (:type shape) %) [:group :svg-raw])
|
||||
(some #(= (:type shape) %) [:group :svg-raw :frame])
|
||||
(obj/set! "filter" (filters/filter-str filter-id shape)))
|
||||
|
||||
wrapper-props
|
||||
(cond-> wrapper-props
|
||||
(= :frame type)
|
||||
(obj/set! "clipPath" (frame/frame-clip-url shape render-id))
|
||||
|
||||
(= :group type)
|
||||
(attrs/add-style-attrs shape render-id))]
|
||||
|
||||
|
|
|
@ -43,24 +43,24 @@
|
|||
transform-mask? (and (= :mask tag)
|
||||
(= "userSpaceOnUse" (get attrs :maskUnits "objectBoundingBox")))
|
||||
|
||||
attrs (-> attrs
|
||||
(usvg/update-attr-ids prefix-id)
|
||||
(usvg/clean-attrs)
|
||||
;; This clasname will be used to change the transform on the viewport
|
||||
;; only necessary for groups because shapes have their own transform
|
||||
(cond-> (and (or transform-gradient?
|
||||
transform-pattern?
|
||||
transform-clippath?
|
||||
transform-filter?
|
||||
transform-mask?)
|
||||
(= :group type))
|
||||
(update :className #(if % (dm/str % " svg-def") "svg-def")))
|
||||
(cond->
|
||||
transform-gradient? (add-matrix :gradientTransform transform)
|
||||
transform-pattern? (add-matrix :patternTransform transform)
|
||||
transform-clippath? (add-matrix :transform transform)
|
||||
(or transform-filter?
|
||||
transform-mask?) (merge attrs bounds)))
|
||||
attrs
|
||||
(-> attrs
|
||||
(usvg/update-attr-ids prefix-id)
|
||||
(usvg/clean-attrs)
|
||||
;; This clasname will be used to change the transform on the viewport
|
||||
;; only necessary for groups because shapes have their own transform
|
||||
(cond-> (and (or transform-gradient?
|
||||
transform-pattern?
|
||||
transform-clippath?
|
||||
transform-filter?
|
||||
transform-mask?)
|
||||
(= :group type))
|
||||
(update :className #(if % (dm/str % " svg-def") "svg-def")))
|
||||
(cond->
|
||||
transform-gradient? (add-matrix :gradientTransform transform)
|
||||
transform-pattern? (add-matrix :patternTransform transform)
|
||||
transform-clippath? (add-matrix :transform transform)
|
||||
(or transform-filter? transform-mask?) (merge bounds)))
|
||||
|
||||
[wrapper wrapper-props] (if (= tag :mask)
|
||||
["g" #js {:className "svg-mask-wrapper"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.shapes.text.fontfaces
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.shapes.embed :as embed]
|
||||
|
@ -76,14 +77,10 @@
|
|||
{::mf/wrap-props false
|
||||
::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]}
|
||||
[props]
|
||||
(let [shapes (->> (obj/get props "shapes")
|
||||
(filterv #(= :text (:type %))))
|
||||
|
||||
content (->> shapes (mapv :content))
|
||||
|
||||
;; Retrieve the fonts ids used by the text shapes
|
||||
fonts (->> content
|
||||
(mapv fonts/get-content-fonts)
|
||||
(let [;; Retrieve the fonts ids used by the text shapes
|
||||
fonts (->> (obj/get props "shapes")
|
||||
(filterv cph/text-shape?)
|
||||
(mapv (comp fonts/get-content-fonts :content))
|
||||
(reduce set/union #{})
|
||||
(hooks/use-equal-memo))]
|
||||
|
||||
|
|
|
@ -9,9 +9,12 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.data.viewer.shortcuts :as sc]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
|
@ -32,12 +35,17 @@
|
|||
|
||||
(defn- calculate-size
|
||||
[frame zoom]
|
||||
(let [{:keys [_ _ width height]} (filters/get-filters-bounds frame)]
|
||||
(let [{:keys [_ _ width height]} (filters/get-filters-bounds frame)
|
||||
padding (filters/calculate-padding frame)
|
||||
x (- (:horizontal padding))
|
||||
y (- (:vertical padding))
|
||||
width (+ width (* 2 (:horizontal padding)))
|
||||
height (+ height (* 2 (:vertical padding)))]
|
||||
{:base-width width
|
||||
:base-height height
|
||||
:width (* width zoom)
|
||||
:height (* height zoom)
|
||||
:vbox (str "0 0 " width " " height)}))
|
||||
:vbox (str x " " y " " width " " height)}))
|
||||
|
||||
(defn- calculate-wrapper
|
||||
[size1 size2 zoom]
|
||||
|
@ -70,6 +78,12 @@
|
|||
(fn []
|
||||
(get-in data [:pages page-id])))
|
||||
|
||||
text-shapes
|
||||
(hooks/use-equal-memo
|
||||
(->> (:objects page)
|
||||
(vals)
|
||||
(filter cph/text-shape?)))
|
||||
|
||||
zoom (:zoom local)
|
||||
frames (:frames page)
|
||||
frame (get frames index)
|
||||
|
@ -214,6 +228,13 @@
|
|||
|
||||
nil))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps text-shapes)
|
||||
(fn []
|
||||
(let [text-nodes (->> text-shapes (mapcat #(txt/node-seq txt/is-text-node? (:content %))))
|
||||
fonts (into #{} (keep :font-id) text-nodes)]
|
||||
(run! fonts/ensure-loaded! fonts))))
|
||||
|
||||
[:div#viewer-layout {:class (dom/classnames
|
||||
:force-visible (:show-thumbnails local)
|
||||
:viewer-layout (not= section :handoff)
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
(def type->options
|
||||
{:multiple [:fill :stroke :image :text :shadow :blur]
|
||||
:frame [:layout :fill :stroke]
|
||||
:frame [:layout :fill :stroke :shadow :blur]
|
||||
:group [:layout :svg]
|
||||
:rect [:layout :fill :stroke :shadow :blur :svg]
|
||||
:circle [:layout :fill :stroke :shadow :blur :svg]
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.shapes.bool :as bool]
|
||||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
|
@ -190,9 +191,18 @@
|
|||
frame (get objects (:id frame))
|
||||
|
||||
zoom (:zoom local 1)
|
||||
width (* (:width frame) zoom)
|
||||
height (* (:height frame) zoom)
|
||||
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
|
||||
|
||||
{:keys [_ _ width height]} (filters/get-filters-bounds frame)
|
||||
padding (filters/calculate-padding frame)
|
||||
x (- (:horizontal padding))
|
||||
y (- (:vertical padding))
|
||||
width (+ width (* 2 (:horizontal padding)))
|
||||
height (+ height (* 2 (:vertical padding)))
|
||||
|
||||
vbox (str x " " y " " width " " height)
|
||||
|
||||
width (* width zoom)
|
||||
height (* height zoom)
|
||||
|
||||
render (mf/use-memo
|
||||
(mf/deps objects)
|
||||
|
|
|
@ -403,9 +403,9 @@
|
|||
modifier-ids (into [frame-id] (cph/get-children-ids objects frame-id))
|
||||
objects (reduce update-fn objects modifier-ids)
|
||||
frame (assoc-in frame [:modifiers :displacement] modifier)
|
||||
|
||||
width (* (:width frame) zoom)
|
||||
height (* (:height frame) zoom)
|
||||
|
||||
vbox (str "0 0 " (:width frame 0)
|
||||
" " (:height frame 0))
|
||||
wrapper (mf/use-memo
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
;; NOTE: this is necessary because the `cph/get-component`
|
||||
;; expects a map of all libraries, including the local one.
|
||||
libraries (assoc libraries (:id local-file) local-file)
|
||||
libraries (assoc libraries (:id local-file) {:data local-file})
|
||||
|
||||
component (cph/get-component libraries library-id component-id)
|
||||
show? (some? component-id)
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
|
||||
;; REFS
|
||||
viewport-ref (mf/use-ref nil)
|
||||
overlays-ref (mf/use-ref nil)
|
||||
|
||||
;; VARS
|
||||
disable-paste (mf/use-var false)
|
||||
|
@ -121,7 +122,7 @@
|
|||
node-editing? (and edition (not= :text (get-in base-objects [edition :type])))
|
||||
text-editing? (and edition (= :text (get-in base-objects [edition :type])))
|
||||
|
||||
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space?)
|
||||
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect)
|
||||
on-context-menu (actions/on-context-menu hover hover-ids)
|
||||
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition)
|
||||
on-drag-enter (actions/on-drag-enter)
|
||||
|
@ -169,7 +170,7 @@
|
|||
|
||||
disabled-guides? (or drawing-tool transform)]
|
||||
|
||||
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
|
||||
(hooks/setup-dom-events viewport-ref overlays-ref zoom disable-paste in-viewport?)
|
||||
(hooks/setup-viewport-size viewport-ref)
|
||||
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?)
|
||||
(hooks/setup-keyboard alt? mod? space?)
|
||||
|
@ -179,7 +180,7 @@
|
|||
(hooks/setup-active-frames base-objects vbox hover active-frames)
|
||||
|
||||
[:div.viewport
|
||||
[:div.viewport-overlays
|
||||
[:div.viewport-overlays {:ref overlays-ref}
|
||||
|
||||
[:& wtr/frame-renderer {:objects base-objects
|
||||
:background background}]
|
||||
|
|
|
@ -97,9 +97,7 @@
|
|||
(st/emit! (dw/handle-area-selection shift? mod?))
|
||||
|
||||
(not drawing-tool)
|
||||
(st/emit! (when (or shift? (not selected?))
|
||||
(dw/select-shape id shift?))
|
||||
(dw/start-move-selected)))))))))))
|
||||
(st/emit! (dw/start-move-selected id shift?)))))))))))
|
||||
|
||||
(defn on-move-selected
|
||||
[hover hover-ids selected space?]
|
||||
|
@ -147,27 +145,25 @@
|
|||
(reset! frame-hover nil))))
|
||||
|
||||
(defn on-click
|
||||
[hover selected edition drawing-path? drawing-tool space?]
|
||||
[hover selected edition drawing-path? drawing-tool space? selrect]
|
||||
(mf/use-callback
|
||||
(mf/deps @hover selected edition drawing-path? drawing-tool @space?)
|
||||
(mf/deps @hover selected edition drawing-path? drawing-tool @space? selrect)
|
||||
(fn [event]
|
||||
(when (or (dom/class? (dom/get-target event) "viewport-controls")
|
||||
(dom/class? (dom/get-target event) "viewport-selrect"))
|
||||
(when (and (nil? selrect)
|
||||
(or (dom/class? (dom/get-target event) "viewport-controls")
|
||||
(dom/class? (dom/get-target event) "viewport-selrect")))
|
||||
(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))]
|
||||
frame? (= :frame (:type @hover))]
|
||||
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt? meta?))
|
||||
|
||||
(when (and hovering?
|
||||
(or (not frame?) mod?)
|
||||
(not @space?)
|
||||
(not selected?)
|
||||
(not edition)
|
||||
(not drawing-path?)
|
||||
(not drawing-tool))
|
||||
|
@ -367,21 +363,23 @@
|
|||
pt (utils/translate-point-to-viewport viewport zoom raw-pt)]
|
||||
(rx/push! move-stream pt)))))
|
||||
|
||||
(defn on-mouse-wheel [viewport-ref zoom]
|
||||
(defn on-mouse-wheel [viewport-ref overlays-ref zoom]
|
||||
(mf/use-callback
|
||||
(mf/deps zoom)
|
||||
(fn [event]
|
||||
(let [viewport (mf/ref-val viewport-ref)
|
||||
overlays (mf/ref-val overlays-ref)
|
||||
event (.getBrowserEvent ^js event)
|
||||
target (dom/get-target event)]
|
||||
(when (.contains ^js viewport target)
|
||||
target (dom/get-target event)
|
||||
mod? (kbd/mod? event)]
|
||||
|
||||
(when (or (dom/is-child? viewport target)
|
||||
(dom/is-child? overlays target))
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [pt (->> (dom/get-client-position event)
|
||||
(utils/translate-point-to-viewport viewport zoom))
|
||||
|
||||
mod? (kbd/mod? event)
|
||||
|
||||
delta-mode (.-deltaMode ^js event)
|
||||
|
||||
unit (cond
|
||||
|
|
|
@ -27,11 +27,11 @@
|
|||
[rumext.alpha :as mf])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
(defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?]
|
||||
(defn setup-dom-events [viewport-ref overlays-ref zoom disable-paste in-viewport?]
|
||||
(let [on-key-down (actions/on-key-down)
|
||||
on-key-up (actions/on-key-up)
|
||||
on-mouse-move (actions/on-mouse-move viewport-ref zoom)
|
||||
on-mouse-wheel (actions/on-mouse-wheel viewport-ref zoom)
|
||||
on-mouse-wheel (actions/on-mouse-wheel viewport-ref overlays-ref zoom)
|
||||
on-paste (actions/on-paste disable-paste in-viewport?)]
|
||||
(mf/use-layout-effect
|
||||
(mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-paste)
|
||||
|
|
|
@ -530,3 +530,8 @@
|
|||
(when onfinish
|
||||
(set! (.-onfinish animation) onfinish)))))
|
||||
|
||||
(defn is-child?
|
||||
[^js node ^js candidate]
|
||||
(and (some? node)
|
||||
(some? candidate)
|
||||
(.contains node candidate)))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue