mirror of
https://github.com/penpot/penpot.git
synced 2025-04-17 17:24:32 -05: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
17
CHANGES.md
17
CHANGES.md
|
@ -22,11 +22,11 @@
|
|||
- Constraints are not well assigned when default and multiselection [Taiga #3069](https://tree.taiga.io/project/penpot/issue/3069)
|
||||
- Exporting big files flow [Taiga #2218](https://tree.taiga.io/project/penpot/us/2218)
|
||||
- Multiexport from main menu [Taiga #520](https://tree.taiga.io/project/penpot/us/28541)
|
||||
- Multipexport assets (aka bulk export) [Taiga #520](https://tree.taiga.io/project/penpot/us/520)
|
||||
- Multiexport assets (aka bulk export) [Taiga #520](https://tree.taiga.io/project/penpot/us/520)
|
||||
- Set the artboard layer fixed at the top side of the layers [Taiga #2636](https://tree.taiga.io/project/penpot/us/2636)
|
||||
- Set an artboard as the file thumbnail [Taiga #1526](https://tree.taiga.io/project/penpot/us/1526)
|
||||
- Social login redesign [Taiga #2974](https://tree.taiga.io/project/penpot/task/2974)
|
||||
- Add border radius to our artboars [Taiga #2056](https://tree.taiga.io/project/penpot/us/2056)
|
||||
- Add border radius to our artboards [Taiga #2056](https://tree.taiga.io/project/penpot/us/2056)
|
||||
- Allow send multiple team invitations at once [Taiga #2798](https://tree.taiga.io/project/penpot/us/2798)
|
||||
- Persist color palette and color picker across refresh [Taiga #1660](https://tree.taiga.io/project/penpot/issue/1660)
|
||||
- Ability to add multiple strokes to a shape [Taiga #2778](https://tree.taiga.io/project/penpot/us/2778)
|
||||
|
@ -42,9 +42,13 @@
|
|||
- Allow registration with invitation token when registration is disabled
|
||||
- Add the ability to disable standard, password login [Taiga #2999](https://tree.taiga.io/project/penpot/us/2999)
|
||||
- Don't stop SVG import when an image cannot be imported [#1531](https://github.com/penpot/penpot/issues/1531)
|
||||
- Fix paste shapes while editing text [Taiga #2396](https://tree.taiga.io/project/penpot/issue/2396)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Avoid numeric inputs to allow big numbers [Taiga #2858](https://tree.taiga.io/project/penpot/issue/2858)
|
||||
- Fix component contex menu size [Taiga #2480](https://tree.taiga.io/project/penpot/issue/2480)
|
||||
- Add shadow to artboard make it lose the fill [Taiga #3139](https://tree.taiga.io/project/penpot/issue/3139)
|
||||
- Avoid numeric inputs to change its value without focusing them [Taiga #3140](https://tree.taiga.io/project/penpot/issue/3140)
|
||||
- Fix comments modal when changing pages [Taiga #2597](https://tree.taiga.io/project/penpot/issue/2508)
|
||||
- Copy paste inside a text layer leaves pasted text transparent [Taiga #3096](https://tree.taiga.io/project/penpot/issue/3096)
|
||||
|
@ -71,10 +75,19 @@
|
|||
- Fix shift+2 shortcut in MacOS with non-english keyboards [Taiga #3038](https://tree.taiga.io/project/penpot/issue/3038)
|
||||
- Some fixes to SVG imports [Taiga #3122](https://tree.taiga.io/project/penpot/issue/3122) [#1720](https://github.com/penpot/penpot/issues/1720) [Taiga #2884](https://tree.taiga.io/project/penpot/issue/2884)
|
||||
- Fix drag guides to delete target area [#1679](https://github.com/penpot/penpot/issues/1679)
|
||||
- Fix undo when rotating groups [Taiga #3136](https://tree.taiga.io/project/penpot/issue/3136)
|
||||
- Fix component name in sidebar widget [Taiga #3144](https://tree.taiga.io/project/penpot/issue/3144)
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
||||
## 1.12.3-beta
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix issue with shift+select to deselect shapes [Taiga #3154](https://tree.taiga.io/project/penpot/issue/3154)
|
||||
- Fix issue with drag-select shapes [Taiga #3165](https://tree.taiga.io/project/penpot/issue/3165)
|
||||
|
||||
## 1.12.2-beta
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{:deps
|
||||
{penpot/common {:local/root "../common"}
|
||||
org.clojure/clojure {:mvn/version "1.10.3"}
|
||||
org.clojure/core.async {:mvn/version "1.5.648"}
|
||||
|
||||
;; Logging
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
[app.srepl.dev :as dev]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.time :as dt]
|
||||
[fipp.edn :refer [pprint]]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.walk :as walk]
|
||||
[cuerdas.core :as str]
|
||||
[expound.alpha :as expound]))
|
||||
[expound.alpha :as expound]
|
||||
[fipp.edn :refer [pprint]]))
|
||||
|
||||
(defn update-file
|
||||
([system id f] (update-file system id f false))
|
||||
|
@ -66,86 +67,48 @@
|
|||
(db/insert! conn :file params)
|
||||
(:id file))))))
|
||||
|
||||
(defn verify-files
|
||||
[system {:keys [age sleep chunk-size max-chunks stop-on-error? verbose?]
|
||||
:or {sleep 1000
|
||||
age "72h"
|
||||
chunk-size 10
|
||||
verbose? false
|
||||
stop-on-error? true
|
||||
max-chunks ##Inf}}]
|
||||
;; (defn check-image-shapes
|
||||
;; [{:keys [data] :as file} stats]
|
||||
;; (println "=> analizing file:" (:name file) (:id file))
|
||||
;; (swap! stats update :total-files (fnil inc 0))
|
||||
;; (let [affected? (atom false)]
|
||||
;; (walk/prewalk (fn [obj]
|
||||
;; (when (and (map? obj) (= :image (:type obj)))
|
||||
;; (when-let [fcolor (some-> obj :fill-color str/upper)]
|
||||
;; (when (or (= fcolor "#B1B2B5")
|
||||
;; (= fcolor "#7B7D85"))
|
||||
;; (reset! affected? true)
|
||||
;; (swap! stats update :affected-shapes (fnil inc 0))
|
||||
;; (println "--> image shape:" ((juxt :id :name :fill-color :fill-opacity) obj)))))
|
||||
;; obj)
|
||||
;; data)
|
||||
;; (when @affected?
|
||||
;; (swap! stats update :affected-files (fnil inc 0)))))
|
||||
|
||||
(letfn [(retrieve-chunk [conn cursor]
|
||||
(let [sql (str "select id, name, modified_at, data from file "
|
||||
" where modified_at > ? and deleted_at is null "
|
||||
" order by modified_at asc limit ?")
|
||||
age (if cursor
|
||||
cursor
|
||||
(-> (dt/now) (dt/minus age)))]
|
||||
(seq (db/exec! conn [sql age chunk-size]))))
|
||||
(defn analyze-files
|
||||
[system {:keys [sleep chunk-size max-chunks on-file]
|
||||
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
|
||||
(let [stats (atom {})]
|
||||
(letfn [(retrieve-chunk [conn cursor]
|
||||
(let [sql (str "select id, name, modified_at, data from file "
|
||||
" where modified_at < ? and deleted_at is null "
|
||||
" order by modified_at desc limit ?")]
|
||||
(->> (db/exec! conn [sql cursor chunk-size])
|
||||
(map #(update % :data blob/decode)))))
|
||||
|
||||
(validate-item [{:keys [id data modified-at] :as file}]
|
||||
(let [data (blob/decode data)
|
||||
valid? (s/valid? ::spec.file/data data)]
|
||||
(process-chunk [chunk]
|
||||
(loop [items chunk]
|
||||
(when-let [item (first items)]
|
||||
(on-file item stats)
|
||||
(recur (rest items)))))]
|
||||
|
||||
(l/debug :hint "validated file"
|
||||
:file-id id
|
||||
:age (-> (dt/diff modified-at (dt/now))
|
||||
(dt/truncate :minutes)
|
||||
(str)
|
||||
(subs 2)
|
||||
(str/lower))
|
||||
:valid valid?)
|
||||
|
||||
(when (and (not valid?) verbose?)
|
||||
(let [edata (-> (s/explain-data ::spec.file/data data)
|
||||
(update ::s/problems #(take 5 %)))]
|
||||
(binding [s/*explain-out* expound/printer]
|
||||
(l/warn ::l/raw (with-out-str (s/explain-out edata))))))
|
||||
|
||||
(when (and (not valid?) stop-on-error?)
|
||||
(throw (ex-info "penpot/abort" {})))
|
||||
|
||||
valid?))
|
||||
|
||||
(validate-chunk [chunk]
|
||||
(loop [items chunk
|
||||
success 0
|
||||
errored 0]
|
||||
|
||||
(if-let [item (first items)]
|
||||
(if (validate-item item)
|
||||
(recur (rest items) (inc success) errored)
|
||||
(recur (rest items) success (inc errored)))
|
||||
[(:modified-at (last chunk))
|
||||
success
|
||||
errored])))
|
||||
|
||||
(fmt-result [ns ne]
|
||||
{:total (+ ns ne)
|
||||
:errors ne
|
||||
:success ns})
|
||||
|
||||
]
|
||||
|
||||
(try
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(loop [cursor nil
|
||||
chunks 0
|
||||
success 0
|
||||
errors 0]
|
||||
(if (< chunks max-chunks)
|
||||
(if-let [chunk (retrieve-chunk conn cursor)]
|
||||
(let [[cursor success' errors'] (validate-chunk chunk)]
|
||||
(loop [cursor (dt/now)
|
||||
chunks 0]
|
||||
(when (< chunks max-chunks)
|
||||
(when-let [chunk (retrieve-chunk conn cursor)]
|
||||
(let [cursor (-> chunk last :modified-at)]
|
||||
(process-chunk chunk)
|
||||
(Thread/sleep (inst-ms (dt/duration sleep)))
|
||||
(recur cursor
|
||||
(inc chunks)
|
||||
(+ success success')
|
||||
(+ errors errors')))
|
||||
(fmt-result success errors))
|
||||
(fmt-result success errors))))
|
||||
(catch Throwable cause
|
||||
(when (not= "penpot/abort" (ex-message cause))
|
||||
(throw cause))
|
||||
:error))))
|
||||
|
||||
(recur cursor (inc chunks))))))
|
||||
@stats))))
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
(ns app.common.data
|
||||
"Data manipulation and query helper functions."
|
||||
(:refer-clojure :exclude [read-string hash-map merge name parse-double group-by iteration])
|
||||
(:refer-clojure :exclude [read-string hash-map merge name update-vals
|
||||
parse-double group-by iteration])
|
||||
#?(:cljs
|
||||
(:require-macros [app.common.data]))
|
||||
(:require
|
||||
|
@ -198,6 +199,23 @@
|
|||
([mfn coll]
|
||||
(into {} (mapm mfn) coll)))
|
||||
|
||||
;; TEMPORARY COPY of clojure.core/update-vals until we migrate to clojure 1.11
|
||||
|
||||
(defn update-vals
|
||||
"m f => {k (f v) ...}
|
||||
Given a map m and a function f of 1-argument, returns a new map where the keys of m
|
||||
are mapped to result of applying f to the corresponding values of m."
|
||||
[m f]
|
||||
(with-meta
|
||||
(persistent!
|
||||
(reduce-kv (fn [acc k v] (assoc! acc k (f v)))
|
||||
(if #?(:clj (instance? clojure.lang.IEditableCollection m)
|
||||
:cljs (implements? core/IEditableCollection m))
|
||||
(transient m)
|
||||
(transient {}))
|
||||
m))
|
||||
(meta m)))
|
||||
|
||||
(defn removev
|
||||
"Returns a vector of the items in coll for which (fn item) returns logical false"
|
||||
[fn coll]
|
||||
|
@ -653,3 +671,13 @@
|
|||
(recur acc (step k))
|
||||
acc)))
|
||||
acc))))))
|
||||
(defn toggle-selection
|
||||
([set value]
|
||||
(toggle-selection set value false))
|
||||
|
||||
([set value toggle?]
|
||||
(if-not toggle?
|
||||
(conj (ordered-set) value)
|
||||
(if (contains? set value)
|
||||
(disj set value)
|
||||
(conj set value)))))
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
(toString [_]
|
||||
(str "matrix(" a "," b "," c "," d "," e "," f ")")))
|
||||
|
||||
(defn ^boolean matrix?
|
||||
(defn matrix?
|
||||
"Return true if `v` is Matrix instance."
|
||||
[v]
|
||||
(instance? Matrix v))
|
||||
|
@ -57,6 +57,15 @@
|
|||
(map (comp d/parse-double first)))]
|
||||
(apply matrix params)))
|
||||
|
||||
(defn close?
|
||||
[m1 m2]
|
||||
(and (mth/close? (.-a m1) (.-a m2))
|
||||
(mth/close? (.-b m1) (.-b m2))
|
||||
(mth/close? (.-c m1) (.-c m2))
|
||||
(mth/close? (.-d m1) (.-d m2))
|
||||
(mth/close? (.-e m1) (.-e m2))
|
||||
(mth/close? (.-f m1) (.-f m2))))
|
||||
|
||||
(defn multiply
|
||||
([^Matrix m1 ^Matrix m2]
|
||||
(let [m1a (.-a m1)
|
||||
|
@ -111,7 +120,7 @@
|
|||
([{x :x y :y :as pt}]
|
||||
(assert (gpt/point? pt))
|
||||
(Matrix. 1 0 0 1 x y))
|
||||
|
||||
|
||||
([x y]
|
||||
(translate-matrix (gpt/point x y))))
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
(defn s [{:keys [x y]}] (str "(" x "," y ")"))
|
||||
|
||||
(defn ^boolean point?
|
||||
(defn point?
|
||||
"Return true if `v` is Point instance."
|
||||
[v]
|
||||
(or (instance? Point v)
|
||||
|
@ -33,8 +33,7 @@
|
|||
(s/def ::point
|
||||
(s/and (s/keys :req-un [::x ::y]) point?))
|
||||
|
||||
|
||||
(defn ^boolean point-like?
|
||||
(defn point-like?
|
||||
[{:keys [x y] :as v}]
|
||||
(and (map? v)
|
||||
(not (nil? x))
|
||||
|
@ -61,6 +60,11 @@
|
|||
([x y]
|
||||
(Point. x y)))
|
||||
|
||||
(defn close?
|
||||
[p1 p2]
|
||||
(and (mth/close? (:x p1) (:x p2))
|
||||
(mth/close? (:y p1) (:y p2))))
|
||||
|
||||
(defn angle->point [{:keys [x y]} angle distance]
|
||||
(point
|
||||
(+ x (* distance (mth/cos angle)))
|
||||
|
|
|
@ -34,6 +34,24 @@
|
|||
:width width
|
||||
:height height})))
|
||||
|
||||
(defn close-rect?
|
||||
[rect1 rect2]
|
||||
(and (mth/close? (:x rect1) (:x rect2))
|
||||
(mth/close? (:y rect1) (:y rect2))
|
||||
(mth/close? (:width rect1) (:width rect2))
|
||||
(mth/close? (:height rect1) (:height rect2))))
|
||||
|
||||
(defn close-selrect?
|
||||
[selrect1 selrect2]
|
||||
(and (mth/close? (:x selrect1) (:x selrect2))
|
||||
(mth/close? (:y selrect1) (:y selrect2))
|
||||
(mth/close? (:x1 selrect1) (:x1 selrect2))
|
||||
(mth/close? (:y1 selrect1) (:y1 selrect2))
|
||||
(mth/close? (:x2 selrect1) (:x2 selrect2))
|
||||
(mth/close? (:y2 selrect1) (:y2 selrect2))
|
||||
(mth/close? (:width selrect1) (:width selrect2))
|
||||
(mth/close? (:height selrect1) (:height selrect2))))
|
||||
|
||||
(defn rect->points [{:keys [x y width height]}]
|
||||
(when (d/num? x y)
|
||||
(let [width (max width 0.01)
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.bool :as gshb]
|
||||
[app.common.geom.shapes.rect :as gshr]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
@ -379,8 +383,24 @@
|
|||
generate-operation
|
||||
(fn [operations attr old new]
|
||||
(let [old-val (get old attr)
|
||||
new-val (get new attr)]
|
||||
(if (= old-val new-val)
|
||||
new-val (get new attr)
|
||||
|
||||
equal? (cond
|
||||
(and (number? old-val) (number? new-val))
|
||||
(mth/close? old-val new-val)
|
||||
|
||||
(and (gmt/matrix? old-val) (gmt/matrix? new-val))
|
||||
(gmt/close? old-val new-val)
|
||||
|
||||
(= attr :points)
|
||||
(every? #(apply gpt/close? %) (d/zip old-val new-val))
|
||||
|
||||
(= attr :selrect)
|
||||
(gshr/close-selrect? old-val new-val)
|
||||
|
||||
:else
|
||||
(= old-val new-val))]
|
||||
(if equal?
|
||||
operations
|
||||
(-> operations
|
||||
(update :rops conj {:type :set :attr attr :val new-val :ignore-touched true})
|
||||
|
@ -390,8 +410,8 @@
|
|||
(fn [changes parent]
|
||||
(let [children (->> parent :shapes (map (d/getf objects)))
|
||||
resized-parent (cond
|
||||
(empty? children)
|
||||
changes
|
||||
(empty? children) ;; a parent with no children will be deleted,
|
||||
nil ;; so it does not need resize
|
||||
|
||||
(= (:type parent) :bool)
|
||||
(gshb/update-bool-selrect parent children objects)
|
||||
|
@ -399,21 +419,22 @@
|
|||
(= (:type parent) :group)
|
||||
(if (:masked-group? parent)
|
||||
(gsh/update-mask-selrect parent children)
|
||||
(gsh/update-group-selrect parent children)))
|
||||
(gsh/update-group-selrect parent children)))]
|
||||
(if resized-parent
|
||||
(let [{rops :rops uops :uops}
|
||||
(reduce #(generate-operation %1 %2 parent resized-parent)
|
||||
{:rops [] :uops []}
|
||||
(keys parent))
|
||||
|
||||
{rops :rops uops :uops}
|
||||
(reduce #(generate-operation %1 %2 parent resized-parent)
|
||||
{:rops [] :uops []}
|
||||
(keys parent))
|
||||
change {:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id parent)}]
|
||||
|
||||
change {:type :mod-obj
|
||||
:page-id page-id
|
||||
:id (:id parent)}]
|
||||
|
||||
(if (seq rops)
|
||||
(-> changes
|
||||
(update :redo-changes conj (assoc change :operations rops))
|
||||
(update :undo-changes conj (assoc change :operations uops)))
|
||||
(if (seq rops)
|
||||
(-> changes
|
||||
(update :redo-changes conj (assoc change :operations rops))
|
||||
(update :undo-changes d/preconj (assoc change :operations uops)))
|
||||
changes))
|
||||
changes)))]
|
||||
|
||||
(-> (reduce resize-parent changes all-parents)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
[app.common.colors :as clr]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def file-version 16)
|
||||
(def file-version 17)
|
||||
(def default-color clr/gray-20)
|
||||
(def root uuid/zero)
|
||||
|
||||
|
|
|
@ -17,24 +17,28 @@
|
|||
;; GENERIC SHAPE SELECTORS AND PREDICATES
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn ^boolean root-frame?
|
||||
(defn root-frame?
|
||||
[{:keys [id type]}]
|
||||
(and (= type :frame)
|
||||
(= id uuid/zero)))
|
||||
|
||||
(defn ^boolean frame-shape?
|
||||
(defn frame-shape?
|
||||
[{:keys [type]}]
|
||||
(= type :frame))
|
||||
|
||||
(defn ^boolean group-shape?
|
||||
(defn group-shape?
|
||||
[{:keys [type]}]
|
||||
(= type :group))
|
||||
|
||||
(defn ^boolean text-shape?
|
||||
(defn text-shape?
|
||||
[{:keys [type]}]
|
||||
(= type :text))
|
||||
|
||||
(defn ^boolean unframed-shape?
|
||||
(defn image-shape?
|
||||
[{:keys [type]}]
|
||||
(= type :image))
|
||||
|
||||
(defn unframed-shape?
|
||||
"Checks if it's a non-frame shape in the top level."
|
||||
[shape]
|
||||
(and (not (frame-shape? shape))
|
||||
|
@ -214,7 +218,7 @@
|
|||
([libraries library-id component-id]
|
||||
(get-in libraries [library-id :data :components component-id])))
|
||||
|
||||
(defn ^boolean is-main-of?
|
||||
(defn is-main-of?
|
||||
[shape-main shape-inst]
|
||||
(and (:shape-ref shape-inst)
|
||||
(or (= (:shape-ref shape-inst) (:id shape-main))
|
||||
|
|
|
@ -10,26 +10,26 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]))
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; TODO: revisit this and rename to file-migrations
|
||||
|
||||
(defmulti migrate :version)
|
||||
|
||||
(defn migrate-data
|
||||
([data]
|
||||
(if (= (:version data) cp/file-version)
|
||||
([data] (migrate-data data cp/file-version))
|
||||
([data to-version]
|
||||
(if (= (:version data) to-version)
|
||||
data
|
||||
(reduce #(migrate-data %1 %2 (inc %2))
|
||||
data
|
||||
(range (:version data 0) cp/file-version))))
|
||||
|
||||
([data _ to-version]
|
||||
(-> data
|
||||
(assoc :version to-version)
|
||||
(migrate))))
|
||||
(let [migrate-fn #(do
|
||||
(l/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
|
||||
(migrate (assoc %1 :version (inc %2))))]
|
||||
(reduce migrate-fn data (range (:version data 0) to-version))))))
|
||||
|
||||
(defn migrate-file
|
||||
[file]
|
||||
|
@ -45,17 +45,16 @@
|
|||
;; Ensure that all :shape attributes on shapes are vectors.
|
||||
(defmethod migrate 2
|
||||
[data]
|
||||
(letfn [(update-object [_ object]
|
||||
(letfn [(update-object [object]
|
||||
(d/update-when object :shapes
|
||||
(fn [shapes]
|
||||
(if (seq? shapes)
|
||||
(into [] shapes)
|
||||
shapes))))
|
||||
(update-page [page]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Changes paths formats
|
||||
(defmethod migrate 3
|
||||
|
@ -89,7 +88,7 @@
|
|||
(empty? (:points shape))
|
||||
(assoc :points (gsh/rect->points (:selrect shape))))))
|
||||
|
||||
(update-object [_ object]
|
||||
(update-object [object]
|
||||
(cond-> object
|
||||
(= :curve (:type object))
|
||||
(assoc :type :path)
|
||||
|
@ -97,25 +96,22 @@
|
|||
(#{:curve :path} (:type object))
|
||||
(migrate-path)
|
||||
|
||||
(= :frame (:type object))
|
||||
(cph/frame-shape? object)
|
||||
(fix-frames-selrects)
|
||||
|
||||
(and (empty? (:points object)) (not= (:id object) uuid/zero))
|
||||
(fix-empty-points)
|
||||
|
||||
;; Setup an empty transformation to re-calculate selrects
|
||||
;; and points data
|
||||
:always
|
||||
(->
|
||||
;; Setup an empty transformation to re-calculate selrects
|
||||
;; and points data
|
||||
(assoc :modifiers {:displacement (gmt/matrix)})
|
||||
(gsh/transform-shape))
|
||||
(-> (assoc :modifiers {:displacement (gmt/matrix)})
|
||||
(gsh/transform-shape))))
|
||||
|
||||
))
|
||||
(update-page [page]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; We did rollback version 4 migration.
|
||||
;; Keep this in order to remember the next version to be 5
|
||||
|
@ -124,61 +120,55 @@
|
|||
;; Put the id of the local file in :component-file in instances of local components
|
||||
(defmethod migrate 5
|
||||
[data]
|
||||
(letfn [(update-object [_ object]
|
||||
(letfn [(update-object [object]
|
||||
(if (and (some? (:component-id object))
|
||||
(nil? (:component-file object)))
|
||||
(assoc object :component-file (:id data))
|
||||
object))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
|
||||
(defn fix-line-paths
|
||||
"Fixes issues with selrect/points for shapes with width/height = 0 (line-like paths)"
|
||||
[_ shape]
|
||||
(if (= (:type shape) :path)
|
||||
(let [{:keys [width height]} (gsh/points->rect (:points shape))]
|
||||
(if (or (mth/almost-zero? width) (mth/almost-zero? height))
|
||||
(let [selrect (gsh/content->selrect (:content shape))
|
||||
points (gsh/rect->points selrect)
|
||||
transform (gmt/matrix)
|
||||
transform-inv (gmt/matrix)]
|
||||
(assoc shape
|
||||
:selrect selrect
|
||||
:points points
|
||||
:transform transform
|
||||
:transform-inverse transform-inv))
|
||||
shape))
|
||||
shape))
|
||||
(update-page [page]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate 6
|
||||
[data]
|
||||
(letfn [(update-container [_ container]
|
||||
(-> container
|
||||
(update :objects #(d/mapm fix-line-paths %))))]
|
||||
;; Fixes issues with selrect/points for shapes with width/height = 0 (line-like paths)"
|
||||
(letfn [(fix-line-paths [shape]
|
||||
(if (= (:type shape) :path)
|
||||
(let [{:keys [width height]} (gsh/points->rect (:points shape))]
|
||||
(if (or (mth/almost-zero? width) (mth/almost-zero? height))
|
||||
(let [selrect (gsh/content->selrect (:content shape))
|
||||
points (gsh/rect->points selrect)
|
||||
transform (gmt/matrix)
|
||||
transform-inv (gmt/matrix)]
|
||||
(assoc shape
|
||||
:selrect selrect
|
||||
:points points
|
||||
:transform transform
|
||||
:transform-inverse transform-inv))
|
||||
shape))
|
||||
shape))
|
||||
|
||||
(update-container [container]
|
||||
(update container :objects d/update-vals fix-line-paths))]
|
||||
|
||||
(-> data
|
||||
(update :components #(d/mapm update-container %))
|
||||
(update :pages-index #(d/mapm update-container %)))))
|
||||
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
;; Remove interactions pointing to deleted frames
|
||||
(defmethod migrate 7
|
||||
[data]
|
||||
(letfn [(update-object [page _ object]
|
||||
(letfn [(update-object [page object]
|
||||
(d/update-when object :interactions
|
||||
(fn [interactions]
|
||||
(filterv #(get-in page [:objects (:destination %)])
|
||||
interactions))))
|
||||
(fn [interactions]
|
||||
(filterv #(get-in page [:objects (:destination %)]) interactions))))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm (partial update-object page) %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update-page [page]
|
||||
(update page :objects d/update-vals (partial update-object page)))]
|
||||
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Remove groups without any shape, both in pages and components
|
||||
|
||||
|
@ -210,7 +200,7 @@
|
|||
[(count deleted)
|
||||
(d/mapm #(clean-parents %2 deleted) result)]))))
|
||||
|
||||
(clean-container [_ container]
|
||||
(clean-container [container]
|
||||
(loop [n 0
|
||||
objects (:objects container)]
|
||||
(let [[deleted objects] (clean-objects objects)]
|
||||
|
@ -219,8 +209,8 @@
|
|||
(assoc container :objects objects)))))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index #(d/mapm clean-container %))
|
||||
(d/update-when :components #(d/mapm clean-container %)))))
|
||||
(update :pages-index d/update-vals clean-container)
|
||||
(update :components d/update-vals clean-container))))
|
||||
|
||||
(defmethod migrate 9
|
||||
[data]
|
||||
|
@ -252,35 +242,35 @@
|
|||
|
||||
(defmethod migrate 10
|
||||
[data]
|
||||
(letfn [(update-page [_ page]
|
||||
(letfn [(update-page [page]
|
||||
(d/update-in-when page [:objects uuid/zero] dissoc :points :selrect))]
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate 11
|
||||
[data]
|
||||
(letfn [(update-object [objects _id shape]
|
||||
(if (= :frame (:type shape))
|
||||
(letfn [(update-object [objects shape]
|
||||
(if (cph/frame-shape? shape)
|
||||
(d/update-when shape :shapes (fn [shapes]
|
||||
(filterv (fn [id] (contains? objects id)) shapes)))
|
||||
shape))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm (partial update-object %) %)))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update-page [page]
|
||||
(update page :objects (fn [objects]
|
||||
(d/update-vals objects (partial update-object objects)))))]
|
||||
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate 12
|
||||
[data]
|
||||
(letfn [(update-grid [_key grid]
|
||||
(letfn [(update-grid [grid]
|
||||
(cond-> grid
|
||||
(= :auto (:size grid))
|
||||
(assoc :size nil)))
|
||||
|
||||
(update-page [_id page]
|
||||
(d/update-in-when page [:options :saved-grids] #(d/mapm update-grid %)))]
|
||||
(update-page [page]
|
||||
(d/update-in-when page [:options :saved-grids] d/update-vals update-grid))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Add rx and ry to images
|
||||
(defmethod migrate 13
|
||||
|
@ -291,83 +281,124 @@
|
|||
(assoc :rx 0)
|
||||
(assoc :ry 0))
|
||||
shape))
|
||||
(update-object [_ object]
|
||||
|
||||
(update-object [object]
|
||||
(cond-> object
|
||||
(= :image (:type object))
|
||||
(cph/image-shape? object)
|
||||
(fix-radius)))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
(update-page [page]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defn set-fills
|
||||
[shape]
|
||||
(let [attrs {:fill-color (:fill-color shape)
|
||||
:fill-color-gradient (:fill-color-gradient shape)
|
||||
:fill-color-ref-file (:fill-color-ref-file shape)
|
||||
:fill-color-ref-id (:fill-color-ref-id shape)
|
||||
:fill-opacity (:fill-opacity shape)}
|
||||
|
||||
clean-attrs (d/without-nils attrs)]
|
||||
(cond-> shape
|
||||
(d/not-empty? clean-attrs)
|
||||
(assoc :fills [clean-attrs]))))
|
||||
|
||||
;; Add fills to shapes
|
||||
(defmethod migrate 14
|
||||
[data]
|
||||
(letfn [(update-object [_ object]
|
||||
(cond-> object
|
||||
(and (not (= :text (:type object))) (nil? (:fills object)))
|
||||
(set-fills)))
|
||||
(letfn [(process-shape [shape]
|
||||
(let [fill-color (str/upper (:fill-color shape))
|
||||
fill-opacity (:fill-opacity shape)]
|
||||
(cond-> shape
|
||||
(and (= 1 fill-opacity)
|
||||
(or (= "#B1B2B5" fill-color)
|
||||
(= "#7B7D85" fill-color)))
|
||||
(dissoc :fill-color :fill-opacity))))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
(update-container [{:keys [objects] :as container}]
|
||||
(loop [objects objects
|
||||
shapes (->> (vals objects)
|
||||
(filter cph/image-shape?))]
|
||||
(if-let [shape (first shapes)]
|
||||
(let [{:keys [id frame-id] :as shape'} (process-shape shape)]
|
||||
(if (identical? shape shape')
|
||||
(recur objects (rest shapes))
|
||||
(recur (-> objects
|
||||
(assoc id shape')
|
||||
(d/update-when frame-id dissoc :thumbnail))
|
||||
(rest shapes))))
|
||||
(assoc container :objects objects))))]
|
||||
|
||||
(defn set-strokes
|
||||
[shape]
|
||||
(let [attrs {:stroke-style (:stroke-style shape)
|
||||
:stroke-alignment (:stroke-alignment shape)
|
||||
:stroke-width (:stroke-width shape)
|
||||
:stroke-color (:stroke-color shape)
|
||||
:stroke-color-ref-id (:stroke-color-ref-id shape)
|
||||
:stroke-color-ref-file (:stroke-color-ref-file shape)
|
||||
:stroke-opacity (:stroke-opacity shape)
|
||||
:stroke-color-gradient (:stroke-color-gradient shape)
|
||||
:stroke-cap-start (:stroke-cap-start shape)
|
||||
:stroke-cap-end (:stroke-cap-end shape)}
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
clean-attrs (d/without-nils attrs)]
|
||||
(cond-> shape
|
||||
(d/not-empty? clean-attrs)
|
||||
(assoc :strokes [clean-attrs]))))
|
||||
|
||||
;; Add strokes to shapes
|
||||
(defmethod migrate 15
|
||||
[data]
|
||||
(letfn [(update-object [_ object]
|
||||
(cond-> object
|
||||
(and (not (= :text (:type object))) (nil? (:strokes object)))
|
||||
(set-strokes)))
|
||||
|
||||
(update-page [_ page]
|
||||
(update page :objects #(d/mapm update-object %)))]
|
||||
(update data :pages-index #(d/mapm update-page %))))
|
||||
|
||||
;; Add fills and strokes to components
|
||||
(defmethod migrate 15 [data] data)
|
||||
|
||||
;; Add fills and strokes
|
||||
(defmethod migrate 16
|
||||
[data]
|
||||
(letfn [(update-object [_ object]
|
||||
(cond-> object
|
||||
(and (not (= :text (:type object))) (nil? (:strokes object)))
|
||||
(set-strokes)
|
||||
(letfn [(assign-fills [shape]
|
||||
(let [attrs {:fill-color (:fill-color shape)
|
||||
:fill-color-gradient (:fill-color-gradient shape)
|
||||
:fill-color-ref-file (:fill-color-ref-file shape)
|
||||
:fill-color-ref-id (:fill-color-ref-id shape)
|
||||
:fill-opacity (:fill-opacity shape)}
|
||||
clean-attrs (d/without-nils attrs)]
|
||||
(cond-> shape
|
||||
(d/not-empty? clean-attrs)
|
||||
(assoc :fills [clean-attrs]))))
|
||||
|
||||
(assign-strokes [shape]
|
||||
(let [attrs {:stroke-style (:stroke-style shape)
|
||||
:stroke-alignment (:stroke-alignment shape)
|
||||
:stroke-width (:stroke-width shape)
|
||||
:stroke-color (:stroke-color shape)
|
||||
:stroke-color-ref-id (:stroke-color-ref-id shape)
|
||||
:stroke-color-ref-file (:stroke-color-ref-file shape)
|
||||
:stroke-opacity (:stroke-opacity shape)
|
||||
:stroke-color-gradient (:stroke-color-gradient shape)
|
||||
:stroke-cap-start (:stroke-cap-start shape)
|
||||
:stroke-cap-end (:stroke-cap-end shape)}
|
||||
clean-attrs (d/without-nils attrs)]
|
||||
(cond-> shape
|
||||
(d/not-empty? clean-attrs)
|
||||
(assoc :strokes [clean-attrs]))))
|
||||
|
||||
(update-object [object]
|
||||
(cond-> object
|
||||
(and (not (cph/text-shape? object))
|
||||
(not (contains? object :strokes)))
|
||||
(assign-strokes)
|
||||
|
||||
(and (not (cph/text-shape? object))
|
||||
(not (contains? object :fills)))
|
||||
(assign-fills)))
|
||||
|
||||
(update-container [container]
|
||||
(update container :objects d/update-vals update-object))]
|
||||
|
||||
(and (not (= :text (:type object))) (nil? (:fills object)))
|
||||
(set-fills)))
|
||||
(update-container [_ container]
|
||||
(update container :objects #(d/mapm update-object %)))]
|
||||
(-> data
|
||||
(update :components #(d/mapm update-container %)))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate 17
|
||||
[data]
|
||||
(letfn [(affected-object? [object]
|
||||
(and (cph/image-shape? object)
|
||||
(some? (:fills object))
|
||||
(= 1 (count (:fills object)))
|
||||
(some? (:fill-color object))
|
||||
(some? (:fill-opacity object))
|
||||
(let [color-old (str/upper (:fill-color object))
|
||||
color-new (str/upper (get-in object [:fills 0 :fill-color]))
|
||||
opacity-old (:fill-opacity object)
|
||||
opacity-new (get-in object [:fills 0 :fill-opacity])]
|
||||
(and (= color-old color-new)
|
||||
(or (= "#B1B2B5" color-old)
|
||||
(= "#7B7D85" color-old))
|
||||
(= 1 opacity-old opacity-new)))))
|
||||
|
||||
(update-object [object]
|
||||
(cond-> object
|
||||
(affected-object? object)
|
||||
(assoc :fills [])))
|
||||
|
||||
(update-container [container]
|
||||
(update container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
;; TODO: pending to do a migration for delete already not used fill
|
||||
;; and stroke props. This should be done for >1.14.x version.
|
||||
|
|
|
@ -63,16 +63,16 @@
|
|||
(filter match?)
|
||||
(seq))))
|
||||
|
||||
(defn ^boolean is-text-node?
|
||||
(defn is-text-node?
|
||||
[node]
|
||||
(and (string? (:text node))
|
||||
(not= (:text node) "")))
|
||||
|
||||
(defn ^boolean is-paragraph-node?
|
||||
(defn is-paragraph-node?
|
||||
[node]
|
||||
(= "paragraph" (:type node)))
|
||||
|
||||
(defn ^boolean is-root-node?
|
||||
(defn is-root-node?
|
||||
[node]
|
||||
(= "root" (:type node)))
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
:components {}
|
||||
:version 7}
|
||||
|
||||
res (cpm/migrate-data data nil 8)]
|
||||
res (cpm/migrate-data data 8)]
|
||||
|
||||
;; (pprint data)
|
||||
;; (pprint res)
|
||||
|
@ -81,7 +81,7 @@
|
|||
(let [id (uuid/custom 1 2)]
|
||||
(into [] (remove #(= id %)) shapes)))))
|
||||
|
||||
res (cpm/migrate-data data nil 8)]
|
||||
res (cpm/migrate-data data 8)]
|
||||
|
||||
;; (pprint res)
|
||||
;; (pprint expect)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{:paths ["src" "vendor" "resources" "test"]
|
||||
:deps
|
||||
{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.16"}
|
||||
funcool/beicon {:mvn/version "2021.07.05-1"}
|
||||
|
|
|
@ -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
Reference in a new issue