Merge pull request #3980 from penpot/niwinz-staging-upgrade-deps

 Add performance enhancements
This commit is contained in:
Aitor Moreno 2024-01-10 11:22:22 +01:00 committed by GitHub
commit 194d3251a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1212 additions and 918 deletions

View file

@ -6,7 +6,7 @@
org.clojure/clojure {:mvn/version "1.12.0-alpha5"} org.clojure/clojure {:mvn/version "1.12.0-alpha5"}
org.clojure/tools.namespace {:mvn/version "1.4.4"} org.clojure/tools.namespace {:mvn/version "1.4.4"}
com.github.luben/zstd-jni {:mvn/version "1.5.5-10"} com.github.luben/zstd-jni {:mvn/version "1.5.5-11"}
io.prometheus/simpleclient {:mvn/version "0.16.0"} io.prometheus/simpleclient {:mvn/version "0.16.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"} io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
@ -17,7 +17,7 @@
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"} io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
io.lettuce/lettuce-core {:mvn/version "6.2.6.RELEASE"} io.lettuce/lettuce-core {:mvn/version "6.3.0.RELEASE"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"} java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti funcool/yetti
@ -29,9 +29,9 @@
com.github.seancorfield/next.jdbc {:mvn/version "1.3.909"} com.github.seancorfield/next.jdbc {:mvn/version "1.3.909"}
metosin/reitit-core {:mvn/version "0.6.0"} metosin/reitit-core {:mvn/version "0.6.0"}
nrepl/nrepl {:mvn/version "1.1.0"} nrepl/nrepl {:mvn/version "1.1.0"}
cider/cider-nrepl {:mvn/version "0.43.1"} cider/cider-nrepl {:mvn/version "0.44.0"}
org.postgresql/postgresql {:mvn/version "42.6.0"} org.postgresql/postgresql {:mvn/version "42.7.1"}
com.zaxxer/HikariCP {:mvn/version "5.1.0"} com.zaxxer/HikariCP {:mvn/version "5.1.0"}
@ -42,7 +42,7 @@
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.8"} com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.8"}
org.jsoup/jsoup {:mvn/version "1.16.2"} org.jsoup/jsoup {:mvn/version "1.17.2"}
org.im4java/im4java org.im4java/im4java
{:git/tag "1.4.0-penpot-2" {:git/tag "1.4.0-penpot-2"
:git/sha "e2b3e16" :git/sha "e2b3e16"
@ -57,7 +57,7 @@
;; Pretty Print specs ;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"} pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.20.138"} software.amazon.awssdk/s3 {:mvn/version "2.22.12"}
} }
:paths ["src" "resources" "target/classes"] :paths ["src" "resources" "target/classes"]

View file

@ -1,18 +1,18 @@
{:deps {:deps
{org.clojure/clojure {:mvn/version "1.11.1"} {org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/data.json {:mvn/version "2.4.0"} org.clojure/data.json {:mvn/version "2.5.0"}
org.clojure/tools.cli {:mvn/version "1.0.219"} org.clojure/tools.cli {:mvn/version "1.0.219"}
org.clojure/clojurescript {:mvn/version "1.11.60"} org.clojure/clojurescript {:mvn/version "1.11.60"}
org.clojure/test.check {:mvn/version "1.1.1"} org.clojure/test.check {:mvn/version "1.1.1"}
org.clojure/data.fressian {:mvn/version "1.0.0"} org.clojure/data.fressian {:mvn/version "1.0.0"}
;; Logging ;; Logging
org.apache.logging.log4j/log4j-api {:mvn/version "2.21.1"} org.apache.logging.log4j/log4j-api {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.21.1"} org.apache.logging.log4j/log4j-core {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.21.1"} org.apache.logging.log4j/log4j-web {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.21.1"} org.apache.logging.log4j/log4j-jul {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.21.1"} org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.22.1"}
org.slf4j/slf4j-api {:mvn/version "2.0.9"} org.slf4j/slf4j-api {:mvn/version "2.0.10"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.32"} pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.32"}
selmer/selmer {:mvn/version "1.12.59"} selmer/selmer {:mvn/version "1.12.59"}
@ -31,7 +31,7 @@
org.graalvm.js/js {:mvn/version "23.0.2"} org.graalvm.js/js {:mvn/version "23.0.2"}
funcool/tubax {:mvn/version "2021.05.20-0"} funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/cuerdas {:mvn/version "2022.06.16-403"} funcool/cuerdas {:mvn/version "2023.11.09-407"}
funcool/promesa {:git/sha "484b7f5c0d08d817746caa685ed9ac5583eb37fa" funcool/promesa {:git/sha "484b7f5c0d08d817746caa685ed9ac5583eb37fa"
:git/url "https://github.com/funcool/promesa"} :git/url "https://github.com/funcool/promesa"}
@ -48,9 +48,10 @@
;; exception printing ;; exception printing
fipp/fipp {:mvn/version "0.6.26"} fipp/fipp {:mvn/version "0.6.26"}
io.github.eerohele/pp io.github.eerohele/pp
{:git/tag "2023-11-25.47" {:git/tag "2024-01-04.60"
:git/sha "15d572c"} :git/sha "e8a9773"}
io.aviso/pretty {:mvn/version "1.4.4"} io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}} environ/environ {:mvn/version "1.2.0"}}

View file

@ -15,6 +15,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.svg :as csvg]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
@ -300,9 +301,13 @@
(-> file (-> file
(update :parent-stack pop)))) (update :parent-stack pop))))
(defn create-shape [file type data] (defn create-shape
(let [obj (-> (cts/setup-shape (assoc data :type type)) [file type data]
(let [obj (-> (assoc data :type type)
(update :svg-attrs csvg/attrs->props)
(cts/setup-shape)
(check-name file :type))] (check-name file :type))]
(-> file (-> file
(commit-shape obj) (commit-shape obj)
(assoc :last-id (:id obj)) (assoc :last-id (:id obj))

View file

@ -608,12 +608,18 @@
(contains? svg-attr-list k) (contains? svg-attr-list k)
(contains? svg-present-list k)) (contains? svg-present-list k))
(cond (cond
(nil? v)
res
(= k :class) (= k :class)
(assoc res :className v) (assoc res :className v)
(= k :style) (= k :style)
(let [v (if (string? v) (parse-style v) v)] (let [v (if (string? v) (parse-style v) v)
(assoc res k (attrs->props v false))) v (not-empty (attrs->props v false))]
(if v
(assoc res k v)
res))
:else :else
(let [k (if (contains? non-react-props k) (let [k (if (contains? non-react-props k)
@ -624,68 +630,6 @@
{} {}
attrs))) attrs)))
(defn clean-attrs
"Transforms attributes to their react equivalent
DEPRECATED: replaced by attrs->props"
([attrs]
(clean-attrs attrs true))
([attrs whitelist?]
(letfn [(known-property? [[key _]]
(or (not whitelist?)
(contains? svg-attr-list key)
(contains? svg-present-list key)))
(camelize [s]
(when (string? s)
#?(:cljs (js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", s)
:clj (str/camel s))))
(transform-key [key]
(if (contains? non-react-props key)
key
(-> (d/name key)
(camelize)
(keyword))))
(format-styles [style-str]
(->> (str/split style-str ";")
(map str/trim)
(map #(str/split % ":"))
(group-by first)
(map (fn [[key val]]
[(transform-key key)
(second (first val))]))
(into {})))
(clean-key [[key val]]
(let [key (keyword key)]
(cond
(= key :class)
[:className val]
(and (= key :style)
(string? val))
[key (format-styles val)]
(and (= key :style)
(map? val))
[key (clean-attrs val false)]
:else
[(transform-key key) val])))]
;; Removed this warning because slows a lot rendering with big svgs
#_(let [filtered-props (->> attrs (remove known-property?) (map first))]
(when (seq filtered-props)
(.warn js/console "Unknown properties: " (str/join ", " filtered-props))))
(into {}
(comp (filter known-property?)
(map clean-key))
attrs))))
(defn update-attr-ids (defn update-attr-ids
"Replaces the ids inside a property" "Replaces the ids inside a property"
[attrs replace-fn] [attrs replace-fn]
@ -723,7 +667,8 @@
(reduce visit-node result (:content node))))] (reduce visit-node result (:content node))))]
(visit-node {} content))) (visit-node {} content)))
(defn extract-defs [{:keys [attrs] :as node}] (defn extract-defs
[{:keys [attrs] :as node}]
(if-not (map? node) (if-not (map? node)
[{} node] [{} node]
@ -741,19 +686,22 @@
[node-defs node]))) [node-defs node])))
(defn find-attr-references [attrs] (defn find-attr-references
[attrs]
(->> attrs (->> attrs
(mapcat (fn [[_ attr-value]] (mapcat (fn [[_ attr-value]]
(if (string? attr-value) (if (string? attr-value)
(extract-ids attr-value) (extract-ids attr-value)
(find-attr-references attr-value)))))) (find-attr-references attr-value))))))
(defn find-node-references [node] (defn find-node-references
[node]
(let [current (->> (find-attr-references (:attrs node)) (into #{})) (let [current (->> (find-attr-references (:attrs node)) (into #{}))
children (->> (:content node) (map find-node-references) (flatten) (into #{}))] children (->> (:content node) (map find-node-references) (flatten) (into #{}))]
(vec (into current children)))) (vec (into current children))))
(defn find-def-references [defs references] (defn find-def-references
[defs references]
(loop [result (into #{} references) (loop [result (into #{} references)
checked? #{} checked? #{}
to-check (first references) to-check (first references)
@ -778,7 +726,8 @@
(first pending) (first pending)
(rest pending)))))) (rest pending))))))
(defn svg-transform-matrix [shape] (defn svg-transform-matrix
[shape]
(if (:svg-viewbox shape) (if (:svg-viewbox shape)
(let [{svg-x :x (let [{svg-x :x
svg-y :y svg-y :y
@ -810,34 +759,39 @@
;; Transforms spec: ;; Transforms spec:
;; https://www.w3.org/TR/SVG11/single-page.html#coords-TransformAttribute ;; https://www.w3.org/TR/SVG11/single-page.html#coords-TransformAttribute
(defn format-translate-params
(defn format-translate-params [params] [params]
(assert (or (= (count params) 1) (= (count params) 2))) (assert (or (= (count params) 1) (= (count params) 2)))
(if (= (count params) 1) (if (= (count params) 1)
[(gpt/point (nth params 0) 0)] [(gpt/point (nth params 0) 0)]
[(gpt/point (nth params 0) (nth params 1))])) [(gpt/point (nth params 0) (nth params 1))]))
(defn format-scale-params [params] (defn format-scale-params
[params]
(assert (or (= (count params) 1) (= (count params) 2))) (assert (or (= (count params) 1) (= (count params) 2)))
(if (= (count params) 1) (if (= (count params) 1)
[(gpt/point (nth params 0))] [(gpt/point (nth params 0))]
[(gpt/point (nth params 0) (nth params 1))])) [(gpt/point (nth params 0) (nth params 1))]))
(defn format-rotate-params [params] (defn format-rotate-params
[params]
(assert (or (= (count params) 1) (= (count params) 3)) (str "??" (count params))) (assert (or (= (count params) 1) (= (count params) 3)) (str "??" (count params)))
(if (= (count params) 1) (if (= (count params) 1)
[(nth params 0) (gpt/point 0 0)] [(nth params 0) (gpt/point 0 0)]
[(nth params 0) (gpt/point (nth params 1) (nth params 2))])) [(nth params 0) (gpt/point (nth params 1) (nth params 2))]))
(defn format-skew-x-params [params] (defn format-skew-x-params
[params]
(assert (= (count params) 1)) (assert (= (count params) 1))
[(nth params 0) 0]) [(nth params 0) 0])
(defn format-skew-y-params [params] (defn format-skew-y-params
[params]
(assert (= (count params) 1)) (assert (= (count params) 1))
[0 (nth params 0)]) [0 (nth params 0)])
(defn to-matrix [{:keys [type params]}] (defn to-matrix
[{:keys [type params]}]
(assert (#{"matrix" "translate" "scale" "rotate" "skewX" "skewY"} type)) (assert (#{"matrix" "translate" "scale" "rotate" "skewX" "skewY"} type))
(case type (case type
"matrix" (apply gmt/matrix params) "matrix" (apply gmt/matrix params)
@ -847,7 +801,8 @@
"skewX" (apply gmt/skew-matrix (format-skew-x-params params)) "skewX" (apply gmt/skew-matrix (format-skew-x-params params))
"skewY" (apply gmt/skew-matrix (format-skew-y-params params)))) "skewY" (apply gmt/skew-matrix (format-skew-y-params params))))
(defn parse-transform [transform-attr] (defn parse-transform
[transform-attr]
(if transform-attr (if transform-attr
(let [process-matrix (let [process-matrix
(fn [[_ type params]] (fn [[_ type params]]
@ -865,7 +820,8 @@
(defn format-move [[x y]] (str "M" x " " y)) (defn format-move [[x y]] (str "M" x " " y))
(defn format-line [[x y]] (str "L" x " " y)) (defn format-line [[x y]] (str "L" x " " y))
(defn points->path [points-str] (defn points->path
[points-str]
(let [points (->> points-str (let [points (->> points-str
(re-seq number-regex) (re-seq number-regex)
(filter (comp not empty? first)) (filter (comp not empty? first))

View file

@ -916,11 +916,21 @@ function simplifyPathData(pdata) {
export function parse(string) { export function parse(string) {
if (!string || string.length === 0) return []; if (!string || string.length === 0) return [];
var source = new Parser(string); try {
var result = Array.from(source); var source = new Parser(string);
var result = Array.from(source);
result = absolutizePathData(result); result = absolutizePathData(result);
result = simplifyPathData(result); result = simplifyPathData(result);
return result; return result;
} catch (cause) {
const msg = "unexpected exception parsing path";
console.group(msg);
console.log(`string: ${string}`)
console.error(cause);
console.groupEnd(msg);
return [];
}
} }

View file

@ -12,21 +12,21 @@
(t/deftest clean-attrs-1 (t/deftest clean-attrs-1
(let [attrs {:class "foobar"} (let [attrs {:class "foobar"}
result (svg/clean-attrs attrs)] result (svg/attrs->props attrs)]
(t/is (= result {:className "foobar"})))) (t/is (= result {:className "foobar"}))))
(t/deftest clean-attrs-2 (t/deftest clean-attrs-2
(let [attrs {:overline-position "top" (let [attrs {:overline-position "top"
:style {:fill "none" :style {:fill "none"
:stroke-dashoffset 1}} :stroke-dashoffset 1}}
result (svg/clean-attrs attrs true)] result (svg/attrs->props attrs true)]
(t/is (= result {:overlinePosition "top", :style {:fill "none", :strokeDashoffset 1}})))) (t/is (= result {:overlinePosition "top", :style {:fill "none", :strokeDashoffset 1}}))))
(t/deftest clean-attrs-3 (t/deftest clean-attrs-3
(let [attrs {:overline-position "top" (let [attrs {:overline-position "top"
:style (str "fill:#00801b;fill-opacity:1;stroke:none;stroke-width:2749.72;" :style (str "fill:#00801b;fill-opacity:1;stroke:none;stroke-width:2749.72;"
"stroke-linecap:round;stroke-dasharray:none;stop-color:#000000")} "stroke-linecap:round;stroke-dasharray:none;stop-color:#000000")}
result (svg/clean-attrs attrs true)] result (svg/attrs->props attrs true)]
(t/is (= result {:overlinePosition "top", (t/is (= result {:overlinePosition "top",
:style {:fill "#00801b", :style {:fill "#00801b",
:fillOpacity "1", :fillOpacity "1",

View file

@ -10,13 +10,13 @@
"url": "https://github.com/penpot/penpot" "url": "https://github.com/penpot/penpot"
}, },
"dependencies": { "dependencies": {
"archiver": "^6.0.0", "archiver": "^6.0.1",
"cookies": "^0.8.0", "cookies": "^0.9.1",
"generic-pool": "^3.9.0", "generic-pool": "^3.9.0",
"inflation": "^2.0.0", "inflation": "^2.1.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"luxon": "^3.4.2", "luxon": "^3.4.4",
"playwright": "^1.37.1", "playwright": "^1.40.1",
"raw-body": "^2.5.2", "raw-body": "^2.5.2",
"xml-js": "^1.6.11", "xml-js": "^1.6.11",
"xregexp": "^5.1.1" "xregexp": "^5.1.1"

View file

@ -135,7 +135,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"archiver@npm:^6.0.0": "archiver@npm:^6.0.1":
version: 6.0.1 version: 6.0.1
resolution: "archiver@npm:6.0.1" resolution: "archiver@npm:6.0.1"
dependencies: dependencies:
@ -453,13 +453,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cookies@npm:^0.8.0": "cookies@npm:^0.9.1":
version: 0.8.0 version: 0.9.1
resolution: "cookies@npm:0.8.0" resolution: "cookies@npm:0.9.1"
dependencies: dependencies:
depd: "npm:~2.0.0" depd: "npm:~2.0.0"
keygrip: "npm:~1.1.0" keygrip: "npm:~1.1.0"
checksum: 0af32f30d1ece0596efc05782c66b9d61659e20c6cc5b695452abf5ceb51883ef43c5c73d86badd7d028a0da7d39f864c95f33640aef04f97fad70f35986bea3 checksum: 3ffa1c0e992b62ee119adae4dd2ddd4a89166fa5434cd9bd9ff84ec4d2f14dfe2318a601280abfe32a4f64f884ec9345fb1912e488b002d188d2efa0d3919ba3
languageName: node languageName: node
linkType: hard linkType: hard
@ -727,13 +727,13 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "exporter@workspace:." resolution: "exporter@workspace:."
dependencies: dependencies:
archiver: "npm:^6.0.0" archiver: "npm:^6.0.1"
cookies: "npm:^0.8.0" cookies: "npm:^0.9.1"
generic-pool: "npm:^3.9.0" generic-pool: "npm:^3.9.0"
inflation: "npm:^2.0.0" inflation: "npm:^2.1.0"
ioredis: "npm:^5.3.2" ioredis: "npm:^5.3.2"
luxon: "npm:^3.4.2" luxon: "npm:^3.4.4"
playwright: "npm:^1.37.1" playwright: "npm:^1.40.1"
raw-body: "npm:^2.5.2" raw-body: "npm:^2.5.2"
shadow-cljs: "npm:2.26.2" shadow-cljs: "npm:2.26.2"
source-map-support: "npm:^0.5.21" source-map-support: "npm:^0.5.21"
@ -1023,7 +1023,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"inflation@npm:^2.0.0": "inflation@npm:^2.1.0":
version: 2.1.0 version: 2.1.0
resolution: "inflation@npm:2.1.0" resolution: "inflation@npm:2.1.0"
checksum: aadfcb8047a7e00d644e2e195f901dd9d7266c2be2326b7f8f6a99298f14916f1e322d00108a7e2778d6e76a8dc2174ddb9ac14bcdfe4f4866dfd612b695ab5d checksum: aadfcb8047a7e00d644e2e195f901dd9d7266c2be2326b7f8f6a99298f14916f1e322d00108a7e2778d6e76a8dc2174ddb9ac14bcdfe4f4866dfd612b695ab5d
@ -1181,7 +1181,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"luxon@npm:^3.4.2": "luxon@npm:^3.4.4":
version: 3.4.4 version: 3.4.4
resolution: "luxon@npm:3.4.4" resolution: "luxon@npm:3.4.4"
checksum: 02e26a0b039c11fd5b75e1d734c8f0332c95510f6a514a9a0991023e43fb233884da02d7f966823ffb230632a733fc86d4a4b1e63c3fbe00058b8ee0f8c728af checksum: 02e26a0b039c11fd5b75e1d734c8f0332c95510f6a514a9a0991023e43fb233884da02d7f966823ffb230632a733fc86d4a4b1e63c3fbe00058b8ee0f8c728af
@ -1555,7 +1555,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"playwright@npm:^1.37.1": "playwright@npm:^1.40.1":
version: 1.40.1 version: 1.40.1
resolution: "playwright@npm:1.40.1" resolution: "playwright@npm:1.40.1"
dependencies: dependencies:

View file

@ -5,7 +5,7 @@
org.clojure/clojure {:mvn/version "1.11.1"} org.clojure/clojure {:mvn/version "1.11.1"}
binaryage/devtools {:mvn/version "RELEASE"} binaryage/devtools {:mvn/version "RELEASE"}
metosin/reitit-core {:mvn/version "0.5.18"} metosin/reitit-core {:mvn/version "0.6.0"}
funcool/okulary {:mvn/version "2022.04.11-16"} funcool/okulary {:mvn/version "2022.04.11-16"}
funcool/potok2 funcool/potok2
@ -19,8 +19,8 @@
:git/url "https://github.com/funcool/beicon.git"} :git/url "https://github.com/funcool/beicon.git"}
funcool/rumext funcool/rumext
{:git/tag "v2.8.1" {:git/tag "v2.9.2"
:git/sha "168738b" :git/sha "faa6e6c"
:git/url "https://github.com/funcool/rumext.git"} :git/url "https://github.com/funcool/rumext.git"}
instaparse/instaparse {:mvn/version "1.4.12"} instaparse/instaparse {:mvn/version "1.4.12"}
@ -43,7 +43,7 @@
:extra-deps :extra-deps
{thheller/shadow-cljs {:mvn/version "2.26.2"} {thheller/shadow-cljs {:mvn/version "2.26.2"}
org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.37.0"}}} cider/cider-nrepl {:mvn/version "0.44.0"}}}
:shadow-cljs :shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]} {:main-opts ["-m" "shadow.cljs.devtools.cli"]}

View file

@ -39,15 +39,15 @@
"storybook:build": "npm run storybook:compile && storybook build" "storybook:build": "npm run storybook:compile && storybook build"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^7.6.6", "@storybook/addon-essentials": "^7.6.7",
"@storybook/addon-interactions": "^7.6.6", "@storybook/addon-interactions": "^7.6.7",
"@storybook/addon-links": "^7.6.6", "@storybook/addon-links": "^7.6.7",
"@storybook/addon-onboarding": "^1.0.10", "@storybook/addon-onboarding": "^1.0.10",
"@storybook/blocks": "^7.6.6", "@storybook/blocks": "^7.6.7",
"@storybook/react": "^7.6.6", "@storybook/react": "^7.6.7",
"@storybook/react-vite": "^7.6.6", "@storybook/react-vite": "^7.6.7",
"@storybook/testing-library": "^0.2.2", "@storybook/testing-library": "^0.2.2",
"@types/node": "^20.10.5", "@types/node": "^20.10.6",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
@ -62,23 +62,23 @@
"gulp-sass": "^5.1.0", "gulp-sass": "^5.1.0",
"gulp-sourcemaps": "^3.0.0", "gulp-sourcemaps": "^3.0.0",
"gulp-svg-sprite": "^2.0.3", "gulp-svg-sprite": "^2.0.3",
"jsdom": "^23.0.1", "jsdom": "^23.1.0",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"marked": "^7.0.5", "marked": "^7.0.5",
"mkdirp": "^3.0.1", "mkdirp": "^3.0.1",
"nodemon": "^3.0.2", "nodemon": "^3.0.2",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.4.32", "postcss": "^8.4.33",
"postcss-clean": "^1.2.2", "postcss-clean": "^1.2.2",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"sass": "^1.69.5", "sass": "^1.69.7",
"shadow-cljs": "2.26.2", "shadow-cljs": "2.26.2",
"storybook": "^7.6.6", "storybook": "^7.6.7",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.10", "vite": "^5.0.11",
"vitest": "^1.1.0" "vitest": "^1.1.3"
}, },
"dependencies": { "dependencies": {
"date-fns": "^2.30.0", "date-fns": "^2.30.0",

View file

@ -45,17 +45,19 @@
;; For each of the 8 handlers gives the multiplier for resize ;; For each of the 8 handlers gives the multiplier for resize
;; for example, right will only grow in the x coordinate and left ;; for example, right will only grow in the x coordinate and left
;; will grow in the inverse of the x coordinate ;; will grow in the inverse of the x coordinate
(def ^:private handler-multipliers (defn get-handler-multiplier
{:right [1 0] [handler]
:bottom [0 1] (case handler
:left [-1 0] :right (gpt/point 1 0)
:top [0 -1] :bottom (gpt/point 0 1)
:top-right [1 -1] :left (gpt/point -1 0)
:top-left [-1 -1] :top (gpt/point 0 -1)
:bottom-right [1 1] :top-right (gpt/point 1 -1)
:bottom-left [-1 1]}) :top-left (gpt/point -1 -1)
:bottom-right (gpt/point 1 1)
:bottom-left (gpt/point -1 1)))
(defn- handler-resize-origin (defn- get-handler-resize-origin
"Given a handler, return the coordinate origin for resizes. "Given a handler, return the coordinate origin for resizes.
This is the opposite of the handler so for right we want the This is the opposite of the handler so for right we want the
left side as origin of the resize. left side as origin of the resize.
@ -64,39 +66,66 @@
mx, my => middle x/y mx, my => middle x/y
ex, ey => end x/y ex, ey => end x/y
" "
[{sx :x sy :y :keys [width height]} handler] [selrect handler]
(let [mx (+ sx (/ width 2)) (let [sx (dm/get-prop selrect :x)
my (+ sy (/ height 2)) sy (dm/get-prop selrect :y)
ex (+ sx width) width (dm/get-prop selrect :width)
ey (+ sy height) height (dm/get-prop selrect :height)
mx (+ sx (/ width 2))
[x y] (case handler my (+ sy (/ height 2))
:right [sx my] ex (+ sx width)
:bottom [mx sy] ey (+ sy height)]
:left [ex my] (case handler
:top [mx ey] :right (gpt/point sx my)
:top-right [sx ey] :bottom (gpt/point mx sy)
:top-left [ex ey] :left (gpt/point ex my)
:bottom-right [sx sy] :top (gpt/point mx ey)
:bottom-left [ex sy])] :top-right (gpt/point sx ey)
(gpt/point x y))) :top-left (gpt/point ex ey)
:bottom-right (gpt/point sx sy)
:bottom-left (gpt/point ex sy))))
(defn- fix-init-point (defn- fix-init-point
"Fix the initial point so the resizes are accurate" "Fix the initial point so the resizes are accurate"
[initial handler shape] [initial handler shape]
(let [{:keys [x y width height]} (:selrect shape)] (let [selrect (dm/get-prop shape :selrect)
(cond-> initial x (dm/get-prop selrect :x)
(contains? #{:left :top-left :bottom-left} handler) y (dm/get-prop selrect :y)
(assoc :x x) width (dm/get-prop selrect :width)
height (dm/get-prop selrect :height)]
(contains? #{:right :top-right :bottom-right} handler) (case handler
(assoc :x (+ x width)) :left
(assoc initial :x x)
(contains? #{:top :top-right :top-left} handler) :top
(assoc :y y) (assoc initial :y y)
(contains? #{:bottom :bottom-right :bottom-left} handler) :top-left
(assoc :y (+ y height))))) (-> initial
(assoc :x x)
(assoc :y y))
:bottom-left
(-> initial
(assoc :x x)
(assoc :y (+ y height)))
:right
(assoc initial :x (+ x width))
:top-right
(-> initial
(assoc :x (+ x width))
(assoc :y y))
:bottom-right
(-> initial
(assoc :x (+ x width))
(assoc :y (+ y height)))
:bottom
(assoc initial :y (+ y height)))))
(defn finish-transform [] (defn finish-transform []
(ptk/reify ::finish-transform (ptk/reify ::finish-transform
@ -104,16 +133,16 @@
(update [_ state] (update [_ state]
(update state :workspace-local dissoc :transform :duplicate-move-started? false)))) (update state :workspace-local dissoc :transform :duplicate-move-started? false))))
;; -- Resize -------------------------------------------------------- ;; -- Resize --------------------------------------------------------
(defn start-resize (defn start-resize
"Enter mouse resize mode, until mouse button is released." "Enter mouse resize mode, until mouse button is released."
[handler ids shape] [handler ids shape]
(letfn [(resize (letfn [(resize [shape initial layout [point lock? center? point-snap]]
[shape initial layout [point lock? center? point-snap]] (let [selrect (dm/get-prop shape :selrect)
(let [{:keys [width height]} (:selrect shape) width (dm/get-prop selrect :width)
{:keys [rotation]} shape height (dm/get-prop selrect :height)
rotation (dm/get-prop shape :rotation)
shape-center (gsh/shape->center shape) shape-center (gsh/shape->center shape)
shape-transform (:transform shape) shape-transform (:transform shape)
@ -129,78 +158,84 @@
shapev (-> (gpt/point width height)) shapev (-> (gpt/point width height))
scale-text (:scale-text layout) scale-text (contains? layout :scale-text)
;; Force lock if the scale text mode is active ;; Force lock if the scale text mode is active
lock? (or lock? scale-text) lock? (or ^boolean lock?
^boolean scale-text)
;; Vector modifiers depending on the handler ;; Difference between the origin point in the
handler-mult (let [[x y] (handler-multipliers handler)] (gpt/point x y)) ;; coordinate system of the rotation
;; Difference between the origin point in the coordinate system of the rotation
deltav (-> (gpt/to-vec initial point) deltav (-> (gpt/to-vec initial point)
(gpt/multiply handler-mult)) ;; Vector modifiers depending on the handler
(gpt/multiply (get-handler-multiplier handler)))
;; Resize vector ;; Resize vector
scalev (-> (gpt/divide (gpt/add shapev deltav) shapev) scalev (-> (gpt/divide (gpt/add shapev deltav) shapev)
(gpt/no-zeros)) (gpt/no-zeros))
scalev (if lock? scalev (if ^boolean lock?
(let [v (cond (let [v (cond
(#{:right :left} handler) (:x scalev) (or (= handler :right)
(#{:top :bottom} handler) (:y scalev) (= handler :left))
:else (max (:x scalev) (:y scalev)))] (dm/get-prop scalev :x)
(gpt/point v v))
(or (= handler :top)
(= handler :bottom))
(dm/get-prop scalev :y)
:else
(mth/max (dm/get-prop scalev :x)
(dm/get-prop scalev :y)))]
(gpt/point v v))
scalev) scalev)
;; Resize origin point given the selected handler ;; Resize origin point given the selected handler
handler-origin (handler-resize-origin (:selrect shape) handler) selrect (dm/get-prop shape :selrect)
handler-origin (get-handler-resize-origin selrect handler)
;; If we want resize from center, displace the shape ;; If we want resize from center, displace the shape
;; so it is still centered after resize. ;; so it is still centered after resize.
displacement displacement (when ^boolean center?
(when center? (-> shape-center
(-> shape-center (gpt/subtract handler-origin)
(gpt/subtract handler-origin) (gpt/multiply scalev)
(gpt/multiply scalev) (gpt/add handler-origin)
(gpt/add handler-origin) (gpt/subtract shape-center)
(gpt/subtract shape-center) (gpt/multiply (gpt/point -1 -1))
(gpt/multiply (gpt/point -1 -1)) (gpt/transform shape-transform)))
(gpt/transform shape-transform)))
resize-origin resize-origin (gmt/transform-point-center handler-origin shape-center shape-transform)
(cond-> (gmt/transform-point-center handler-origin shape-center shape-transform) resize-origin (if (some? displacement)
(some? displacement) (gpt/add resize-origin displacement)
(gpt/add displacement)) resize-origin)
;; When the horizontal/vertical scale a flex children with auto/fill ;; When the horizontal/vertical scale a flex children with auto/fill
;; we change it too fixed ;; we change it too fixed
set-fix-width? set-fix-width?
(not (mth/close? (:x scalev) 1)) (not (mth/close? (dm/get-prop scalev :x) 1))
set-fix-height? set-fix-height?
(not (mth/close? (:y scalev) 1)) (not (mth/close? (dm/get-prop scalev :y) 1))
modifiers modifiers (cond-> (ctm/empty)
(-> (ctm/empty) (some? displacement)
(ctm/move displacement)
(cond-> displacement :always
(ctm/move displacement)) (ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse) ^boolean set-fix-width?
(ctm/change-property :layout-item-h-sizing :fix)
(cond-> set-fix-width? ^boolean set-fix-height?
(ctm/change-property :layout-item-h-sizing :fix)) (ctm/change-property :layout-item-v-sizing :fix)
(cond-> set-fix-height? ^boolean scale-text
(ctm/change-property :layout-item-v-sizing :fix)) (ctm/scale-content (dm/get-prop scalev :x)))
(cond-> scale-text
(ctm/scale-content (:x scalev))))
modif-tree (dwm/create-modif-tree ids modifiers)] modif-tree (dwm/create-modif-tree ids modifiers)]
(rx/of (dwm/set-modifiers modif-tree scale-text)))) (rx/of (dwm/set-modifiers modif-tree scale-text))))
;; Unifies the instantaneous proportion lock modifier ;; Unifies the instantaneous proportion lock modifier
@ -208,7 +243,10 @@
;; lock flag that can be activated on element options. ;; lock flag that can be activated on element options.
(normalize-proportion-lock [[point shift? alt?]] (normalize-proportion-lock [[point shift? alt?]]
(let [proportion-lock? (:proportion-lock shape)] (let [proportion-lock? (:proportion-lock shape)]
[point (or proportion-lock? shift?) alt?]))] [point
(or ^boolean proportion-lock?
^boolean shift?)
alt?]))]
(reify (reify
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -218,15 +256,16 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [initial-position @ms/mouse-position (let [initial-position @ms/mouse-position
stopper (->> stream stopper (->> stream
(rx/filter mse/mouse-event?) (rx/filter mse/mouse-event?)
(rx/filter mse/mouse-up-event?)) (rx/filter mse/mouse-up-event?))
layout (:workspace-layout state) layout (:workspace-layout state)
page-id (:current-page-id state) page-id (:current-page-id state)
focus (:workspace-focus-selected state) focus (:workspace-focus-selected state)
zoom (get-in state [:workspace-local :zoom] 1) zoom (dm/get-in state [:workspace-local :zoom] 1)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
resizing-shapes (map #(get objects %) ids)] shapes (map (d/getf objects) ids)]
(rx/concat (rx/concat
(->> ms/mouse-position (->> ms/mouse-position
@ -234,7 +273,7 @@
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt) (rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
(rx/map normalize-proportion-lock) (rx/map normalize-proportion-lock)
(rx/switch-map (fn [[point _ _ :as current]] (rx/switch-map (fn [[point _ _ :as current]]
(->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point) (->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
(rx/map #(conj current %))))) (rx/map #(conj current %)))))
(rx/mapcat (partial resize shape initial-position layout)) (rx/mapcat (partial resize shape initial-position layout))
(rx/take-until stopper)) (rx/take-until stopper))

View file

@ -12,7 +12,7 @@
[clojure.data.json :as json] [clojure.data.json :as json]
[clojure.java.io :as io] [clojure.java.io :as io]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2.util :as mfu])) [rumext.v2.compiler :as mfu]))
;; Should match with the `ROOT_NAME` constant in gulpfile.js ;; Should match with the `ROOT_NAME` constant in gulpfile.js
(def ROOT-NAME "app") (def ROOT-NAME "app")
@ -141,7 +141,7 @@
(mfu/compile-concat :safe? false))) (mfu/compile-concat :safe? false)))
`~(binding [*css-prefix* prefix] `~(binding [*css-prefix* prefix]
(-> (into [] xform-css-case params) (-> (into [] xform-css-case params)
(mfu/compile-concat :safe? false)))))) (mfu/compile-concat :safe? false))))))
(defmacro css-case* (defmacro css-case*
[& params] [& params]

View file

@ -6,16 +6,19 @@
(ns app.main.ui.components.link (ns app.main.ui.components.link
(:require (:require
[app.common.data :as d]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc link [{:keys [action klass data-test keyboard-action children]}] (mf/defc link
(let [keyboard-action (or keyboard-action action)] {::mf/wrap-props false}
[{:keys [action klass data-test keyboard-action children]}]
(let [keyboard-action (d/nilv keyboard-action action)]
[:a {:on-click action [:a {:on-click action
:class klass :class klass
:on-key-down (fn [event] :on-key-down (fn [event]
(when (kbd/enter? event) (when ^boolean (kbd/enter? event)
(keyboard-action event))) (keyboard-action event)))
:tab-index "0" :tab-index "0"
:data-test data-test} :data-test data-test}
[:* children]])) children]))

View file

@ -10,6 +10,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.array :as array]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -17,41 +18,32 @@
(mf/defc tab-element (mf/defc tab-element
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [{:keys [children]}]
(let [children (unchecked-get props "children")] [:div {:class (stl/css :tab-element)} children])
[:div {:class (stl/css :tab-element)}
children]))
(mf/defc tab-container (mf/defc tab-container
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [{:keys [children selected on-change-tab collapsable handle-collapse header-class content-class]}]
(let [children (->> (let [children (-> (array/normalize-to-array children)
(unchecked-get props "children") (array/without-nils))
(filter some?))
selected (unchecked-get props "selected")
on-change (unchecked-get props "on-change-tab")
collapsable? (unchecked-get props "collapsable?")
handle-collapse (unchecked-get props "handle-collapse")
class (unchecked-get props "class")
content-class (unchecked-get props "content-class")
state (mf/use-state #(or selected (-> children first .-props .-id))) selected* (mf/use-state #(or selected (-> children first .-props .-id)))
selected (or selected @state) selected (or selected @selected*)
select-fn on-click (mf/use-fn
(mf/use-fn (mf/deps on-change-tab)
(mf/deps on-change) (fn [event]
(fn [event] (let [id (-> event
(let [id (-> event (dom/get-current-target)
(dom/get-current-target) (dom/get-data "id")
(dom/get-data "id") (keyword))]
(keyword))] (reset! selected* id)
(reset! state id) (when (fn? on-change-tab)
(when (fn? on-change) (on-change id)))))] (on-change-tab id)))))]
[:div {:class (stl/css :tab-container)} [:div {:class (stl/css :tab-container)}
[:div {:class (dm/str class " " (stl/css :tab-container-tabs))} [:div {:class (dm/str header-class " " (stl/css :tab-container-tabs))}
(when collapsable? (when ^boolean collapsable
[:button [:button
{:on-click handle-collapse {:on-click handle-collapse
:class (stl/css :collapse-sidebar) :class (stl/css :collapse-sidebar)
@ -61,13 +53,16 @@
(for [tab children] (for [tab children]
(let [props (.-props tab) (let [props (.-props tab)
id (.-id props) id (.-id props)
title (.-title props)] title (.-title props)
[:div sid (d/name id)]
{:key (str/concat "tab-" (d/name id)) [:div {:key (str/concat "tab-" sid)
:data-id (d/name id) :data-id sid
:on-click select-fn :on-click on-click
:class (stl/css-case :tab-container-tab-title true :class (stl/css-case
:current (= selected id))} :tab-container-tab-title true
:current (= selected id))}
title]))]] title]))]]
[:div {:class (dm/str content-class " " (stl/css :tab-container-content))} [:div {:class (dm/str content-class " " (stl/css :tab-container-content))}
(d/seek #(= selected (-> % .-props .-id)) children)]])) (d/seek #(= selected (-> % .-props .-id))
children)]]))

View file

@ -29,7 +29,7 @@
(cond (cond
(map? node) (map? node)
[:> (d/name tag) (clj->js (csvg/clean-attrs attrs)) [:> (d/name tag) (obj/map->obj (csvg/attrs->props attrs))
(for [child content] (for [child content]
[:& render-xml {:xml child :key (swap! internal-counter inc)}])] [:& render-xml {:xml child :key (swap! internal-counter inc)}])]

View file

@ -13,6 +13,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.bounds :as gsb] [app.common.geom.shapes.bounds :as gsb]
[app.common.svg :as csvg] [app.common.svg :as csvg]
[app.util.object :as obj]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defn add-matrix [attrs transform-key transform-matrix] (defn add-matrix [attrs transform-key transform-matrix]
@ -23,7 +24,9 @@
(str transform-matrix " " val) (str transform-matrix " " val)
(str transform-matrix))))) (str transform-matrix)))))
(mf/defc svg-node [{:keys [type node prefix-id transform bounds]}] (mf/defc svg-node
{::mf/wrap-props false}
[{:keys [type node prefix-id transform bounds]}]
(cond (cond
(string? node) node (string? node) node
@ -48,7 +51,7 @@
attrs attrs
(-> attrs (-> attrs
(csvg/update-attr-ids prefix-id) (csvg/update-attr-ids prefix-id)
(csvg/clean-attrs) (csvg/attrs->props)
;; This clasname will be used to change the transform on the viewport ;; This clasname will be used to change the transform on the viewport
;; only necessary for groups because shapes have their own transform ;; only necessary for groups because shapes have their own transform
(cond-> (and (or transform-gradient? (cond-> (and (or transform-gradient?
@ -80,7 +83,7 @@
:transform (str transform)}] :transform (str transform)}]
[mf/Fragment #js {}])] [mf/Fragment #js {}])]
[:> (name tag) (clj->js attrs) [:> (name tag) (obj/map->obj attrs)
[:> wrapper wrapper-props [:> wrapper wrapper-props
(for [[index node] (d/enumerate content)] (for [[index node] (d/enumerate content)]
[:& svg-node {:key (dm/str "node-" index) [:& svg-node {:key (dm/str "node-" index)

View file

@ -10,7 +10,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.svg :as csvg] [app.common.svg :as csvg]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as usa] [app.main.ui.shapes.attrs :as attrs]
[app.util.object :as obj] [app.util.object :as obj]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -41,7 +41,7 @@
props (mf/with-memo [shape render-id] props (mf/with-memo [shape render-id]
(-> #js {} (-> #js {}
(usa/add-fill-props! shape render-id) (attrs/add-fill-props! shape render-id)
(obj/unset! "transform") (obj/unset! "transform")
(obj/set! "x" x) (obj/set! "x" x)
(obj/set! "y" y) (obj/set! "y" y)
@ -79,8 +79,7 @@
props props
(mf/with-memo [shape render-id] (mf/with-memo [shape render-id]
(let [element-id (dm/get-in shape [:svg-attrs :id]) (let [element-id (dm/get-in shape [:svg-attrs :id])
props (-> #js {} props (attrs/add-fill-props! #js {} shape render-id)]
(usa/add-fill-props! shape render-id))]
(when (and (some? element-id) (when (and (some? element-id)
(contains? ids-mapping element-id)) (contains? ids-mapping element-id))

View file

@ -311,7 +311,7 @@
[:& tab-container [:& tab-container
{:on-change-tab set-tab! {:on-change-tab set-tab!
:selected @active-color-tab :selected @active-color-tab
:collapsable? false} :collapsable false}
[:& tab-element {:id :ramp :title i/rgba-refactor} [:& tab-element {:id :ramp :title i/rgba-refactor}
(if picking-color? (if picking-color?

View file

@ -502,7 +502,7 @@
[:& tab-container [:& tab-container
{:on-change-tab on-tab-change {:on-change-tab on-tab-change
:selected selected-tab :selected selected-tab
:collapsable? false} :collapsable false}
[:& tab-element {:id :libraries :title (tr "workspace.libraries.libraries")} [:& tab-element {:id :libraries :title (tr "workspace.libraries.libraries")}
[:div {:class (stl/css :libraries-content)} [:div {:class (stl/css :libraries-content)}
[:& libraries-tab {:file-id file-id [:& libraries-tab {:file-id file-id

View file

@ -83,10 +83,9 @@
[:& tab-container [:& tab-container
{:on-change-tab on-tab-change {:on-change-tab on-tab-change
:selected section :selected section
:shortcuts? shortcuts? :collapsable true
:collapsable? true
:handle-collapse handle-collapse :handle-collapse handle-collapse
:class (stl/css :tab-spacing)} :header-class (stl/css :tab-spacing)}
[:& tab-element {:id :layers :title (tr "workspace.sidebar.layers")} [:& tab-element {:id :layers :title (tr "workspace.sidebar.layers")}
[:div {:class (stl/css :layers-tab) [:div {:class (stl/css :layers-tab)
:style #js {"--height" (str size-pages "px")}} :style #js {"--height" (str size-pages "px")}}

View file

@ -26,6 +26,7 @@
[app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.array :as array]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -120,7 +121,8 @@
:workspace? true}]) :workspace? true}])
(mf/defc section-icon (mf/defc section-icon
[{:keys [section] :as props}] {::mf/wrap-props false}
[{:keys [section]}]
(case section (case section
:colors i/drop-refactor :colors i/drop-refactor
:components i/component-refactor :components i/component-refactor
@ -130,28 +132,42 @@
(mf/defc asset-section (mf/defc asset-section
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [children file-id title section assets-count open?]}] [{:keys [children file-id title section assets-count open?]}]
(let [children (->> (if (array? children) children [children]) (let [children (-> (array/normalize-to-array children)
(filter some?)) (array/without-nils))
get-role #(.. % -props -role)
title-buttons (filter #(= (get-role %) :title-button) children)
content (filter #(= (get-role %) :content) children)]
[:div {:class (stl/css :asset-section)}
[:& title-bar {:collapsable true
:collapsed (not open?)
:all-clickable true
:on-collapsed #(st/emit! (dw/set-assets-section-open file-id section (not open?)))
:class (stl/css :title-spacing)
:title (mf/html [:span {:class (stl/css :title-name)}
[:span {:class (stl/css :section-icon)}
[:& section-icon {:section section}]]
[:span {:class (stl/css :section-name)}
title]
[:span {:class (stl/css :num-assets)} is-button? #(= :title-button (.. % -props -role))
assets-count]])} is-content? #(= :content (.. % -props -role))
title-buttons]
(when ^boolean open? buttons (array/filter is-button? children)
content)])) content (array/filter is-content? children)
on-collapsed
(mf/use-fn
(mf/deps file-id section open?)
(fn [_]
(st/emit! (dw/set-assets-section-open file-id section (not open?)))))
title
(mf/html
[:span {:class (stl/css :title-name)}
[:span {:class (stl/css :section-icon)}
[:& section-icon {:section section}]]
[:span {:class (stl/css :section-name)}
title]
[:span {:class (stl/css :num-assets)}
assets-count]])]
[:div {:class (stl/css :asset-section)}
[:& title-bar
{:collapsable true
:collapsed (not open?)
:all-clickable true
:on-collapsed on-collapsed
:class (stl/css :title-spacing)
:title title}
buttons]
(when ^boolean open? content)]))
(mf/defc asset-section-block (mf/defc asset-section-block
{::mf/wrap-props false} {::mf/wrap-props false}

View file

@ -103,9 +103,9 @@
[:& tab-container [:& tab-container
{:on-change-tab on-change-tab {:on-change-tab on-change-tab
:selected section :selected section
:collapsable? false :collapsable false
:content-class (stl/css :content-class) :content-class (stl/css :content-class)
:class (stl/css :tab-spacing)} :header-class (stl/css :tab-spacing)}
[:& tab-element {:id :design [:& tab-element {:id :design
:title (tr "workspace.options.design")} :title (tr "workspace.options.design")}
[:div {:class (stl/css :element-options)} [:div {:class (stl/css :element-options)}

View file

@ -299,6 +299,7 @@
(not (cfh/is-direct-child-of-root? shape)) (not (cfh/is-direct-child-of-root? shape))
(empty? (get shape :fills))))) (empty? (get shape :fills)))))
hover-shape hover-shape
(->> ids (->> ids
(remove remove-id?) (remove remove-id?)

View file

@ -8,6 +8,7 @@
"Selection handlers component." "Selection handlers component."
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@ -19,11 +20,11 @@
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.css-cursors :as cur] [app.main.ui.css-cursors :as cur]
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]] [app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
[app.util.array :as array]
[app.util.debug :as dbg] [app.util.debug :as dbg]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.object :as obj] [app.util.object :as obj]
[rumext.v2 :as mf] [rumext.v2 :as mf]))
[rumext.v2.util :refer [map->obj]]))
(def rotation-handler-size 20) (def rotation-handler-size 20)
(def resize-point-radius 4) (def resize-point-radius 4)
@ -37,134 +38,150 @@
(def small-selrect-side 30) (def small-selrect-side 30)
(mf/defc selection-rect (mf/defc selection-rect
{::mf/wrap-props false}
[{:keys [transform rect zoom color on-move-selected on-context-menu]}] [{:keys [transform rect zoom color on-move-selected on-context-menu]}]
(when rect (let [x (dm/get-prop rect :x)
(let [{:keys [x y width height]} rect] y (dm/get-prop rect :y)
[:rect.main.viewport-selrect width (dm/get-prop rect :width)
{:x x height (dm/get-prop rect :height)]
:y y [:rect.main.viewport-selrect
:width width {:x x
:height height :y y
:transform (str transform) :width width
:on-pointer-down on-move-selected :height height
:on-context-menu on-context-menu :transform (str transform)
:style {:stroke color :on-pointer-down on-move-selected
:stroke-width (/ selection-rect-width zoom) :on-context-menu on-context-menu
:fill "none"}}]))) :style {:stroke color
:stroke-width (/ selection-rect-width zoom)
:fill "none"}}]))
(defn- handlers-for-selection [{:keys [x y width height]} {:keys [type]} zoom] (defn- calculate-handlers
(let [threshold-small (/ 25 zoom) "Calculates selection handlers for the current selection."
threshold-tiny (/ 10 zoom) [selection shape zoom]
(let [x (dm/get-prop selection :x)
y (dm/get-prop selection :y)
width (dm/get-prop selection :width)
height (dm/get-prop selection :height)
small-width? (<= width threshold-small) threshold-small (/ 25 zoom)
tiny-width? (<= width threshold-tiny) threshold-tiny (/ 10 zoom)
small-height? (<= height threshold-small) small-width? (<= width threshold-small)
tiny-height? (<= height threshold-tiny) tiny-width? (<= width threshold-tiny)
vertical-line? (and (= type :path) tiny-width?) small-height? (<= height threshold-small)
horizontal-line? (and (= type :path) tiny-height?) tiny-height? (<= height threshold-tiny)
align (if (or small-width? small-height?) path? (cfh/path-shape? shape)
:outside vertical-line? (and ^boolean path? ^boolean tiny-width?)
:inside)] horizontal-line? (and ^boolean path? ^boolean tiny-height?)
(->>
[;; TOP-LEFT
{:type :rotation
:position :top-left
:props {:cx x :cy y}}
{:type :rotation align (if (or ^boolean small-width? ^boolean small-height?)
:position :top-right :outside
:props {:cx (+ x width) :cy y}} :inside)
{:type :rotation result #js [#js {:type :rotation
:position :bottom-right :position :top-left
:props {:cx (+ x width) :cy (+ y height)}} :props #js {:cx x :cy y}}
{:type :rotation #js {:type :rotation
:position :bottom-left :position :top-right
:props {:cx x :cy (+ y height)}} :props #js {:cx (+ x width) :cy y}}
(when-not horizontal-line? #js {:type :rotation
(let [x (if small-width? (+ x (/ (- width threshold-small) 2)) x) :position :bottom-right
length (if small-width? threshold-small width)] :props #js {:cx (+ x width) :cy (+ y height)}}
{:type :resize-side
:position :top
:props {:x x
:y y
:length length
:angle 0
:align align
:show-handler? tiny-width?}}))
(when-not horizontal-line? #js {:type :rotation
(let [x (if small-width? (+ x (/ (+ width threshold-small) 2)) (+ x width)) :position :bottom-left
length (if small-width? threshold-small width)] :props #js {:cx x :cy (+ y height)}}]]
{:type :resize-side
:position :bottom
:props {:x x
:y (+ y height)
:length length
:angle 180
:align align
:show-handler? tiny-width?}}))
(when-not vertical-line?
(let [y (if small-height? (+ y (/ (- height threshold-small) 2)) y)
length (if small-height? threshold-small height)]
{:type :resize-side
:position :right
:props {:x (+ x width)
:y y
:length length
:angle 90
:align align
:show-handler? tiny-height?}}))
(when-not vertical-line? (when-not ^boolean horizontal-line?
(let [y (if small-height? (+ y (/ (+ height threshold-small) 2)) (+ y height)) (array/conj! result
length (if small-height? threshold-small height)] #js {:type :resize-side
{:type :resize-side :position :top
:position :left :props #js {:x (if ^boolean small-width?
:props {:x x (+ x (/ (- width threshold-small) 2))
:y y x)
:length length :y y
:angle 270 :length (if ^boolean small-width?
:align align threshold-small
:show-handler? tiny-height?}})) width)
:angle 0
:align align
:show-handler tiny-width?}}
#js {:type :resize-side
:position :bottom
:props #js {:x (if ^boolean small-width?
(+ x (/ (+ width threshold-small) 2))
(+ x width))
:y (+ y height)
:length (if small-width? threshold-small width)
:angle 180
:align align
:show-handler tiny-width?}}))
(when (and (not tiny-width?) (not tiny-height?)) (when-not vertical-line?
{:type :resize-point (array/conj! result
:position :top-left #js {:type :resize-side
:props {:cx x :cy y :align align}}) :position :right
:props #js {:x (+ x width)
:y (if small-height? (+ y (/ (- height threshold-small) 2)) y)
:length (if small-height? threshold-small height)
:angle 90
:align align
:show-handler tiny-height?}}
(when (and (not tiny-width?) (not tiny-height?)) #js {:type :resize-side
{:type :resize-point :position :left
:position :top-right :props #js {:x x
:props {:cx (+ x width) :cy y :align align}}) :y (if ^boolean small-height?
(+ y (/ (+ height threshold-small) 2))
(+ y height))
:length (if ^boolean small-height?
threshold-small
height)
:angle 270
:align align
:show-handler tiny-height?}}))
(when (and (not tiny-width?) (not tiny-height?)) (when (and (not tiny-width?) (not tiny-height?))
{:type :resize-point (array/conj! result
:position :bottom-right #js {:type :resize-point
:props {:cx (+ x width) :cy (+ y height) :align align}}) :position :top-left
:props #js {:cx x :cy y :align align}}
#js {:type :resize-point
:position :top-right
:props #js {:cx (+ x width) :cy y :align align}}
#js {:type :resize-point
:position :bottom-right
:props #js {:cx (+ x width) :cy (+ y height) :align align}}
#js {:type :resize-point
:position :bottom-left
:props #js {:cx x :cy (+ y height) :align align}}))))
(when (and (not tiny-width?) (not tiny-height?)) (mf/defc rotation-handler
{:type :resize-point {::mf/wrap-props false}
:position :bottom-left [{:keys [cx cy transform position rotation zoom on-rotate] :as props}]
:props {:cx x :cy (+ y height) :align align}})] (let [size (/ rotation-handler-size zoom)
delta-x (if (or (= position :top-left)
(= position :bottom-left))
size
0)
delta-y (if (or (= :top-left position)
(= :top-right position))
size
0)
(filterv (comp not nil?))))) x (- cx delta-x)
y (- cy delta-y)
(mf/defc rotation-handler [{:keys [cx cy transform position rotation zoom on-rotate]}] angle (case position
(let [size (/ rotation-handler-size zoom) :top-left 0
x (- cx (if (#{:top-left :bottom-left} position) size 0)) :top-right 90
y (- cy (if (#{:top-left :top-right} position) size 0)) :bottom-right 180
angle (case position :bottom-left 270)]
:top-left 0
:top-right 90
:bottom-right 180
:bottom-left 270)]
[:rect {:x x [:rect {:x x
:y y :y y
:class (cur/get-dynamic "rotate" (+ rotation angle)) :class (cur/get-dynamic "rotate" (+ rotation angle))
@ -176,13 +193,20 @@
:on-pointer-down on-rotate}])) :on-pointer-down on-rotate}]))
(mf/defc resize-point-handler (mf/defc resize-point-handler
[{:keys [cx cy zoom position on-resize transform rotation color align]}] {::mf/wrap-props false}
(let [layout (mf/deref refs/workspace-layout) [{:keys [cx cy zoom position on-resize transform rotation color align scale-text]}]
scale-text (:scale-text layout) (let [cursor (if (or (= position :top-left)
cursor (if (#{:top-left :bottom-right} position) (= position :bottom-right))
(if scale-text (cur/get-dynamic "scale-nesw" rotation) (cur/get-dynamic "resize-nesw" rotation)) (if ^boolean scale-text
(if scale-text (cur/get-dynamic "scale-nwse" rotation) (cur/get-dynamic "resize-nwse" rotation))) (cur/get-dynamic "scale-nesw" rotation)
{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)] (cur/get-dynamic "resize-nesw" rotation))
(if ^boolean scale-text
(cur/get-dynamic "scale-nwse" rotation)
(cur/get-dynamic "resize-nwse" rotation)))
pt (gpt/transform (gpt/point cx cy) transform)
cx' (dm/get-prop pt :x)
cy' (dm/get-prop pt :y)]
[:g.resize-handler [:g.resize-handler
[:circle {:r (/ resize-point-radius zoom) [:circle {:r (/ resize-point-radius zoom)
@ -196,49 +220,68 @@
(if (= align :outside) (if (= align :outside)
(let [resize-point-circle-radius (/ resize-point-circle-radius zoom) (let [resize-point-circle-radius (/ resize-point-circle-radius zoom)
offset-x (if (#{:top-right :bottom-right} position) 0 (- resize-point-circle-radius)) offset-x (if (or (= position :top-right)
offset-y (if (#{:bottom-left :bottom-right} position) 0 (- resize-point-circle-radius)) (= position :bottom-right))
cx (+ cx offset-x) 0
cy (+ cy offset-y) (- resize-point-circle-radius))
{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)] offset-y (if (or (= position :bottom-left)
(= position :bottom-right))
0
(- resize-point-circle-radius))
cx (+ cx offset-x)
cy (+ cy offset-y)
pt (gpt/transform (gpt/point cx cy) transform)
cx' (dm/get-prop pt :x)
cy' (dm/get-prop pt :y)]
[:rect {:x cx' [:rect {:x cx'
:y cy' :y cy'
:data-position (name position)
:class cursor :class cursor
:width resize-point-circle-radius
:height resize-point-circle-radius
:transform (when rotation (dm/fmt "rotate(%, %, %)" rotation cx' cy'))
:style {:fill (if (dbg/enabled? :handlers) "red" "none") :style {:fill (if (dbg/enabled? :handlers) "red" "none")
:stroke-width 0} :stroke-width 0}
:on-pointer-down #(on-resize {:x cx' :y cy'} %)}]) :width resize-point-circle-radius
:height resize-point-circle-radius
:transform (when (some? rotation)
(dm/fmt "rotate(%, %, %)" rotation cx' cy'))
:on-pointer-down on-resize}])
[:circle {:on-pointer-down #(on-resize {:x cx' :y cy'} %) [:circle {:on-pointer-down on-resize
:r (/ resize-point-circle-radius zoom) :r (/ resize-point-circle-radius zoom)
:data-position (name position)
:cx cx' :cx cx'
:cy cy' :cy cy'
:data-x cx'
:data-y cy'
:class cursor :class cursor
:style {:fill (if (dbg/enabled? :handlers) "red" "none") :style {:fill (if (dbg/enabled? :handlers) "red" "none")
:stroke-width 0}}])])) :stroke-width 0}}])]))
;; The side handler is always rendered horizontally and then rotated
(mf/defc resize-side-handler (mf/defc resize-side-handler
"The side handler is always rendered horizontally and then rotated" {::mf/wrap-props false}
[{:keys [x y length align angle zoom position rotation transform on-resize color show-handler?]}] [{:keys [x y length align angle zoom position rotation transform on-resize color show-handler scale-text]}]
(let [res-point (if (#{:top :bottom} position) (let [height (/ resize-side-height zoom)
{:y y} offset-y (if (= align :outside) (- height) (- (/ height 2)))
{:x x}) target-y (+ y offset-y)
layout (mf/deref refs/workspace-layout) transform-str (dm/str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))
scale-text (:scale-text layout) cursor (if (or (= position :left)
height (/ resize-side-height zoom) (= position :right))
offset-y (if (= align :outside) (- height) (- (/ height 2))) (if ^boolean scale-text
target-y (+ y offset-y) (cur/get-dynamic "scale-ew" rotation)
transform-str (dm/str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))] (cur/get-dynamic "resize-ew" rotation))
(if ^boolean scale-text
(cur/get-dynamic "scale-ns" rotation)
(cur/get-dynamic "resize-ns" rotation)))]
[:g.resize-handler [:g.resize-handler
(when show-handler? (when ^boolean show-handler
[:circle {:r (/ resize-point-radius zoom) [:circle {:r (/ resize-point-radius zoom)
:style {:fillOpacity 1 :style {:fillOpacity 1
:stroke color :stroke color
:strokeWidth "1px" :strokeWidth "1px"
:fill "var(--color-white)" :fill "var(--color-white)"
:vectorEffect "non-scaling-stroke"} :vectorEffect "non-scaling-stroke"}
:data-position (name position)
:cx (+ x (/ length 2)) :cx (+ x (/ length 2))
:cy y :cy y
:transform transform-str}]) :transform transform-str}])
@ -246,42 +289,25 @@
:y target-y :y target-y
:width length :width length
:height height :height height
:class (if (#{:left :right} position) :class cursor
(if scale-text (cur/get-dynamic "scale-ew" rotation) (cur/get-dynamic "resize-ew" rotation)) :data-position (name position)
(if scale-text (cur/get-dynamic "scale-ns" rotation) (cur/get-dynamic "resize-ns" rotation)))
:transform transform-str :transform transform-str
:on-pointer-down #(on-resize res-point %) :on-pointer-down on-resize
:style {:fill (if (dbg/enabled? :handlers) "yellow" "none") :style {:fill (if (dbg/enabled? :handlers) "yellow" "none")
:stroke-width 0}}]])) :stroke-width 0}}]]))
(defn minimum-selrect [{:keys [x y width height] :as selrect}]
(let [final-width (max width min-selrect-side)
final-height (max height min-selrect-side)
offset-x (/ (- final-width width) 2)
offset-y (/ (- final-height height) 2)]
{:x (- x offset-x)
:y (- y offset-y)
:width final-width
:height final-height}))
(mf/defc controls-selection (mf/defc controls-selection
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [{:keys [shape zoom color on-move-selected on-context-menu disable-handlers]}]
(let [shape (obj/get props "shape") (let [selrect (dm/get-prop shape :selrect)
zoom (obj/get props "zoom") transform-type (mf/deref refs/current-transform)
color (obj/get props "color") transform (gsh/transform-str shape)]
on-move-selected (obj/get props "on-move-selected")
on-context-menu (obj/get props "on-context-menu")
disable-handlers (obj/get props "disable-handlers")
current-transform (mf/deref refs/current-transform) (when (and (some? selrect)
(not (:transforming shape))
selrect (:selrect shape) (not (or (= transform-type :move)
transform (gsh/transform-str shape)] (= transform-type :rotate))))
[:g.controls {:pointer-events (if ^boolean disable-handlers "none" "visible")}
(when (and (not (:transforming shape))
(not (#{:move :rotate} current-transform)))
[:g.controls {:pointer-events (if disable-handlers "none" "visible")}
;; Selection rect ;; Selection rect
[:& selection-rect {:rect selrect [:& selection-rect {:rect selrect
:transform transform :transform transform
@ -292,54 +318,61 @@
(mf/defc controls-handlers (mf/defc controls-handlers
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [{:keys [shape zoom color on-resize on-rotate disable-handlers]}]
(let [shape (obj/get props "shape") (let [transform-type (mf/deref refs/current-transform)
zoom (obj/get props "zoom") read-only? (mf/use-ctx ctx/workspace-read-only?)
color (obj/get props "color")
on-resize (obj/get props "on-resize")
on-rotate (obj/get props "on-rotate")
disable-handlers (obj/get props "disable-handlers")
current-transform (mf/deref refs/current-transform)
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
selrect (:selrect shape) layout (mf/deref refs/workspace-layout)
transform (gsh/transform-matrix shape) scale-text? (contains? layout :scale-text)
rotation (-> (gpt/point 1 0) selrect (dm/get-prop shape :selrect)
(gpt/transform (:transform shape)) transform (gsh/transform-matrix shape)
(gpt/angle)
(mod 360))]
(when (and (not (#{:move :rotate} current-transform)) rotation (-> (gpt/point 1 0)
(not workspace-read-only?) (gpt/transform (:transform shape))
(not (:transforming shape))) (gpt/angle)
[:g.controls {:pointer-events (if disable-handlers "none" "visible")} (mod 360))
;; Handlers
(for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)]
(let [rotation
(cond
(and (#{:top-left :bottom-right} position)
(or (and (:flip-x shape) (not (:flip-y shape)))
(and (:flip-y shape) (not (:flip-x shape)))))
(- rotation 90)
(and (#{:top-right :bottom-left} position) flip-x (get shape :flip-x)
(or (and (:flip-x shape) (not (:flip-y shape))) flip-y (get shape :flip-y)
(and (:flip-y shape) (not (:flip-x shape))))) half-flip? (or (and (some? flip-x) (not (some? flip-y)))
(+ rotation 90) (and (some? flip-y) (not (some? flip-x))))]
:else (when (and (not ^boolean read-only?)
rotation) (not (:transforming shape))
(not (or (= transform-type :move)
(= transform-type :rotate))))
common-props {:key (dm/str (name type) "-" (name position)) [:g.controls {:pointer-events (if ^boolean disable-handlers "none" "visible")}
:zoom zoom (for [handler (calculate-handlers selrect shape zoom)]
:position position (let [type (obj/get handler "type")
:on-rotate on-rotate position (obj/get handler "position")
:on-resize (partial on-resize position) props (obj/get handler "props")
:transform transform rotation (cond
:rotation rotation (and ^boolean half-flip?
:color color} (or (= position :top-left)
props (map->obj (merge common-props props))] (= position :bottom-right)))
(- rotation 90)
(and ^boolean half-flip?
(or (= position :top-right)
(= position :bottom-left)))
(- rotation 90)
:else
rotation)
props (obj/merge!
#js {:key (dm/str (name type) "-" (name position))
:scale-text scale-text?
:zoom zoom
:position position
:on-rotate on-rotate
:on-resize on-resize
:transform transform
:rotation rotation
:color color}
props)]
(case type (case type
:rotation [:> rotation-handler props] :rotation [:> rotation-handler props]
:resize-point [:> resize-point-handler props] :resize-point [:> resize-point-handler props]
@ -348,8 +381,12 @@
;; --- Selection Handlers (Component) ;; --- Selection Handlers (Component)
(mf/defc text-edition-selection (mf/defc text-edition-selection
[{:keys [shape color zoom] :as props}] {::mf/wrap-props false}
(let [{:keys [x y width height]} shape] [{:keys [shape color zoom]}]
(let [x (dm/get-prop shape :x)
y (dm/get-prop shape :y)
width (dm/get-prop shape :width)
height (dm/get-prop shape :height)]
[:g.controls [:g.controls
[:rect.main {:x x :y y [:rect.main {:x x :y y
:transform (gsh/transform-str shape) :transform (gsh/transform-str shape)
@ -362,23 +399,31 @@
:fill "none"}}]])) :fill "none"}}]]))
(mf/defc multiple-handlers (mf/defc multiple-handlers
[{:keys [shapes selected zoom color disable-handlers] :as props}] {::mf/wrap-props false}
[{:keys [shapes selected zoom color disable-handlers]}]
(let [shape (mf/with-memo [shapes] (let [shape (mf/with-memo [shapes]
(-> shapes (-> shapes
(gsh/shapes->rect) (gsh/shapes->rect)
(assoc :type :multiple) (assoc :type :multiple)
(cts/setup-shape))) (cts/setup-shape)))
on-resize on-resize
(fn [current-position _initial-position event] (mf/use-fn
(when (dom/left-mouse? event) (mf/deps selected shape)
(dom/stop-propagation event) (fn [event]
(st/emit! (dw/start-resize current-position selected shape)))) (when (dom/left-mouse? event)
(dom/stop-propagation event)
(let [target (dom/get-current-target event)
position (keyword (dom/get-data target "position"))]
(st/emit! (dw/start-resize position selected shape))))))
on-rotate on-rotate
(fn [event] (mf/use-fn
(when (dom/left-mouse? event) (mf/deps shapes)
(dom/stop-propagation event) (fn [event]
(st/emit! (dw/start-rotate shapes))))] (when (dom/left-mouse? event)
(dom/stop-propagation event)
(st/emit! (dw/start-rotate shapes)))))]
[:& controls-handlers [:& controls-handlers
{:shape shape {:shape shape
@ -389,7 +434,8 @@
:on-rotate on-rotate}])) :on-rotate on-rotate}]))
(mf/defc multiple-selection (mf/defc multiple-selection
[{:keys [shapes zoom color disable-handlers on-move-selected on-context-menu] :as props}] {::mf/wrap-props false}
[{:keys [shapes zoom color disable-handlers on-move-selected on-context-menu]}]
(let [shape (mf/with-memo [shapes] (let [shape (mf/with-memo [shapes]
(-> shapes (-> shapes
(gsh/shapes->rect) (gsh/shapes->rect)
@ -405,20 +451,27 @@
:on-context-menu on-context-menu}])) :on-context-menu on-context-menu}]))
(mf/defc single-handlers (mf/defc single-handlers
[{:keys [shape zoom color disable-handlers] :as props}] {::mf/wrap-props false}
(let [shape-id (:id shape) [{:keys [shape zoom color disable-handlers]}]
(let [shape-id (dm/get-prop shape :id)
on-resize on-resize
(fn [current-position _initial-position event] (mf/use-fn
(when (dom/left-mouse? event) (mf/deps shape-id shape)
(dom/stop-propagation event) (fn [event]
(st/emit! (dw/start-resize current-position #{shape-id} shape)))) (when (dom/left-mouse? event)
(dom/stop-propagation event)
(let [target (dom/get-current-target event)
position (keyword (dom/get-data target "position"))]
(st/emit! (dw/start-resize position #{shape-id} shape))))))
on-rotate on-rotate
(fn [event] (mf/use-fn
(when (dom/left-mouse? event) (mf/deps shape)
(dom/stop-propagation event) (fn [event]
(st/emit! (dw/start-rotate [shape]))))] (when (dom/left-mouse? event)
(dom/stop-propagation event)
(st/emit! (dw/start-rotate [shape])))))]
[:& controls-handlers [:& controls-handlers
{:shape shape {:shape shape
@ -429,7 +482,8 @@
:on-resize on-resize}])) :on-resize on-resize}]))
(mf/defc single-selection (mf/defc single-selection
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}] {::mf/wrap-props false}
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu]}]
[:& controls-selection [:& controls-selection
{:shape shape {:shape shape
:zoom zoom :zoom zoom
@ -439,23 +493,25 @@
:on-context-menu on-context-menu}]) :on-context-menu on-context-menu}])
(mf/defc selection-area (mf/defc selection-area
{::mf/wrap [mf/memo]} {::mf/wrap-props false}
[{:keys [shapes edition zoom disable-handlers on-move-selected on-context-menu] :as props}] [{:keys [shapes edition zoom disable-handlers on-move-selected on-context-menu]}]
(let [num (count shapes) (let [total (count shapes)
{:keys [type] :as shape} (first shapes)
shape (first shapes)
shape-id (dm/get-prop shape :id)
;; Note that we don't use mf/deref to avoid a repaint dependency here ;; Note that we don't use mf/deref to avoid a repaint dependency here
objects (deref refs/workspace-page-objects) objects (deref refs/workspace-page-objects)
color (if (and (= num 1) color (if (and (= total 1) ^boolean (ctn/in-any-component? objects shape))
(ctn/in-any-component? objects shape)) selection-rect-color-component
selection-rect-color-component selection-rect-color-normal)]
selection-rect-color-normal)]
(cond (cond
(zero? num) (zero? total)
nil nil
(> num 1) (> total 1)
[:& multiple-selection [:& multiple-selection
{:shapes shapes {:shapes shapes
:zoom zoom :zoom zoom
@ -464,13 +520,14 @@
:on-move-selected on-move-selected :on-move-selected on-move-selected
:on-context-menu on-context-menu}] :on-context-menu on-context-menu}]
(and (= type :text) (= edition (:id shape))) (and (cfh/text-shape? shape)
(= edition shape-id))
[:& text-edition-selection [:& text-edition-selection
{:shape shape {:shape shape
:zoom zoom :zoom zoom
:color color}] :color color}]
(= edition (:id shape)) (= edition shape-id)
nil nil
:else :else
@ -483,23 +540,25 @@
:on-context-menu on-context-menu}]))) :on-context-menu on-context-menu}])))
(mf/defc selection-handlers (mf/defc selection-handlers
{::mf/wrap [mf/memo]} {::mf/wrap-props false}
[{:keys [shapes selected edition zoom disable-handlers] :as props}] [{:keys [shapes selected edition zoom disable-handlers]}]
(let [num (count shapes) (let [total (count shapes)
{:keys [type] :as shape} (first shapes)
shape (first shapes)
shape-id (dm/get-prop shape :id)
;; Note that we don't use mf/deref to avoid a repaint dependency here ;; Note that we don't use mf/deref to avoid a repaint dependency here
objects (deref refs/workspace-page-objects) objects (deref refs/workspace-page-objects)
color (if (and (= num 1) color (if (and (= total 1) ^boolean (ctn/in-any-component? objects shape))
(ctn/in-any-component? objects shape)) selection-rect-color-component
selection-rect-color-component selection-rect-color-normal)]
selection-rect-color-normal)]
(cond (cond
(zero? num) (zero? total)
nil nil
(> num 1) (> total 1)
[:& multiple-handlers [:& multiple-handlers
{:shapes shapes {:shapes shapes
:selected selected :selected selected
@ -507,10 +566,11 @@
:color color :color color
:disable-handlers disable-handlers}] :disable-handlers disable-handlers}]
(and (= type :text) (= edition (:id shape))) (and (cfh/text-shape? shape)
(= edition shape-id))
nil nil
(= edition (:id shape)) (= edition shape-id)
[:& path-editor [:& path-editor
{:zoom zoom {:zoom zoom
:shape shape}] :shape shape}]

View file

@ -6,7 +6,7 @@
(ns app.util.array (ns app.util.array
"A collection of helpers for work with javascript arrays." "A collection of helpers for work with javascript arrays."
(:refer-clojure :exclude [conj! conj])) (:refer-clojure :exclude [conj! conj filter]))
(defn conj (defn conj
"A conj like function for js arrays." "A conj like function for js arrays."
@ -15,6 +15,37 @@
(defn conj! (defn conj!
"A conj! like function for js arrays." "A conj! like function for js arrays."
[a v] ([a v]
(.push ^js a v) (.push ^js a v)
a) a)
([a v1 v2]
(.push ^js a v1 v2)
a)
([a v1 v2 v3]
(.push ^js a v1 v2 v3)
a)
([a v1 v2 v3 v4]
(.push ^js a v1 v2 v3 v4)
a)
([a v1 v2 v3 v4 v5]
(.push ^js a v1 v2 v3 v4 v5)
a)
([a v1 v2 v3 v4 v5 v6]
(.push ^js a v1 v2 v3 v4 v5 v6)
a))
(defn normalize-to-array
"If `o` is an array, returns it as-is, if not, wrap into an array."
[o]
(if (array? o)
o
#js [o]))
(defn without-nils
[^js/Array o]
(.filter o (fn [v] (some? v))))
(defn filter
"A specific filter for js arrays."
[pred ^js/Array o]
(.filter o pred))

View file

@ -20,12 +20,12 @@
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.http :as http] [app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.import.parser :as cip]
[app.util.json :as json] [app.util.json :as json]
[app.util.sse :as sse] [app.util.sse :as sse]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[app.util.zip :as uz] [app.util.zip :as uz]
[app.worker.impl :as impl] [app.worker.impl :as impl]
[app.worker.import.parser :as parser]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[tubax.core :as tubax])) [tubax.core :as tubax]))
@ -199,7 +199,8 @@
(rx/tap #(progress! context :upload-media name)) (rx/tap #(progress! context :upload-media name))
(rx/merge-map #(rp/cmd! :upload-file-media-object %)))) (rx/merge-map #(rp/cmd! :upload-file-media-object %))))
(defn resolve-text-content [node context] (defn resolve-text-content
[node context]
(let [resolve (:resolve context)] (let [resolve (:resolve context)]
(->> node (->> node
(ct/transform-nodes (ct/transform-nodes
@ -258,8 +259,8 @@
(defn process-import-node (defn process-import-node
[context file node] [context file node]
(let [type (cip/get-type node) (let [type (parser/get-type node)
close? (cip/close? node)] close? (parser/close? node)]
(if close? (if close?
(case type (case type
:frame (fb/close-artboard file) :frame (fb/close-artboard file)
@ -269,11 +270,11 @@
#_default file) #_default file)
(let [resolve (:resolve context) (let [resolve (:resolve context)
old-id (cip/get-id node) old-id (parser/get-id node)
interactions (->> (cip/parse-interactions node) interactions (->> (parser/parse-interactions node)
(mapv #(update % :destination resolve))) (mapv #(update % :destination resolve)))
data (-> (cip/parse-data type node) data (-> (parser/parse-data type node)
(resolve-data-ids type context) (resolve-data-ids type context)
(cond-> (some? old-id) (cond-> (some? old-id)
(assoc :id (resolve old-id))) (assoc :id (resolve old-id)))
@ -318,17 +319,17 @@
(defn resolve-media (defn resolve-media
[context file-id node] [context file-id node]
(if (or (and (not (cip/close? node)) (if (or (and (not (parser/close? node))
(cip/has-image? node)) (parser/has-image? node))
(cip/has-stroke-images? node) (parser/has-stroke-images? node)
(cip/has-fill-images? node)) (parser/has-fill-images? node))
(let [name (cip/get-image-name node) (let [name (parser/get-image-name node)
has-image (cip/has-image? node) has-image (parser/has-image? node)
image-data (cip/get-image-data node) image-data (parser/get-image-data node)
image-fill (cip/get-image-fill node) image-fill (parser/get-image-fill node)
fill-images-data (->> (cip/get-fill-images-data node) fill-images-data (->> (parser/get-fill-images-data node)
(map #(assoc % :type :fill))) (map #(assoc % :type :fill)))
stroke-images-data (->> (cip/get-stroke-images-data node) stroke-images-data (->> (parser/get-stroke-images-data node)
(map #(assoc % :type :stroke))) (map #(assoc % :type :stroke)))
images-data (concat images-data (concat
@ -366,18 +367,18 @@
(rx/observe-on :async)))) (rx/observe-on :async))))
(defn media-node? [node] (defn media-node? [node]
(or (and (cip/shape? node) (or (and (parser/shape? node)
(cip/has-image? node) (parser/has-image? node)
(not (cip/close? node))) (not (parser/close? node)))
(cip/has-stroke-images? node) (parser/has-stroke-images? node)
(cip/has-fill-images? node))) (parser/has-fill-images? node)))
(defn import-page (defn import-page
[context file [page-id page-name content]] [context file [page-id page-name content]]
(let [nodes (->> content cip/node-seq) (let [nodes (->> content parser/node-seq)
file-id (:id file) file-id (:id file)
resolve (:resolve context) resolve (:resolve context)
page-data (-> (cip/parse-page-data content) page-data (-> (parser/parse-page-data content)
(assoc :name page-name) (assoc :name page-name)
(assoc :id (resolve page-id))) (assoc :id (resolve page-id)))
flows (->> (get-in page-data [:options :flows]) flows (->> (get-in page-data [:options :flows])
@ -411,32 +412,32 @@
(rx/merge-map (rx/merge-map
(fn [pre-proc] (fn [pre-proc]
(->> (rx/from nodes) (->> (rx/from nodes)
(rx/filter cip/shape?) (rx/filter parser/shape?)
(rx/map (fn [node] (or (get pre-proc node) node))) (rx/map (fn [node] (or (get pre-proc node) node)))
(rx/reduce (partial process-import-node context) file) (rx/reduce (partial process-import-node context) file)
(rx/map (comp fb/close-page setup-interactions)))))))) (rx/map (comp fb/close-page setup-interactions))))))))
(defn import-component [context file node] (defn import-component [context file node]
(let [resolve (:resolve context) (let [resolve (:resolve context)
content (cip/find-node node :g) content (parser/find-node node :g)
file-id (:id file) file-id (:id file)
old-id (cip/get-id node) old-id (parser/get-id node)
id (resolve old-id) id (resolve old-id)
path (get-in node [:attrs :penpot:path] "") path (get-in node [:attrs :penpot:path] "")
type (cip/get-type content) type (parser/get-type content)
main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] ""))) main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] "")))
main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] ""))) main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] "")))
data (-> (cip/parse-data type content) data (-> (parser/parse-data type content)
(assoc :path path) (assoc :path path)
(assoc :id id) (assoc :id id)
(assoc :main-instance-id main-instance-id) (assoc :main-instance-id main-instance-id)
(assoc :main-instance-page main-instance-page)) (assoc :main-instance-page main-instance-page))
file (-> file (fb/start-component data type)) file (-> file (fb/start-component data type))
children (cip/node-seq node)] children (parser/node-seq node)]
(->> (rx/from children) (->> (rx/from children)
(rx/filter cip/shape?) (rx/filter parser/shape?)
(rx/skip 1) ;; Skip the outer component and the respective closint tag (rx/skip 1) ;; Skip the outer component and the respective closint tag
(rx/skip-last 1) ;; because they are handled in start-component an finish-component (rx/skip-last 1) ;; because they are handled in start-component an finish-component
(rx/mapcat (partial resolve-media context file-id)) (rx/mapcat (partial resolve-media context file-id))
@ -445,18 +446,18 @@
(defn import-deleted-component [context file node] (defn import-deleted-component [context file node]
(let [resolve (:resolve context) (let [resolve (:resolve context)
content (cip/find-node node :g) content (parser/find-node node :g)
file-id (:id file) file-id (:id file)
old-id (cip/get-id node) old-id (parser/get-id node)
id (resolve old-id) id (resolve old-id)
path (get-in node [:attrs :penpot:path] "") path (get-in node [:attrs :penpot:path] "")
main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] ""))) main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] "")))
main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] ""))) main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] "")))
main-instance-x (get-in node [:attrs :penpot:main-instance-x] "") main-instance-x (get-in node [:attrs :penpot:main-instance-x] "")
main-instance-y (get-in node [:attrs :penpot:main-instance-y] "") main-instance-y (get-in node [:attrs :penpot:main-instance-y] "")
type (cip/get-type content) type (parser/get-type content)
data (-> (cip/parse-data type content) data (-> (parser/parse-data type content)
(assoc :path path) (assoc :path path)
(assoc :id id) (assoc :id id)
(assoc :main-instance-id main-instance-id) (assoc :main-instance-id main-instance-id)
@ -466,10 +467,10 @@
file (-> file (fb/start-component data)) file (-> file (fb/start-component data))
component-id (:current-component-id file) component-id (:current-component-id file)
children (cip/node-seq node)] children (parser/node-seq node)]
(->> (rx/from children) (->> (rx/from children)
(rx/filter cip/shape?) (rx/filter parser/shape?)
(rx/skip 1) (rx/skip 1)
(rx/skip-last 1) (rx/skip-last 1)
(rx/mapcat (partial resolve-media context file-id)) (rx/mapcat (partial resolve-media context file-id))
@ -510,7 +511,7 @@
(assoc :id (resolve id)))] (assoc :id (resolve id)))]
(fb/add-library-color file color)))] (fb/add-library-color file color)))]
(->> (get-file context :colors) (->> (get-file context :colors)
(rx/merge-map (comp d/kebab-keys cip/string->uuid)) (rx/merge-map (comp d/kebab-keys parser/string->uuid))
(rx/reduce add-color file))) (rx/reduce add-color file)))
(rx/of file))) (rx/of file)))
@ -520,7 +521,7 @@
(if (:has-typographies context) (if (:has-typographies context)
(let [resolve (:resolve context)] (let [resolve (:resolve context)]
(->> (get-file context :typographies) (->> (get-file context :typographies)
(rx/merge-map (comp d/kebab-keys cip/string->uuid)) (rx/merge-map (comp d/kebab-keys parser/string->uuid))
(rx/map (fn [[id typography]] (rx/map (fn [[id typography]]
(-> typography (-> typography
(d/kebab-keys) (d/kebab-keys)
@ -534,7 +535,7 @@
(if (:has-media context) (if (:has-media context)
(let [resolve (:resolve context)] (let [resolve (:resolve context)]
(->> (get-file context :media-list) (->> (get-file context :media-list)
(rx/merge-map (comp d/kebab-keys cip/string->uuid)) (rx/merge-map (comp d/kebab-keys parser/string->uuid))
(rx/mapcat (rx/mapcat
(fn [[id media]] (fn [[id media]]
(let [media (assoc media :id (resolve id))] (let [media (assoc media :id (resolve id))]
@ -558,7 +559,7 @@
[context file] [context file]
(if (:has-components context) (if (:has-components context)
(let [split-components (let [split-components
(fn [content] (->> (cip/node-seq content) (fn [content] (->> (parser/node-seq content)
(filter #(= :symbol (:tag %)))))] (filter #(= :symbol (:tag %)))))]
(->> (get-file context :components) (->> (get-file context :components)
@ -570,7 +571,7 @@
[context file] [context file]
(if (:has-deleted-components context) (if (:has-deleted-components context)
(let [split-components (let [split-components
(fn [content] (->> (cip/node-seq content) (fn [content] (->> (parser/node-seq content)
(filter #(= :symbol (:tag %)))))] (filter #(= :symbol (:tag %)))))]
(->> (get-file context :deleted-components) (->> (get-file context :deleted-components)
@ -644,7 +645,7 @@
(rx/filter (fn [data] (= "application/zip" (:type data)))) (rx/filter (fn [data] (= "application/zip" (:type data))))
(rx/merge-map #(zip/loadAsync (:body %))) (rx/merge-map #(zip/loadAsync (:body %)))
(rx/merge-map #(get-file {:zip %} :manifest)) (rx/merge-map #(get-file {:zip %} :manifest))
(rx/map (comp d/kebab-keys cip/string->uuid)) (rx/map (comp d/kebab-keys parser/string->uuid))
(rx/map #(hash-map :uri (:uri file) :data % :type "application/zip"))) (rx/map #(hash-map :uri (:uri file) :data % :type "application/zip")))
(->> st (->> st
(rx/filter (fn [data] (= "application/octet-stream" (:type data)))) (rx/filter (fn [data] (= "application/octet-stream" (:type data))))

View file

@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.util.import.parser (ns app.worker.import.parser
(:require (:require
[app.common.colors :as cc] [app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]

File diff suppressed because it is too large Load diff