mirror of
https://github.com/penpot/penpot.git
synced 2025-05-21 15:26:10 +02:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
80bf7cc1e5
30 changed files with 356 additions and 219 deletions
|
@ -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]}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))))))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
CREATE INDEX server_error_report__created_at__idx
|
||||
ON server_error_report ( created_at );
|
|
@ -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))))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]]]]]]]]]]]]])
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))))))
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue