Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2023-08-07 12:59:17 +02:00
commit 80bf7cc1e5
30 changed files with 356 additions and 219 deletions

View file

@ -21,8 +21,8 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti
{:git/tag "v9.15"
:git/sha "aa9b967"
{:git/tag "v9.16"
:git/sha "7df3e08"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}

View file

@ -71,7 +71,7 @@ penpot - error report v2 {{id}}
{% if value %}
<div class="table-row multiline">
<div id="value" class="table-key">VALIDATION VALUE: </div>
<div id="value" class="table-key">VALUE: </div>
<div class="table-val">
<pre>{{value}}</pre>
</div>

View file

@ -264,7 +264,7 @@
content->>'~:hint' AS hint
FROM server_error_report
ORDER BY created_at DESC
LIMIT 100")
LIMIT 200")
(defn error-list-handler
[{:keys [::db/pool]} request]

View file

@ -10,6 +10,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.http.session :as-alias session]
@ -30,14 +31,14 @@
(let [claims (-> {}
(into (::session/token-claims request))
(into (::actoken/token-claims request)))]
{:path (:path request)
:method (:method request)
:params (:params request)
:ip-addr (parse-client-ip request)
:user-agent (yrq/get-header request "user-agent")
:profile-id (:uid claims)
:version (or (yrq/get-header request "x-frontend-version")
"unknown")}))
{:request/path (:path request)
:request/method (:method request)
:request/params (:params request)
:request/user-agent (yrq/get-header request "user-agent")
:request/ip-addr (parse-client-ip request)
:request/profile-id (:uid claims)
:version/frontend (or (yrq/get-header request "x-frontend-version") "unknown")
:version/backend (:full cf/version)}))
(defmulti handle-exception
(fn [err & _rest]

View file

@ -22,9 +22,10 @@
(:import
com.fasterxml.jackson.core.JsonParseException
com.fasterxml.jackson.core.io.JsonEOFException
com.fasterxml.jackson.databind.exc.MismatchedInputException
io.undertow.server.RequestTooBigException
java.io.OutputStream
java.io.InputStream))
java.io.InputStream
java.io.OutputStream))
(set! *warn-on-reflection* true)
@ -78,11 +79,13 @@
(or (instance? JsonEOFException cause)
(instance? JsonParseException cause))
(instance? JsonParseException cause)
(instance? MismatchedInputException cause))
(raise (ex/error :type :validation
:code :malformed-json
:hint (ex-message cause)
:cause cause))
:else
(raise cause)))]
@ -118,7 +121,9 @@
(t/write! tw data)))
(catch java.io.IOException _)
(catch Throwable cause
(l/error :hint "unexpected error on encoding response" :cause cause))
(binding [l/*context* {:value data}]
(l/error :hint "unexpected error on encoding response"
:cause cause)))
(finally
(.close ^OutputStream output-stream))))))
@ -131,8 +136,9 @@
(catch java.io.IOException _)
(catch Throwable cause
(binding [l/*context* {:value data}]
(l/error :hint "unexpected error on encoding response"
:cause cause))
:cause cause)))
(finally
(.close ^OutputStream output-stream))))))

View file

@ -40,35 +40,33 @@
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
(us/assert! ::l/record record)
(let [data (ex-data cause)]
(merge
{:context (-> context
(let [data (ex-data cause)
ctx (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :version (:full cf/version))
(assoc :logger-name logger)
(assoc :logger-level level)
(dissoc :params)
(pp/pprint-str :width 200))
:props (pp/pprint-str props :width 200)
(assoc :logger/name logger)
(assoc :logger/level level)
(dissoc :request/params :value :params :data))]
(merge
{:context (-> (into (sorted-map) ctx)
(pp/pprint-str :width 200 :length 50 :level 10))
:props (pp/pprint-str props :width 200 :length 50)
:hint (or (ex-message cause) @message)
:trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false)}
(when-let [params (:params context)]
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :width 200)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :width 200 :length 50 :level 10)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :width 200)})
(when-let [value (-> data ::sm/explain :value)]
{:value (pp/pprint-str value :width 200)})
(when-let [explain (ex/explain data)]
(when-let [explain (ex/explain data {:level 10 :length 50})]
{:explain explain}))))
(defn error-record?
[{:keys [::l/level ::l/cause]}]
(and (= :error level)

View file

@ -30,7 +30,9 @@
"```\n"
"- host: `" (:host report) "`\n"
"- tenant: `" (:tenant report) "`\n"
"- version: `" (:version report) "`\n"
"- request-path: `" (:request-path report) "`\n"
"- frontend-version: `" (:frontend-version report) "`\n"
"- backend-version: `" (:backend-version report) "`\n"
"\n"
"Trace:\n"
(:trace report)
@ -54,8 +56,10 @@
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:version (:full cf/version)
:profile-id (:profile-id context)
:backend-version (or (:version/backend context) (:full cf/version))
:frontend-version (:version/frontend context)
:profile-id (:request/profile-id context)
:request-path (:request/path context)
:trace (ex/format-throwable cause :detail? false :header? false)})
(defn handle-event

View file

@ -327,6 +327,9 @@
{:name "0105-mod-file-change-table"
:fn (mg/resource "app/migrations/sql/0105-mod-file-change-table.sql")}
{:name "0105-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0105-mod-server-error-report-table.sql")}
])
(defn apply-migrations!

View file

@ -0,0 +1,2 @@
CREATE INDEX server_error_report__created_at__idx
ON server_error_report ( created_at );

View file

@ -489,16 +489,8 @@
(l/error :hint "worker: unhandled exception" :cause cause))))))
(defn- get-error-context
[error item]
(let [data (ex-data error)]
(merge
{:hint (ex-message error)
:spec-problems (some->> data ::s/problems (take 10) seq vec)
:spec-value (some->> data ::s/value)
:data (some-> data (dissoc ::s/problems ::s/value ::s/spec))
:params item}
(when-let [explain (ex/explain data)]
{:spec-explain explain}))))
[_ item]
{:params item})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CRON
@ -597,10 +589,10 @@
(catch InterruptedException _
(l/debug :hint "cron: task interrupted" :task-id id))
(catch Throwable cause
(binding [l/*context* (get-error-context cause task)]
(l/error :hint "cron: unhandled exception on running task"
::l/context (get-error-context cause task)
:task-id id
:cause cause))
:cause cause)))
(finally
(when-not (px/interrupted? :current)
(schedule-cron-task cfg task))))))

View file

@ -93,6 +93,13 @@
[:component-id {:optional true} ::sm/uuid]
[:ignore-touched {:optional true} :boolean]]]
[:fix-obj
[:map {:title "FixObjChange"}
[:type [:= :fix-obj]]
[:id ::sm/uuid]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]]]
[:mov-objects
[:map {:title "MovObjectsChange"}
[:type [:= :mov-objects]]
@ -392,6 +399,12 @@
(d/update-in-when data [:pages-index page-id] ctst/delete-shape id ignore-touched)
(d/update-in-when data [:components component-id] ctst/delete-shape id ignore-touched)))
(defmethod process-change :fix-obj
[data {:keys [page-id component-id] :as params}]
(if page-id
(d/update-in-when data [:pages-index page-id] ctst/fix-shape-children params)
(d/update-in-when data [:components component-id] ctst/fix-shape-children params)))
;; FIXME: remove, seems like this method is already unused
;; reg-objects operation "regenerates" the geometry and selrect of the parent groups
(defmethod process-change :reg-objects

View file

@ -29,8 +29,10 @@
(def uuid-rx
#"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
(def max-safe-int (int 1e6))
(def min-safe-int (int -1e6))
;; Integer/MAX_VALUE
(def max-safe-int 2147483647)
;; Integer/MIN_VALUE
(def min-safe-int -2147483648)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULT SPECS

View file

@ -103,7 +103,7 @@
[:map {:title "Fill"}
[:fill-color {:optional true} ::ctc/rgb-color]
[:fill-opacity {:optional true} ::sm/safe-number]
[:fill-color-gradient {:optional true} ::ctc/gradient]
[:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]]
[:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]]
[:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]]])
@ -201,7 +201,7 @@
(sm/def! ::group-attrs
[:map {:title "GroupAttrs"}
[:type [:= :group]]
[:shapes [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]])
[:shapes {:optional true} [:maybe [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]])
(sm/def! ::frame-attrs
[:map {:title "FrameAttrs"}
@ -214,18 +214,19 @@
(sm/def! ::bool-attrs
[:map {:title "BoolAttrs"}
[:type [:= :bool]]
[:shapes [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]
[:shapes {:optional true} [:maybe [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]]
;; FIXME: improve this schema
[:bool-type :keyword]
;; FIXME: improve this schema
[:bool-content
[:vector {:gen/max 2}
[:map
[:command :keyword]
[:relative {:optional true} :boolean]
[:params [:map-of {:gen/max 5} :keyword ::sm/safe-number]]]]]])
[:prev-pos {:optional true} ::gpt/point]
[:params {:optional true}
[:map-of {:gen/max 5} :keyword ::sm/safe-number]]]]]])
(sm/def! ::rect-attrs
[:map {:title "RectAttrs"}
@ -252,7 +253,12 @@
(sm/def! ::path-attrs
[:map {:title "PathAttrs"}
[:type [:= :path]]
[:x {:optional true} [:maybe ::sm/safe-number]]
[:y {:optional true} [:maybe ::sm/safe-number]]
[:width {:optional true} [:maybe ::sm/safe-number]]
[:height {:optional true} [:maybe ::sm/safe-number]]
[:content
{:optional true}
[:vector
[:map
[:command :keyword]

View file

@ -21,6 +21,8 @@
[:type [:= "root"]]
[:key {:optional true} :string]
[:children
{:optional true}
[:maybe
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
[:type [:= "paragraph-set"]]
@ -58,7 +60,7 @@
[:text-decoration {:optional true} :string]
[:text-transform {:optional true} :string]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]])
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])

View file

@ -93,13 +93,21 @@
(let [parent-id (or (:parent-id target)
(:frame-id target))
children-ids (cph/get-children-ids objects shape-id)]
(-> (reduce dissoc objects children-ids)
(dissoc shape-id)
(-> (reduce dissoc objects (cons shape-id children-ids))
(d/update-when parent-id delete-from-parent)))
objects))]
(update container :objects delete-from-objects))))
(defn fix-shape-children
"Checks and fix the children relations of the shape. If a children does not
exists on the objects tree, it will be removed from shape."
[{:keys [objects] :as container} {:keys [id] :as params}]
(let [contains? (partial contains? objects)]
(d/update-in-when container [:objects id :shapes]
(fn [shapes]
(into [] (filter contains?) shapes)))))
(defn get-frames
"Retrieves all frame objects as vector"
([objects] (get-frames objects nil))
@ -349,6 +357,7 @@
(some? force-id) force-id
keep-ids? (:id object)
:else (uuid/next))]
(loop [child-ids (seq (:shapes object))
new-direct-children []
new-children []

View file

@ -104,7 +104,7 @@ WORKDIR /opt/penpot/exporter
USER penpot:penpot
RUN set -ex; \
yarn; \
yarn run playwright install chromium;
yarn --network-timeout 1000000; \
yarn --network-timeout 1000000 run playwright install chromium;
CMD ["node", "app.js"]

View file

@ -50,6 +50,23 @@
(mf/mount (mf/element ui/app) (dom/get-element "app"))
(mf/mount (mf/element modal) (dom/get-element "modal")))
(defn- initialize-profile
"Event used mainly on application bootstrap; it fetches the profile
and if and only if the fetched profile corresponds to an
authenticated user; proceed to fetch teams."
[stream]
(rx/merge
(rx/of (du/fetch-profile))
(->> stream
(rx/filter (ptk/type? ::profile-fetched))
(rx/take 1)
(rx/map deref)
(rx/mapcat (fn [profile]
(if (du/is-authenticated? profile)
(rx/of (du/fetch-teams))
(rx/empty))))
(rx/observe-on :async))))
(defn initialize
[]
(ptk/reify ::initialize
@ -61,14 +78,19 @@
(watch [_ _ stream]
(rx/merge
(rx/of (ev/initialize)
(feat/initialize)
(du/initialize-profile))
(feat/initialize))
(initialize-profile stream)
;; Once profile is fetched, initialize all penpot application
;; routes
(->> stream
(rx/filter du/profile-fetched?)
(rx/take 1)
(rx/map #(rt/init-routes)))
;; Once profile fetched and the current user is authenticated,
;; proceed to initialize the websockets connection.
(->> stream
(rx/filter du/profile-fetched?)
(rx/map deref)

View file

@ -117,28 +117,6 @@
(->> (rp/cmd! :get-profile)
(rx/map profile-fetched)))))
;; --- EVENT: INITIALIZE PROFILE
(defn initialize-profile
"Event used mainly on application bootstrap; it fetches the profile
and if and only if the fetched profile corresponds to an
authenticated user; proceed to fetch teams."
[]
(ptk/reify ::initialize-profile
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (fetch-profile))
(->> stream
(rx/filter (ptk/type? ::profile-fetched))
(rx/take 1)
(rx/map deref)
(rx/mapcat (fn [profile]
(if (= uuid/zero (:id profile))
(rx/empty)
(rx/of (fetch-teams)))))
(rx/observe-on :async))))))
;; --- EVENT: login
(defn- logged-in
@ -164,7 +142,8 @@
(when (is-authenticated? profile)
(->> (rx/of (profile-fetched profile)
(fetch-teams)
(get-redirect-event))
(get-redirect-event)
(ws/initialize))
(rx/observe-on :async)))))))
(declare login-from-register)

View file

@ -46,6 +46,7 @@
[app.main.data.workspace.drawing.common :as dwdc]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.fix-bool-contents :as fbc]
[app.main.data.workspace.fix-broken-shape-links :as fbs]
[app.main.data.workspace.fix-deleted-fonts :as fdf]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.guides :as dwgu]
@ -132,8 +133,10 @@
has-graphics? (-> file :media seq)
components-v2 (features/active-feature? state :components-v2)]
(rx/merge
(rx/of (fbc/fix-bool-contents))
(rx/of (fdf/fix-deleted-fonts))
(rx/of (fbc/fix-bool-contents)
(fdf/fix-deleted-fonts)
(fbs/fix-broken-shapes))
(if (and has-graphics? components-v2)
(rx/of (remove-graphics (:id file) (:name file)))
(rx/empty)))))))
@ -1832,9 +1835,14 @@
detach? (or (foreign-instance? shape paste-objects state)
(and (ctk/in-component-copy-not-root? shape)
(not= (:id component-shape)
(:id component-shape-parent))))]
(:id component-shape-parent))))
assign-shapes? (and (or (cph/group-shape? shape)
(cph/bool-shape? shape))
(nil? (:shapes shape)))]
(-> shape
(assoc :frame-id frame-id :parent-id parent-id)
(cond-> assign-shapes?
(assoc :shapes []))
(cond-> detach?
;; this is used later, if the paste needs to create a new component from the detached shape
(-> (assoc :saved-component-root (:component-root shape))

View file

@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as log]
[app.common.pages :as cp]
[app.common.pages.changes :as cpc]
@ -206,6 +207,7 @@
path (if (= file-id current-file-id)
[:workspace-data]
[:workspace-libraries file-id :data])]
(try
(dm/assert!
"expect valid vector of changes"
@ -218,7 +220,11 @@
(ctst/update-object-indices page-id))))
(catch :default err
(log/error :js/error err)
(when-let [data (ex-data err)]
(js/console.log (ex/explain data)))
(when (ex/error? err)
(js/console.log (.-stack ^js err)))
(vreset! error err)
state))))

View file

@ -353,9 +353,12 @@
(-> state
(assoc-in [:workspace-global :picking-color?] true)
(assoc ::md/modal {:id (random-uuid)
:data {:color colors/black :opacity 1}
:type :colorpicker
:props {:on-change handle-change-color}
:props {:data {:color colors/black
:opacity 1}
:disable-opacity false
:disable-gradient false
:on-change handle-change-color}
:allow-click-outside true})))))))
(defn color-att->text

View file

@ -0,0 +1,55 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.fix-broken-shape-links
(:require
[app.main.data.workspace.changes :as dch]
[beicon.core :as rx]
[potok.core :as ptk]))
(defn- generate-changes
[attr {:keys [objects id] :as container}]
(let [base {:type :fix-obj attr id}
contains? (partial contains? objects)
xform (comp
;; FIXME: Ensure all obj have id field (this is needed
;; because some bug adds an ephimeral shape with id ZERO,
;; with a single attr `:shapes` having a vector of ids
;; pointing to not existing shapes). That happens on
;; components. THIS IS A WORKAOURD
(map (fn [[id obj]]
(if (some? (:id obj))
obj
(assoc obj :id id))))
;; Remove all valid shapes
(remove (fn [obj]
(every? contains? (:shapes obj))))
(map (fn [obj]
(assoc base :id (:id obj)))))]
(sequence xform objects)))
(defn fix-broken-shapes
[]
(ptk/reify ::fix-broken-shape-links
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
changes (concat
(mapcat (partial generate-changes :page-id)
(vals (:pages-index data)))
(mapcat (partial generate-changes :component-id)
(vals (:components data))))]
(if (seq changes)
(rx/of (dch/commit-changes
{:origin it
:redo-changes (vec changes)
:undo-changes []
:save-undo? false}))
(rx/empty))))))

View file

@ -419,6 +419,8 @@
:else
(let [frame? (cph/frame-shape? obj)
group? (cph/group-shape? obj)
bool? (cph/bool-shape? obj)
new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (:name obj)
@ -437,9 +439,15 @@
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes
:main-instance?
:use-for-thumbnail?)
(cond->
(or group? bool?)
(assoc :shapes []))
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))

View file

@ -252,7 +252,7 @@
(let [all-ids (into empty-parents ids)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter cph/group-shape?)
(filter #(or (cph/group-shape? %) (cph/bool-shape? %)))
(remove #(->> (:shapes %) (remove contains?) seq))
(map :id))
parents (into #{} xform all-parents)]

View file

@ -116,56 +116,64 @@
(assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :style :fill-opacity])
(d/parse-double 1)))))))
(defn setup-stroke [shape]
(let [stroke-linecap (-> (or (dm/get-in shape [:svg-attrs :stroke-linecap])
(dm/get-in shape [:svg-attrs :style :stroke-linecap]))
((d/nilf str/trim))
((d/nilf keyword)))
color-attr (str/trim (dm/get-in shape [:svg-attrs :stroke]))
color-attr (if (= color-attr "currentColor") clr/black color-attr)
color-style (str/trim (dm/get-in shape [:svg-attrs :style :stroke]))
color-style (if (= color-style "currentColor") clr/black color-style)
(defn- setup-stroke
[shape]
(let [attrs (get shape :svg-attrs)
style (get attrs :style)
shape
(cond-> shape
;; Color present as attribute
(uc/color? color-attr)
(-> (update :svg-attrs dissoc :stroke)
(assoc-in [:strokes 0 :stroke-color] (uc/parse-color color-attr)))
stroke (or (str/trim (:stroke attrs))
(str/trim (:stroke style)))
;; Color present as style
(uc/color? color-style)
(-> (update-in [:svg-attrs :style] dissoc :stroke)
(assoc-in [:strokes 0 :stroke-color] (uc/parse-color color-style)))
color (cond
(= stroke "currentColor") clr/black
(= stroke "none") nil
:else (uc/parse-color stroke))
(dm/get-in shape [:svg-attrs :stroke-opacity])
(-> (update :svg-attrs dissoc :stroke-opacity)
(assoc-in [:strokes 0 :stroke-opacity] (-> (dm/get-in shape [:svg-attrs :stroke-opacity])
(d/parse-double 1))))
opacity (when (some? color)
(d/parse-double
(or (:stroke-opacity attrs)
(:stroke-opacity style))
1))
(dm/get-in shape [:svg-attrs :style :stroke-opacity])
(-> (update-in [:svg-attrs :style] dissoc :stroke-opacity)
(assoc-in [:strokes 0 :stroke-opacity] (-> (dm/get-in shape [:svg-attrs :style :stroke-opacity])
(d/parse-double 1))))
width (when (some? color)
(d/parse-double
(or (:stroke-width attrs)
(:stroke-width style))
1))
(dm/get-in shape [:svg-attrs :stroke-width])
(-> (update :svg-attrs dissoc :stroke-width)
(assoc-in [:strokes 0 :stroke-width] (-> (dm/get-in shape [:svg-attrs :stroke-width])
(d/parse-double))))
linecap (or (get attrs :stroke-linecap)
(get style :stroke-linecap))
linecap (some-> linecap str/trim keyword)
(dm/get-in shape [:svg-attrs :style :stroke-width])
(-> (update-in [:svg-attrs :style] dissoc :stroke-width)
(assoc-in [:strokes 0 :stroke-width] (-> (dm/get-in shape [:svg-attrs :style :stroke-width])
(d/parse-double))))
attrs (-> attrs
(dissoc :stroke)
(dissoc :stroke-width)
(dissoc :stroke-opacity)
(update :style (fn [style]
(-> style
(dissoc :stroke)
(dissoc :stroke-linecap)
(dissoc :stroke-width)
(dissoc :stroke-opacity)))))]
(and stroke-linecap (= (:type shape) :path))
(-> (update-in [:svg-attrs :style] dissoc :stroke-linecap)
(cond-> (#{:round :square} stroke-linecap)
(assoc :stroke-cap-start stroke-linecap
:stroke-cap-end stroke-linecap))))]
(cond-> (assoc shape :svg-attrs attrs)
(some? color)
(assoc-in [:strokes 0 :stroke-color] color)
(cond-> shape
(d/any-key? (dm/get-in shape [:strokes 0]) :stroke-color :stroke-opacity :stroke-width :stroke-cap-start :stroke-cap-end)
(and (some? color) (some? opacity))
(assoc-in [:strokes 0 :stroke-opacity] opacity)
(and (some? color) (some? width))
(assoc-in [:strokes 0 :stroke-width] width)
(and (some? linecap) (= (:type shape) :path)
(or (= linecap :round) (= linecap :square)))
(assoc :stroke-cap-start linecap
:stroke-cap-end linecap)
(d/any-key? (dm/get-in shape [:strokes 0])
:stroke-color :stroke-opacity :stroke-width
:stroke-cap-start :stroke-cap-end)
(assoc-in [:strokes 0 :stroke-style] :svg))))
(defn setup-opacity [shape]

View file

@ -115,7 +115,7 @@
(mf/set-ref-val! prev-val-ref node))))]
(mf/with-effect []
(mf/with-layout-effect []
;; On dismount we need to disconnect the current observer
(fn []
(when-let [observer (mf/ref-val observer-ref)]

View file

@ -89,7 +89,7 @@
(obj/merge! attrs (clj->js fill-attrs)))))
(defn add-stroke [attrs stroke-data render-id index]
(let [stroke-style (:stroke-style stroke-data :none)
(let [stroke-style (:stroke-style stroke-data :solid)
stroke-color-gradient-id (str "stroke-color-gradient_" render-id "_" index)
stroke-width (:stroke-width stroke-data 1)]
(if (not= stroke-style :none)

View file

@ -29,7 +29,7 @@
(defn- event-type-names
[]
{:click (tr "workspace.options.interaction-on-click")
; TODO: need more UX research
;; TODO: need more UX research
;; :mouse-over (tr "workspace.options.interaction-while-hovering")
;; :mouse-press (tr "workspace.options.interaction-while-pressing")
:mouse-enter (tr "workspace.options.interaction-mouse-enter")
@ -315,11 +315,12 @@
(when @extended-open?
[:div.element-set-content
; Trigger select
;; Trigger select
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-trigger")]
[:select.input-select
{:value (str (:event-type interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:event-type interaction))
:on-change change-event-type}
(for [[value name] (event-type-names)]
(when-not (and (= value :after-delay)
@ -327,7 +328,7 @@
[:option {:key (dm/str value)
:value (dm/str value)} name]))]]
; Delay
;; Delay
(when (ctsi/has-delay interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-delay")]
@ -338,22 +339,24 @@
:title (tr "workspace.options.interaction-ms")}]
[:span.after (tr "workspace.options.interaction-ms")]]])
; Action select
;; Action select
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-action")]
[:select.input-select
{:value (str (:action-type interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:action-type interaction))
:on-change change-action-type}
(for [[value name] (action-type-names)]
[:option {:key (dm/str "action-" value)
:value (str value)} name])]]
; Destination
;; Destination
(when (ctsi/has-destination interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-destination")]
[:select.input-select
{:value (str (:destination interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:destination interaction))
:on-change change-destination}
(if (= (:action-type interaction) :close-overlay)
[:option {:value ""} (tr "workspace.options.interaction-self")]
@ -364,7 +367,7 @@
[:option {:key (dm/str "destination-" (:id frame))
:value (str (:id frame))} (:name frame)]))]])
; Preserve scroll
;; Preserve scroll
(when (ctsi/has-preserve-scroll interaction)
[:div.interactions-element
[:div.input-checkbox
@ -375,7 +378,7 @@
[:label {:for (str "preserve-" index)}
(tr "workspace.options.interaction-preserve-scroll")]]])
; URL
;; URL
(when (ctsi/has-url interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-url")]
@ -386,11 +389,12 @@
(when (ctsi/has-overlay-opts interaction)
[:*
; Overlay position relative-to (select)
;; Overlay position relative-to (select)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-relative-to")]
[:select.input-select
{:value (str (:position-relative-to interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:position-relative-to interaction))
:on-change change-position-relative-to}
(when (not= (:overlay-pos-type interaction) :manual)
[:*
@ -401,16 +405,17 @@
[:option {:key (dm/str "position-relative-to-" (:id shape))
:value (str (:id shape))} (:name shape) " (" (tr "workspace.options.interaction-self") ")"]]]
; Overlay position (select)
;; Overlay position (select)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-position")]
[:select.input-select
{:value (str (:overlay-pos-type interaction))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (:overlay-pos-type interaction))
:on-change (partial change-overlay-pos-type (:id shape))}
(for [[value name] (overlay-pos-type-names)]
[:option {:value (str value)} name])]]
; Overlay position (buttons)
;; Overlay position (buttons)
[:div.interactions-element.interactions-pos-buttons
[:div.element-set-actions-button
{:class (dom/classnames :active (= overlay-pos-type :center))
@ -441,7 +446,7 @@
:on-click #(toggle-overlay-pos-type :bottom-center)}
i/position-bottom-center]]
; Overlay click outside
;; Overlay click outside
[:div.interactions-element
[:div.input-checkbox
[:input {:type "checkbox"
@ -451,7 +456,7 @@
[:label {:for (str "close-" index)}
(tr "workspace.options.interaction-close-outside")]]]
; Overlay background
;; Overlay background
[:div.interactions-element
[:div.input-checkbox
[:input {:type "checkbox"
@ -463,17 +468,18 @@
(when (ctsi/has-animation? interaction)
[:*
; Animation select
;; Animation select
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-animation")]
[:select.input-select
{:value (str (-> interaction :animation :animation-type))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (-> interaction :animation :animation-type))
:on-change change-animation-type}
[:option {:value ""} (tr "workspace.options.interaction-animation-none")]
(for [[value name] (animation-type-names interaction)]
[:option {:value (str value)} name])]]
; Direction
;; Direction
(when (ctsi/has-way? interaction)
[:div.interactions-element.interactions-way-buttons
[:div.input-radio
@ -493,7 +499,7 @@
:on-change change-way}]
[:label {:for "way-out"} (tr "workspace.options.interaction-out")]]])
; Direction
;; Direction
(when (ctsi/has-direction? interaction)
[:div.interactions-element.interactions-direction-buttons
[:div.element-set-actions-button
@ -513,7 +519,7 @@
:on-click #(change-direction :up)}
i/animate-up]])
; Duration
;; Duration
(when (ctsi/has-duration? interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-duration")]
@ -524,12 +530,13 @@
:title (tr "workspace.options.interaction-ms")}]
[:span.after (tr "workspace.options.interaction-ms")]]])
; Easing
;; Easing
(when (ctsi/has-easing? interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-easing")]
[:select.input-select
{:value (str (-> interaction :animation :easing))
{:data-mousetrap-dont-stop true ;; makes mousetrap to not stop at this element
:value (str (-> interaction :animation :easing))
:on-change change-easing}
(for [[value name] (easing-names)]
[:option {:value (str value)} name])]
@ -541,7 +548,7 @@
:ease-out i/easing-ease-out
:ease-in-out i/easing-ease-in-out)]])
; Offset effect
;; Offset effect
(when (ctsi/has-offset-effect? interaction)
[:div.interactions-element
[:div.input-checkbox

View file

@ -325,7 +325,7 @@
layout-container-ids layout-container-values
layout-item-ids layout-item-values]
(mf/use-memo
(mf/deps objects-no-measures)
(mf/deps shapes objects-no-measures)
(fn []
(into
[]

View file

@ -8,6 +8,7 @@
"Color conversion utils."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[app.util.strings :as ust]
@ -176,14 +177,16 @@
(= id :multiple)
(= file-id :multiple)))
(defn color? [^string color-str]
(and (not (nil? color-str))
(seq color-str)
(gcolor/isValidColor color-str)))
(defn color?
[color]
(and (string? color)
(gcolor/isValidColor color)))
(defn parse-color [^string color-str]
(let [result (gcolor/parse color-str)]
(str (.-hex ^js result))))
(defn parse-color
[color]
(when (color? color)
(let [result (gcolor/parse color)]
(dm/str (.-hex ^js result)))))
(def color-names
(obj/get-keys ^js gcolor/names))