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/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_hotspot {:mvn/version "0.16.0"}
@ -17,7 +17,7 @@
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"}
funcool/yetti
@ -29,9 +29,9 @@
com.github.seancorfield/next.jdbc {:mvn/version "1.3.909"}
metosin/reitit-core {:mvn/version "0.6.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"}
@ -42,7 +42,7 @@
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
{:git/tag "1.4.0-penpot-2"
:git/sha "e2b3e16"
@ -57,7 +57,7 @@
;; Pretty Print specs
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"]

View file

@ -1,18 +1,18 @@
{:deps
{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/clojurescript {:mvn/version "1.11.60"}
org.clojure/test.check {:mvn/version "1.1.1"}
org.clojure/data.fressian {:mvn/version "1.0.0"}
;; Logging
org.apache.logging.log4j/log4j-api {:mvn/version "2.21.1"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.21.1"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.21.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.21.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.21.1"}
org.slf4j/slf4j-api {:mvn/version "2.0.9"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.22.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.22.1"}
org.slf4j/slf4j-api {:mvn/version "2.0.10"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.32"}
selmer/selmer {:mvn/version "1.12.59"}
@ -31,7 +31,7 @@
org.graalvm.js/js {:mvn/version "23.0.2"}
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"
:git/url "https://github.com/funcool/promesa"}
@ -48,9 +48,10 @@
;; exception printing
fipp/fipp {:mvn/version "0.6.26"}
io.github.eerohele/pp
{:git/tag "2023-11-25.47"
:git/sha "15d572c"}
{:git/tag "2024-01-04.60"
:git/sha "e8a9773"}
io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}}

View file

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

View file

@ -608,12 +608,18 @@
(contains? svg-attr-list k)
(contains? svg-present-list k))
(cond
(nil? v)
res
(= k :class)
(assoc res :className v)
(= k :style)
(let [v (if (string? v) (parse-style v) v)]
(assoc res k (attrs->props v false)))
(let [v (if (string? v) (parse-style v) v)
v (not-empty (attrs->props v false))]
(if v
(assoc res k v)
res))
:else
(let [k (if (contains? non-react-props k)
@ -624,68 +630,6 @@
{}
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
"Replaces the ids inside a property"
[attrs replace-fn]
@ -723,7 +667,8 @@
(reduce visit-node result (:content node))))]
(visit-node {} content)))
(defn extract-defs [{:keys [attrs] :as node}]
(defn extract-defs
[{:keys [attrs] :as node}]
(if-not (map? node)
[{} node]
@ -741,19 +686,22 @@
[node-defs node])))
(defn find-attr-references [attrs]
(defn find-attr-references
[attrs]
(->> attrs
(mapcat (fn [[_ attr-value]]
(if (string? attr-value)
(extract-ids 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 #{}))
children (->> (:content node) (map find-node-references) (flatten) (into #{}))]
(vec (into current children))))
(defn find-def-references [defs references]
(defn find-def-references
[defs references]
(loop [result (into #{} references)
checked? #{}
to-check (first references)
@ -778,7 +726,8 @@
(first pending)
(rest pending))))))
(defn svg-transform-matrix [shape]
(defn svg-transform-matrix
[shape]
(if (:svg-viewbox shape)
(let [{svg-x :x
svg-y :y
@ -810,34 +759,39 @@
;; Transforms spec:
;; https://www.w3.org/TR/SVG11/single-page.html#coords-TransformAttribute
(defn format-translate-params [params]
(defn format-translate-params
[params]
(assert (or (= (count params) 1) (= (count params) 2)))
(if (= (count params) 1)
[(gpt/point (nth params 0) 0)]
[(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)))
(if (= (count params) 1)
[(gpt/point (nth params 0))]
[(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)))
(if (= (count params) 1)
[(nth params 0) (gpt/point 0 0)]
[(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))
[(nth params 0) 0])
(defn format-skew-y-params [params]
(defn format-skew-y-params
[params]
(assert (= (count params) 1))
[0 (nth params 0)])
(defn to-matrix [{:keys [type params]}]
(defn to-matrix
[{:keys [type params]}]
(assert (#{"matrix" "translate" "scale" "rotate" "skewX" "skewY"} type))
(case type
"matrix" (apply gmt/matrix params)
@ -847,7 +801,8 @@
"skewX" (apply gmt/skew-matrix (format-skew-x-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
(let [process-matrix
(fn [[_ type params]]
@ -865,7 +820,8 @@
(defn format-move [[x y]] (str "M" 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
(re-seq number-regex)
(filter (comp not empty? first))

View file

@ -916,11 +916,21 @@ function simplifyPathData(pdata) {
export function parse(string) {
if (!string || string.length === 0) return [];
var source = new Parser(string);
var result = Array.from(source);
try {
var source = new Parser(string);
var result = Array.from(source);
result = absolutizePathData(result);
result = simplifyPathData(result);
result = absolutizePathData(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
(let [attrs {:class "foobar"}
result (svg/clean-attrs attrs)]
result (svg/attrs->props attrs)]
(t/is (= result {:className "foobar"}))))
(t/deftest clean-attrs-2
(let [attrs {:overline-position "top"
:style {:fill "none"
: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/deftest clean-attrs-3
(let [attrs {:overline-position "top"
:style (str "fill:#00801b;fill-opacity:1;stroke:none;stroke-width:2749.72;"
"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",
:style {:fill "#00801b",
:fillOpacity "1",

View file

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

View file

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

View file

@ -5,7 +5,7 @@
org.clojure/clojure {:mvn/version "1.11.1"}
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/potok2
@ -19,8 +19,8 @@
:git/url "https://github.com/funcool/beicon.git"}
funcool/rumext
{:git/tag "v2.8.1"
:git/sha "168738b"
{:git/tag "v2.9.2"
:git/sha "faa6e6c"
:git/url "https://github.com/funcool/rumext.git"}
instaparse/instaparse {:mvn/version "1.4.12"}
@ -43,7 +43,7 @@
:extra-deps
{thheller/shadow-cljs {:mvn/version "2.26.2"}
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
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}

View file

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

View file

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

View file

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

View file

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

View file

@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.icons :as i]
[app.util.array :as array]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
@ -17,41 +18,32 @@
(mf/defc tab-element
{::mf/wrap-props false}
[props]
(let [children (unchecked-get props "children")]
[:div {:class (stl/css :tab-element)}
children]))
[{:keys [children]}]
[:div {:class (stl/css :tab-element)} children])
(mf/defc tab-container
{::mf/wrap-props false}
[props]
(let [children (->>
(unchecked-get props "children")
(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")
[{:keys [children selected on-change-tab collapsable handle-collapse header-class content-class]}]
(let [children (-> (array/normalize-to-array children)
(array/without-nils))
state (mf/use-state #(or selected (-> children first .-props .-id)))
selected (or selected @state)
selected* (mf/use-state #(or selected (-> children first .-props .-id)))
selected (or selected @selected*)
select-fn
(mf/use-fn
(mf/deps on-change)
(fn [event]
(let [id (-> event
(dom/get-current-target)
(dom/get-data "id")
(keyword))]
(reset! state id)
(when (fn? on-change) (on-change id)))))]
on-click (mf/use-fn
(mf/deps on-change-tab)
(fn [event]
(let [id (-> event
(dom/get-current-target)
(dom/get-data "id")
(keyword))]
(reset! selected* id)
(when (fn? on-change-tab)
(on-change-tab id)))))]
[:div {:class (stl/css :tab-container)}
[:div {:class (dm/str class " " (stl/css :tab-container-tabs))}
(when collapsable?
[:div {:class (dm/str header-class " " (stl/css :tab-container-tabs))}
(when ^boolean collapsable
[:button
{:on-click handle-collapse
:class (stl/css :collapse-sidebar)
@ -61,13 +53,16 @@
(for [tab children]
(let [props (.-props tab)
id (.-id props)
title (.-title props)]
[:div
{:key (str/concat "tab-" (d/name id))
:data-id (d/name id)
:on-click select-fn
:class (stl/css-case :tab-container-tab-title true
:current (= selected id))}
title (.-title props)
sid (d/name id)]
[:div {:key (str/concat "tab-" sid)
:data-id sid
:on-click on-click
:class (stl/css-case
:tab-container-tab-title true
:current (= selected id))}
title]))]]
[: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
(map? node)
[:> (d/name tag) (clj->js (csvg/clean-attrs attrs))
[:> (d/name tag) (obj/map->obj (csvg/attrs->props attrs))
(for [child content]
[:& 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.bounds :as gsb]
[app.common.svg :as csvg]
[app.util.object :as obj]
[rumext.v2 :as mf]))
(defn add-matrix [attrs transform-key transform-matrix]
@ -23,7 +24,9 @@
(str transform-matrix " " val)
(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
(string? node) node
@ -48,7 +51,7 @@
attrs
(-> attrs
(csvg/update-attr-ids prefix-id)
(csvg/clean-attrs)
(csvg/attrs->props)
;; 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?
@ -80,7 +83,7 @@
:transform (str transform)}]
[mf/Fragment #js {}])]
[:> (name tag) (clj->js attrs)
[:> (name tag) (obj/map->obj attrs)
[:> wrapper wrapper-props
(for [[index node] (d/enumerate content)]
[:& svg-node {:key (dm/str "node-" index)

View file

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

View file

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

View file

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

View file

@ -83,10 +83,9 @@
[:& tab-container
{:on-change-tab on-tab-change
:selected section
:shortcuts? shortcuts?
:collapsable? true
:collapsable true
:handle-collapse handle-collapse
:class (stl/css :tab-spacing)}
:header-class (stl/css :tab-spacing)}
[:& tab-element {:id :layers :title (tr "workspace.sidebar.layers")}
[:div {:class (stl/css :layers-tab)
: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.context :as ctx]
[app.main.ui.icons :as i]
[app.util.array :as array]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
[app.util.i18n :as i18n :refer [tr]]
@ -120,7 +121,8 @@
:workspace? true}])
(mf/defc section-icon
[{:keys [section] :as props}]
{::mf/wrap-props false}
[{:keys [section]}]
(case section
:colors i/drop-refactor
:components i/component-refactor
@ -130,28 +132,42 @@
(mf/defc asset-section
{::mf/wrap-props false}
[{:keys [children file-id title section assets-count open?]}]
(let [children (->> (if (array? children) children [children])
(filter some?))
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]
(let [children (-> (array/normalize-to-array children)
(array/without-nils))
[:span {:class (stl/css :num-assets)}
assets-count]])}
title-buttons]
(when ^boolean open?
content)]))
is-button? #(= :title-button (.. % -props -role))
is-content? #(= :content (.. % -props -role))
buttons (array/filter is-button? children)
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/wrap-props false}

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
(ns app.util.array
"A collection of helpers for work with javascript arrays."
(:refer-clojure :exclude [conj! conj]))
(:refer-clojure :exclude [conj! conj filter]))
(defn conj
"A conj like function for js arrays."
@ -15,6 +15,37 @@
(defn conj!
"A conj! like function for js arrays."
[a v]
(.push ^js a v)
a)
([a v]
(.push ^js a v)
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.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.import.parser :as cip]
[app.util.json :as json]
[app.util.sse :as sse]
[app.util.webapi :as wapi]
[app.util.zip :as uz]
[app.worker.impl :as impl]
[app.worker.import.parser :as parser]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[tubax.core :as tubax]))
@ -199,7 +199,8 @@
(rx/tap #(progress! context :upload-media name))
(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)]
(->> node
(ct/transform-nodes
@ -258,8 +259,8 @@
(defn process-import-node
[context file node]
(let [type (cip/get-type node)
close? (cip/close? node)]
(let [type (parser/get-type node)
close? (parser/close? node)]
(if close?
(case type
:frame (fb/close-artboard file)
@ -269,11 +270,11 @@
#_default file)
(let [resolve (:resolve context)
old-id (cip/get-id node)
interactions (->> (cip/parse-interactions node)
old-id (parser/get-id node)
interactions (->> (parser/parse-interactions node)
(mapv #(update % :destination resolve)))
data (-> (cip/parse-data type node)
data (-> (parser/parse-data type node)
(resolve-data-ids type context)
(cond-> (some? old-id)
(assoc :id (resolve old-id)))
@ -318,17 +319,17 @@
(defn resolve-media
[context file-id node]
(if (or (and (not (cip/close? node))
(cip/has-image? node))
(cip/has-stroke-images? node)
(cip/has-fill-images? node))
(let [name (cip/get-image-name node)
has-image (cip/has-image? node)
image-data (cip/get-image-data node)
image-fill (cip/get-image-fill node)
fill-images-data (->> (cip/get-fill-images-data node)
(if (or (and (not (parser/close? node))
(parser/has-image? node))
(parser/has-stroke-images? node)
(parser/has-fill-images? node))
(let [name (parser/get-image-name node)
has-image (parser/has-image? node)
image-data (parser/get-image-data node)
image-fill (parser/get-image-fill node)
fill-images-data (->> (parser/get-fill-images-data node)
(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)))
images-data (concat
@ -366,18 +367,18 @@
(rx/observe-on :async))))
(defn media-node? [node]
(or (and (cip/shape? node)
(cip/has-image? node)
(not (cip/close? node)))
(cip/has-stroke-images? node)
(cip/has-fill-images? node)))
(or (and (parser/shape? node)
(parser/has-image? node)
(not (parser/close? node)))
(parser/has-stroke-images? node)
(parser/has-fill-images? node)))
(defn import-page
[context file [page-id page-name content]]
(let [nodes (->> content cip/node-seq)
(let [nodes (->> content parser/node-seq)
file-id (:id file)
resolve (:resolve context)
page-data (-> (cip/parse-page-data content)
page-data (-> (parser/parse-page-data content)
(assoc :name page-name)
(assoc :id (resolve page-id)))
flows (->> (get-in page-data [:options :flows])
@ -411,32 +412,32 @@
(rx/merge-map
(fn [pre-proc]
(->> (rx/from nodes)
(rx/filter cip/shape?)
(rx/filter parser/shape?)
(rx/map (fn [node] (or (get pre-proc node) node)))
(rx/reduce (partial process-import-node context) file)
(rx/map (comp fb/close-page setup-interactions))))))))
(defn import-component [context file node]
(let [resolve (:resolve context)
content (cip/find-node node :g)
content (parser/find-node node :g)
file-id (:id file)
old-id (cip/get-id node)
old-id (parser/get-id node)
id (resolve old-id)
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-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 :id id)
(assoc :main-instance-id main-instance-id)
(assoc :main-instance-page main-instance-page))
file (-> file (fb/start-component data type))
children (cip/node-seq node)]
children (parser/node-seq node)]
(->> (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-last 1) ;; because they are handled in start-component an finish-component
(rx/mapcat (partial resolve-media context file-id))
@ -445,18 +446,18 @@
(defn import-deleted-component [context file node]
(let [resolve (:resolve context)
content (cip/find-node node :g)
content (parser/find-node node :g)
file-id (:id file)
old-id (cip/get-id node)
old-id (parser/get-id node)
id (resolve old-id)
path (get-in node [:attrs :penpot:path] "")
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-x (get-in node [:attrs :penpot:main-instance-x] "")
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 :id id)
(assoc :main-instance-id main-instance-id)
@ -466,10 +467,10 @@
file (-> file (fb/start-component data))
component-id (:current-component-id file)
children (cip/node-seq node)]
children (parser/node-seq node)]
(->> (rx/from children)
(rx/filter cip/shape?)
(rx/filter parser/shape?)
(rx/skip 1)
(rx/skip-last 1)
(rx/mapcat (partial resolve-media context file-id))
@ -510,7 +511,7 @@
(assoc :id (resolve id)))]
(fb/add-library-color file color)))]
(->> (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/of file)))
@ -520,7 +521,7 @@
(if (:has-typographies context)
(let [resolve (:resolve context)]
(->> (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]]
(-> typography
(d/kebab-keys)
@ -534,7 +535,7 @@
(if (:has-media context)
(let [resolve (:resolve context)]
(->> (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
(fn [[id media]]
(let [media (assoc media :id (resolve id))]
@ -558,7 +559,7 @@
[context file]
(if (:has-components context)
(let [split-components
(fn [content] (->> (cip/node-seq content)
(fn [content] (->> (parser/node-seq content)
(filter #(= :symbol (:tag %)))))]
(->> (get-file context :components)
@ -570,7 +571,7 @@
[context file]
(if (:has-deleted-components context)
(let [split-components
(fn [content] (->> (cip/node-seq content)
(fn [content] (->> (parser/node-seq content)
(filter #(= :symbol (:tag %)))))]
(->> (get-file context :deleted-components)
@ -644,7 +645,7 @@
(rx/filter (fn [data] (= "application/zip" (:type data))))
(rx/merge-map #(zip/loadAsync (:body %)))
(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")))
(->> st
(rx/filter (fn [data] (= "application/octet-stream" (:type data))))

View file

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

File diff suppressed because it is too large Load diff