mirror of
https://github.com/penpot/penpot.git
synced 2026-05-02 22:58:35 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
470cf08134
@ -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"]
|
||||
|
||||
@ -255,6 +255,7 @@
|
||||
(assoc shape
|
||||
:type :frame ; Old groups must be converted
|
||||
:fills (or (:fills shape) []) ; to frames and conform to spec
|
||||
:shapes (or (:shapes shape) [])
|
||||
:hide-in-viewer (or (:hide-in-viewer shape) true)
|
||||
:rx (or (:rx shape) 0)
|
||||
:ry (or (:ry shape) 0))
|
||||
|
||||
@ -1030,7 +1030,8 @@
|
||||
(defn ignore-sync
|
||||
[conn {:keys [file-id date] :as params}]
|
||||
(db/update! conn :file
|
||||
{:ignore-sync-until date}
|
||||
{:ignore-sync-until date
|
||||
:modified-at (dt/now)}
|
||||
{:id file-id}
|
||||
{::db/return-keys true}))
|
||||
|
||||
|
||||
@ -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"}}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -32,13 +32,17 @@
|
||||
([objects id]
|
||||
(is-direct-child-of-root? (get objects id)))
|
||||
([shape]
|
||||
(and (some? shape) (= (dm/get-prop shape :frame-id) uuid/zero))))
|
||||
(and (some? shape)
|
||||
(= (dm/get-prop shape :frame-id) uuid/zero))))
|
||||
|
||||
(defn root-frame?
|
||||
([objects id]
|
||||
(root-frame? (get objects id)))
|
||||
(if (= id uuid/zero)
|
||||
false
|
||||
(root-frame? (get objects id))))
|
||||
([shape]
|
||||
(and (some? shape)
|
||||
(not= (dm/get-prop shape :id) uuid/zero)
|
||||
(= (dm/get-prop shape :type) :frame)
|
||||
(= (dm/get-prop shape :frame-id) uuid/zero))))
|
||||
|
||||
|
||||
@ -502,6 +502,3 @@
|
||||
:hint "error on validating file referential integrity"
|
||||
:file-id (:id file)
|
||||
:details errors)))
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1111,7 +1111,6 @@
|
||||
|
||||
(defn assign-cell-positions
|
||||
[parent objects]
|
||||
(prn ">>>>assign-cell-positions" (:name parent))
|
||||
(-> parent
|
||||
(check-deassigned-cells objects)
|
||||
(reassign-positions)
|
||||
@ -1128,7 +1127,6 @@
|
||||
;; - (maybe) create group/frames. This case will assigna a cell that had one of its children
|
||||
(defn assign-cells
|
||||
[parent objects]
|
||||
(prn ">assign-cells")
|
||||
(let [parent (assign-cell-positions parent objects)
|
||||
|
||||
shape-has-cell?
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"]}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -1 +0,0 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="12" xmlns="http://www.w3.org/2000/svg" height="12" id="screenshot-c8a53282-38ff-8005-8002-95490755d8cd" viewBox="0 0 12 12" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g id="shape-c8a53282-38ff-8005-8002-95490755d8cd"><defs><clipPath class="frame-clip-def frame-clip" id="frame-clip-c8a53282-38ff-8005-8002-95490755d8cd-rumext-id-1"><rect rx="0" ry="0" x="0" y="0" width="12" height="12" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g clip-path="url(#frame-clip-c8a53282-38ff-8005-8002-95490755d8cd-rumext-id-1)"><clipPath class="frame-clip-def frame-clip" id="frame-clip-c8a53282-38ff-8005-8002-95490755d8cd-rumext-id-1"><rect rx="0" ry="0" x="0" y="0" width="12" height="12" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath><g class="fills" id="fills-c8a53282-38ff-8005-8002-95490755d8cd"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="12" height="12" class="frame-background"/></g><g class="frame-children"><g id="shape-c8a53282-38ff-8005-8002-95490755d8d0"><g class="fills" id="fills-c8a53282-38ff-8005-8002-95490755d8d0"><path rx="0" ry="0" d="M2.000,10.000L10.000,2.000M2.000,2.000L10.000,2.000L10.000,10.000" style="fill: rgb(0, 0, 0);"/></g><g id="strokes-c8a53282-38ff-8005-8002-95490755d8d0" class="strokes"><g class="stroke-shape"><path rx="0" ry="0" d="M2.000,10.000L10.000,2.000M2.000,2.000L10.000,2.000L10.000,10.000" style="fill: none; stroke-width: 1; stroke: rgb(143, 157, 163); stroke-opacity: 1;"/></g></g></g></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@ -38,10 +38,12 @@
|
||||
@include buttonStyle;
|
||||
@include flexCenter;
|
||||
@include focusPrimary;
|
||||
@include tabTitleTipography;
|
||||
background-color: var(--button-primary-background-color-rest);
|
||||
border: $s-1 solid var(--button-primary-border-color-rest);
|
||||
color: var(--button-primary-foreground-color-rest);
|
||||
border-radius: $br-8;
|
||||
min-height: $s-32;
|
||||
svg,
|
||||
span svg {
|
||||
stroke: var(--button-primary-foreground-color-rest);
|
||||
@ -54,6 +56,7 @@
|
||||
span svg {
|
||||
stroke: var(--button-primary-foreground-color-hover);
|
||||
}
|
||||
text-decoration: none;
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--button-primary-background-color-active);
|
||||
@ -600,6 +603,22 @@
|
||||
color: var(--modal-button-foreground-color-error);
|
||||
}
|
||||
|
||||
.loader-base {
|
||||
@include flexCenter;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
z-index: $z-index-alert;
|
||||
background-color: var(--loader-background);
|
||||
:global(svg#loader-pencil) {
|
||||
height: $s-100;
|
||||
width: $s-100;
|
||||
animation: loaderColor 5s infinite ease;
|
||||
fill: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
// UI ELEMENTS
|
||||
.asset-element {
|
||||
@include titleTipography;
|
||||
|
||||
@ -3,14 +3,33 @@
|
||||
flex-direction: column;
|
||||
overflow: scroll;
|
||||
height: 100%;
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
width: 100vw;
|
||||
margin: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.debug-icons-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
h2 {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
width: 100vw;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.subtitle-old {
|
||||
color: #ff3277;
|
||||
}
|
||||
|
||||
.icon-item,
|
||||
.cursor-item {
|
||||
.cursor-item,
|
||||
.icon-item-old {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -24,12 +43,28 @@
|
||||
height: 100%;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
fill: black;
|
||||
fill: none;
|
||||
color: transparent;
|
||||
stroke: black;
|
||||
stroke: #91fadb;
|
||||
}
|
||||
|
||||
span {
|
||||
color: white;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
word-break: break-word;
|
||||
min-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.cursor-item div,
|
||||
.icon-item-old svg {
|
||||
fill: #aab5ba;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.cursor-item {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@ -2310,6 +2310,7 @@
|
||||
|
||||
;; Transform
|
||||
|
||||
(dm/export dwt/trigger-bounding-box-cloaking)
|
||||
(dm/export dwt/start-resize)
|
||||
(dm/export dwt/update-dimensions)
|
||||
(dm/export dwt/change-orientation)
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
@ -825,12 +826,11 @@
|
||||
second)
|
||||
0)))))
|
||||
|
||||
(defn- component-swap
|
||||
"Swaps a component with another one"
|
||||
[shape file-id id-new-component]
|
||||
(defn- add-component-for-swap
|
||||
[shape file-id id-new-component target-cell]
|
||||
(dm/assert! (uuid? id-new-component))
|
||||
(dm/assert! (uuid? file-id))
|
||||
(ptk/reify ::component-swap
|
||||
(ptk/reify ::add-component-for-swap
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (wsh/lookup-page state)
|
||||
@ -841,6 +841,7 @@
|
||||
position (gpt/point (:x shape) (:y shape))
|
||||
changes (-> (pcb/empty-changes it (:id page))
|
||||
(pcb/with-objects objects))
|
||||
position (-> position (with-meta {:cell target-cell}))
|
||||
|
||||
[new-shape changes]
|
||||
(dwlh/generate-instantiate-component changes
|
||||
@ -856,12 +857,30 @@
|
||||
|
||||
;; We need to set the same index as the original shape
|
||||
changes (pcb/change-parent changes (:parent-id shape) [new-shape] index {:component-swap true})]
|
||||
|
||||
;; First delete so we don't break the grid layout cells
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update [(:id new-shape)])
|
||||
(dws/select-shape (:id new-shape) true)
|
||||
(dwsh/delete-shapes nil (d/ordered-set (:id shape)) {:component-swap true}))))))
|
||||
(dws/select-shape (:id new-shape) true))))))
|
||||
|
||||
(defn- component-swap
|
||||
"Swaps a component with another one"
|
||||
[shape file-id id-new-component]
|
||||
(dm/assert! (uuid? id-new-component))
|
||||
(dm/assert! (uuid? file-id))
|
||||
(ptk/reify ::component-swap
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
;; First delete shapes so we have space in the layout otherwise we can have problems
|
||||
;; in the grid creating new rows/columns to make space
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
parent (get objects (:parent-id shape))
|
||||
|
||||
;; If the target parent is a grid layout we need to pass the target cell
|
||||
target-cell (when (ctl/grid-layout? parent)
|
||||
(ctl/get-cell-by-shape-id parent (:id shape)))]
|
||||
(rx/of (dwsh/delete-shapes nil (d/ordered-set (:id shape)) {:component-swap true})
|
||||
(add-component-for-swap shape file-id id-new-component target-cell)
|
||||
(ptk/data-event :layout/update [(:parent-id shape)]))))))
|
||||
|
||||
(defn component-multi-swap
|
||||
"Swaps several components with another one"
|
||||
@ -1049,8 +1068,10 @@
|
||||
:links [{:label (tr "workspace.updates.more-info")
|
||||
:callback do-more-info}]
|
||||
:actions [{:label (tr "workspace.updates.update")
|
||||
:type :primary
|
||||
:callback do-update}
|
||||
{:label (tr "workspace.updates.dismiss")
|
||||
:type :secondary
|
||||
:callback do-dismiss}]
|
||||
:tag :sync-dialog)))))))
|
||||
|
||||
|
||||
@ -177,7 +177,11 @@
|
||||
|
||||
changes
|
||||
(if (ctl/grid-layout? objects (:parent-id first-shape))
|
||||
(let [[row column] (gslg/get-drop-cell (:parent-id first-shape) objects position)]
|
||||
(let [target-cell (-> position meta :cell)
|
||||
[row column]
|
||||
(if (some? target-cell)
|
||||
[(:row target-cell) (:column target-cell)]
|
||||
(gslg/get-drop-cell (:parent-id first-shape) objects position))]
|
||||
(-> changes
|
||||
(pcb/update-shapes
|
||||
[(:parent-id first-shape)]
|
||||
|
||||
@ -370,7 +370,7 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn update-shape-flags
|
||||
[ids {:keys [blocked hidden] :as flags}]
|
||||
[ids {:keys [blocked hidden transforming] :as flags}]
|
||||
(dm/assert!
|
||||
"expected valid coll of uuids"
|
||||
(every? uuid? ids))
|
||||
@ -386,14 +386,15 @@
|
||||
(fn [obj]
|
||||
(cond-> obj
|
||||
(boolean? blocked) (assoc :blocked blocked)
|
||||
(boolean? hidden) (assoc :hidden hidden)))
|
||||
(boolean? hidden) (assoc :hidden hidden)
|
||||
(boolean? transforming) (assoc :transforming transforming)))
|
||||
objects (wsh/lookup-page-objects state)
|
||||
;; We have change only the hidden behaviour, to hide only the
|
||||
;; selected shape, block behaviour remains the same.
|
||||
ids (if (boolean? blocked)
|
||||
(into ids (->> ids (mapcat #(cfh/get-children-ids objects %))))
|
||||
ids)]
|
||||
(rx/of (dch/update-shapes ids update-fn {:attrs #{:blocked :hidden}}))))))
|
||||
(rx/of (dch/update-shapes ids update-fn {:attrs #{:blocked :hidden :transforming}}))))))
|
||||
|
||||
(defn toggle-visibility-selected
|
||||
[]
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
[app.main.data.workspace.collapse :as dwc]
|
||||
[app.main.data.workspace.modifiers :as dwm]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.snap :as snap]
|
||||
@ -44,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.
|
||||
@ -63,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
|
||||
@ -103,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)
|
||||
@ -128,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
|
||||
@ -207,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]
|
||||
@ -217,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
|
||||
@ -233,13 +273,33 @@
|
||||
(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))
|
||||
(rx/of (dwm/apply-modifiers)
|
||||
(finish-transform))))))))
|
||||
|
||||
(defn trigger-bounding-box-cloaking
|
||||
"Trigger the bounding box cloaking (with default timer of 1sec)
|
||||
|
||||
Used to hide bounding-box of shape after changes in sidebar->measures."
|
||||
[ids]
|
||||
(dm/assert!
|
||||
"expected valid coll of uuids"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::trigger-bounding-box-cloaking
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(rx/concat
|
||||
(rx/of (dwsh/update-shape-flags ids {:transforming true}))
|
||||
(->> (rx/timer 1000)
|
||||
(rx/map (fn []
|
||||
(dwsh/update-shape-flags ids {:transforming false})))
|
||||
(rx/take-until
|
||||
(rx/filter (ptk/type? ::trigger-bounding-box-cloaking) stream)))))))
|
||||
|
||||
(defn update-dimensions
|
||||
"Change size of shapes, from the sideber options form.
|
||||
Will ignore pixel snap used in the options side panel"
|
||||
|
||||
@ -211,7 +211,7 @@
|
||||
|
||||
;; Font is currently downloading. We attach the caller to the promise
|
||||
(contains? @loading font-id)
|
||||
(p/resolved (get @loading font-id))
|
||||
(get @loading font-id)
|
||||
|
||||
;; First caller, we create the promise and then wait
|
||||
:else
|
||||
@ -220,13 +220,13 @@
|
||||
(swap! loading dissoc font-id)
|
||||
(resolve font-id))
|
||||
|
||||
load-p (->> (p/create
|
||||
(fn [resolve _]
|
||||
(-> font
|
||||
(assoc ::on-loaded (partial on-load resolve))
|
||||
(load-font))))
|
||||
load-p (-> (p/create
|
||||
(fn [resolve _]
|
||||
(-> font
|
||||
(assoc ::on-loaded (partial on-load resolve))
|
||||
(load-font))))
|
||||
;; We need to wait for the font to be loaded
|
||||
(p/delay 120))]
|
||||
(p/then (partial p/delay 120)))]
|
||||
|
||||
(swap! loading assoc font-id load-p)
|
||||
load-p))))))
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -5,3 +5,7 @@
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "./common.scss";
|
||||
|
||||
.verify-token {
|
||||
@extend .loader-base;
|
||||
}
|
||||
|
||||
@ -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]))
|
||||
|
||||
@ -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)]]))
|
||||
|
||||
@ -13,23 +13,23 @@
|
||||
|
||||
(mf/defc title-bar
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [collapsable? collapsed? on-collapsed title children on-btn-click btn-children class clickable-all?]}]
|
||||
[{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable]}]
|
||||
(let [klass (dm/str (stl/css :title-bar) " " class)]
|
||||
[:div {:class klass}
|
||||
(if collapsable?
|
||||
(if ^boolean collapsable
|
||||
[:div {:class (stl/css :title-wrapper)}
|
||||
(if clickable-all?
|
||||
(if ^boolean all-clickable
|
||||
[:button {:class (stl/css :toggle-btn)
|
||||
:on-click on-collapsed}
|
||||
[:span {:class (stl/css-case
|
||||
:collapsabled-icon true
|
||||
:rotated collapsed?)}
|
||||
:rotated collapsed)}
|
||||
i/arrow-refactor]
|
||||
[:div {:class (stl/css :title)} title]]
|
||||
[:*
|
||||
[:button {:class (stl/css-case
|
||||
:collapsabled-icon true
|
||||
:rotated collapsed?)
|
||||
:rotated collapsed)
|
||||
:on-click on-collapsed}
|
||||
i/arrow-refactor]
|
||||
[:div {:class (stl/css :title)} title]])]
|
||||
|
||||
@ -102,26 +102,26 @@
|
||||
[:h3 "Titles"]
|
||||
[:& component-wrapper
|
||||
{:title "Title"}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title "Title"}]]
|
||||
[:& title-bar {:collapsable false
|
||||
:title "Title"}]]
|
||||
[:& component-wrapper
|
||||
{:title "Title and action button"}
|
||||
[:& title-bar {:collapsable? false
|
||||
[:& title-bar {:collapsable false
|
||||
:title "Title"
|
||||
:on-btn-click on-btn-click
|
||||
:btn-children i/add-refactor}]]
|
||||
[:& component-wrapper
|
||||
{:title "Collapsed title and action button"}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? collapsed?
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed collapsed?
|
||||
:on-collapsed toggle-collapsed
|
||||
:title "Title"
|
||||
:on-btn-click on-btn-click
|
||||
:btn-children i/add-refactor}]]
|
||||
[:& component-wrapper
|
||||
{:title "Collapsed title and children"}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? collapsed?
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed collapsed?
|
||||
:on-collapsed toggle-collapsed
|
||||
:title "Title"}
|
||||
[:& tab-container {:on-change-tab set-tab
|
||||
|
||||
@ -400,7 +400,6 @@
|
||||
(def ^:icon msg-warning-refactor (icon-xref :msg-warning-refactor))
|
||||
(def ^:icon move-refactor (icon-xref :move-refactor))
|
||||
(def ^:icon open-link-refactor (icon-xref :open-link-refactor))
|
||||
(def ^:icon open-refactor (icon-xref :open-refactor))
|
||||
(def ^:icon padding-bottom-refactor (icon-xref :padding-bottom-refactor))
|
||||
(def ^:icon padding-top-refactor (icon-xref :padding-top-refactor))
|
||||
(def ^:icon padding-top-bottom-refactor (icon-xref :padding-top-bottom-refactor))
|
||||
@ -496,17 +495,19 @@
|
||||
refactor? (fn [[key]] (str/ends-with? key "Refactor"))]
|
||||
[:*
|
||||
[:section.debug-icons-preview
|
||||
[:h2 "Classic"]
|
||||
[:h2.subtitle-old "Classic (Deprecated)"]
|
||||
(for [[key val] (remove refactor? entries)]
|
||||
[:div.icon-item {:key key}
|
||||
[:div.icon-item-old {:key key
|
||||
:title key}
|
||||
val
|
||||
[:span key]])]
|
||||
|
||||
[:section.debug-icons-preview
|
||||
[:h2 "Refactor"]
|
||||
(for [[key val] (filter refactor? entries)]
|
||||
[:div.icon-item {:key key}
|
||||
(deref val)
|
||||
[:div.icon-item {:key key
|
||||
:title key}
|
||||
val
|
||||
[:span key]])]]))
|
||||
|
||||
(defn key->icon
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.loader
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
@ -15,4 +16,5 @@
|
||||
(mf/defc loader
|
||||
[]
|
||||
(when (mf/deref st/loader)
|
||||
[:div.loader-content i/loader-pencil]))
|
||||
[:div {:class (stl/css :loader-content)}
|
||||
i/loader-pencil]))
|
||||
|
||||
11
frontend/src/app/main/ui/loader.scss
Normal file
11
frontend/src/app/main/ui/loader.scss
Normal file
@ -0,0 +1,11 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@import "refactor/common-refactor.scss";
|
||||
|
||||
.loader-content {
|
||||
@extend .loader-base;
|
||||
}
|
||||
@ -46,17 +46,22 @@
|
||||
content
|
||||
(for [[index link] (d/enumerate links)]
|
||||
[:* {:key (dm/str "link-" index)}
|
||||
" " [:& lb/link-button {:class "link"
|
||||
" " [:& lb/link-button {:class (stl/css :link)
|
||||
:on-click (:callback link)
|
||||
:value (:label link)}]])]
|
||||
|
||||
(when (or (= controls :bottom-actions) (= controls :inline-actions))
|
||||
|
||||
[:div {:class (stl/css :actions)}
|
||||
(for [action actions]
|
||||
[:button {:key (uuid/next)
|
||||
:class (stl/css :action-bnt)
|
||||
:class (stl/css-case :action-btn true
|
||||
:primary (= :primary (:type action))
|
||||
:secondary (= :secondary (:type action))
|
||||
:danger (= :danger (:type action)))
|
||||
:on-click (:callback action)}
|
||||
(:label action)])])]
|
||||
|
||||
(when (= controls :close)
|
||||
[:button {:class (stl/css :btn-close)
|
||||
:on-click on-close} i/close-refactor])]])
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
background-color: var(--bg-color);
|
||||
color: var(--fg-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
--bg-color: var(--alert-background-color-warning);
|
||||
--fg-color: var(--alert-foreground-color-warning);
|
||||
@ -30,16 +31,18 @@
|
||||
--bg-color: var(--alert-background-color-neutral);
|
||||
--fg-color: var(--alert-foreground-color-neutral-active);
|
||||
}
|
||||
|
||||
.banner.info .icon {
|
||||
--fg-color: var(--alert-foreground-color-neutral);
|
||||
}
|
||||
|
||||
.banner.info:hover .icon {
|
||||
--fg-color: var(--alert-foreground-color-neutral);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: $s-16 1fr $s-40;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $s-8 $s-8 $s-8 $s-16;
|
||||
gap: $s-8;
|
||||
height: 100%;
|
||||
@ -94,20 +97,46 @@
|
||||
|
||||
.content {
|
||||
@include flexRow;
|
||||
gap: $s-8;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
@include titleTipography;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.link {
|
||||
@include titleTipography;
|
||||
color: var(--modal-link-foreground-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@include flexRow;
|
||||
gap: $s-8;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@extend .button-tertiary;
|
||||
height: $s-32;
|
||||
width: $s-32;
|
||||
color: black;
|
||||
@include tabTitleTipography;
|
||||
min-height: $s-32;
|
||||
min-width: $s-32;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
}
|
||||
&.primary {
|
||||
@extend .button-primary;
|
||||
padding: $s-8 $s-24;
|
||||
}
|
||||
&.secondary {
|
||||
@extend .button-secondary;
|
||||
padding: $s-8 $s-24;
|
||||
}
|
||||
&.danger {
|
||||
@extend .modal-danger-btn;
|
||||
padding: $s-8 $s-24;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
@ -116,6 +145,6 @@
|
||||
width: $s-32;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: black;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@
|
||||
|
||||
[:> fm/submit-button*
|
||||
{:label (if @loading (tr "labels.sending") (tr "labels.send"))
|
||||
:class (stl/css :feedback-button-link)
|
||||
:disabled @loading}]
|
||||
|
||||
[:hr]
|
||||
@ -88,7 +89,7 @@
|
||||
[:p {:class (stl/css :field-text)} (tr "feedback.discourse-subtitle1")]
|
||||
|
||||
[:a
|
||||
{:class (stl/css :btn-secondary :btn-large)
|
||||
{:class (stl/css :feedback-button-link)
|
||||
:href "https://community.penpot.app"
|
||||
:target "_blank"}
|
||||
(tr "feedback.discourse-go-to")]
|
||||
@ -98,7 +99,7 @@
|
||||
[:p {:class (stl/css :field-text)} (tr "feedback.twitter-subtitle1")]
|
||||
|
||||
[:a
|
||||
{:class (stl/css :btn-secondary :btn-large)
|
||||
{:class (stl/css :feedback-button-link)
|
||||
:href "https://twitter.com/penpotapp"
|
||||
:target "_blank"}
|
||||
(tr "feedback.twitter-go-to")]]))
|
||||
|
||||
@ -23,3 +23,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feedback-button-link {
|
||||
@extend .button-primary;
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
a {
|
||||
a:not(.button-primary) {
|
||||
color: $df-secondary;
|
||||
}
|
||||
}
|
||||
@ -211,7 +211,7 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
a {
|
||||
a:not(.button-primary) {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding: $s-8 $s-8 $s-8 $s-24;
|
||||
color: $df-secondary;
|
||||
|
||||
a {
|
||||
font-weight: $fw400;
|
||||
@ -59,14 +60,13 @@
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $db-secondary;
|
||||
fill: currentColor;
|
||||
margin-right: $s-8;
|
||||
height: $s-12;
|
||||
width: $s-12;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $df-secondary;
|
||||
font-size: $fs-14;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -119,12 +119,11 @@
|
||||
|
||||
&.current {
|
||||
background-color: $db-cuaternary;
|
||||
color: $da-primary;
|
||||
|
||||
a {
|
||||
font-weight: $fw400;
|
||||
color: $da-primary;
|
||||
}
|
||||
span {
|
||||
color: $da-primary;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
&::before {
|
||||
|
||||
@ -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)}])]
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -607,6 +607,6 @@
|
||||
(let [props (obj/merge props #js {:data data :key (dm/str file-id)})]
|
||||
[:> viewer-content props])
|
||||
|
||||
[:div.loader-content.viewer-loader
|
||||
[:div {:class (stl/css :loader-content)}
|
||||
i/loader-pencil]))
|
||||
|
||||
|
||||
@ -175,8 +175,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
:global(svg#loader-pencil) {
|
||||
fill: var(--icon-foreground);
|
||||
.loader-content {
|
||||
@extend .loader-base;
|
||||
}
|
||||
|
||||
/** FULLSCREEN */
|
||||
|
||||
@ -15,9 +15,9 @@
|
||||
(mf/defc annotation
|
||||
[{:keys [content] :as props}]
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.options.component.annotation")
|
||||
:class (stl/css :title-spacing-annotation)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.options.component.annotation")
|
||||
:class (stl/css :title-spacing-annotation)}
|
||||
[:& copy-button {:data content}]]
|
||||
|
||||
[:div {:class (stl/css :annotation-content)} content]])
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
(let [shapes (->> shapes (filter has-blur?))]
|
||||
(when (seq shapes)
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "inspect.attributes.blur")
|
||||
:class (stl/css :title-spacing-blur)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "inspect.attributes.blur")
|
||||
:class (stl/css :title-spacing-blur)}
|
||||
(when (= (count shapes) 1)
|
||||
[:& copy-button {:data (css/get-css-property objects (first shapes) :filter)}])]
|
||||
|
||||
|
||||
@ -54,9 +54,9 @@
|
||||
(let [shapes (filter has-fill? shapes)]
|
||||
(when (seq shapes)
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "inspect.attributes.fill")
|
||||
:class (stl/css :title-spacing-fill)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "inspect.attributes.fill")
|
||||
:class (stl/css :title-spacing-fill)}]
|
||||
|
||||
[:div {:class (stl/css :attributes-content)}
|
||||
(for [shape shapes]
|
||||
|
||||
@ -31,9 +31,9 @@
|
||||
(mf/defc geometry-panel
|
||||
[{:keys [objects shapes]}]
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "inspect.attributes.size")
|
||||
:class (stl/css :title-spacing-geometry)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "inspect.attributes.size")
|
||||
:class (stl/css :title-spacing-geometry)}
|
||||
|
||||
(when (= (count shapes) 1)
|
||||
[:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
|
||||
|
||||
@ -45,9 +45,9 @@
|
||||
|
||||
(when (seq shapes)
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title "Layout"
|
||||
:class (stl/css :title-spacing-layout)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title "Layout"
|
||||
:class (stl/css :title-spacing-layout)}
|
||||
|
||||
(when (= (count shapes) 1)
|
||||
[:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
|
||||
|
||||
@ -63,9 +63,9 @@
|
||||
|
||||
(when some-layout-prop?
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title menu-title
|
||||
:class (stl/css :title-spacing-layout-element)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title menu-title
|
||||
:class (stl/css :title-spacing-layout-element)}
|
||||
(when (= (count shapes) 1)
|
||||
[:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
|
||||
|
||||
|
||||
@ -53,9 +53,9 @@
|
||||
|
||||
(when (and (seq shapes) (> (count shapes) 0))
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "inspect.attributes.shadow")
|
||||
:class (stl/css :title-spacing-shadow)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "inspect.attributes.shadow")
|
||||
:class (stl/css :title-spacing-shadow)}]
|
||||
|
||||
[:div {:class (stl/css :attributes-content)}
|
||||
(for [shape shapes]
|
||||
|
||||
@ -61,9 +61,9 @@
|
||||
(let [shapes (->> shapes (filter has-stroke?))]
|
||||
(when (seq shapes)
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "inspect.attributes.stroke")
|
||||
:class (stl/css :title-spacing-stroke)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "inspect.attributes.stroke")
|
||||
:class (stl/css :title-spacing-stroke)}]
|
||||
|
||||
[:div {:class (stl/css :attributes-content)}
|
||||
(for [shape shapes]
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
(let [shape (first shapes)]
|
||||
(when (seq (:svg-attrs shape))
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.sidebar.options.svg-attrs.title")
|
||||
:class (stl/css :title-spacing-svg)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.sidebar.options.svg-attrs.title")
|
||||
:class (stl/css :title-spacing-svg)}]
|
||||
[:& svg-block {:shape shape}]])))
|
||||
|
||||
@ -189,9 +189,9 @@
|
||||
[{:keys [shapes]}]
|
||||
(when-let [shapes (seq (filter has-text? shapes))]
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "inspect.attributes.typography")
|
||||
:class (stl/css :title-spacing-text)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "inspect.attributes.typography")
|
||||
:class (stl/css :title-spacing-text)}]
|
||||
|
||||
(for [shape shapes]
|
||||
[:& text-block {:shape shape
|
||||
|
||||
@ -136,9 +136,9 @@
|
||||
vec))))
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.options.export")
|
||||
:class (stl/css :title-spacing-export-viewer)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.options.export")
|
||||
:class (stl/css :title-spacing-export-viewer)}
|
||||
[:button {:class (stl/css :add-export)
|
||||
:on-click add-export} i/add-refactor]]]
|
||||
|
||||
|
||||
@ -62,7 +62,8 @@
|
||||
@include flexColumn;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: $s-8;
|
||||
gap: $s-12;
|
||||
margin-right: $s-8;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
|
||||
@ -30,9 +30,7 @@
|
||||
grid-template-columns: auto 1fr auto;
|
||||
|
||||
.workspace-loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@include flexCenter;
|
||||
grid-area: viewport;
|
||||
background-color: var(--loader-background);
|
||||
:global(svg#loader-pencil) {
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -199,9 +199,9 @@
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :section)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.libraries.in-this-file")
|
||||
:class (stl/css :title-spacing-lib)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.libraries.in-this-file")
|
||||
:class (stl/css :title-spacing-lib)}]
|
||||
[:div {:class (stl/css :section-list)}
|
||||
|
||||
[:div {:class (stl/css :section-list-item)}
|
||||
@ -245,9 +245,9 @@
|
||||
i/detach-refactor]])]]
|
||||
|
||||
[:div {:class (stl/css :section)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.libraries.shared-libraries")
|
||||
:class (stl/css :title-spacing-lib)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.libraries.shared-libraries")
|
||||
:class (stl/css :title-spacing-lib)}]
|
||||
[:div {:class (stl/css :libraries-search)}
|
||||
[:& search-bar {:on-change change-search-term
|
||||
:value search-term
|
||||
@ -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
|
||||
|
||||
@ -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")}}
|
||||
|
||||
@ -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,30 +132,45 @@
|
||||
(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?)
|
||||
:clickable-all? 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}
|
||||
[{:keys [children]}]
|
||||
[:* children])
|
||||
|
||||
@ -161,11 +178,11 @@
|
||||
[rename components-to-group group-name]
|
||||
(let [undo-id (js/Symbol)]
|
||||
(st/emit! (dwu/start-undo-transaction undo-id))
|
||||
(apply st/emit!
|
||||
(->> components-to-group
|
||||
(map #(rename
|
||||
(:id %)
|
||||
(add-group % group-name)))))
|
||||
(->> components-to-group
|
||||
(map #(rename
|
||||
(:id %)
|
||||
(add-group % group-name)))
|
||||
(run! st/emit!))
|
||||
(st/emit! (dwu/commit-undo-transaction undo-id))))
|
||||
|
||||
(defn on-drop-asset
|
||||
|
||||
@ -52,9 +52,9 @@
|
||||
(fn []
|
||||
(st/emit! (dw/set-assets-section-open file-id :library (not open?)))))]
|
||||
[:div {:class (stl/css :library-title)}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not open?)
|
||||
:clickable-all? true
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not open?)
|
||||
:all-clickable true
|
||||
:on-collapsed toggle-open
|
||||
:title (if local?
|
||||
(mf/html [:div {:class (stl/css :special-title)}
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
(mf/use-fn #(swap! menu-state cmm/close-context-menu))]
|
||||
[:div {:class (stl/css :group-title)
|
||||
:on-context-menu on-context-menu}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not group-open?)
|
||||
:clickable-all? true
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not group-open?)
|
||||
:all-clickable true
|
||||
:on-collapsed on-fold-group
|
||||
:title (mf/html [:* (when-not (empty? other-path)
|
||||
[:span {:class (stl/css :pre-path)
|
||||
|
||||
@ -374,7 +374,7 @@
|
||||
i/tick-refactor])]])]
|
||||
|
||||
[:div {:class (stl/css :tool-window-bar)}
|
||||
[:& title-bar {:collapsable? false
|
||||
[:& title-bar {:collapsable false
|
||||
:title (:name page)
|
||||
:on-btn-click toggle-search
|
||||
:btn-children i/search-refactor}]]))]))
|
||||
|
||||
@ -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)}
|
||||
|
||||
@ -77,8 +77,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-value?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-value?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (case type
|
||||
:multiple (tr "workspace.options.blur-options.title.multiple")
|
||||
|
||||
@ -215,8 +215,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-colors?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-colors?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr "workspace.options.selection-color")
|
||||
:class (stl/css-case :title-spacing-selected-colors (not has-colors?))}]]
|
||||
|
||||
@ -484,8 +484,8 @@
|
||||
:on-click on-component-back}
|
||||
[:span i/arrow-refactor]
|
||||
[:span (tr "workspace.options.component")]]
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr "workspace.options.component")
|
||||
:class (stl/css :title-spacing-component)}])]
|
||||
|
||||
@ -213,6 +213,7 @@
|
||||
.component-list-empty {
|
||||
@include titleTipography;
|
||||
margin: 0 $s-4 0 $s-8;
|
||||
color: $df-secondary;
|
||||
}
|
||||
|
||||
.component-list {
|
||||
|
||||
@ -157,8 +157,8 @@
|
||||
(when in-frame?
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr "workspace.options.constraints")}]]
|
||||
(when open?
|
||||
|
||||
@ -171,8 +171,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-exports?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-exports?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr (if (> (count ids) 1) "workspace.options.export-multiple" "workspace.options.export"))
|
||||
:class (stl/css-case :title-spacing-export (not has-exports?))}
|
||||
|
||||
@ -137,8 +137,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-fills?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-fills?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title label
|
||||
:class (stl/css-case :title-spacing-fill (not has-fills?))}
|
||||
|
||||
@ -294,8 +294,8 @@
|
||||
handle-create-grid (mf/use-fn (mf/deps id) #(st/emit! (dw/add-frame-grid id)))]
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:& title-bar {:collapsable? has-frame-grids?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-frame-grids?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:class (stl/css-case :title-spacing-board-grid (not has-frame-grids?))
|
||||
:title (tr "workspace.options.guides.title")}
|
||||
|
||||
@ -165,8 +165,8 @@
|
||||
|
||||
[:div {:class (stl/css :grid-cell-menu)}
|
||||
[:div {:class (stl/css :grid-cell-menu-title)}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not open?)
|
||||
:on-collapsed #(swap! state* update :open not)
|
||||
:title "Grid cell"}]]
|
||||
|
||||
|
||||
@ -157,9 +157,9 @@
|
||||
[{:keys [flows]}]
|
||||
(when (seq flows)
|
||||
[:div {:class (stl/css :interaction-options)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.options.flows.flow-starts")
|
||||
:class (stl/css :title-spacing-layout-flow)}]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.options.flows.flow-starts")
|
||||
:class (stl/css :title-spacing-layout-flow)}]
|
||||
(for [flow flows]
|
||||
[:& flow-item {:flow flow :key (str (:id flow))}])]))
|
||||
|
||||
@ -170,9 +170,9 @@
|
||||
add-flow (mf/use-fn #(st/emit! (dwi/add-flow-selected-frame)))]
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.options.flows.flow")
|
||||
:class (stl/css :title-spacing-layout-flow)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.options.flows.flow")
|
||||
:class (stl/css :title-spacing-layout-flow)}
|
||||
(when (nil? flow)
|
||||
[:button {:class (stl/css :add-flow-btn)
|
||||
:title (tr "workspace.options.flows.add-flow-start")
|
||||
@ -712,9 +712,9 @@
|
||||
[:div {:class (stl/css :interaction-options)}
|
||||
(when (and shape (not (cfh/unframed-shape? shape)))
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.options.interactions")
|
||||
:class (stl/css :title-spacing-layout-interactions)}
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.options.interactions")
|
||||
:class (stl/css :title-spacing-layout-interactions)}
|
||||
|
||||
[:button {:class (stl/css :add-interaction-btn)
|
||||
:on-click add-interaction}
|
||||
|
||||
@ -27,11 +27,17 @@
|
||||
}
|
||||
|
||||
.help-content {
|
||||
padding: $s-20;
|
||||
padding: $s-32 0;
|
||||
width: $s-200;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.help-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: $s-40;
|
||||
gap: $s-12;
|
||||
}
|
||||
|
||||
.interactions-help-icon {
|
||||
@ -40,7 +46,6 @@
|
||||
height: $s-48;
|
||||
border-radius: $br-circle;
|
||||
background-color: var(--pill-background-color);
|
||||
margin: 0 auto $s-12 auto;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
|
||||
@ -895,8 +895,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-layout?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-layout?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title "Layout"
|
||||
:class (stl/css-case :title-spacing-layout (not has-layout?))}
|
||||
|
||||
@ -361,8 +361,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-content?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-content?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title title
|
||||
:class (stl/css-case :title-spacing-layout-element true
|
||||
|
||||
@ -203,7 +203,8 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(st/emit! (udw/update-dimensions ids attr value))))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value))))
|
||||
|
||||
on-proportion-lock-change
|
||||
(mf/use-fn
|
||||
@ -225,6 +226,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(doall (map #(do-position-change %1 %2 value attr) shapes frames))))
|
||||
|
||||
;; ROTATION
|
||||
@ -233,7 +235,8 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value]
|
||||
(st/emit! (udw/increase-rotation ids value))))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/increase-rotation ids value))))
|
||||
|
||||
;; RADIUS
|
||||
|
||||
|
||||
@ -288,8 +288,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-shadows?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-shadows?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (case type
|
||||
:multiple (tr "workspace.options.shadow-options.title.multiple")
|
||||
|
||||
@ -163,8 +163,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? has-strokes?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-strokes?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title label
|
||||
:class (stl/css-case :title-spacing-stroke (not has-strokes?))}
|
||||
|
||||
@ -86,8 +86,8 @@
|
||||
(when-not (empty? attrs)
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-set-title)}
|
||||
[:& title-bar {:collapsable? has-attributes?
|
||||
:collapsed? (not open?)
|
||||
[:& title-bar {:collapsable has-attributes?
|
||||
:collapsed (not open?)
|
||||
:on-collapsed toggle-content
|
||||
:title (tr "workspace.sidebar.options.svg-attrs.title")
|
||||
:class (stl/css-case :title-spacing-svg-attrs (not has-attributes?))}]]
|
||||
|
||||
@ -283,8 +283,8 @@
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not main-menu-open?)
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not main-menu-open?)
|
||||
:on-collapsed toggle-main-menu
|
||||
:title label
|
||||
:class (stl/css :title-spacing-text)}
|
||||
|
||||
@ -28,9 +28,9 @@
|
||||
on-close (mf/use-fn #(st/emit! (dwu/commit-undo-transaction :options)))]
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable? false
|
||||
:title (tr "workspace.options.canvas-background")
|
||||
:class (stl/css :title-spacing-page)}]]
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.options.canvas-background")
|
||||
:class (stl/css :title-spacing-page)}]]
|
||||
[:div {:class (stl/css :element-content)}
|
||||
[:& color-row
|
||||
{:disable-gradient true
|
||||
|
||||
@ -210,15 +210,15 @@
|
||||
[:div {:class (stl/css :sitemap)
|
||||
:style #js {"--height" (str size "px")}}
|
||||
|
||||
[:& title-bar {:collapsable? true
|
||||
:collapsed? (not show-pages?)
|
||||
:on-collapsed toggle-pages
|
||||
:clickable-all? true
|
||||
:title (tr "workspace.sidebar.sitemap")
|
||||
:class (stl/css :title-spacing-sitemap)}
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not show-pages?)
|
||||
:on-collapsed toggle-pages
|
||||
:all-clickable true
|
||||
:title (tr "workspace.sidebar.sitemap")
|
||||
:class (stl/css :title-spacing-sitemap)}
|
||||
|
||||
(if ^boolean read-only?
|
||||
[:di {:class (stl/css :view-only-mode)}
|
||||
[:div {:class (stl/css :view-only-mode)}
|
||||
(tr "labels.view-only")]
|
||||
[:button {:class (stl/css :add-page)
|
||||
:on-click on-create}
|
||||
|
||||
@ -341,11 +341,17 @@
|
||||
|
||||
handle-context-menu
|
||||
(mf/use-callback
|
||||
(mf/deps (:id shape) (:id cell) selected?)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [position (dom/get-client-position event)]
|
||||
(st/emit! (dw/show-grid-cell-context-menu {:position position :grid-id (:id shape)})))))]
|
||||
(if selected?
|
||||
(st/emit! (dw/show-grid-cell-context-menu {:position position :grid-id (:id shape)}))
|
||||
|
||||
;; If right-click on a non-selected cell we select the cell and then open the menu
|
||||
(st/emit! (dwge/set-selection (:id shape) (:id cell))
|
||||
(dw/show-grid-cell-context-menu {:position position :grid-id (:id shape)}))))))]
|
||||
|
||||
[:g.cell-editor
|
||||
;; DEBUG OVERLAY
|
||||
|
||||
@ -299,6 +299,7 @@
|
||||
(not (cfh/is-direct-child-of-root? shape))
|
||||
(empty? (get shape :fills)))))
|
||||
|
||||
|
||||
hover-shape
|
||||
(->> ids
|
||||
(remove remove-id?)
|
||||
|
||||
@ -113,7 +113,8 @@
|
||||
(defn- show-outline?
|
||||
[shape]
|
||||
(and (not (:hidden shape))
|
||||
(not (:blocked shape))))
|
||||
(not (:blocked shape))
|
||||
(not (:transforming shape))))
|
||||
|
||||
(mf/defc shape-outlines
|
||||
{::mf/wrap-props false}
|
||||
|
||||
@ -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,41 +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 (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
|
||||
@ -291,53 +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?))
|
||||
[: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]
|
||||
@ -346,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)
|
||||
@ -360,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
|
||||
@ -387,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)
|
||||
@ -403,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
|
||||
@ -427,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
|
||||
@ -437,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
|
||||
@ -462,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
|
||||
@ -481,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
|
||||
@ -505,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}]
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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))))
|
||||
|
||||
@ -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]
|
||||
@ -393,7 +393,7 @@
|
||||
(try
|
||||
(->> (if-let [shape-id (some-> shape-id parse-uuid)]
|
||||
(let [page (dm/get-in file [:data :pages-index (get @st/state :current-page-id)])]
|
||||
(cfv/validate-shape (uuid shape-id) file page libraries))
|
||||
(cfv/validate-shape shape-id file page libraries))
|
||||
(cfv/validate-file file libraries))
|
||||
(group-by :code)
|
||||
(clj->js))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user