mirror of
https://github.com/penpot/penpot.git
synced 2025-03-14 08:41:48 -05:00
commit
0eb2336bc6
24 changed files with 648 additions and 327 deletions
12
CHANGES.md
12
CHANGES.md
|
@ -32,6 +32,14 @@
|
||||||
- After team onboarding importing a file will import into the team drafts [Taiga #2408](https://tree.taiga.io/project/penpot/issue/2408)
|
- After team onboarding importing a file will import into the team drafts [Taiga #2408](https://tree.taiga.io/project/penpot/issue/2408)
|
||||||
- Fix problem exporting shapes from handoff mode [Taiga #2386](https://tree.taiga.io/project/penpot/issue/2386)
|
- Fix problem exporting shapes from handoff mode [Taiga #2386](https://tree.taiga.io/project/penpot/issue/2386)
|
||||||
- Fix lock/hide elements in context menu when multiples shapes selected [Taiga #2340](https://tree.taiga.io/project/penpot/issue/2340)
|
- Fix lock/hide elements in context menu when multiples shapes selected [Taiga #2340](https://tree.taiga.io/project/penpot/issue/2340)
|
||||||
|
- Fix problem with booleans [Taiga #2356](https://tree.taiga.io/project/penpot/issue/2356)
|
||||||
|
- Fix line-height/letter-spacing inputs behaviour [Taiga #2331](https://tree.taiga.io/project/penpot/issue/2331)
|
||||||
|
- Fix dotted style in strokes [Taiga #2312](https://tree.taiga.io/project/penpot/issue/2312)
|
||||||
|
- Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310)
|
||||||
|
- Fix problem with multiple exports [Taiga #2468](https://tree.taiga.io/project/penpot/issue/2468)
|
||||||
|
- Allow import to continue from recoverable failures [#1412](https://github.com/penpot/penpot/issues/1412)
|
||||||
|
- Improved behaviour on text options when not text is selected [Taiga #2390](https://tree.taiga.io/project/penpot/issue/2390)
|
||||||
|
- Fix decimal numbers in export viewbox [Taiga #2290](https://tree.taiga.io/project/penpot/issue/2290)
|
||||||
|
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
@ -75,8 +83,6 @@
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
|
|
||||||
- Update log4j2 dependency.
|
- Update log4j2 dependency.
|
||||||
>>>>>>> main
|
|
||||||
|
|
||||||
|
|
||||||
# 1.10.2-beta
|
# 1.10.2-beta
|
||||||
|
|
||||||
|
@ -134,6 +140,8 @@
|
||||||
- Add placeholder to create shareable link
|
- Add placeholder to create shareable link
|
||||||
- Fix project files count not refreshing correctly after import [Taiga #2216](https://tree.taiga.io/project/penpot/issue/2216)
|
- Fix project files count not refreshing correctly after import [Taiga #2216](https://tree.taiga.io/project/penpot/issue/2216)
|
||||||
- Remove button after import process finish [Taiga #2215](https://tree.taiga.io/project/penpot/issue/2215)
|
- Remove button after import process finish [Taiga #2215](https://tree.taiga.io/project/penpot/issue/2215)
|
||||||
|
- Fix problem with styles in the viewer [Taiga #2467](https://tree.taiga.io/project/penpot/issue/2467)
|
||||||
|
- Fix default state in viewer [Taiga #2465](https://tree.taiga.io/project/penpot/issue/2465)
|
||||||
|
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.pages.changes :as ch]
|
[app.common.pages.changes :as ch]
|
||||||
[app.common.pages.init :as init]
|
[app.common.pages.init :as init]
|
||||||
[app.common.pages.spec :as spec]
|
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
@ -21,15 +20,14 @@
|
||||||
(def conjv (fnil conj []))
|
(def conjv (fnil conj []))
|
||||||
(def conjs (fnil conj #{}))
|
(def conjs (fnil conj #{}))
|
||||||
|
|
||||||
;; This flag controls if we should execute spec validation after every commit
|
|
||||||
(def verify-on-commit? true)
|
|
||||||
|
|
||||||
(defn- commit-change
|
(defn- commit-change
|
||||||
([file change]
|
([file change]
|
||||||
(commit-change file change nil))
|
(commit-change file change nil))
|
||||||
|
|
||||||
([file change {:keys [add-container?]
|
([file change {:keys [add-container?
|
||||||
:or {add-container? false}}]
|
fail-on-spec?]
|
||||||
|
:or {add-container? false
|
||||||
|
fail-on-spec? false}}]
|
||||||
(let [component-id (:current-component-id file)
|
(let [component-id (:current-component-id file)
|
||||||
change (cond-> change
|
change (cond-> change
|
||||||
(and add-container? (some? component-id))
|
(and add-container? (some? component-id))
|
||||||
|
@ -39,11 +37,20 @@
|
||||||
(assoc :page-id (:current-page-id file)
|
(assoc :page-id (:current-page-id file)
|
||||||
:frame-id (:current-frame-id file)))]
|
:frame-id (:current-frame-id file)))]
|
||||||
|
|
||||||
(when verify-on-commit?
|
(when fail-on-spec?
|
||||||
(us/assert ::spec/change change))
|
(us/verify :app.common.pages.spec/change change))
|
||||||
(-> file
|
|
||||||
(update :changes conjv change)
|
(let [valid? (us/valid? :app.common.pages.spec/change change)]
|
||||||
(update :data ch/process-changes [change] verify-on-commit?)))))
|
#?(:cljs
|
||||||
|
(when-not valid? (.warn js/console "Invalid shape" (clj->js change))))
|
||||||
|
|
||||||
|
(cond-> file
|
||||||
|
valid?
|
||||||
|
(-> (update :changes conjv change)
|
||||||
|
(update :data ch/process-changes [change] false))
|
||||||
|
|
||||||
|
(not valid?)
|
||||||
|
(update :errors conjv change))))))
|
||||||
|
|
||||||
(defn- lookup-objects
|
(defn- lookup-objects
|
||||||
([file]
|
([file]
|
||||||
|
@ -56,15 +63,16 @@
|
||||||
(get shape-id)))
|
(get shape-id)))
|
||||||
|
|
||||||
(defn- commit-shape [file obj]
|
(defn- commit-shape [file obj]
|
||||||
(let [parent-id (-> file :parent-stack peek)]
|
(let [parent-id (-> file :parent-stack peek)
|
||||||
(-> file
|
change {:type :add-obj
|
||||||
(commit-change
|
:id (:id obj)
|
||||||
{:type :add-obj
|
:obj obj
|
||||||
:id (:id obj)
|
:parent-id parent-id}
|
||||||
:obj obj
|
|
||||||
:parent-id parent-id}
|
|
||||||
|
|
||||||
{:add-container? true}))))
|
fail-on-spec? (or (= :group (:type obj))
|
||||||
|
(= :frame (:type obj)))]
|
||||||
|
|
||||||
|
(commit-change file change {:add-container? true :fail-on-spec? fail-on-spec?})))
|
||||||
|
|
||||||
(defn setup-rect-selrect [obj]
|
(defn setup-rect-selrect [obj]
|
||||||
(let [rect (select-keys obj [:x :y :width :height])
|
(let [rect (select-keys obj [:x :y :width :height])
|
||||||
|
@ -421,35 +429,36 @@
|
||||||
(defn add-interaction
|
(defn add-interaction
|
||||||
[file from-id interaction-src]
|
[file from-id interaction-src]
|
||||||
|
|
||||||
(assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id))
|
(let [shape (lookup-shape file from-id)]
|
||||||
|
(if (nil? shape)
|
||||||
|
file
|
||||||
|
(let [{:keys [event-type action-type]} (read-classifier interaction-src)
|
||||||
|
{:keys [delay]} (read-event-opts interaction-src)
|
||||||
|
{:keys [destination overlay-pos-type overlay-position url
|
||||||
|
close-click-outside background-overlay preserve-scroll]}
|
||||||
|
(read-action-opts interaction-src)
|
||||||
|
|
||||||
(let [{:keys [event-type action-type]} (read-classifier interaction-src)
|
interactions (-> shape
|
||||||
{:keys [delay]} (read-event-opts interaction-src)
|
:interactions
|
||||||
{:keys [destination overlay-pos-type overlay-position url
|
(conjv
|
||||||
close-click-outside background-overlay preserve-scroll]}
|
(d/without-nils {:event-type event-type
|
||||||
(read-action-opts interaction-src)
|
:action-type action-type
|
||||||
|
:delay delay
|
||||||
|
:destination destination
|
||||||
|
:overlay-pos-type overlay-pos-type
|
||||||
|
:overlay-position overlay-position
|
||||||
|
:url url
|
||||||
|
:close-click-outside close-click-outside
|
||||||
|
:background-overlay background-overlay
|
||||||
|
:preserve-scroll preserve-scroll})))]
|
||||||
|
(commit-change
|
||||||
|
file
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id (:current-page-id file)
|
||||||
|
:id from-id
|
||||||
|
|
||||||
interactions (-> (lookup-shape file from-id)
|
:operations
|
||||||
:interactions
|
[{:type :set :attr :interactions :val interactions}]})))))
|
||||||
(conjv
|
|
||||||
(d/without-nils {:event-type event-type
|
|
||||||
:action-type action-type
|
|
||||||
:delay delay
|
|
||||||
:destination destination
|
|
||||||
:overlay-pos-type overlay-pos-type
|
|
||||||
:overlay-position overlay-position
|
|
||||||
:url url
|
|
||||||
:close-click-outside close-click-outside
|
|
||||||
:background-overlay background-overlay
|
|
||||||
:preserve-scroll preserve-scroll})))]
|
|
||||||
(commit-change
|
|
||||||
file
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id (:current-page-id file)
|
|
||||||
:id from-id
|
|
||||||
|
|
||||||
:operations
|
|
||||||
[{:type :set :attr :interactions :val interactions}]})))
|
|
||||||
|
|
||||||
(defn generate-changes
|
(defn generate-changes
|
||||||
[file]
|
[file]
|
||||||
|
|
|
@ -451,9 +451,6 @@
|
||||||
rt-modif (:rotation modifiers)]
|
rt-modif (:rotation modifiers)]
|
||||||
|
|
||||||
(cond-> (gmt/matrix)
|
(cond-> (gmt/matrix)
|
||||||
(some? displacement)
|
|
||||||
(gmt/multiply displacement)
|
|
||||||
|
|
||||||
(some? resize-1)
|
(some? resize-1)
|
||||||
(-> (gmt/translate origin-1)
|
(-> (gmt/translate origin-1)
|
||||||
(gmt/multiply resize-transform)
|
(gmt/multiply resize-transform)
|
||||||
|
@ -468,6 +465,9 @@
|
||||||
(gmt/multiply resize-transform-inverse)
|
(gmt/multiply resize-transform-inverse)
|
||||||
(gmt/translate (gpt/negate origin-2)))
|
(gmt/translate (gpt/negate origin-2)))
|
||||||
|
|
||||||
|
(some? displacement)
|
||||||
|
(gmt/multiply displacement)
|
||||||
|
|
||||||
(some? rt-modif)
|
(some? rt-modif)
|
||||||
(-> (gmt/translate center)
|
(-> (gmt/translate center)
|
||||||
(gmt/multiply (gmt/rotate-matrix rt-modif))
|
(gmt/multiply (gmt/rotate-matrix rt-modif))
|
||||||
|
|
|
@ -212,6 +212,27 @@
|
||||||
(d/seek overlap-single?)
|
(d/seek overlap-single?)
|
||||||
(some?))))
|
(some?))))
|
||||||
|
|
||||||
|
(defn fix-move-to
|
||||||
|
[content]
|
||||||
|
;; Remove the field `:prev` and makes the necessaries `move-to`
|
||||||
|
;; then clean the subpaths
|
||||||
|
|
||||||
|
(loop [current (first content)
|
||||||
|
content (rest content)
|
||||||
|
prev nil
|
||||||
|
result []]
|
||||||
|
|
||||||
|
(if (nil? current)
|
||||||
|
result
|
||||||
|
|
||||||
|
(let [result (if (not= (:prev current) prev)
|
||||||
|
(conj result (upc/make-move-to (:prev current)))
|
||||||
|
result)]
|
||||||
|
(recur (first content)
|
||||||
|
(rest content)
|
||||||
|
(gsp/command->point current)
|
||||||
|
(conj result (dissoc current :prev)))))))
|
||||||
|
|
||||||
(defn create-union [content-a content-a-split content-b content-b-split sr-a sr-b]
|
(defn create-union [content-a content-a-split content-b content-b-split sr-a sr-b]
|
||||||
;; Pick all segments in content-a that are not inside content-b
|
;; Pick all segments in content-a that are not inside content-b
|
||||||
;; Pick all segments in content-b that are not inside content-a
|
;; Pick all segments in content-b that are not inside content-a
|
||||||
|
@ -225,7 +246,7 @@
|
||||||
|
|
||||||
content-geom (gsp/content->geom-data content)
|
content-geom (gsp/content->geom-data content)
|
||||||
|
|
||||||
content-sr (gsp/content->selrect content)
|
content-sr (gsp/content->selrect (fix-move-to content))
|
||||||
|
|
||||||
;; Overlapping segments should be added when they are part of the border
|
;; Overlapping segments should be added when they are part of the border
|
||||||
border-content
|
border-content
|
||||||
|
@ -265,36 +286,22 @@
|
||||||
;; Pick all segments
|
;; Pick all segments
|
||||||
(d/concat-vec content-a content-b))
|
(d/concat-vec content-a content-b))
|
||||||
|
|
||||||
(defn fix-move-to
|
|
||||||
[content]
|
|
||||||
;; Remove the field `:prev` and makes the necessaries `move-to`
|
|
||||||
;; then clean the subpaths
|
|
||||||
|
|
||||||
(loop [current (first content)
|
|
||||||
content (rest content)
|
|
||||||
prev nil
|
|
||||||
result []]
|
|
||||||
|
|
||||||
(if (nil? current)
|
|
||||||
result
|
|
||||||
|
|
||||||
(let [result (if (not= (:prev current) prev)
|
|
||||||
(conj result (upc/make-move-to (:prev current)))
|
|
||||||
result)]
|
|
||||||
(recur (first content)
|
|
||||||
(rest content)
|
|
||||||
(gsp/command->point current)
|
|
||||||
(conj result (dissoc current :prev)))))))
|
|
||||||
|
|
||||||
(defn content-bool-pair
|
(defn content-bool-pair
|
||||||
[bool-type content-a content-b]
|
[bool-type content-a content-b]
|
||||||
|
|
||||||
(let [content-a (-> content-a (close-paths) (add-previous))
|
(let [;; We need to reverse the second path when making a difference/intersection/exclude
|
||||||
|
;; and both shapes are in the same direction
|
||||||
|
should-reverse? (and (not= :union bool-type)
|
||||||
|
(= (ups/clockwise? content-b)
|
||||||
|
(ups/clockwise? content-a)))
|
||||||
|
|
||||||
|
content-a (-> content-a
|
||||||
|
(close-paths)
|
||||||
|
(add-previous))
|
||||||
|
|
||||||
content-b (-> content-b
|
content-b (-> content-b
|
||||||
(close-paths)
|
(close-paths)
|
||||||
(cond-> (ups/clockwise? content-b)
|
(cond-> should-reverse? (ups/reverse-content))
|
||||||
(ups/reverse-content))
|
|
||||||
(add-previous))
|
(add-previous))
|
||||||
|
|
||||||
sr-a (gsp/content->selrect content-a)
|
sr-a (gsp/content->selrect content-a)
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
(def max-safe-int (int 1e6))
|
(def max-safe-int (int 1e6))
|
||||||
(def min-safe-int (int -1e6))
|
(def min-safe-int (int -1e6))
|
||||||
|
|
||||||
|
(def valid? s/valid?)
|
||||||
|
|
||||||
;; --- Conformers
|
;; --- Conformers
|
||||||
|
|
||||||
(defn uuid-conformer
|
(defn uuid-conformer
|
||||||
|
@ -216,8 +218,11 @@
|
||||||
:code :spec-validation
|
:code :spec-validation
|
||||||
:hint hint
|
:hint hint
|
||||||
:ctx ctx
|
:ctx ctx
|
||||||
|
:value val
|
||||||
::s/problems (::s/problems data)))))
|
::s/problems (::s/problems data)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro assert
|
(defmacro assert
|
||||||
"Development only assertion macro."
|
"Development only assertion macro."
|
||||||
[spec x]
|
[spec x]
|
||||||
|
|
|
@ -80,9 +80,11 @@
|
||||||
(p/then (fn [results]
|
(p/then (fn [results]
|
||||||
(reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results)))
|
(reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results)))
|
||||||
(p/then (fn [fzip]
|
(p/then (fn [fzip]
|
||||||
|
(.generateAsync ^js fzip #js {:type "uint8array"})))
|
||||||
|
(p/then (fn [data]
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"content-type" "application/zip"}
|
:headers {"content-type" "application/zip"}
|
||||||
:body (.generateNodeStream ^js fzip)})))))
|
:body data})))))
|
||||||
|
|
||||||
(defn- perform-export
|
(defn- perform-export
|
||||||
[params]
|
[params]
|
||||||
|
|
|
@ -322,6 +322,10 @@
|
||||||
fill: $color-success;
|
fill: $color-success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-msg-warning {
|
||||||
|
fill: $color-warning;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-close {
|
.icon-close {
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
fill: $color-danger;
|
fill: $color-danger;
|
||||||
|
@ -392,6 +396,13 @@
|
||||||
fill: $color-white;
|
fill: $color-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
background: $color-warning-lighter;
|
||||||
|
.icon {
|
||||||
|
background: $color-warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -29,11 +29,13 @@
|
||||||
[app.main.ui.shapes.text :as text]
|
[app.main.ui.shapes.text :as text]
|
||||||
[app.main.ui.shapes.text.fontfaces :as ff]
|
[app.main.ui.shapes.text.fontfaces :as ff]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
[app.util.strings :as ust]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[debug :refer [debug?]]
|
[debug :refer [debug?]]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(def ^:const viewbox-decimal-precision 3)
|
||||||
(def ^:private default-color clr/canvas)
|
(def ^:private default-color clr/canvas)
|
||||||
|
|
||||||
(mf/defc background
|
(mf/defc background
|
||||||
|
@ -139,8 +141,13 @@
|
||||||
;; Don't wrap svg elements inside a <g> otherwise some can break
|
;; Don't wrap svg elements inside a <g> otherwise some can break
|
||||||
[:> svg-raw-wrapper {:shape shape :frame frame}]))))))
|
[:> svg-raw-wrapper {:shape shape :frame frame}]))))))
|
||||||
|
|
||||||
(defn get-viewbox [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
|
(defn format-viewbox
|
||||||
(str/fmt "%s %s %s %s" x y width height))
|
"Format a viewbox given a rectangle"
|
||||||
|
[{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
|
||||||
|
(str/join
|
||||||
|
" "
|
||||||
|
(->> [x y width height]
|
||||||
|
(map #(ust/format-precision % viewbox-decimal-precision)))))
|
||||||
|
|
||||||
(mf/defc page-svg
|
(mf/defc page-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
|
@ -160,7 +167,7 @@
|
||||||
vport (when (and (some? width) (some? height))
|
vport (when (and (some? width) (some? height))
|
||||||
{:width width :height height})
|
{:width width :height height})
|
||||||
dim (calculate-dimensions data vport)
|
dim (calculate-dimensions data vport)
|
||||||
vbox (get-viewbox dim)
|
vbox (format-viewbox dim)
|
||||||
background-color (get-in data [:options :background] default-color)
|
background-color (get-in data [:options :background] default-color)
|
||||||
frame-wrapper
|
frame-wrapper
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
|
@ -221,15 +228,15 @@
|
||||||
|
|
||||||
width (* (:width frame) zoom)
|
width (* (:width frame) zoom)
|
||||||
height (* (:height frame) zoom)
|
height (* (:height frame) zoom)
|
||||||
vbox (str "0 0 " (:width frame 0)
|
vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)})
|
||||||
" " (:height frame 0))
|
|
||||||
wrapper (mf/use-memo
|
wrapper (mf/use-memo
|
||||||
(mf/deps objects)
|
(mf/deps objects)
|
||||||
#(frame-wrapper-factory objects))]
|
#(frame-wrapper-factory objects))]
|
||||||
|
|
||||||
[:svg {:view-box vbox
|
[:svg {:view-box vbox
|
||||||
:width width
|
:width (ust/format-precision width viewbox-decimal-precision)
|
||||||
:height height
|
:height (ust/format-precision height viewbox-decimal-precision)
|
||||||
:version "1.1"
|
:version "1.1"
|
||||||
:xmlns "http://www.w3.org/2000/svg"
|
:xmlns "http://www.w3.org/2000/svg"
|
||||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
|
@ -255,18 +262,20 @@
|
||||||
|
|
||||||
group (get objects group-id)
|
group (get objects group-id)
|
||||||
|
|
||||||
|
|
||||||
width (* (:width group) zoom)
|
width (* (:width group) zoom)
|
||||||
height (* (:height group) zoom)
|
height (* (:height group) zoom)
|
||||||
vbox (str "0 0 " (:width group 0)
|
vbox (format-viewbox {:width (:width group 0)
|
||||||
" " (:height group 0))
|
:height (:height group 0)})
|
||||||
|
|
||||||
group-wrapper
|
group-wrapper
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
(mf/deps objects)
|
(mf/deps objects)
|
||||||
#(group-wrapper-factory objects))]
|
#(group-wrapper-factory objects))]
|
||||||
|
|
||||||
[:svg {:view-box vbox
|
[:svg {:view-box vbox
|
||||||
:width width
|
:width (ust/format-precision width viewbox-decimal-precision)
|
||||||
:height height
|
:height (ust/format-precision height viewbox-decimal-precision)
|
||||||
:version "1.1"
|
:version "1.1"
|
||||||
:xmlns "http://www.w3.org/2000/svg"
|
:xmlns "http://www.w3.org/2000/svg"
|
||||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
|
@ -281,7 +290,7 @@
|
||||||
root (get objects id)
|
root (get objects id)
|
||||||
|
|
||||||
{:keys [width height]} (:selrect root)
|
{:keys [width height]} (:selrect root)
|
||||||
vbox (str "0 0 " width " " height)
|
vbox (format-viewbox {:width width :height height})
|
||||||
|
|
||||||
modifier (-> (gpt/point (:x root) (:y root))
|
modifier (-> (gpt/point (:x root) (:y root))
|
||||||
(gpt/negate)
|
(gpt/negate)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.simple-math :as sm]
|
[app.util.simple-math :as sm]
|
||||||
|
[app.util.strings :as ust]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(defn num? [val]
|
(defn num? [val]
|
||||||
|
@ -24,13 +25,16 @@
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
::mf/forward-ref true}
|
::mf/forward-ref true}
|
||||||
[props external-ref]
|
[props external-ref]
|
||||||
(let [value-str (obj/get props "value")
|
(let [value-str (obj/get props "value")
|
||||||
min-val-str (obj/get props "min")
|
min-val-str (obj/get props "min")
|
||||||
max-val-str (obj/get props "max")
|
max-val-str (obj/get props "max")
|
||||||
wrap-value? (obj/get props "data-wrap")
|
step-val-str (obj/get props "step")
|
||||||
on-change (obj/get props "onChange")
|
wrap-value? (obj/get props "data-wrap")
|
||||||
title (obj/get props "title")
|
on-change (obj/get props "onChange")
|
||||||
default-val (obj/get props "default" 0)
|
on-blur (obj/get props "onBlur")
|
||||||
|
title (obj/get props "title")
|
||||||
|
default-val (obj/get props "default" 0)
|
||||||
|
precision (obj/get props "precision")
|
||||||
|
|
||||||
;; We need a ref pointing to the input dom element, but the user
|
;; We need a ref pointing to the input dom element, but the user
|
||||||
;; of this component may provide one (that is forwarded here).
|
;; of this component may provide one (that is forwarded here).
|
||||||
|
@ -56,6 +60,15 @@
|
||||||
(string? max-val-str)
|
(string? max-val-str)
|
||||||
(d/parse-integer max-val-str))
|
(d/parse-integer max-val-str))
|
||||||
|
|
||||||
|
step-val (cond
|
||||||
|
(number? step-val-str)
|
||||||
|
step-val-str
|
||||||
|
|
||||||
|
(string? step-val-str)
|
||||||
|
(d/parse-integer step-val-str)
|
||||||
|
|
||||||
|
:else 1)
|
||||||
|
|
||||||
parse-value
|
parse-value
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps ref min-val max-val value)
|
(mf/deps ref min-val max-val value)
|
||||||
|
@ -65,7 +78,10 @@
|
||||||
(sm/expr-eval value))]
|
(sm/expr-eval value))]
|
||||||
(when (num? new-value)
|
(when (num? new-value)
|
||||||
(-> new-value
|
(-> new-value
|
||||||
(math/round)
|
(cond-> (number? precision)
|
||||||
|
(math/precision precision))
|
||||||
|
(cond-> (nil? precision)
|
||||||
|
(math/round))
|
||||||
(cljs.core/max us/min-safe-int)
|
(cljs.core/max us/min-safe-int)
|
||||||
(cljs.core/min us/max-safe-int)
|
(cljs.core/min us/max-safe-int)
|
||||||
(cond->
|
(cond->
|
||||||
|
@ -80,7 +96,9 @@
|
||||||
(mf/deps ref)
|
(mf/deps ref)
|
||||||
(fn [new-value]
|
(fn [new-value]
|
||||||
(let [input-node (mf/ref-val ref)]
|
(let [input-node (mf/ref-val ref)]
|
||||||
(dom/set-value! input-node (str new-value)))))
|
(dom/set-value! input-node (if (some? precision)
|
||||||
|
(ust/format-precision new-value precision)
|
||||||
|
(str new-value))))))
|
||||||
|
|
||||||
apply-value
|
apply-value
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -97,18 +115,18 @@
|
||||||
(let [current-value (parse-value)]
|
(let [current-value (parse-value)]
|
||||||
(when current-value
|
(when current-value
|
||||||
(let [increment (if (kbd/shift? event)
|
(let [increment (if (kbd/shift? event)
|
||||||
(if up? 10 -10)
|
(if up? (* step-val 10) (* step-val -10))
|
||||||
(if up? 1 -1))
|
(if up? step-val (- step-val)))
|
||||||
|
|
||||||
new-value (+ current-value increment)
|
new-value (+ current-value increment)
|
||||||
new-value (cond
|
new-value (cond
|
||||||
(and wrap-value? (num? max-val) (num? min-val)
|
(and wrap-value? (num? max-val) (num? min-val)
|
||||||
(> new-value max-val) up?)
|
(> new-value max-val) up?)
|
||||||
(-> new-value (- max-val) (+ min-val) (- 1))
|
(-> new-value (- max-val) (+ min-val) (- step-val))
|
||||||
|
|
||||||
(and wrap-value? (num? min-val) (num? max-val)
|
(and wrap-value? (num? min-val) (num? max-val)
|
||||||
(< new-value min-val) down?)
|
(< new-value min-val) down?)
|
||||||
(-> new-value (- min-val) (+ max-val) (+ 1))
|
(-> new-value (- min-val) (+ max-val) (+ step-val))
|
||||||
|
|
||||||
(and (num? min-val) (< new-value min-val))
|
(and (num? min-val) (< new-value min-val))
|
||||||
min-val
|
min-val
|
||||||
|
@ -144,12 +162,13 @@
|
||||||
|
|
||||||
handle-blur
|
handle-blur
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps parse-value apply-value update-input)
|
(mf/deps parse-value apply-value update-input on-blur)
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(let [new-value (or (parse-value) default-val)]
|
(let [new-value (or (parse-value) default-val)]
|
||||||
(if new-value
|
(if new-value
|
||||||
(apply-value new-value)
|
(apply-value new-value)
|
||||||
(update-input new-value)))))
|
(update-input new-value)))
|
||||||
|
(when on-blur (on-blur))))
|
||||||
|
|
||||||
props (-> props
|
props (-> props
|
||||||
(obj/without ["value" "onChange"])
|
(obj/without ["value" "onChange"])
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
(filter #(= :ready (:status %)))
|
(filter #(= :ready (:status %)))
|
||||||
(mapv #(assoc % :status :importing))))
|
(mapv #(assoc % :status :importing))))
|
||||||
|
|
||||||
(defn update-status [files file-id status progress]
|
(defn update-status [files file-id status progress errors]
|
||||||
(->> files
|
(->> files
|
||||||
(mapv (fn [file]
|
(mapv (fn [file]
|
||||||
(cond-> file
|
(cond-> file
|
||||||
|
@ -106,7 +106,10 @@
|
||||||
(assoc :status status)
|
(assoc :status status)
|
||||||
|
|
||||||
(and (= file-id (:file-id file)) (= status :import-progress))
|
(and (= file-id (:file-id file)) (= status :import-progress))
|
||||||
(assoc :progress progress))))))
|
(assoc :progress progress)
|
||||||
|
|
||||||
|
(= file-id (:file-id file))
|
||||||
|
(assoc :errors errors))))))
|
||||||
|
|
||||||
(defn parse-progress-message
|
(defn parse-progress-message
|
||||||
[message]
|
[message]
|
||||||
|
@ -139,9 +142,10 @@
|
||||||
|
|
||||||
(let [loading? (or (= :analyzing (:status file))
|
(let [loading? (or (= :analyzing (:status file))
|
||||||
(= :importing (:status file)))
|
(= :importing (:status file)))
|
||||||
load-success? (= :import-success (:status file))
|
|
||||||
analyze-error? (= :analyze-error (:status file))
|
analyze-error? (= :analyze-error (:status file))
|
||||||
|
import-finish? (= :import-finish (:status file))
|
||||||
import-error? (= :import-error (:status file))
|
import-error? (= :import-error (:status file))
|
||||||
|
import-warn? (d/not-empty? (:errors file))
|
||||||
ready? (= :ready (:status file))
|
ready? (= :ready (:status file))
|
||||||
is-shared? (:shared file)
|
is-shared? (:shared file)
|
||||||
progress (:progress file)
|
progress (:progress file)
|
||||||
|
@ -177,7 +181,8 @@
|
||||||
[:div.file-entry
|
[:div.file-entry
|
||||||
{:class (dom/classnames
|
{:class (dom/classnames
|
||||||
:loading loading?
|
:loading loading?
|
||||||
:success load-success?
|
:success (and import-finish? (not import-warn?) (not import-error?))
|
||||||
|
:warning (and import-finish? import-warn? (not import-error?))
|
||||||
:error (or import-error? analyze-error?)
|
:error (or import-error? analyze-error?)
|
||||||
:editable (and ready? (not editing?)))}
|
:editable (and ready? (not editing?)))}
|
||||||
|
|
||||||
|
@ -185,8 +190,9 @@
|
||||||
[:div.file-icon
|
[:div.file-icon
|
||||||
(cond loading? i/loader-pencil
|
(cond loading? i/loader-pencil
|
||||||
ready? i/logo-icon
|
ready? i/logo-icon
|
||||||
load-success? i/tick
|
import-warn? i/msg-warning
|
||||||
import-error? i/close
|
import-error? i/close
|
||||||
|
import-finish? i/tick
|
||||||
analyze-error? i/close)]
|
analyze-error? i/close)]
|
||||||
|
|
||||||
(if editing?
|
(if editing?
|
||||||
|
@ -212,7 +218,7 @@
|
||||||
[:div.error-message
|
[:div.error-message
|
||||||
(tr "dashboard.import.import-error")]
|
(tr "dashboard.import.import-error")]
|
||||||
|
|
||||||
(and (not load-success?) (some? progress))
|
(and (not import-finish?) (some? progress))
|
||||||
[:div.progress-message (parse-progress-message progress)])
|
[:div.progress-message (parse-progress-message progress)])
|
||||||
|
|
||||||
[:div.linked-libraries
|
[:div.linked-libraries
|
||||||
|
@ -258,9 +264,8 @@
|
||||||
:project-id project-id
|
:project-id project-id
|
||||||
:files files})
|
:files files})
|
||||||
(rx/subs
|
(rx/subs
|
||||||
(fn [{:keys [file-id status message] :as msg}]
|
(fn [{:keys [file-id status message errors] :as msg}]
|
||||||
(log/debug :msg msg)
|
(swap! state update :files update-status file-id status message errors))))))
|
||||||
(swap! state update :files update-status file-id status message))))))
|
|
||||||
|
|
||||||
handle-cancel
|
handle-cancel
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -291,7 +296,8 @@
|
||||||
(st/emit! (modal/hide))
|
(st/emit! (modal/hide))
|
||||||
(when on-finish-import (on-finish-import))))
|
(when on-finish-import (on-finish-import))))
|
||||||
|
|
||||||
success-files (->> @state :files (filter #(= (:status %) :import-success)) count)
|
warning-files (->> @state :files (filter #(and (= (:status %) :import-finish) (d/not-empty? (:errors %)))) count)
|
||||||
|
success-files (->> @state :files (filter #(and (= (:status %) :import-finish) (empty? (:errors %)))) count)
|
||||||
pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0)
|
pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0)
|
||||||
pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0)]
|
pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0)]
|
||||||
|
|
||||||
|
@ -316,11 +322,15 @@
|
||||||
{:on-click handle-cancel} i/close]]
|
{:on-click handle-cancel} i/close]]
|
||||||
|
|
||||||
[:div.modal-content
|
[:div.modal-content
|
||||||
(when (and (= :importing (:status @state))
|
(when (and (= :importing (:status @state)) (not pending-import?))
|
||||||
(not pending-import?))
|
(if (> warning-files 0)
|
||||||
[:div.feedback-banner
|
[:div.feedback-banner.warning
|
||||||
[:div.icon i/checkbox-checked]
|
[:div.icon i/msg-warning]
|
||||||
[:div.message (tr "dashboard.import.import-message" success-files)]])
|
[:div.message (tr "dashboard.import.import-warning" warning-files success-files)]]
|
||||||
|
|
||||||
|
[:div.feedback-banner
|
||||||
|
[:div.icon i/checkbox-checked]
|
||||||
|
[:div.message (tr "dashboard.import.import-message" success-files)]]))
|
||||||
|
|
||||||
(for [file (->> (:files @state) (filterv (comp not :deleted?)))]
|
(for [file (->> (:files @state) (filterv (comp not :deleted?)))]
|
||||||
(let [editing? (and (some? (:file-id file))
|
(let [editing? (and (some? (:file-id file))
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
[width style]
|
[width style]
|
||||||
(let [values (case style
|
(let [values (case style
|
||||||
:mixed [5 5 1 5]
|
:mixed [5 5 1 5]
|
||||||
:dotted [5 5]
|
;; We want 0 so they are circles
|
||||||
|
:dotted [(- width) 5]
|
||||||
:dashed [10 10]
|
:dashed [10 10]
|
||||||
nil)]
|
nil)]
|
||||||
|
|
||||||
|
@ -132,9 +133,13 @@
|
||||||
;; for inner or outer strokes.
|
;; for inner or outer strokes.
|
||||||
(and (spec/stroke-caps-line (:stroke-cap-start shape))
|
(and (spec/stroke-caps-line (:stroke-cap-start shape))
|
||||||
(= (:stroke-cap-start shape) (:stroke-cap-end shape))
|
(= (:stroke-cap-start shape) (:stroke-cap-end shape))
|
||||||
(not (#{:inner :outer} (:stroke-alignment shape))))
|
(not (#{:inner :outer} (:stroke-alignment shape)))
|
||||||
|
(not= :dotted stroke-style))
|
||||||
(assoc :strokeLinecap (:stroke-cap-start shape))
|
(assoc :strokeLinecap (:stroke-cap-start shape))
|
||||||
|
|
||||||
|
(= :dotted stroke-style)
|
||||||
|
(assoc :strokeLinecap "round")
|
||||||
|
|
||||||
;; For other cap types we use markers.
|
;; For other cap types we use markers.
|
||||||
(and (or (spec/stroke-caps-marker (:stroke-cap-start shape))
|
(and (or (spec/stroke-caps-marker (:stroke-cap-start shape))
|
||||||
(and (spec/stroke-caps-line (:stroke-cap-start shape))
|
(and (spec/stroke-caps-line (:stroke-cap-start shape))
|
||||||
|
|
|
@ -14,11 +14,10 @@
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(defn generate-root-styles
|
(defn generate-root-styles
|
||||||
[shape node]
|
[_shape node]
|
||||||
(let [valign (:vertical-align node "top")
|
(let [valign (:vertical-align node "top")
|
||||||
width (some-> (:width shape) (+ 1))
|
base #js {:height "100%"
|
||||||
base #js {:height (or (:height shape) "100%")
|
:width "100%"
|
||||||
:width (or width "100%")
|
|
||||||
:fontFamily "sourcesanspro"}]
|
:fontFamily "sourcesanspro"}]
|
||||||
(cond-> base
|
(cond-> base
|
||||||
(= valign "top") (obj/set! "justifyContent" "flex-start")
|
(= valign "top") (obj/set! "justifyContent" "flex-start")
|
||||||
|
|
|
@ -299,7 +299,7 @@
|
||||||
[{:keys [file layout project page-id] :as props}]
|
[{:keys [file layout project page-id] :as props}]
|
||||||
(let [team-id (:team-id project)
|
(let [team-id (:team-id project)
|
||||||
zoom (mf/deref refs/selected-zoom)
|
zoom (mf/deref refs/selected-zoom)
|
||||||
params {:page-id page-id :file-id (:id file)}
|
params {:page-id page-id :file-id (:id file) :section "interactions"}
|
||||||
|
|
||||||
go-back
|
go-back
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
(ns app.main.ui.workspace.shapes.text.editor
|
(ns app.main.ui.workspace.shapes.text.editor
|
||||||
(:require
|
(:require
|
||||||
["draft-js" :as draft]
|
["draft-js" :as draft]
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
@ -72,20 +71,18 @@
|
||||||
(def empty-editor-state
|
(def empty-editor-state
|
||||||
(ted/create-editor-state nil default-decorator))
|
(ted/create-editor-state nil default-decorator))
|
||||||
|
|
||||||
(defn get-content-changes
|
(defn get-blocks-to-setup [block-changes]
|
||||||
[old-state state]
|
(->> block-changes
|
||||||
(let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state)))
|
(filter (fn [[_ v]]
|
||||||
:keywordize-keys false)
|
(nil? (:old v))))
|
||||||
new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state)))
|
(mapv first)))
|
||||||
|
|
||||||
:keywordize-keys false)]
|
(defn get-blocks-to-add-styles
|
||||||
(->> old-blocks
|
[block-changes]
|
||||||
(d/mapm
|
(->> block-changes
|
||||||
(fn [bkey bstate]
|
(filter (fn [[_ v]]
|
||||||
{:old (get bstate "text")
|
(and (not= (:old v) (:new v)) (= (:old v) ""))))
|
||||||
:new (get-in new-blocks [bkey "text"])}))
|
(mapv first)))
|
||||||
(filter #(contains? new-blocks (first %)))
|
|
||||||
(into {}))))
|
|
||||||
|
|
||||||
(mf/defc text-shape-edit-html
|
(mf/defc text-shape-edit-html
|
||||||
{::mf/wrap [mf/memo]
|
{::mf/wrap [mf/memo]
|
||||||
|
@ -143,25 +140,35 @@
|
||||||
(fn [state]
|
(fn [state]
|
||||||
(let [old-state (mf/ref-val prev-value)]
|
(let [old-state (mf/ref-val prev-value)]
|
||||||
(if (and (some? state) (some? old-state))
|
(if (and (some? state) (some? old-state))
|
||||||
(let [block-states (get-content-changes old-state state)
|
(let [block-changes (ted/get-content-changes old-state state)
|
||||||
|
prev-data (ted/get-editor-current-inline-styles old-state)
|
||||||
block-to-add-styles
|
block-to-setup (get-blocks-to-setup block-changes)
|
||||||
(->> block-states
|
block-to-add-styles (get-blocks-to-add-styles block-changes)]
|
||||||
(filter
|
(-> state
|
||||||
(fn [[_ v]]
|
(ted/setup-block-styles block-to-setup prev-data)
|
||||||
(and (not= (:old v) (:new v))
|
(ted/apply-block-styles-to-content block-to-add-styles)))
|
||||||
(= (:old v) ""))))
|
|
||||||
(mapv first))]
|
|
||||||
(ted/apply-block-styles-to-content state block-to-add-styles))
|
|
||||||
state))))
|
state))))
|
||||||
|
|
||||||
on-change
|
on-change
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(fn [val]
|
(fn [val]
|
||||||
(let [val (handle-change val)
|
(let [prev-val (mf/ref-val prev-value)
|
||||||
val (if (true? @blurred)
|
styleOverride (ted/get-style-override prev-val)
|
||||||
(ted/add-editor-blur-selection val)
|
|
||||||
(ted/remove-editor-blur-selection val))]
|
;; If the content and the selection are the same we keep the style override
|
||||||
|
keep-style? (and (some? styleOverride)
|
||||||
|
(ted/content-equals prev-val val)
|
||||||
|
(ted/selection-equals prev-val val))
|
||||||
|
|
||||||
|
val (cond-> (handle-change val)
|
||||||
|
@blurred
|
||||||
|
(ted/add-editor-blur-selection)
|
||||||
|
|
||||||
|
(not @blurred)
|
||||||
|
(ted/remove-editor-blur-selection)
|
||||||
|
|
||||||
|
keep-style?
|
||||||
|
(ted/set-style-override styleOverride))]
|
||||||
(st/emit! (dwt/update-editor-state shape val)))))
|
(st/emit! (dwt/update-editor-state shape val)))))
|
||||||
|
|
||||||
on-editor
|
on-editor
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.editable-select :refer [editable-select]]
|
[app.main.ui.components.editable-select :refer [editable-select]]
|
||||||
|
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
|
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
|
[app.util.strings :as ust]
|
||||||
[app.util.timers :as tm]
|
[app.util.timers :as tm]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[goog.events :as events]
|
[goog.events :as events]
|
||||||
|
@ -30,7 +32,7 @@
|
||||||
(defn- attr->string [value]
|
(defn- attr->string [value]
|
||||||
(if (= value :multiple)
|
(if (= value :multiple)
|
||||||
""
|
""
|
||||||
(str value)))
|
(ust/format-precision value 2)))
|
||||||
|
|
||||||
(defn- get-next-font
|
(defn- get-next-font
|
||||||
[{:keys [id] :as current} fonts]
|
[{:keys [id] :as current} fonts]
|
||||||
|
@ -350,20 +352,19 @@
|
||||||
letter-spacing (or letter-spacing "0")
|
letter-spacing (or letter-spacing "0")
|
||||||
|
|
||||||
handle-change
|
handle-change
|
||||||
(fn [event attr]
|
(fn [value attr]
|
||||||
(let [new-spacing (dom/get-target-val event)]
|
(on-change {attr (str value)}))]
|
||||||
(on-change {attr new-spacing})))]
|
|
||||||
|
|
||||||
[:div.spacing-options
|
[:div.spacing-options
|
||||||
[:div.input-icon
|
[:div.input-icon
|
||||||
[:span.icon-before.tooltip.tooltip-bottom
|
[:span.icon-before.tooltip.tooltip-bottom
|
||||||
{:alt (tr "workspace.options.text-options.line-height")}
|
{:alt (tr "workspace.options.text-options.line-height")}
|
||||||
i/line-height]
|
i/line-height]
|
||||||
[:input.input-text
|
[:> numeric-input
|
||||||
{:type "number"
|
{:min -200
|
||||||
:step "0.1"
|
:max 200
|
||||||
:min "-200"
|
:step 0.1
|
||||||
:max "200"
|
:precision 2
|
||||||
:value (attr->string line-height)
|
:value (attr->string line-height)
|
||||||
:placeholder (tr "settings.multiple")
|
:placeholder (tr "settings.multiple")
|
||||||
:on-change #(handle-change % :line-height)
|
:on-change #(handle-change % :line-height)
|
||||||
|
@ -373,11 +374,11 @@
|
||||||
[:span.icon-before.tooltip.tooltip-bottom
|
[:span.icon-before.tooltip.tooltip-bottom
|
||||||
{:alt (tr "workspace.options.text-options.letter-spacing")}
|
{:alt (tr "workspace.options.text-options.letter-spacing")}
|
||||||
i/letter-spacing]
|
i/letter-spacing]
|
||||||
[:input.input-text
|
[:> numeric-input
|
||||||
{:type "number"
|
{:min -200
|
||||||
:step "0.1"
|
:max 200
|
||||||
:min "-200"
|
:step 0.1
|
||||||
:max "200"
|
:precision 2
|
||||||
:value (attr->string letter-spacing)
|
:value (attr->string letter-spacing)
|
||||||
:placeholder (tr "settings.multiple")
|
:placeholder (tr "settings.multiple")
|
||||||
:on-change #(handle-change % :letter-spacing)
|
:on-change #(handle-change % :letter-spacing)
|
||||||
|
|
|
@ -16,30 +16,54 @@
|
||||||
(defn- text-corrected-transform
|
(defn- text-corrected-transform
|
||||||
"If we apply a scale directly to the texts it will show deformed so we need to create this
|
"If we apply a scale directly to the texts it will show deformed so we need to create this
|
||||||
correction matrix to \"undo\" the resize but keep the other transformations."
|
correction matrix to \"undo\" the resize but keep the other transformations."
|
||||||
[{:keys [points transform transform-inverse]} current-transform modifiers]
|
[{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers]
|
||||||
|
|
||||||
(let [corner-pt (first points)
|
(let [corner-pt (first points)
|
||||||
transform (or transform (gmt/matrix))
|
corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse))
|
||||||
transform-inverse (or transform-inverse (gmt/matrix))
|
|
||||||
|
|
||||||
current-transform
|
resize-x? (some? (:resize-vector modifiers))
|
||||||
(if (some? (:resize-vector modifiers))
|
resize-y? (some? (:resize-vector-2 modifiers))
|
||||||
(gmt/multiply
|
|
||||||
current-transform
|
|
||||||
transform
|
|
||||||
(gmt/scale-matrix (gpt/inverse (:resize-vector modifiers)) (gpt/transform corner-pt transform-inverse))
|
|
||||||
transform-inverse)
|
|
||||||
current-transform)
|
|
||||||
|
|
||||||
current-transform
|
flip-x? (neg? (get-in modifiers [:resize-vector :x]))
|
||||||
(if (some? (:resize-vector-2 modifiers))
|
flip-y? (or (neg? (get-in modifiers [:resize-vector :y]))
|
||||||
(gmt/multiply
|
(neg? (get-in modifiers [:resize-vector-2 :y])))
|
||||||
current-transform
|
|
||||||
transform
|
result (cond-> (gmt/matrix)
|
||||||
(gmt/scale-matrix (gpt/inverse (:resize-vector-2 modifiers)) (gpt/transform corner-pt transform-inverse))
|
(and (some? transform) (or resize-x? resize-y?))
|
||||||
transform-inverse)
|
(gmt/multiply transform)
|
||||||
current-transform)]
|
|
||||||
current-transform))
|
resize-x?
|
||||||
|
(gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt)
|
||||||
|
|
||||||
|
resize-y?
|
||||||
|
(gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt)
|
||||||
|
|
||||||
|
flip-x?
|
||||||
|
(gmt/scale (gpt/point -1 1) corner-pt)
|
||||||
|
|
||||||
|
flip-y?
|
||||||
|
(gmt/scale (gpt/point 1 -1) corner-pt)
|
||||||
|
|
||||||
|
(and (some? transform) (or resize-x? resize-y?))
|
||||||
|
(gmt/multiply transform-inverse))
|
||||||
|
|
||||||
|
[width height]
|
||||||
|
(if (or resize-x? resize-y?)
|
||||||
|
(let [pc (-> (gpt/point x y)
|
||||||
|
(gpt/transform transform)
|
||||||
|
(gpt/transform current-transform))
|
||||||
|
|
||||||
|
pw (-> (gpt/point (+ x width) y)
|
||||||
|
(gpt/transform transform)
|
||||||
|
(gpt/transform current-transform))
|
||||||
|
|
||||||
|
ph (-> (gpt/point x (+ y height))
|
||||||
|
(gpt/transform transform)
|
||||||
|
(gpt/transform current-transform))]
|
||||||
|
[(gpt/distance pc pw) (gpt/distance pc ph)])
|
||||||
|
[width height])]
|
||||||
|
|
||||||
|
[result width height]))
|
||||||
|
|
||||||
(defn get-nodes
|
(defn get-nodes
|
||||||
"Retrieve the DOM nodes to apply the matrix transformation"
|
"Retrieve the DOM nodes to apply the matrix transformation"
|
||||||
|
@ -48,6 +72,7 @@
|
||||||
|
|
||||||
frame? (= :frame type)
|
frame? (= :frame type)
|
||||||
group? (= :group type)
|
group? (= :group type)
|
||||||
|
text? (= :text type)
|
||||||
mask? (and group? masked-group?)
|
mask? (and group? masked-group?)
|
||||||
|
|
||||||
;; When the shape is a frame we maybe need to move its thumbnail
|
;; When the shape is a frame we maybe need to move its thumbnail
|
||||||
|
@ -68,6 +93,11 @@
|
||||||
group?
|
group?
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
text?
|
||||||
|
[shape-node
|
||||||
|
(dom/query shape-node "foreignObject")
|
||||||
|
(dom/query shape-node ".text-shape")]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[shape-node])))
|
[shape-node])))
|
||||||
|
|
||||||
|
@ -76,11 +106,23 @@
|
||||||
(when-let [nodes (get-nodes shape)]
|
(when-let [nodes (get-nodes shape)]
|
||||||
(let [transform (get transforms id)
|
(let [transform (get transforms id)
|
||||||
modifiers (get-in modifiers [id :modifiers])
|
modifiers (get-in modifiers [id :modifiers])
|
||||||
transform (case type
|
|
||||||
:text (text-corrected-transform shape transform modifiers)
|
[text-transform text-width text-height]
|
||||||
transform)]
|
(when (= :text type)
|
||||||
|
(text-corrected-transform shape transform modifiers))]
|
||||||
|
|
||||||
(doseq [node nodes]
|
(doseq [node nodes]
|
||||||
(when (and (some? transform) (some? node))
|
(cond
|
||||||
|
(dom/class? node "text-shape")
|
||||||
|
(when (some? text-transform)
|
||||||
|
(dom/set-attribute node "transform" (str text-transform)))
|
||||||
|
|
||||||
|
(= (dom/get-tag-name node) "foreignObject")
|
||||||
|
(when (and (some? text-width) (some? text-height))
|
||||||
|
(dom/set-attribute node "width" text-width)
|
||||||
|
(dom/set-attribute node "height" text-height))
|
||||||
|
|
||||||
|
(and (some? transform) (some? node))
|
||||||
(dom/set-attribute node "transform" (str transform))))))))
|
(dom/set-attribute node "transform" (str transform))))))))
|
||||||
|
|
||||||
(defn remove-transform [shapes]
|
(defn remove-transform [shapes]
|
||||||
|
@ -88,7 +130,13 @@
|
||||||
(when-let [nodes (get-nodes shape)]
|
(when-let [nodes (get-nodes shape)]
|
||||||
(doseq [node nodes]
|
(doseq [node nodes]
|
||||||
(when (some? node)
|
(when (some? node)
|
||||||
(dom/remove-attribute node "transform"))))))
|
(cond
|
||||||
|
(= (dom/get-tag-name node) "foreignObject")
|
||||||
|
;; The shape width/height will be automaticaly setup when the modifiers are applied
|
||||||
|
nil
|
||||||
|
|
||||||
|
:else
|
||||||
|
(dom/remove-attribute node "transform")))))))
|
||||||
|
|
||||||
(defn format-viewbox [vbox]
|
(defn format-viewbox [vbox]
|
||||||
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
|
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
|
||||||
|
|
|
@ -17,21 +17,24 @@
|
||||||
;; --- Deprecated methods
|
;; --- Deprecated methods
|
||||||
|
|
||||||
(defn event->inner-text
|
(defn event->inner-text
|
||||||
[e]
|
[^js e]
|
||||||
(.-innerText (.-target e)))
|
(when (some? e)
|
||||||
|
(.-innerText (.-target e))))
|
||||||
|
|
||||||
(defn event->value
|
(defn event->value
|
||||||
[e]
|
[^js e]
|
||||||
(.-value (.-target e)))
|
(when (some? e)
|
||||||
|
(.-value (.-target e))))
|
||||||
|
|
||||||
(defn event->target
|
(defn event->target
|
||||||
[e]
|
[^js e]
|
||||||
(.-target e))
|
(when (some? e)
|
||||||
|
(.-target e)))
|
||||||
|
|
||||||
;; --- New methods
|
;; --- New methods
|
||||||
|
|
||||||
(defn set-html-title
|
(defn set-html-title
|
||||||
[title]
|
[^string title]
|
||||||
(set! (.-title globals/document) title))
|
(set! (.-title globals/document) title))
|
||||||
|
|
||||||
(defn set-page-style
|
(defn set-page-style
|
||||||
|
@ -61,98 +64,117 @@
|
||||||
(dom/getElement id))
|
(dom/getElement id))
|
||||||
|
|
||||||
(defn get-elements-by-tag
|
(defn get-elements-by-tag
|
||||||
[node tag]
|
[^js node tag]
|
||||||
(.getElementsByTagName node tag))
|
(when (some? node)
|
||||||
|
(.getElementsByTagName node tag)))
|
||||||
|
|
||||||
(defn stop-propagation
|
(defn stop-propagation
|
||||||
[e]
|
[^js event]
|
||||||
(when e
|
(when event
|
||||||
(.stopPropagation e)))
|
(.stopPropagation event)))
|
||||||
|
|
||||||
(defn prevent-default
|
(defn prevent-default
|
||||||
[e]
|
[^js event]
|
||||||
(when e
|
(when event
|
||||||
(.preventDefault e)))
|
(.preventDefault event)))
|
||||||
|
|
||||||
(defn get-target
|
(defn get-target
|
||||||
"Extract the target from event instance."
|
"Extract the target from event instance."
|
||||||
[event]
|
[^js event]
|
||||||
(.-target event))
|
(when (some? event)
|
||||||
|
(.-target event)))
|
||||||
|
|
||||||
(defn get-current-target
|
(defn get-current-target
|
||||||
"Extract the current target from event instance (different from target
|
"Extract the current target from event instance (different from target
|
||||||
when event triggered in a child of the subscribing element)."
|
when event triggered in a child of the subscribing element)."
|
||||||
[event]
|
[^js event]
|
||||||
(.-currentTarget event))
|
(when (some? event)
|
||||||
|
(.-currentTarget event)))
|
||||||
|
|
||||||
(defn get-parent
|
(defn get-parent
|
||||||
[dom]
|
[^js node]
|
||||||
(.-parentElement ^js dom))
|
(when (some? node)
|
||||||
|
(.-parentElement ^js node)))
|
||||||
|
|
||||||
(defn get-value
|
(defn get-value
|
||||||
"Extract the value from dom node."
|
"Extract the value from dom node."
|
||||||
[node]
|
[^js node]
|
||||||
(.-value node))
|
(when (some? node)
|
||||||
|
(.-value node)))
|
||||||
|
|
||||||
(defn get-attribute
|
(defn get-attribute
|
||||||
"Extract the value of one attribute of a dom node."
|
"Extract the value of one attribute of a dom node."
|
||||||
[node attr-name]
|
[^js node ^string attr-name]
|
||||||
(.getAttribute ^js node attr-name))
|
(when (some? node)
|
||||||
|
(.getAttribute ^js node attr-name)))
|
||||||
|
|
||||||
(def get-target-val (comp get-value get-target))
|
(def get-target-val (comp get-value get-target))
|
||||||
|
|
||||||
(defn click
|
(defn click
|
||||||
"Click a node"
|
"Click a node"
|
||||||
[node]
|
[^js node]
|
||||||
(.click node))
|
(when (some? node)
|
||||||
|
(.click node)))
|
||||||
|
|
||||||
(defn get-files
|
(defn get-files
|
||||||
"Extract the files from dom node."
|
"Extract the files from dom node."
|
||||||
[node]
|
[^js node]
|
||||||
(array-seq (.-files node)))
|
(when (some? node)
|
||||||
|
(array-seq (.-files node))))
|
||||||
|
|
||||||
(defn checked?
|
(defn checked?
|
||||||
"Check if the node that represents a radio
|
"Check if the node that represents a radio
|
||||||
or checkbox is checked or not."
|
or checkbox is checked or not."
|
||||||
[node]
|
[^js node]
|
||||||
(.-checked node))
|
(when (some? node)
|
||||||
|
(.-checked node)))
|
||||||
|
|
||||||
(defn valid?
|
(defn valid?
|
||||||
"Check if the node that is a form input
|
"Check if the node that is a form input
|
||||||
has a valid value, against html5 form validation
|
has a valid value, against html5 form validation
|
||||||
properties (required, min/max, pattern...)."
|
properties (required, min/max, pattern...)."
|
||||||
[node]
|
[^js node]
|
||||||
(.-valid (.-validity node)))
|
(when (some? node)
|
||||||
|
(when-let [validity (.-validity node)]
|
||||||
|
(.-valid validity))))
|
||||||
|
|
||||||
(defn set-validity!
|
(defn set-validity!
|
||||||
"Manually set the validity status of a node that
|
"Manually set the validity status of a node that
|
||||||
is a form input. If the state is an empty string,
|
is a form input. If the state is an empty string,
|
||||||
the input will be valid. If not, the string will
|
the input will be valid. If not, the string will
|
||||||
be set as the error message."
|
be set as the error message."
|
||||||
[node status]
|
[^js node status]
|
||||||
(.setCustomValidity node status)
|
(when (some? node)
|
||||||
(.reportValidity node))
|
(.setCustomValidity node status)
|
||||||
|
(.reportValidity node)))
|
||||||
|
|
||||||
(defn clean-value!
|
(defn clean-value!
|
||||||
[node]
|
[^js node]
|
||||||
(set! (.-value node) ""))
|
(when (some? node)
|
||||||
|
(set! (.-value node) "")))
|
||||||
|
|
||||||
(defn set-value!
|
(defn set-value!
|
||||||
[node value]
|
[^js node value]
|
||||||
(set! (.-value ^js node) value))
|
(when (some? node)
|
||||||
|
(set! (.-value ^js node) value)))
|
||||||
|
|
||||||
(defn select-text!
|
(defn select-text!
|
||||||
[node]
|
[^js node]
|
||||||
(.select ^js node))
|
(when (some? node)
|
||||||
|
(.select ^js node)))
|
||||||
|
|
||||||
(defn ^boolean equals?
|
(defn ^boolean equals?
|
||||||
[node-a node-b]
|
[^js node-a ^js node-b]
|
||||||
(.isEqualNode ^js node-a node-b))
|
|
||||||
|
(or (and (nil? node-a) (nil? node-b))
|
||||||
|
(and (some? node-a)
|
||||||
|
(.isEqualNode ^js node-a node-b))))
|
||||||
|
|
||||||
(defn get-event-files
|
(defn get-event-files
|
||||||
"Extract the files from event instance."
|
"Extract the files from event instance."
|
||||||
[event]
|
[^js event]
|
||||||
(get-files (get-target event)))
|
(when (some? event)
|
||||||
|
(get-files (get-target event))))
|
||||||
|
|
||||||
(defn create-element
|
(defn create-element
|
||||||
([tag]
|
([tag]
|
||||||
|
@ -161,50 +183,58 @@
|
||||||
(.createElementNS globals/document ns tag)))
|
(.createElementNS globals/document ns tag)))
|
||||||
|
|
||||||
(defn set-html!
|
(defn set-html!
|
||||||
[el html]
|
[^js el html]
|
||||||
(set! (.-innerHTML el) html))
|
(when (some? el)
|
||||||
|
(set! (.-innerHTML el) html)))
|
||||||
|
|
||||||
(defn append-child!
|
(defn append-child!
|
||||||
[el child]
|
[^js el child]
|
||||||
(.appendChild ^js el child))
|
(when (some? el)
|
||||||
|
(.appendChild ^js el child)))
|
||||||
|
|
||||||
(defn get-first-child
|
(defn get-first-child
|
||||||
[el]
|
[^js el]
|
||||||
(.-firstChild el))
|
(when (some? el)
|
||||||
|
(.-firstChild el)))
|
||||||
|
|
||||||
(defn get-tag-name
|
(defn get-tag-name
|
||||||
[el]
|
[^js el]
|
||||||
(.-tagName el))
|
(when (some? el)
|
||||||
|
(.-tagName el)))
|
||||||
|
|
||||||
(defn get-outer-html
|
(defn get-outer-html
|
||||||
[el]
|
[^js el]
|
||||||
(.-outerHTML el))
|
(when (some? el)
|
||||||
|
(.-outerHTML el)))
|
||||||
|
|
||||||
(defn get-inner-text
|
(defn get-inner-text
|
||||||
[el]
|
[^js el]
|
||||||
(.-innerText el))
|
(when (some? el)
|
||||||
|
(.-innerText el)))
|
||||||
|
|
||||||
(defn query
|
(defn query
|
||||||
[el query]
|
[^js el ^string query]
|
||||||
(when (some? el)
|
(when (some? el)
|
||||||
(.querySelector el query)))
|
(.querySelector el query)))
|
||||||
|
|
||||||
(defn get-client-position
|
(defn get-client-position
|
||||||
[event]
|
[^js event]
|
||||||
(let [x (.-clientX event)
|
(let [x (.-clientX event)
|
||||||
y (.-clientY event)]
|
y (.-clientY event)]
|
||||||
(gpt/point x y)))
|
(gpt/point x y)))
|
||||||
|
|
||||||
(defn get-offset-position
|
(defn get-offset-position
|
||||||
[event]
|
[^js event]
|
||||||
(let [x (.-offsetX event)
|
(when (some? event)
|
||||||
y (.-offsetY event)]
|
(let [x (.-offsetX event)
|
||||||
(gpt/point x y)))
|
y (.-offsetY event)]
|
||||||
|
(gpt/point x y))))
|
||||||
|
|
||||||
(defn get-client-size
|
(defn get-client-size
|
||||||
[node]
|
[^js node]
|
||||||
{:width (.-clientWidth ^js node)
|
(when (some? node)
|
||||||
:height (.-clientHeight ^js node)})
|
{:width (.-clientWidth ^js node)
|
||||||
|
:height (.-clientHeight ^js node)}))
|
||||||
|
|
||||||
(defn get-bounding-rect
|
(defn get-bounding-rect
|
||||||
[node]
|
[node]
|
||||||
|
@ -222,12 +252,12 @@
|
||||||
:height (.-innerHeight ^js js/window)})
|
:height (.-innerHeight ^js js/window)})
|
||||||
|
|
||||||
(defn focus!
|
(defn focus!
|
||||||
[node]
|
[^js node]
|
||||||
(when (some? node)
|
(when (some? node)
|
||||||
(.focus node)))
|
(.focus node)))
|
||||||
|
|
||||||
(defn blur!
|
(defn blur!
|
||||||
[node]
|
[^js node]
|
||||||
(when (some? node)
|
(when (some? node)
|
||||||
(.blur node)))
|
(.blur node)))
|
||||||
|
|
||||||
|
@ -245,8 +275,9 @@
|
||||||
:hint "seems like the current browser does not support fullscreen api.")))
|
:hint "seems like the current browser does not support fullscreen api.")))
|
||||||
|
|
||||||
(defn ^boolean blob?
|
(defn ^boolean blob?
|
||||||
[v]
|
[^js v]
|
||||||
(instance? js/Blob v))
|
(when (some? v)
|
||||||
|
(instance? js/Blob v)))
|
||||||
|
|
||||||
(defn create-blob
|
(defn create-blob
|
||||||
"Create a blob from content."
|
"Create a blob from content."
|
||||||
|
@ -265,20 +296,24 @@
|
||||||
{:pre [(blob? b)]}
|
{:pre [(blob? b)]}
|
||||||
(js/URL.createObjectURL b))
|
(js/URL.createObjectURL b))
|
||||||
|
|
||||||
(defn set-property! [node property value]
|
(defn set-property! [^js node property value]
|
||||||
(.setAttribute node property value))
|
(when (some? node)
|
||||||
|
(.setAttribute node property value)))
|
||||||
|
|
||||||
(defn set-text! [node text]
|
(defn set-text! [^js node text]
|
||||||
(set! (.-textContent node) text))
|
(when (some? node)
|
||||||
|
(set! (.-textContent node) text)))
|
||||||
|
|
||||||
(defn set-css-property! [node property value]
|
(defn set-css-property! [^js node property value]
|
||||||
(.setProperty (.-style ^js node) property value))
|
(when (some? node)
|
||||||
|
(.setProperty (.-style ^js node) property value)))
|
||||||
|
|
||||||
(defn capture-pointer [event]
|
(defn capture-pointer [^js event]
|
||||||
(-> event get-target (.setPointerCapture (.-pointerId event))))
|
(when (some? event)
|
||||||
|
(-> event get-target (.setPointerCapture (.-pointerId event)))))
|
||||||
|
|
||||||
(defn release-pointer [event]
|
(defn release-pointer [^js event]
|
||||||
(when (.-pointerId event)
|
(when (and (some? event) (.-pointerId event))
|
||||||
(-> event get-target (.releasePointerCapture (.-pointerId event)))))
|
(-> event get-target (.releasePointerCapture (.-pointerId event)))))
|
||||||
|
|
||||||
(defn get-root []
|
(defn get-root []
|
||||||
|
@ -295,19 +330,23 @@
|
||||||
(partition 2 params))))
|
(partition 2 params))))
|
||||||
|
|
||||||
(defn ^boolean class? [node class-name]
|
(defn ^boolean class? [node class-name]
|
||||||
(let [class-list (.-classList ^js node)]
|
(when (some? node)
|
||||||
(.contains ^js class-list class-name)))
|
(let [class-list (.-classList ^js node)]
|
||||||
|
(.contains ^js class-list class-name))))
|
||||||
|
|
||||||
(defn add-class! [node class-name]
|
(defn add-class! [^js node class-name]
|
||||||
(let [class-list (.-classList ^js node)]
|
(when (some? node)
|
||||||
(.add ^js class-list class-name)))
|
(let [class-list (.-classList ^js node)]
|
||||||
|
(.add ^js class-list class-name))))
|
||||||
|
|
||||||
(defn remove-class! [node class-name]
|
(defn remove-class! [^js node class-name]
|
||||||
(let [class-list (.-classList ^js node)]
|
(when (some? node)
|
||||||
(.remove ^js class-list class-name)))
|
(let [class-list (.-classList ^js node)]
|
||||||
|
(.remove ^js class-list class-name))))
|
||||||
|
|
||||||
(defn child? [node1 node2]
|
(defn child? [^js node1 ^js node2]
|
||||||
(.contains ^js node2 ^js node1))
|
(when (some? node1)
|
||||||
|
(.contains ^js node2 ^js node1)))
|
||||||
|
|
||||||
(defn get-user-agent []
|
(defn get-user-agent []
|
||||||
(.-userAgent globals/navigator))
|
(.-userAgent globals/navigator))
|
||||||
|
@ -315,11 +354,13 @@
|
||||||
(defn get-active []
|
(defn get-active []
|
||||||
(.-activeElement globals/document))
|
(.-activeElement globals/document))
|
||||||
|
|
||||||
(defn active? [node]
|
(defn active? [^js node]
|
||||||
(= (get-active) node))
|
(when (some? node)
|
||||||
|
(= (get-active) node)))
|
||||||
|
|
||||||
(defn get-data [^js node ^string attr]
|
(defn get-data [^js node ^string attr]
|
||||||
(.getAttribute node (str "data-" attr)))
|
(when (some? node)
|
||||||
|
(.getAttribute node (str "data-" attr))))
|
||||||
|
|
||||||
(defn mtype->extension [mtype]
|
(defn mtype->extension [mtype]
|
||||||
;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
||||||
|
@ -336,42 +377,53 @@
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defn set-attribute [^js node ^string attr value]
|
(defn set-attribute [^js node ^string attr value]
|
||||||
(.setAttribute node attr value))
|
(when (some? node)
|
||||||
|
(.setAttribute node attr value)))
|
||||||
|
|
||||||
(defn remove-attribute [^js node ^string attr]
|
(defn remove-attribute [^js node ^string attr]
|
||||||
(.removeAttribute node attr))
|
(when (some? node)
|
||||||
|
(.removeAttribute node attr)))
|
||||||
|
|
||||||
(defn get-scroll-pos
|
(defn get-scroll-pos
|
||||||
[element]
|
[^js element]
|
||||||
(.-scrollTop ^js element))
|
(when (some? element)
|
||||||
|
(.-scrollTop element)))
|
||||||
|
|
||||||
(defn set-scroll-pos!
|
(defn set-scroll-pos!
|
||||||
[element scroll]
|
[^js element scroll]
|
||||||
(obj/set! ^js element "scrollTop" scroll))
|
(when (some? element)
|
||||||
|
(obj/set! element "scrollTop" scroll)))
|
||||||
|
|
||||||
(defn scroll-into-view!
|
(defn scroll-into-view!
|
||||||
([element]
|
([^js element]
|
||||||
(.scrollIntoView ^js element false))
|
(when (some? element)
|
||||||
([element scroll-top]
|
(.scrollIntoView element false)))
|
||||||
(.scrollIntoView ^js element scroll-top)))
|
|
||||||
|
([^js element scroll-top]
|
||||||
|
(when (some? element)
|
||||||
|
(.scrollIntoView element scroll-top))))
|
||||||
|
|
||||||
(defn scroll-into-view-if-needed!
|
(defn scroll-into-view-if-needed!
|
||||||
([element]
|
([^js element]
|
||||||
(.scrollIntoViewIfNeeded ^js element false))
|
(when (some? element)
|
||||||
([element scroll-top]
|
(.scrollIntoViewIfNeeded ^js element false)))
|
||||||
(.scrollIntoViewIfNeeded ^js element scroll-top)))
|
|
||||||
|
([^js element scroll-top]
|
||||||
|
(when (some? element)
|
||||||
|
(.scrollIntoViewIfNeeded ^js element scroll-top))))
|
||||||
|
|
||||||
(defn is-in-viewport?
|
(defn is-in-viewport?
|
||||||
[element]
|
[^js element]
|
||||||
(let [rect (.getBoundingClientRect element)
|
(when (some? element)
|
||||||
height (or (.-innerHeight js/window)
|
(let [rect (.getBoundingClientRect element)
|
||||||
(.. js/document -documentElement -clientHeight))
|
height (or (.-innerHeight js/window)
|
||||||
width (or (.-innerWidth js/window)
|
(.. js/document -documentElement -clientHeight))
|
||||||
(.. js/document -documentElement -clientWidth))]
|
width (or (.-innerWidth js/window)
|
||||||
(and (>= (.-top rect) 0)
|
(.. js/document -documentElement -clientWidth))]
|
||||||
(>= (.-left rect) 0)
|
(and (>= (.-top rect) 0)
|
||||||
(<= (.-bottom rect) height)
|
(>= (.-left rect) 0)
|
||||||
(<= (.-right rect) width))))
|
(<= (.-bottom rect) height)
|
||||||
|
(<= (.-right rect) width)))))
|
||||||
|
|
||||||
(defn trigger-download-uri
|
(defn trigger-download-uri
|
||||||
[filename mtype uri]
|
[filename mtype uri]
|
||||||
|
|
38
frontend/src/app/util/strings.cljs
Normal file
38
frontend/src/app/util/strings.cljs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
;; 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) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.util.strings
|
||||||
|
(:require
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
|
||||||
|
(def ^:const trail-zeros-regex-1 #"\.0+$")
|
||||||
|
(def ^:const trail-zeros-regex-2 #"(\.\d*[^0])0+$")
|
||||||
|
|
||||||
|
(defn format-precision
|
||||||
|
"Creates a number with predetermined precision and then removes the trailing 0.
|
||||||
|
Examples:
|
||||||
|
12.0123, 0 => 12
|
||||||
|
12.0123, 1 => 12
|
||||||
|
12.0123, 2 => 12.01"
|
||||||
|
[num precision]
|
||||||
|
|
||||||
|
(try
|
||||||
|
(if (number? num)
|
||||||
|
(let [num-str (.toFixed num precision)
|
||||||
|
|
||||||
|
;; Remove all trailing zeros after the comma 100.00000
|
||||||
|
num-str (str/replace num-str trail-zeros-regex-1 "")
|
||||||
|
|
||||||
|
;; Remove trailing zeros after a decimal number: 0.001|00|
|
||||||
|
num-str (if-let [m (re-find trail-zeros-regex-2 num-str)]
|
||||||
|
(str/replace num-str (first m) (second m))
|
||||||
|
num-str)]
|
||||||
|
num-str)
|
||||||
|
(str num))
|
||||||
|
(catch :default _
|
||||||
|
(str num))))
|
||||||
|
|
|
@ -111,6 +111,16 @@
|
||||||
[state]
|
[state]
|
||||||
(impl/cursorToEnd state))
|
(impl/cursorToEnd state))
|
||||||
|
|
||||||
|
(defn setup-block-styles
|
||||||
|
[state blocks attrs]
|
||||||
|
(if (empty? blocks)
|
||||||
|
state
|
||||||
|
(->> blocks
|
||||||
|
(reduce
|
||||||
|
(fn [state block-key]
|
||||||
|
(impl/updateBlockData state block-key (clj->js attrs)))
|
||||||
|
state))))
|
||||||
|
|
||||||
(defn apply-block-styles-to-content
|
(defn apply-block-styles-to-content
|
||||||
[state blocks]
|
[state blocks]
|
||||||
(if (empty? blocks)
|
(if (empty? blocks)
|
||||||
|
@ -130,3 +140,37 @@
|
||||||
(defn insert-text [state text attrs]
|
(defn insert-text [state text attrs]
|
||||||
(let [style (txt/attrs-to-styles attrs)]
|
(let [style (txt/attrs-to-styles attrs)]
|
||||||
(impl/insertText state text (clj->js attrs) (clj->js style))))
|
(impl/insertText state text (clj->js attrs) (clj->js style))))
|
||||||
|
|
||||||
|
(defn get-style-override [state]
|
||||||
|
(.getInlineStyleOverride state))
|
||||||
|
|
||||||
|
(defn set-style-override [state inline-style]
|
||||||
|
(impl/setInlineStyleOverride state inline-style))
|
||||||
|
|
||||||
|
(defn content-equals [state other]
|
||||||
|
(.equals (.getCurrentContent state) (.getCurrentContent other)))
|
||||||
|
|
||||||
|
(defn selection-equals [state other]
|
||||||
|
(impl/selectionEquals (.getSelection state) (.getSelection other)))
|
||||||
|
|
||||||
|
(defn get-content-changes
|
||||||
|
[old-state state]
|
||||||
|
(let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state)))
|
||||||
|
:keywordize-keys false)
|
||||||
|
new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state)))
|
||||||
|
:keywordize-keys false)]
|
||||||
|
(merge
|
||||||
|
(into {}
|
||||||
|
(comp (filter #(contains? new-blocks (first %)))
|
||||||
|
(map (fn [[bkey bstate]]
|
||||||
|
[bkey
|
||||||
|
{:old (get bstate "text")
|
||||||
|
:new (get-in new-blocks [bkey "text"])}])))
|
||||||
|
old-blocks)
|
||||||
|
(into {}
|
||||||
|
(comp (filter #(not (contains? old-blocks (first %))))
|
||||||
|
(map (fn [[bkey bstate]]
|
||||||
|
[bkey
|
||||||
|
{:old nil
|
||||||
|
:new (get bstate "text")}])))
|
||||||
|
new-blocks))))
|
||||||
|
|
|
@ -121,15 +121,33 @@ export function updateCurrentBlockData(state, attrs) {
|
||||||
return EditorState.push(state, content, "change-block-data");
|
return EditorState.push(state, content, "change-block-data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addStylesToOverride(styles, other) {
|
||||||
|
let result = styles;
|
||||||
|
|
||||||
|
for (let style of other) {
|
||||||
|
const [p, k, v] = style.split("$$$");
|
||||||
|
const prefix = [p, k, ""].join("$$$");
|
||||||
|
|
||||||
|
const curValue = result.find((it) => it.startsWith(prefix))
|
||||||
|
if (curValue) {
|
||||||
|
result = result.remove(curValue);
|
||||||
|
}
|
||||||
|
result = result.add(style);
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export function applyInlineStyle(state, styles) {
|
export function applyInlineStyle(state, styles) {
|
||||||
const userSelection = state.getSelection();
|
const userSelection = state.getSelection();
|
||||||
let selection = userSelection;
|
let selection = userSelection;
|
||||||
|
let result = state;
|
||||||
|
|
||||||
if (selection.isCollapsed()) {
|
if (selection.isCollapsed()) {
|
||||||
selection = getSelectAllSelection(state);
|
const currentOverride = state.getCurrentInlineStyle() || new OrderedSet();
|
||||||
|
const styleOverride = addStylesToOverride(currentOverride, styles)
|
||||||
|
return EditorState.setInlineStyleOverride(state, styleOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = state;
|
|
||||||
let content = null;
|
let content = null;
|
||||||
|
|
||||||
for (let style of styles) {
|
for (let style of styles) {
|
||||||
|
@ -300,6 +318,7 @@ export function getBlockData(state, blockKey) {
|
||||||
|
|
||||||
export function updateBlockData(state, blockKey, data) {
|
export function updateBlockData(state, blockKey, data) {
|
||||||
const userSelection = state.getSelection();
|
const userSelection = state.getSelection();
|
||||||
|
const inlineStyleOverride = state.getInlineStyleOverride();
|
||||||
const content = state.getCurrentContent();
|
const content = state.getCurrentContent();
|
||||||
const block = content.getBlockForKey(blockKey);
|
const block = content.getBlockForKey(blockKey);
|
||||||
const newBlock = mergeBlockData(block, data);
|
const newBlock = mergeBlockData(block, data);
|
||||||
|
@ -312,8 +331,10 @@ export function updateBlockData(state, blockKey, data) {
|
||||||
blockData
|
blockData
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = EditorState.push(state, newContent, 'change-block-data');
|
let result = EditorState.push(state, newContent, 'change-block-data');
|
||||||
return EditorState.acceptSelection(result, userSelection);
|
result = EditorState.acceptSelection(result, userSelection);
|
||||||
|
result = EditorState.setInlineStyleOverride(result, inlineStyleOverride);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSelection(state) {
|
export function getSelection(state) {
|
||||||
|
@ -376,3 +397,15 @@ export function insertText(state, text, attrs, inlineStyles) {
|
||||||
const resultSelection = SelectionState.createEmpty(selection.getStartKey());
|
const resultSelection = SelectionState.createEmpty(selection.getStartKey());
|
||||||
return EditorState.push(state, newContent, 'insert-fragment');
|
return EditorState.push(state, newContent, 'insert-fragment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setInlineStyleOverride(state, inlineStyles) {
|
||||||
|
return EditorState.setInlineStyleOverride(state, inlineStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectionEquals(selection, other) {
|
||||||
|
return selection.getAnchorKey() === other.getAnchorKey() &&
|
||||||
|
selection.getAnchorOffset() === other.getAnchorOffset() &&
|
||||||
|
selection.getFocusKey() === other.getFocusKey() &&
|
||||||
|
selection.getFocusOffset() === other.getFocusOffset() &&
|
||||||
|
selection.getIsBackward() === other.getIsBackward();
|
||||||
|
}
|
||||||
|
|
|
@ -176,7 +176,9 @@
|
||||||
(rx/tap #(reset! revn (:revn %)))
|
(rx/tap #(reset! revn (:revn %)))
|
||||||
(rx/ignore))
|
(rx/ignore))
|
||||||
|
|
||||||
(rp/mutation :persist-temp-file {:id file-id}))))
|
(->> (rp/mutation :persist-temp-file {:id file-id})
|
||||||
|
;; We use merge to keep some information not stored in back-end
|
||||||
|
(rx/map #(merge file %))))))
|
||||||
|
|
||||||
(defn upload-media-files
|
(defn upload-media-files
|
||||||
"Upload a image to the backend and returns its id"
|
"Upload a image to the backend and returns its id"
|
||||||
|
@ -457,8 +459,7 @@
|
||||||
|
|
||||||
(let [progress-str (rx/subject)
|
(let [progress-str (rx/subject)
|
||||||
context (assoc context :progress progress-str)]
|
context (assoc context :progress progress-str)]
|
||||||
(rx/merge
|
[progress-str
|
||||||
progress-str
|
|
||||||
(->> (rx/of file)
|
(->> (rx/of file)
|
||||||
(rx/flat-map (partial process-pages context))
|
(rx/flat-map (partial process-pages context))
|
||||||
(rx/tap #(progress! context :process-colors))
|
(rx/tap #(progress! context :process-colors))
|
||||||
|
@ -470,7 +471,7 @@
|
||||||
(rx/tap #(progress! context :process-components))
|
(rx/tap #(progress! context :process-components))
|
||||||
(rx/flat-map (partial process-library-components context))
|
(rx/flat-map (partial process-library-components context))
|
||||||
(rx/flat-map (partial send-changes context))
|
(rx/flat-map (partial send-changes context))
|
||||||
(rx/tap #(rx/end! progress-str))))))
|
(rx/tap #(rx/end! progress-str)))]))
|
||||||
|
|
||||||
(defn create-files
|
(defn create-files
|
||||||
[context files]
|
[context files]
|
||||||
|
@ -482,7 +483,6 @@
|
||||||
(rx/flat-map
|
(rx/flat-map
|
||||||
(fn [context]
|
(fn [context]
|
||||||
(->> (create-file context)
|
(->> (create-file context)
|
||||||
(rx/tap #(.log js/console "create-file" (clj->js %)))
|
|
||||||
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
||||||
|
|
||||||
(->> (rx/from files)
|
(->> (rx/from files)
|
||||||
|
@ -509,20 +509,29 @@
|
||||||
(let [context {:project-id project-id
|
(let [context {:project-id project-id
|
||||||
:resolve (resolve-factory)}]
|
:resolve (resolve-factory)}]
|
||||||
(->> (create-files context files)
|
(->> (create-files context files)
|
||||||
(rx/catch #(.error js/console "IMPORT ERROR" %))
|
(rx/catch #(.error js/console "IMPORT ERROR" (clj->js %)))
|
||||||
(rx/flat-map
|
(rx/flat-map
|
||||||
(fn [[file data]]
|
(fn [[file data]]
|
||||||
(->> (rx/concat
|
(->> (rx/concat
|
||||||
(->> (uz/load-from-url (:uri data))
|
(->> (uz/load-from-url (:uri data))
|
||||||
(rx/map #(-> context (assoc :zip %) (merge data)))
|
(rx/map #(-> context (assoc :zip %) (merge data)))
|
||||||
(rx/flat-map #(process-file % file)))
|
(rx/flat-map
|
||||||
(rx/of
|
(fn [context]
|
||||||
{:status :import-success
|
;; process file retrieves a stream that will emit progress notifications
|
||||||
:file-id (:file-id data)}))
|
;; and other that will emit the files once imported
|
||||||
|
(let [[progress-stream file-stream] (process-file context file)]
|
||||||
|
(rx/merge
|
||||||
|
progress-stream
|
||||||
|
(->> file-stream
|
||||||
|
(rx/map
|
||||||
|
(fn [file]
|
||||||
|
{:status :import-finish
|
||||||
|
:errors (:errors file)
|
||||||
|
:file-id (:file-id data)})))))))))
|
||||||
|
|
||||||
(rx/catch
|
(rx/catch
|
||||||
(fn [err]
|
(fn [err]
|
||||||
(.error js/console "ERROR" (:file-id data) err)
|
(.error js/console "ERROR" (str (:file-id data)) (clj->js err) (clj->js (.-data err)))
|
||||||
(rx/of {:status :import-error
|
(rx/of {:status :import-error
|
||||||
:file-id (:file-id data)
|
:file-id (:file-id data)
|
||||||
:error (.-message err)
|
:error (.-message err)
|
||||||
|
|
|
@ -3322,3 +3322,6 @@ msgstr "Insufficient members to leave team, you probably want to delete it."
|
||||||
|
|
||||||
msgid "errors.auth.unable-to-login"
|
msgid "errors.auth.unable-to-login"
|
||||||
msgstr "Looks like you are not authenticated or session expired."
|
msgstr "Looks like you are not authenticated or session expired."
|
||||||
|
|
||||||
|
msgid "dashboard.import.import-warning"
|
||||||
|
msgstr "Some files containted invalid objects that have been removed."
|
||||||
|
|
|
@ -3309,3 +3309,6 @@ msgstr "Actualizar"
|
||||||
|
|
||||||
msgid "workspace.viewport.click-to-close-path"
|
msgid "workspace.viewport.click-to-close-path"
|
||||||
msgstr "Pulsar para cerrar la ruta"
|
msgstr "Pulsar para cerrar la ruta"
|
||||||
|
|
||||||
|
msgid "dashboard.import.import-warning"
|
||||||
|
msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados."
|
||||||
|
|
Loading…
Add table
Reference in a new issue