0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-14 08:41:48 -05:00

Merge pull request #1449 from penpot/bugfixes

Bugfixes
This commit is contained in:
Andrey Antukh 2022-01-10 15:36:59 +01:00 committed by GitHub
commit 0eb2336bc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 648 additions and 327 deletions

View file

@ -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!)

View file

@ -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]

View 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))

View file

@ -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)

View file

@ -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]

View file

@ -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]

View file

@ -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 {

View file

@ -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;

View file

@ -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)

View file

@ -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"])

View file

@ -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))

View 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))

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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]

View 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))))

View file

@ -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))))

View file

@ -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();
}

View file

@ -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)

View file

@ -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."

View file

@ -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."