Compare commits

..

15 Commits

Author SHA1 Message Date
Andrey Antukh
a2d3616171 📎 Update changelog. 2021-12-20 11:55:32 +01:00
Andrey Antukh
a83e37493a ⬆️ Update log4j2 dependency. 2021-12-20 11:52:32 +01:00
Andrey Antukh
384f0a05c6 🐛 Fix race condition issues on workspace. 2021-12-10 12:32:10 +01:00
Andrey Antukh
a3016b8400 Make the media uploading idempotent. 2021-12-10 12:19:12 +01:00
Andrey Antukh
64c456678b Merge pull request #1401 from penpot/fix-destination
🐛 Fix error importing file with null destination in one interaction
2021-12-10 11:21:27 +01:00
Andrés Moya
16ed09a303 🐛 Fix error importing file with null destination in one interaction 2021-12-10 10:50:18 +01:00
Andrey Antukh
f8cecfd61f 🐛 Fix unexpected behavior of grid options on right sidebar. 2021-12-03 14:52:40 +01:00
Andrey Antukh
8a2a1d6d70 ♻️ Ensure a correct usage of concat/into operations. 2021-12-03 14:52:40 +01:00
Andrey Antukh
4ad34ab5c8 📎 Update version number. 2021-11-24 13:06:36 +01:00
Andrey Antukh
33c7847dfc 🐛 Fix team deletion flow on dashboard. 2021-11-24 13:05:54 +01:00
alonso.torres
7a04f15710 🐛 Fix problems with team management. 2021-11-24 13:05:48 +01:00
Andrey Antukh
b8043a2432 📎 Update ci config. 2021-11-18 17:19:55 +01:00
Andrey Antukh
ed5de525aa 📎 Increase default db pool size to 50. 2021-11-18 17:19:55 +01:00
Andrey Antukh
8105d9388b ♻️ Refactor rlimit usage (backend). 2021-11-18 17:19:55 +01:00
Andrey Antukh
8151dcc05f 📎 Improve services defmethod linter hook. 2021-11-18 17:19:55 +01:00
78 changed files with 965 additions and 768 deletions

View File

@@ -36,17 +36,23 @@ jobs:
- run:
name: common lint
working_directory: "./common"
command: "clj-kondo --parallel --lint src/"
command: |
clj-kondo --version
clj-kondo --parallel --lint src/
- run:
name: frontend lint
working_directory: "./frontend"
command: "clj-kondo --parallel --lint src/"
command: |
clj-kondo --version
clj-kondo --parallel --lint src/
- run:
name: backend lint
working_directory: "./backend"
command: "clj-kondo --parallel --lint src/"
command: |
clj-kondo --version
clj-kondo --parallel --lint src/
# run backend test
- run:

View File

@@ -51,18 +51,28 @@
(defn service-defmethod
[{:keys [:node]}]
(let [[rnode rtype & other] (:children node)
(let [[rnode rtype ?meta & other] (:children node)
rsym (gensym (name (:k rtype)))
result (api/list-node
[(api/token-node (symbol "do"))
(api/list-node
[(api/token-node (symbol "declare"))
(api/token-node rsym)])
(if (= :map (:tag ?meta))
(api/list-node
[(api/token-node (symbol "reset-meta!"))
(api/token-node rsym)
?meta])
(api/list-node
[(api/token-node (symbol "comment"))
(api/token-node rsym)]))
(api/list-node
(into [(api/token-node (symbol "defmethod"))
(api/token-node rsym)
rtype]
other))])]
(cons ?meta other)))])]
;; (prn "==============" rtype (into {} ?meta))
;; (prn (api/sexpr result))
{:node result}))

View File

@@ -8,6 +8,25 @@
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)
# 1.10.2-beta
### :bug: Bugs fixed
- Fix corner case issues with media file uploads.
- Fix issue with default page grids validation.
- Fix issue related to some raceconditions on workspace navigation events.
### :arrow_up: Deps updates
- Update log4j2 dependency.
# 1.10.1-beta
### :bug: Bugs fixed
- Fix problems with team management [#1353](https://github.com/penpot/penpot/issues/1353)
## 1.10.0-beta

View File

@@ -2,7 +2,13 @@
export PENPOT_FLAGS="enable-asserts enable-audit-log $PENPOT_FLAGS"
export OPTIONS="-A:jmx-remote:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Dlog4j2.configurationFile=log4j2-devenv.xml -J-Djdk.attach.allowAttachSelf -J-XX:+UseZGC -J-XX:ConcGCThreads=1 -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m";
export OPTIONS="-A:jmx-remote:dev \
-J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-J-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \
-J-Dlog4j2.configurationFile=log4j2-devenv.xml \
-J-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory \
-J-XX:+UseShenandoahGC -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m";
# export OPTIONS="$OPTIONS -J-XX:+UnlockDiagnosticVMOptions";
# export OPTIONS="$OPTIONS -J-XX:-TieredCompilation -J-XX:CompileThreshold=10000";

View File

@@ -62,8 +62,9 @@
:assets-path "/internal/assets/"
:rlimits-password 10
:rlimits-image 2
:rlimit-password 10
:rlimit-image 2
:rlimit-font 5
:smtp-default-reply-to "Penpot <no-reply@example.com>"
:smtp-default-from "Penpot <no-reply@example.com>"
@@ -151,8 +152,9 @@
(s/def ::public-uri ::us/string)
(s/def ::redis-uri ::us/string)
(s/def ::registration-domain-whitelist ::us/set-of-str)
(s/def ::rlimits-image ::us/integer)
(s/def ::rlimits-password ::us/integer)
(s/def ::rlimit-font ::us/integer)
(s/def ::rlimit-image ::us/integer)
(s/def ::rlimit-password ::us/integer)
(s/def ::smtp-default-from ::us/string)
(s/def ::smtp-default-reply-to ::us/string)
(s/def ::smtp-host ::us/string)
@@ -237,8 +239,9 @@
::redis-uri
::registration-domain-whitelist
::registration-enabled
::rlimits-image
::rlimits-password
::rlimit-font
::rlimit-image
::rlimit-password
::sentry-dsn
::sentry-debug
::sentry-attach-stack-trace

View File

@@ -113,7 +113,7 @@
(.setIdleTimeout 120000) ;; 2min
(.setMaxLifetime 1800000) ;; 30min
(.setMinimumIdle (:min-pool-size cfg 0))
(.setMaximumPoolSize (:max-pool-size cfg 30))
(.setMaximumPoolSize (:max-pool-size cfg 50))
(.setConnectionInitSql initsql)
(.setInitializationFailTimeout -1))

View File

@@ -127,24 +127,6 @@
:audit (ig/ref :app.loggers.audit/collector)
:public-uri (cf/get :public-uri)}
;; RLimit definition for password hashing
:app.rlimits/password
(cf/get :rlimits-password)
;; RLimit definition for image processing
:app.rlimits/image
(cf/get :rlimits-image)
;; RLimit definition for font processing
:app.rlimits/font
(cf/get :rlimits-font 2)
;; A collection of rlimits as hash-map.
:app.rlimits/all
{:password (ig/ref :app.rlimits/password)
:image (ig/ref :app.rlimits/image)
:font (ig/ref :app.rlimits/font)}
:app.rpc/rpc
{:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http.session/session)
@@ -152,7 +134,6 @@
:metrics (ig/ref :app.metrics/metrics)
:storage (ig/ref :app.storage/storage)
:msgbus (ig/ref :app.msgbus/msgbus)
:rlimits (ig/ref :app.rlimits/all)
:public-uri (cf/get :public-uri)
:audit (ig/ref :app.loggers.audit/collector)}

View File

@@ -12,7 +12,6 @@
[app.common.media :as cm]
[app.common.spec :as us]
[app.config :as cf]
[app.rlimits :as rlm]
[app.util.svg :as svg]
[buddy.core.bytes :as bb]
[buddy.core.codecs :as bc]
@@ -51,7 +50,6 @@
:code :media-type-not-allowed
:hint "Seems like you are uploading an invalid media object"))))
(defmulti process :cmd)
(defmulti process-error class)
@@ -66,17 +64,11 @@
(throw error))
(defn run
[{:keys [rlimits] :as cfg} {:keys [rlimit] :or {rlimit :image} :as params}]
(us/assert map? rlimits)
(let [rlimit (get rlimits rlimit)]
(when-not rlimit
(ex/raise :type :internal
:code :rlimit-not-configured
:hint ":image rlimit not configured"))
(try
(rlm/execute rlimit (process params))
(catch Throwable e
(process-error e)))))
[params]
(try
(process params)
(catch Throwable e
(process-error e))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Thumbnails Generation

View File

@@ -1,45 +0,0 @@
;; 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.rlimits
"Resource usage limits (in other words: semaphores)."
(:require
[app.common.spec :as us]
[clojure.spec.alpha :as s]
[integrant.core :as ig])
(:import
java.util.concurrent.Semaphore))
(s/def ::rlimit #(instance? Semaphore %))
(s/def ::rlimits (s/map-of ::us/keyword ::rlimit))
(derive ::password ::instance)
(derive ::image ::instance)
(derive ::font ::instance)
(defmethod ig/pre-init-spec ::instance [_]
(s/spec int?))
(defmethod ig/init-key ::instance
[_ permits]
(Semaphore. (int permits)))
(defn acquire!
[sem]
(.acquire ^Semaphore sem))
(defn release!
[sem]
(.release ^Semaphore sem))
(defmacro execute
[rlinst & body]
`(try
(acquire! ~rlinst)
~@body
(finally
(release! ~rlinst))))

View File

@@ -13,11 +13,10 @@
[app.db :as db]
[app.loggers.audit :as audit]
[app.metrics :as mtx]
[app.rlimits :as rlm]
[app.util.retry :as retry]
[app.util.rlimit :as rlimit]
[app.util.services :as sv]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]))
(defn- default-handler
@@ -74,29 +73,15 @@
[cfg f mdata]
(mtx/wrap-summary f (::mobj cfg) [(::sv/name mdata)]))
;; Wrap the rpc handler with a semaphore if it is specified in the
;; metadata asocciated with the handler.
(defn- wrap-with-rlimits
[cfg f mdata]
(if-let [key (:rlimit mdata)]
(let [rlinst (get-in cfg [:rlimits key])]
(when-not rlinst
(ex/raise :type :internal
:code :rlimit-not-configured
:hint (str/fmt "%s rlimit not configured" key)))
(l/trace :action "add rlimit"
:handler (::sv/name mdata))
(fn [cfg params]
(rlm/execute rlinst (f cfg params))))
f))
(defn- wrap-impl
[{:keys [audit] :as cfg} f mdata]
(let [f (wrap-with-rlimits cfg f mdata)
f (retry/wrap-retry cfg f mdata)
f (wrap-with-metrics cfg f mdata)
spec (or (::sv/spec mdata) (s/spec any?))
auth? (:auth mdata true)]
(let [f (as-> f $
(rlimit/wrap-rlimit cfg $ mdata)
(retry/wrap-retry cfg $ mdata)
(wrap-with-metrics cfg $ mdata))
spec (or (::sv/spec mdata) (s/spec any?))
auth? (:auth mdata true)]
(l/trace :action "register" :name (::sv/name mdata))
(with-meta
@@ -188,7 +173,7 @@
(defmethod ig/pre-init-spec ::rpc [_]
(s/keys :req-un [::storage ::session ::tokens ::audit
::mtx/metrics ::rlm/rlimits ::db/pool]))
::mtx/metrics ::db/pool]))
(defmethod ig/init-key ::rpc
[_ cfg]

View File

@@ -12,9 +12,7 @@
[app.rpc.queries.comments :as comments]
[app.rpc.queries.files :as files]
[app.util.blob :as blob]
#_:clj-kondo/ignore
[app.util.retry :as retry]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]))

View File

@@ -9,10 +9,12 @@
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.media :as media]
[app.rpc.queries.teams :as teams]
[app.storage :as sto]
[app.util.rlimit :as rlimit]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]))
@@ -37,6 +39,7 @@
::font-id ::font-family ::font-weight ::font-style]))
(sv/defmethod ::create-font-variant
{::rlimit/permits (cf/get :rlimit-font)}
[{:keys [pool] :as cfg} {:keys [team-id profile-id] :as params}]
(db/with-atomic [conn pool]
(let [cfg (assoc cfg :conn conn)]
@@ -45,10 +48,9 @@
(defn create-font-variant
[{:keys [conn storage] :as cfg} {:keys [data] :as params}]
(let [data (media/run cfg {:cmd :generate-fonts :input data :rlimit :font})
(let [data (media/run {:cmd :generate-fonts :input data})
storage (media/configure-assets-storage storage conn)
otf (when-let [fdata (get data "font/otf")]
(sto/put-object storage {:content (sto/content fdata)
:content-type "font/otf"}))

View File

@@ -10,11 +10,13 @@
[app.common.media :as cm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.media :as media]
[app.rpc.queries.teams :as teams]
[app.storage :as sto]
[app.util.http :as http]
[app.util.rlimit :as rlimit]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
@@ -47,6 +49,7 @@
:opt-un [::id]))
(sv/defmethod ::upload-file-media-object
{::rlimit/permits (cf/get :rlimit-image)}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(let [file (select-file conn file-id)]
@@ -89,6 +92,15 @@
:content-type mtype
:expired-at (dt/in-future {:minutes 30})}))))
;; NOTE: we use the `on conflict do update` instead of `do nothing`
;; because postgresql does not returns anything if no update is
;; performed, the `do update` does the trick.
(def sql:create-file-media-object
"insert into file_media_object (id, file_id, is_local, name, media_id, thumbnail_id, width, height, mtype)
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
on conflict (id) do update set created_at=file_media_object.created_at
returning *")
(defn create-file-media-object
[{:keys [conn storage] :as cfg} {:keys [id file-id is-local name content] :as params}]
@@ -96,14 +108,14 @@
(let [storage (media/configure-assets-storage storage conn)
source-path (fs/path (:tempfile content))
source-mtype (:content-type content)
source-info (media/run cfg {:cmd :info :input {:path source-path :mtype source-mtype}})
source-info (media/run {:cmd :info :input {:path source-path :mtype source-mtype}})
thumb (when (and (not (svg-image? source-info))
(big-enough-for-thumbnail? source-info))
(media/run cfg (assoc thumbnail-options
:cmd :generic-thumbnail
:input {:mtype (:mtype source-info)
:path source-path})))
(media/run (assoc thumbnail-options
:cmd :generic-thumbnail
:input {:mtype (:mtype source-info)
:path source-path})))
image (if (= (:mtype source-info) "image/svg+xml")
(let [data (slurp source-path)]
@@ -115,17 +127,15 @@
thumb (when thumb
(sto/put-object storage {:content (sto/content (:data thumb) (:size thumb))
:content-type (:mtype thumb)}))]
(db/insert! conn :file-media-object
{:id (or id (uuid/next))
:file-id file-id
:is-local is-local
:name name
:media-id (:id image)
:thumbnail-id (:id thumb)
:width (:width source-info)
:height (:height source-info)
:mtype source-mtype})))
(db/exec-one! conn [sql:create-file-media-object
(or id (uuid/next))
file-id is-local name
(:id image)
(:id thumb)
(:width source-info)
(:height source-info)
source-mtype])))
;; --- Create File Media Object (from URL)

View File

@@ -19,6 +19,7 @@
[app.rpc.mutations.teams :as teams]
[app.rpc.queries.profile :as profile]
[app.storage :as sto]
[app.util.rlimit :as rlimit]
[app.util.services :as sv]
[app.util.time :as dt]
[buddy.hashers :as hashers]
@@ -128,7 +129,8 @@
(s/def ::register-profile
(s/keys :req-un [::token ::fullname]))
(sv/defmethod ::register-profile {:auth false :rlimit :password}
(sv/defmethod ::register-profile
{:auth false ::rlimit/permits (cf/get :rlimit-password)}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
@@ -281,7 +283,8 @@
(s/keys :req-un [::email ::password]
:opt-un [::scope ::invitation-token]))
(sv/defmethod ::login {:auth false :rlimit :password}
(sv/defmethod ::login
{:auth false ::rlimit/permits (cf/get :rlimit-password)}
[{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}]
(letfn [(check-password [profile password]
(when (= (:password profile) "!")
@@ -371,7 +374,8 @@
(s/def ::update-profile-password
(s/keys :req-un [::profile-id ::password ::old-password]))
(sv/defmethod ::update-profile-password {:rlimit :password}
(sv/defmethod ::update-profile-password
{::rlimit/permits (cf/get :rlimit-password)}
[{:keys [pool] :as cfg} {:keys [password] :as params}]
(db/with-atomic [conn pool]
(let [profile (validate-password! conn params)]
@@ -404,11 +408,12 @@
(s/keys :req-un [::profile-id ::file]))
(sv/defmethod ::update-profile-photo
{::rlimit/permits (cf/get :rlimit-image)}
[{:keys [pool storage] :as cfg} {:keys [profile-id file] :as params}]
(db/with-atomic [conn pool]
(media/validate-media-type (:content-type file) #{"image/jpeg" "image/png" "image/webp"})
(media/run cfg {:cmd :info :input {:path (:tempfile file)
:mtype (:content-type file)}})
(media/run {:cmd :info :input {:path (:tempfile file)
:mtype (:content-type file)}})
(let [profile (db/get-by-id conn :profile profile-id)
storage (media/configure-assets-storage storage conn)
@@ -554,7 +559,8 @@
(s/def ::recover-profile
(s/keys :req-un [::token ::password]))
(sv/defmethod ::recover-profile {:auth false :rlimit :password}
(sv/defmethod ::recover-profile
{:auth false ::rlimit/permits (cf/get :rlimit-password)}
[{:keys [pool tokens] :as cfg} {:keys [token password]}]
(letfn [(validate-token [token]
(let [tdata (tokens :verify {:token token :iss :password-recovery})]

View File

@@ -10,6 +10,7 @@
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.media :as media]
@@ -18,6 +19,7 @@
[app.rpc.queries.profile :as profile]
[app.rpc.queries.teams :as teams]
[app.storage :as sto]
[app.util.rlimit :as rlimit]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
@@ -108,10 +110,10 @@
(sv/defmethod ::leave-team
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
(db/with-atomic [conn pool]
(let [perms (teams/check-read-permissions! conn profile-id id)
(let [perms (teams/get-permissions conn profile-id id)
members (teams/retrieve-team-members conn id)]
(when (some :is-owner perms)
(when (:is-owner perms)
(ex/raise :type :validation
:code :owner-cant-leave-team
:hint "reasing owner before leave"))
@@ -169,8 +171,7 @@
(sv/defmethod ::update-team-member-role
[{:keys [pool] :as cfg} {:keys [team-id profile-id member-id role] :as params}]
(db/with-atomic [conn pool]
(let [perms (teams/check-read-permissions! conn profile-id team-id)
(let [perms (teams/get-permissions conn profile-id team-id)
;; We retrieve all team members instead of query the
;; database for a single member. This is just for
;; convenience, if this bocomes a bottleneck or problematic,
@@ -178,8 +179,8 @@
members (teams/retrieve-team-members conn team-id)
member (d/seek #(= member-id (:id %)) members)
is-owner? (some :is-owner perms)
is-admin? (some :is-admin perms)]
is-owner? (:is-owner perms)
is-admin? (:is-admin perms)]
;; If no member is found, just 404
(when-not member
@@ -232,9 +233,9 @@
(sv/defmethod ::delete-team-member
[{:keys [pool] :as cfg} {:keys [team-id profile-id member-id] :as params}]
(db/with-atomic [conn pool]
(let [perms (teams/check-read-permissions! conn profile-id team-id)]
(when-not (or (some :is-owner perms)
(some :is-admin perms))
(let [perms (teams/get-permissions conn profile-id team-id)]
(when-not (or (:is-owner perms)
(:is-admin perms))
(ex/raise :type :validation
:code :insufficient-permissions))
@@ -259,12 +260,13 @@
(s/keys :req-un [::profile-id ::team-id ::file]))
(sv/defmethod ::update-team-photo
{::rlimit/permits (cf/get :rlimit-image)}
[{:keys [pool storage] :as cfg} {:keys [profile-id file team-id] :as params}]
(db/with-atomic [conn pool]
(teams/check-edition-permissions! conn profile-id team-id)
(media/validate-media-type (:content-type file) #{"image/jpeg" "image/png" "image/webp"})
(media/run cfg {:cmd :info :input {:path (:tempfile file)
:mtype (:content-type file)}})
(media/run {:cmd :info :input {:path (:tempfile file)
:mtype (:content-type file)}})
(let [team (teams/retrieve-team conn profile-id team-id)
storage (media/configure-assets-storage storage conn)
@@ -284,16 +286,13 @@
(defn upload-photo
[{:keys [storage] :as cfg} {:keys [file]}]
(let [thumb (media/run cfg
{:cmd :profile-thumbnail
:format :jpeg
:quality 85
:width 256
:height 256
:input {:path (fs/path (:tempfile file))
:mtype (:content-type file)}})]
(let [thumb (media/run {:cmd :profile-thumbnail
:format :jpeg
:quality 85
:width 256
:height 256
:input {:path (fs/path (:tempfile file))
:mtype (:content-type file)}})]
(sto/put-object storage
{:content (sto/content (:data thumb) (:size thumb))
:content-type (:mtype thumb)})))

View File

@@ -6,7 +6,6 @@
(ns app.util.emails
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.util.template :as tmpl]
@@ -199,7 +198,7 @@
(ex/raise :type :internal
:code :missing-email-templates))
{:subject subj
:body (d/concat
:body (into
[{:type "text/plain"
:content text}]
(when html

View File

@@ -0,0 +1,36 @@
;; 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.rlimit
"Resource usage limits (in other words: semaphores)."
(:require
[app.common.logging :as l]
[app.util.services :as sv])
(:import
java.util.concurrent.Semaphore))
(defn acquire!
[sem]
(.acquire ^Semaphore sem))
(defn release!
[sem]
(.release ^Semaphore sem))
(defn wrap-rlimit
[_cfg f mdata]
(if-let [permits (::permits mdata)]
(let [sem (Semaphore. permits)]
(l/debug :hint "wrapping rlimit" :handler (::sv/name mdata) :permits permits)
(fn [cfg params]
(try
(acquire! sem)
(f cfg params)
(finally
(release! sem)))))
f))

View File

@@ -86,3 +86,48 @@
(t/is (= 312043 (:size mobj1)))
(t/is (= 3887 (:size mobj2)))))
))
(t/deftest media-object-upload-idempotency
(let [prof (th/create-profile* 1)
proj (th/create-project* 1 {:profile-id (:id prof)
:team-id (:default-team-id prof)})
file (th/create-file* 1 {:profile-id (:id prof)
:project-id (:default-project-id prof)
:is-shared false})
mfile {:filename "sample.jpg"
:tempfile (th/tempfile "app/test_files/sample.jpg")
:content-type "image/jpeg"
:size 312043}
params {::th/type :upload-file-media-object
:profile-id (:id prof)
:file-id (:id file)
:is-local true
:name "testfile"
:content mfile
:id (uuid/next)}]
;; First try
(let [{:keys [result error] :as out} (th/mutation! params)]
;; (th/print-result! out)
(t/is (nil? error))
(t/is (= (:id params) (:id result)))
(t/is (= (:file-id params) (:file-id result)))
(t/is (= 800 (:width result)))
(t/is (= 800 (:height result)))
(t/is (= "image/jpeg" (:mtype result)))
(t/is (uuid? (:media-id result)))
(t/is (uuid? (:thumbnail-id result))))
;; Second try
(let [{:keys [result error] :as out} (th/mutation! params)]
;; (th/print-result! out)
(t/is (nil? error))
(t/is (= (:id params) (:id result)))
(t/is (= (:file-id params) (:file-id result)))
(t/is (= 800 (:width result)))
(t/is (= 800 (:height result)))
(t/is (= "image/jpeg" (:mtype result)))
(t/is (uuid? (:media-id result)))
(t/is (uuid? (:thumbnail-id result))))))

View File

@@ -8,12 +8,17 @@
;; Logging
org.clojure/tools.logging {:mvn/version "1.1.0"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.14.1"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.14.1"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.14.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.14.1"}
org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.14.1"}
org.apache.logging.log4j/log4j-api {:mvn/version "2.16.0"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.16.0"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.16.0"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.16.0"}
org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.16.0"}
org.slf4j/slf4j-api {:mvn/version "2.0.0-alpha1"}
org.slf4j/jcl-over-slf4j {:mvn/version "2.0.0-alpha1"}
org.slf4j/log4j-over-slf4j {:mvn/version "2.0.0-alpha1"}
org.slf4j/osgi-over-slf4j {:mvn/version "2.0.0-alpha1"}
org.slf4j/jul-to-slf4j {:mvn/version "2.0.0-alpha1"}
com.lmax/disruptor {:mvn/version "3.4.4"}
selmer/selmer {:mvn/version "1.12.40"}
expound/expound {:mvn/version "0.8.9"}

View File

@@ -37,7 +37,7 @@
;; --- Development Stuff
(defn- run-tests
([] (run-tests #"^app.common.tests.*"))
([] (run-tests #"^app.common.*-test$"))
([o]
(repl/refresh)
(cond

View File

@@ -6,7 +6,7 @@
(ns app.common.data
"Data manipulation and query helper functions."
(:refer-clojure :exclude [concat read-string hash-map merge name])
(:refer-clojure :exclude [read-string hash-map merge name])
#?(:cljs
(:require-macros [app.common.data]))
(:require
@@ -60,19 +60,37 @@
m)
(dissoc m k)))
(defn concat
[& colls]
(loop [result (transient (first colls))
colls (next colls)]
(defn- transient-concat
[c1 colls]
(loop [result (transient c1)
colls colls]
(if colls
(recur (reduce conj! result (first colls))
(next colls))
(persistent! result))))
(defn concat-set
([] #{})
([c1]
(if (set? c1) c1 (into #{} c1)))
([c1 & more]
(if (set? c1)
(transient-concat c1 more)
(transient-concat #{} (cons c1 more)))))
(defn concat-vec
([] [])
([c1]
(if (vector? c1) c1 (into [] c1)))
([c1 & more]
(if (vector? c1)
(transient-concat c1 more)
(transient-concat [] (cons c1 more)))))
(defn preconj
[coll elem]
(assert (vector? coll))
(concat [elem] coll))
(into [elem] coll))
(defn enumerate
([items] (enumerate items 0))
@@ -144,10 +162,15 @@
(reduce #(dissoc! %1 %2) (transient data) keys))))
(defn remove-at-index
"Takes a vector and returns a vector with an element in the
specified index removed."
[v index]
(vec (core/concat
(subvec v 0 index)
(subvec v (inc index)))))
;; The subvec function returns a SubVector type that is an vector
;; but does not have transient impl, because of this, we need to
;; pass an explicit vector as first argument.
(concat-vec []
(subvec v 0 index)
(subvec v (inc index))))
(defn zip [col1 col2]
(map vector col1 col2))
@@ -433,18 +456,18 @@
(str maybe-keyword)))))
(defn with-next
"Given a collectin will return a new collection where each element
is paried with the next item in the collection
(with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]"
"Given a collection will return a new collection where each element
is paired with the next item in the collection
(with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]"
[coll]
(map vector
coll
(concat [] (rest coll) [nil])))
(concat (rest coll) [nil])))
(defn with-prev
"Given a collectin will return a new collection where each element
is paried with the previous item in the collection
(with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]"
"Given a collection will return a new collection where each element
is paired with the previous item in the collection
(with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]"
[coll]
(map vector
coll
@@ -453,12 +476,12 @@
(defn with-prev-next
"Given a collection will return a new collection where every item is paired
with the previous and the next item of a collection
(with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]"
(with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]"
[coll]
(map vector
coll
(concat [nil] coll)
(concat [] (rest coll) [nil])))
(concat (rest coll) [nil])))
(defn prefix-keyword
"Given a keyword and a prefix will return a new keyword with the prefix attached

View File

@@ -6,7 +6,6 @@
(ns app.common.geom.align
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[clojure.spec.alpha :as s]))
@@ -16,11 +15,15 @@
(declare calc-align-pos)
;; TODO: revisit on how to reuse code and dont have this function
;; duplicated because the implementation right now differs from the
;; original function.
;; Duplicated from pages/helpers to remove cyclic dependencies
(defn- get-children [id objects]
(let [shapes (vec (get-in objects [id :shapes]))]
(if shapes
(d/concat shapes (mapcat #(get-children % objects) shapes))
(into shapes (mapcat #(get-children % objects)) shapes)
[])))
(defn- recursive-move

View File

@@ -100,7 +100,7 @@
(defn curve-tangent
"Retrieve the tangent vector to the curve in the point `t`"
[[start end h1 h2] t]
(let [coords [[(:x start) (:x h1) (:x h2) (:x end)]
[(:y start) (:y h1) (:y h2) (:y end)]]
@@ -316,15 +316,13 @@
:line-to [prev-point (command->point command)]
;; We return the bezier extremities
:curve-to (d/concat
[prev-point
(command->point command)]
(let [curve [prev-point
(command->point command)
(command->point command :c1)
(command->point command :c2)]]
(->> (curve-extremities curve)
(mapv #(curve-values curve %)))))
:curve-to (into [prev-point (command->point command)]
(let [curve [prev-point
(command->point command)
(command->point command :c1)
(command->point command :c2)]]
(->> (curve-extremities curve)
(map #(curve-values curve %)))))
[])
selrect (gpr/points->selrect points)]
(-> selrect
@@ -342,20 +340,19 @@
(command->point command)]
;; We return the bezier extremities
:curve-to (d/concat
[(command->point prev)
(command->point command)]
(let [curve [(command->point prev)
(command->point command)
(command->point command :c1)
(command->point command :c2)]]
(->> (curve-extremities curve)
(mapv #(curve-values curve %)))))
:curve-to (into [(command->point prev)
(command->point command)]
(let [curve [(command->point prev)
(command->point command)
(command->point command :c1)
(command->point command :c2)]]
(->> (curve-extremities curve)
(map #(curve-values curve %)))))
[]))
extremities (mapcat calc-extremities
content
(d/concat [nil] content))
(concat [nil] content))
selrect (gpr/points->selrect extremities)]
@@ -410,14 +407,16 @@
(let [initial (first segments)
lines (rest segments)]
(d/concat [{:command :move-to
:params (select-keys initial [:x :y])}]
(->> lines
(mapv #(hash-map :command :line-to
:params (select-keys % [:x :y]))))
(d/concat-vec
[{:command :move-to
:params (select-keys initial [:x :y])}]
(when closed?
[{:command :close-path}])))))
(->> lines
(map #(hash-map :command :line-to
:params (select-keys % [:x :y]))))
(when closed?
[{:command :close-path}])))))
(defonce num-segments 10)
@@ -770,7 +769,7 @@
ts-3 (check-range c1-half c1-to c2-from c2-half)
ts-4 (check-range c1-half c1-to c2-half c2-to)]
(d/concat [] ts-1 ts-2 ts-3 ts-4)))))))
(d/concat-vec ts-1 ts-2 ts-3 ts-4)))))))
(remove-close-ts [{cp1 :p1 cp2 :p2}]
(fn [{:keys [p1 p2]}]

View File

@@ -214,7 +214,7 @@
not-mask-shapes (without-obj shapes mask-id)
new-index (if (nil? index) nil (max (dec index) 0))
new-shapes (insert-items other-ids new-index not-mask-shapes)]
(d/concat [mask-id] new-shapes))))
(into [mask-id] new-shapes))))
(add-to-parent [parent index shapes]
(let [parent (-> parent

View File

@@ -97,7 +97,7 @@
(let [old-obj (get objects id)
new-obj (update-fn old-obj)
attrs (or attrs (d/concat #{} (keys old-obj) (keys new-obj)))
attrs (or attrs (d/concat-set (keys old-obj) (keys new-obj)))
{rops :rops uops :uops}
(reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)

View File

@@ -8,7 +8,7 @@
(:require
[app.common.uuid :as uuid]))
(def file-version 11)
(def file-version 12)
(def default-color "#b1b2b5") ;; $color-gray-20
(def root uuid/zero)

View File

@@ -103,7 +103,6 @@
"Retrieve all children ids recursively for a given object. The
children's order will be breadth first."
[id objects]
(loop [result (transient [])
pending (transient [])
next id]
@@ -214,10 +213,10 @@
[objects index ids]
(let [[before after] (split-at index objects)
p? (set ids)]
(d/concat []
(remove p? before)
ids
(remove p? after))))
(d/concat-vec []
(remove p? before)
ids
(remove p? after))))
(defn append-at-the-end
[prev-ids ids]
@@ -233,24 +232,25 @@
([objects {:keys [include-frames? include-frame-children?]
:or {include-frames? false
include-frame-children? true}}]
(let [lookup #(get objects %)
root (lookup uuid/zero)
(let [lookup #(get objects %)
root (lookup uuid/zero)
root-children (:shapes root)
lookup-shapes
(fn [result id]
(if (nil? id)
result
(let [obj (lookup id)
typ (:type obj)
(let [obj (lookup id)
typ (:type obj)
children (:shapes obj)]
(cond-> result
(or (not= :frame typ) include-frames?)
(d/concat [obj])
(conj obj)
(and (= :frame typ) include-frame-children?)
(d/concat (map lookup children))))))]
(into (map lookup) children)))))]
(reduce lookup-shapes [] root-children))))
@@ -299,15 +299,13 @@
(some? (:shapes object))
(assoc :shapes (mapv :id new-direct-children)))
new-object (update-new-object new-object object)
new-objects (d/concat [new-object] new-children)
updated-object (update-original-object object new-object)
new-object (update-new-object new-object object)
new-objects (into [new-object] new-children)
updated-object (update-original-object object new-object)
updated-objects (if (identical? object updated-object)
updated-children
(d/concat [updated-object] updated-children))]
(into [updated-object] updated-children))]
[new-object new-objects updated-objects])
@@ -320,9 +318,9 @@
(recur
(next child-ids)
(d/concat new-direct-children [new-child])
(d/concat new-children new-child-objects)
(d/concat updated-children updated-child-objects))))))))
(into new-direct-children [new-child])
(into new-children new-child-objects)
(into updated-children updated-child-objects))))))))
(defn indexed-shapes
"Retrieves a list with the indexes for each element in the layer tree.

View File

@@ -12,28 +12,25 @@
[clojure.set :as set]))
(defn calculate-frame-z-index [z-index frame-id objects]
(let [is-frame? (fn [id] (= :frame (get-in objects [id :type])))
(let [is-frame? (fn [id] (= :frame (get-in objects [id :type])))
frame-shapes (->> objects (vals) (filterv #(= (:frame-id %) frame-id)))
children (or (get-in objects [frame-id :shapes]) [])]
children (or (get-in objects [frame-id :shapes]) [])]
(if (empty? children)
z-index
(loop [current (peek children)
pending (pop children)
current-idx (count frame-shapes)
z-index z-index]
(let [children (get-in objects [current :shapes])
(let [children (get-in objects [current :shapes])
is-frame? (is-frame? current)
pending (if (not is-frame?)
(d/concat pending children)
pending)]
pending (if (not is-frame?)
(d/concat-vec pending children)
pending)]
(if (empty? pending)
(-> z-index
(assoc current current-idx))
(assoc z-index current current-idx)
(recur (peek pending)
(pop pending)
(dec current-idx)

View File

@@ -268,3 +268,16 @@
(update page :objects #(d/mapm (partial update-object %) %)))]
(update data :pages-index #(d/mapm update-page %))))
(defmethod migrate 12
[data]
(letfn [(update-grid [_key grid]
(cond-> grid
(= :auto (:size grid))
(assoc :size nil)))
(update-page [_id page]
(d/update-in-when page [:options :saved-grids] #(d/mapm update-grid %)))]
(update data :pages-index #(d/mapm update-page %))))

View File

@@ -213,26 +213,25 @@
;; Pick all segments in content-a that are not inside content-b
;; Pick all segments in content-b that are not inside content-a
(let [content
(d/concat
[]
(concat
(->> content-a-split (filter #(not (contains-segment? % content-b))))
(->> content-b-split (filter #(not (contains-segment? % content-a)))))
;; Overlapping segments should be added when they are part of the border
border-content
(->> content-b-split
(filterv #(and (contains-segment? % content-a)
(overlap-segment? % content-a-split)
(not (inside-segment? % content)))))]
(filter #(and (contains-segment? % content-a)
(overlap-segment? % content-a-split)
(not (inside-segment? % content)))))]
(d/concat content border-content)))
;; Ensure that the output is always a vector
(d/concat-vec content border-content)))
(defn create-difference [content-a content-a-split content-b content-b-split]
;; Pick all segments in content-a that are not inside content-b
;; Pick all segments in content b that are inside content-a
;; removing overlapping
(d/concat
[]
(d/concat-vec
(->> content-a-split (filter #(not (contains-segment? % content-b))))
;; Reverse second content so we can have holes inside other shapes
@@ -243,15 +242,14 @@
(defn create-intersection [content-a content-a-split content-b content-b-split]
;; Pick all segments in content-a that are inside content-b
;; Pick all segments in content-b that are inside content-a
(d/concat
[]
(d/concat-vec
(->> content-a-split (filter #(contains-segment? % content-b)))
(->> content-b-split (filter #(contains-segment? % content-a)))))
(defn create-exclusion [content-a content-b]
;; Pick all segments
(d/concat [] content-a content-b))
(d/concat-vec content-a content-b))
(defn fix-move-to

View File

@@ -31,23 +31,22 @@
:blur])
(def style-properties
(d/concat
style-group-properties
[:fill-color
:fill-opacity
:fill-color-gradient
:fill-color-ref-file
:fill-color-ref-id
:fill-image
:stroke-color
:stroke-color-ref-file
:stroke-color-ref-id
:stroke-opacity
:stroke-style
:stroke-width
:stroke-alignment
:stroke-cap-start
:stroke-cap-end]))
(into style-group-properties
[:fill-color
:fill-opacity
:fill-color-gradient
:fill-color-ref-file
:fill-color-ref-id
:fill-image
:stroke-color
:stroke-color-ref-file
:stroke-color-ref-id
:stroke-opacity
:stroke-style
:stroke-width
:stroke-alignment
:stroke-cap-start
:stroke-cap-end]))
(defn make-corner-arc
"Creates a curvle corner for border radius"

View File

@@ -90,7 +90,7 @@
[subpath other]
(assert (pt= (:to subpath) (:from other)))
(-> subpath
(update :data d/concat (rest (:data other)))
(update :data d/concat-vec (rest (:data other)))
(assoc :to (:to other))))
(defn- merge-paths

View File

@@ -72,17 +72,17 @@
(s/keys :opt-un [::destination ::preserve-scroll]))
(defmethod action-opts-spec :open-overlay [_]
(s/keys :req-un [::destination
::overlay-position
(s/keys :req-un [::overlay-position
::overlay-pos-type]
:opt-un [::close-click-outside
:opt-un [::destination
::close-click-outside
::background-overlay]))
(defmethod action-opts-spec :toggle-overlay [_]
(s/keys :req-un [::destination
::overlay-position
(s/keys :req-un [::overlay-position
::overlay-pos-type]
:opt-un [::close-click-outside
:opt-un [::destination
::close-click-outside
::background-overlay]))
(defmethod action-opts-spec :close-overlay [_]

View File

@@ -15,11 +15,12 @@
(s/def :artboard-grid.color/color ::us/string)
(s/def :artboard-grid.color/opacity ::us/safe-number)
(s/def :artboard-grid/size ::us/safe-integer)
(s/def :artboard-grid/size (s/nilable ::us/safe-integer))
(s/def :artboard-grid/item-length (s/nilable ::us/safe-number))
(s/def :artboard-grid/color (s/keys :req-un [:artboard-grid.color/color
:artboard-grid.color/opacity]))
(s/def :artboard-grid/type #{:stretch :left :center :right})
(s/def :artboard-grid/item-length (s/nilable ::us/safe-integer))
(s/def :artboard-grid/gutter (s/nilable ::us/safe-integer))
(s/def :artboard-grid/margin (s/nilable ::us/safe-integer))

View File

@@ -0,0 +1,58 @@
;; 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.common.data-test
(:require
[app.common.data :as d]
[clojure.test :as t]))
(t/deftest concat-vec
(t/is (= [1 2 3]
(d/concat-vec [1] #{2} [3])))
(t/is (= [1 2]
(d/concat-vec '(1) [2])))
(t/is (= [1]
(d/concat-vec [1])))
(t/is (= [] (d/concat-vec))))
(t/deftest concat-set
(t/is (= #{} (d/concat-set)))
(t/is (= #{1 2}
(d/concat-set [1] [2]))))
(t/deftest remove-at-index
(t/is (= [1 2 3 4]
(d/remove-at-index [1 2 3 4 5] 4)))
(t/is (= [1 2 3 4]
(d/remove-at-index [5 1 2 3 4] 0)))
(t/is (= [1 2 3 4]
(d/remove-at-index [1 5 2 3 4] 1)))
)
(t/deftest with-next
(t/is (= [[0 1] [1 2] [2 3] [3 4] [4 nil]]
(d/with-next (range 5)))))
(t/deftest with-prev
(t/is (= [[0 nil] [1 0] [2 1] [3 2] [4 3]]
(d/with-prev (range 5)))))
(t/deftest with-prev-next
(t/is (= [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]
(d/with-prev-next (range 5)))))
(t/deftest join
(t/is (= [[1 :a] [1 :b] [2 :a] [2 :b] [3 :a] [3 :b]]
(d/join [1 2 3] [:a :b])))
(t/is (= [1 10 100 2 20 200 3 30 300]
(d/join [1 2 3] [1 10 100] *))))

View File

@@ -201,7 +201,7 @@
(recur (->> (get svgdata "elements")
(filter #(= (get % "name") "g"))
(map (partial set-path-color id color mapping))
(update result "elements" d/concat))
(update result "elements" into))
(rest layers))
;; Now we have the result containing the svgdata of a
@@ -232,8 +232,8 @@
elements (cond->> elements
(not (empty? gradient-defs))
(d/concat [{"type" "element" "name" "defs" "attributes" {}
"elements" gradient-defs}]))]
(into [{"type" "element" "name" "defs" "attributes" {}
"elements" gradient-defs}]))]
(-> result
(assoc "name" "g")

View File

@@ -406,7 +406,7 @@
border: 1px solid transparent;
position: relative;
height: 38px;
margin-right: $size-2;
// margin-left: $size-2;
max-height: 30px;
position: relative;
width: 60%;
@@ -494,12 +494,10 @@
.editable-select svg {
fill: $color-gray-40;
}
.dropdown-button {
top: 4px;
}
.editable-select.input-option .input-text {
padding: 0;
padding-top: 0.18rem;
padding-left: 0.25rem;
}
.input-element {

View File

@@ -266,12 +266,10 @@
(ptk/reify ::finalize-page
ptk/UpdateEvent
(update [_ state]
(let [page-id (or page-id (get-in state [:workspace-data :pages 0]))
local (-> (:workspace-local state)
(dissoc
:edition
:edit-path
:selected))]
(let [local (-> (:workspace-local state)
(dissoc :edition
:edit-path
:selected))]
(-> state
(assoc-in [:workspace-cache page-id] local)
(dissoc :current-page-id :workspace-local :trimmed-page :workspace-drawing))))))
@@ -348,8 +346,7 @@
(declare purge-page)
(declare go-to-file)
;; TODO: properly handle positioning on undo.
;; TODO: for some reason, the page-id here in some circumstances is `nil`
(defn delete-page
[id]
(ptk/reify ::delete-page
@@ -546,7 +543,7 @@
(disj flags flag)
(conj flags flag)))
stored
(into #{} flags)))))))
(d/concat-set flags)))))))
;; --- Set element options mode
@@ -669,6 +666,7 @@
(watch [_ _ _]
(rx/of (dch/update-shapes [id] #(merge % attrs))))))
(defn start-rename-shape
[id]
(us/verify ::us/uuid id)
@@ -784,8 +782,7 @@
groups-to-delete)
u-del-change
(d/concat
[]
(concat
;; Create the groups
(map (fn [group-id]
(let [group (get objects group-id)]
@@ -936,25 +933,25 @@
:page-id page-id
:shapes (vec parents)}]
rchanges (d/concat []
r-mov-change
r-del-change
r-mask-change
r-detach-change
r-deroot-change
r-reroot-change
r-unconstraint-change
r-reg-change)
rchanges (d/concat-vec
r-mov-change
r-del-change
r-mask-change
r-detach-change
r-deroot-change
r-reroot-change
r-unconstraint-change
r-reg-change)
uchanges (d/concat []
u-del-change
u-reroot-change
u-deroot-change
u-detach-change
u-mask-change
u-mov-change
u-unconstraint-change
u-reg-change)]
uchanges (d/concat-vec
u-del-change
u-reroot-change
u-deroot-change
u-detach-change
u-mask-change
u-mov-change
u-unconstraint-change
u-reg-change)]
[rchanges uchanges]))
(defn relocate-shapes
@@ -970,18 +967,15 @@
objects (wsh/lookup-page-objects state page-id)
;; Ignore any shape whose parent is also intented to be moved
ids (cp/clean-loops objects ids)
ids (cp/clean-loops objects ids)
;; If we try to move a parent into a child we remove it
ids (filter #(not (cp/is-parent? objects parent-id %)) ids)
parents (reduce (fn [result id]
(conj result (cp/get-parent id objects)))
#{parent-id} ids)
ids (filter #(not (cp/is-parent? objects parent-id %)) ids)
parents (into #{parent-id} (map #(cp/get-parent % objects)) ids)
groups-to-delete
(loop [current-id (first parents)
to-check (rest parents)
(loop [current-id (first parents)
to-check (rest parents)
removed-id? (set ids)
result #{}]
@@ -995,7 +989,7 @@
(empty? (remove removed-id? (:shapes group))))
;; Adds group to the remove and check its parent
(let [to-check (d/concat [] to-check [(cp/get-parent current-id objects)]) ]
(let [to-check (concat to-check [(cp/get-parent current-id objects)])]
(recur (first to-check)
(rest to-check)
(conj removed-id? current-id)
@@ -1022,6 +1016,10 @@
#{}
ids)
;; TODO: Probably implementing this using loop/recur will
;; be more efficient than using reduce and continuos data
;; desturcturing.
;; Sets the correct components metadata for the moved shapes
;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside
;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component
@@ -1111,7 +1109,7 @@
(defn relocate-page
[id index]
(ptk/reify ::relocate-pages
(ptk/reify ::relocate-page
ptk/WatchEvent
(watch [it state _]
(let [cidx (-> (get-in state [:workspace-data :pages])
@@ -1209,7 +1207,7 @@
(boolean? hidden) (assoc :hidden hidden)))
objects (wsh/lookup-page-objects state)
ids (d/concat [id] (cp/get-children id objects))]
ids (into [id] (cp/get-children id objects))]
(rx/of (dch/update-shapes ids update-fn))))))

View File

@@ -50,7 +50,7 @@
(let [old-obj (get objects id)
new-obj (update-fn old-obj)
attrs (or attrs (d/concat #{} (keys old-obj) (keys new-obj)))
attrs (or attrs (d/concat-set (keys old-obj) (keys new-obj)))
{rops :rops uops :uops}
(reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)
@@ -114,9 +114,9 @@
:changes changes}))))
(defn commit-changes
[{:keys [redo-changes undo-changes origin save-undo? file-id]
[{:keys [redo-changes undo-changes
origin save-undo? file-id]
:or {save-undo? true}}]
(log/debug :msg "commit-changes"
:js/redo-changes redo-changes
:js/undo-changes undo-changes)

View File

@@ -31,6 +31,12 @@
(s/def ::set-of-string (s/every string? :kind set?))
(s/def ::ordered-set-of-uuid (s/every uuid? :kind d/ordered-set?))
(defn initialized?
"Check if the state is properly intialized in a workspace. This means
it has the `:current-page-id` and `:current-file-id` properly set."
[state]
(and (uuid? (:current-file-id state))
(uuid? (:current-page-id state))))
;; --- Helpers

View File

@@ -51,7 +51,7 @@
(empty? (remove removed-id? (:shapes group))))
;; Adds group to the remove and check its parent
(let [to-check (d/concat [] to-check [(cp/get-parent current-id objects)]) ]
(let [to-check (concat to-check [(cp/get-parent current-id objects)]) ]
(recur (first to-check)
(rest to-check)
(conj removed-id? current-id)
@@ -131,17 +131,21 @@
uchanges (->> ids-to-delete
(reduce add-deleted-group uchanges))]
[group rchanges uchanges]))
(defn prepare-remove-group
[page-id group objects]
(let [shapes (:shapes group)
(let [shapes (into [] (:shapes group)) ; ensure we always have vector
parent-id (cp/get-parent (:id group) objects)
parent (get objects parent-id)
index-in-parent (->> (:shapes parent)
(map-indexed vector)
(filter #(#{(:id group)} (second %)))
(ffirst))
index-in-parent
(->> (:shapes parent)
(map-indexed vector)
(filter #(#{(:id group)} (second %)))
(ffirst))
rchanges [{:type :mov-objects
:page-id page-id
:parent-id parent-id
@@ -223,39 +227,41 @@
[(first shapes) [] []]
(prepare-create-group objects page-id shapes "Group-1" true))
rchanges (d/concat rchanges
[{:type :mod-obj
:page-id page-id
:id (:id group)
:operations [{:type :set
:attr :masked-group?
:val true}
{:type :set
:attr :selrect
:val (-> shapes first :selrect)}
{:type :set
:attr :points
:val (-> shapes first :points)}
{:type :set
:attr :transform
:val (-> shapes first :transform)}
{:type :set
:attr :transform-inverse
:val (-> shapes first :transform-inverse)}]}
{:type :reg-objects
:page-id page-id
:shapes [(:id group)]}])
rchanges (d/concat-vec
rchanges
[{:type :mod-obj
:page-id page-id
:id (:id group)
:operations [{:type :set
:attr :masked-group?
:val true}
{:type :set
:attr :selrect
:val (-> shapes first :selrect)}
{:type :set
:attr :points
:val (-> shapes first :points)}
{:type :set
:attr :transform
:val (-> shapes first :transform)}
{:type :set
:attr :transform-inverse
:val (-> shapes first :transform-inverse)}]}
{:type :reg-objects
:page-id page-id
:shapes [(:id group)]}])
uchanges (conj uchanges
{:type :mod-obj
:page-id page-id
:id (:id group)
:operations [{:type :set
:attr :masked-group?
:val nil}]}
{:type :reg-objects
:page-id page-id
:shapes [(:id group)]})]
uchanges (d/concat-vec
uchanges
{:type :mod-obj
:page-id page-id
:id (:id group)
:operations [{:type :set
:attr :masked-group?
:val nil}]}
{:type :reg-objects
:page-id page-id
:shapes [(:id group)]})]
(rx/of (dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges

View File

@@ -666,14 +666,14 @@
(dwlh/generate-sync-file file-id :typographies library-id state)]
xf-fcat (comp (remove nil?) (map first) (mapcat identity))
rchanges (d/concat []
(sequence xf-fcat library-changes)
(sequence xf-fcat file-changes))
rchanges (d/concat-vec
(sequence xf-fcat library-changes)
(sequence xf-fcat file-changes))
xf-scat (comp (remove nil?) (map second) (mapcat identity))
uchanges (d/concat []
(sequence xf-scat library-changes)
(sequence xf-scat file-changes))]
uchanges (d/concat-vec
(sequence xf-scat library-changes)
(sequence xf-scat file-changes))]
(log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes
rchanges
@@ -720,8 +720,8 @@
(let [file (dwlh/get-file state file-id)
[rchanges1 uchanges1] (dwlh/generate-sync-file file-id :components library-id state)
[rchanges2 uchanges2] (dwlh/generate-sync-library file-id :components library-id state)
rchanges (d/concat rchanges1 rchanges2)
uchanges (d/concat uchanges1 uchanges2)]
rchanges (d/concat-vec rchanges1 rchanges2)
uchanges (d/concat-vec uchanges1 uchanges2)]
(when rchanges
(log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes
rchanges

View File

@@ -54,8 +54,8 @@
(defn concat-changes
[[rchanges1 uchanges1] [rchanges2 uchanges2]]
[(d/concat rchanges1 rchanges2)
(d/concat uchanges1 uchanges2)])
[(d/concat-vec rchanges1 rchanges2)
(d/concat-vec uchanges1 uchanges2)])
(defn get-local-file
[state]
@@ -134,6 +134,10 @@
[(first shapes) [] []]
(dwg/prepare-create-group objects page-id shapes "Component-1" true))
;; Asserts for documentation purposes
_ (us/assert vector? rchanges)
_ (us/assert vector? uchanges)
[new-shape new-shapes updated-shapes]
(make-component-shape group objects file-id)
@@ -288,8 +292,8 @@
state
(cp/make-container page :page))]
(recur (next pages)
(d/concat rchanges page-rchanges)
(d/concat uchanges page-uchanges)))
(into rchanges page-rchanges)
(into uchanges page-uchanges)))
[rchanges uchanges]))))
(defn generate-sync-library
@@ -315,8 +319,8 @@
(cp/make-container local-component
:component))]
(recur (next local-components)
(d/concat rchanges comp-rchanges)
(d/concat uchanges comp-uchanges)))
(into rchanges comp-rchanges)
(into uchanges comp-uchanges)))
[rchanges uchanges]))))
(defn- generate-sync-container
@@ -341,8 +345,8 @@
container
shape)]
(recur (next shapes)
(d/concat rchanges shape-rchanges)
(d/concat uchanges shape-uchanges)))
(into rchanges shape-rchanges)
(into uchanges shape-uchanges)))
[rchanges uchanges]))))
(defn- has-asset-reference-fn
@@ -438,7 +442,7 @@
:fill-color-ref-id nil
:fill-color-ref-file nil)))]
(generate-sync-text-shape shape container update-node))
(loop [attrs (seq color-sync-attrs)
(loop [attrs (seq color-sync-attrs)
roperations []
uoperations []]
(let [[attr-ref-id attr-ref-file color-attr attr] (first attrs)]
@@ -490,8 +494,8 @@
:val (get shape attr-ref-file)
:ignore-touched true}])]
(recur (next attrs)
(concat roperations roperations')
(concat uoperations uoperations'))))))))))
(into roperations roperations')
(into uoperations uoperations'))))))))))
(defmethod generate-sync-shape :typographies
[_ library-id state container shape]
@@ -734,8 +738,8 @@
moved
false)]
[(d/concat rchanges child-rchanges)
(d/concat uchanges child-uchanges)])))
[(d/concat-vec rchanges child-rchanges)
(d/concat-vec uchanges child-uchanges)])))
(defn generate-sync-shape-inverse
"Generate changes to update the component a shape is linked to, from
@@ -862,8 +866,8 @@
rchanges (mapv check-local rchanges)
uchanges (mapv check-local uchanges)]
[(d/concat rchanges child-rchanges)
(d/concat uchanges child-uchanges)])))
[(d/concat-vec rchanges child-rchanges)
(d/concat-vec uchanges child-uchanges)])))
; ---- Operation generation helpers ----
@@ -963,33 +967,32 @@
update-new-shape
update-original-shape)
rchanges (d/concat
(mapv (fn [shape']
(make-change
container
(as-> {:type :add-obj
:id (:id shape')
:parent-id (:parent-id shape')
:index index
:ignore-touched true
:obj shape'} $
(cond-> $
(:frame-id shape')
(assoc :frame-id (:frame-id shape'))))))
new-shapes)
[(make-change
container
{:type :reg-objects
:shapes all-parents})])
rchanges (d/concat-vec
(map (fn [shape']
(make-change
container
(as-> {:type :add-obj
:id (:id shape')
:parent-id (:parent-id shape')
:index index
:ignore-touched true
:obj shape'} $
(cond-> $
(:frame-id shape')
(assoc :frame-id (:frame-id shape'))))))
new-shapes)
[(make-change
container
{:type :reg-objects
:shapes all-parents})])
uchanges (d/concat
(mapv (fn [shape']
(make-change
container
{:type :del-obj
:id (:id shape')
:ignore-touched true}))
new-shapes))]
uchanges (mapv (fn [shape']
(make-change
container
{:type :del-obj
:id (:id shape')
:ignore-touched true}))
new-shapes)]
(if (and (cp/touched-group? parent-shape :shapes-group) omit-touched?)
empty-changes
@@ -1024,47 +1027,46 @@
update-new-shape
update-original-shape)
rchanges (d/concat
(mapv (fn [shape']
{:type :add-obj
:id (:id shape')
:component-id (:id component)
:parent-id (:parent-id shape')
:index index
:ignore-touched true
:obj shape'})
new-shapes)
[{:type :reg-objects
:component-id (:id component)
:shapes all-parents}]
(mapv (fn [shape']
{:type :mod-obj
:page-id (:id page)
:id (:id shape')
:operations [{:type :set
:attr :component-id
:val (:component-id shape')}
{:type :set
:attr :component-file
:val (:component-file shape')}
{:type :set
:attr :component-root?
:val (:component-root? shape')}
{:type :set
:attr :shape-ref
:val (:shape-ref shape')}
{:type :set
:attr :touched
:val (:touched shape')}]})
updated-shapes))
rchanges (d/concat-vec
(map (fn [shape']
{:type :add-obj
:id (:id shape')
:component-id (:id component)
:parent-id (:parent-id shape')
:index index
:ignore-touched true
:obj shape'})
new-shapes)
[{:type :reg-objects
:component-id (:id component)
:shapes all-parents}]
(map (fn [shape']
{:type :mod-obj
:page-id (:id page)
:id (:id shape')
:operations [{:type :set
:attr :component-id
:val (:component-id shape')}
{:type :set
:attr :component-file
:val (:component-file shape')}
{:type :set
:attr :component-root?
:val (:component-root? shape')}
{:type :set
:attr :shape-ref
:val (:shape-ref shape')}
{:type :set
:attr :touched
:val (:touched shape')}]})
updated-shapes))
uchanges (d/concat
(mapv (fn [shape']
{:type :del-obj
:id (:id shape')
:page-id (:id page)
:ignore-touched true})
new-shapes))]
uchanges (mapv (fn [shape']
{:type :del-obj
:id (:id shape')
:page-id (:id page)
:ignore-touched true})
new-shapes)]
[rchanges uchanges]))
@@ -1102,13 +1104,13 @@
(:frame-id shape')
(assoc :frame-id (:frame-id shape')))))))
uchanges (d/concat
[(add-change (:id shape))]
(map add-change children)
[(make-change
container
{:type :reg-objects
:shapes (vec parents)})])]
uchanges (d/concat-vec
[(add-change (:id shape))]
(map add-change children)
[(make-change
container
{:type :reg-objects
:shapes (vec parents)})])]
(if (and (cp/touched-group? parent :shapes-group) omit-touched?)
empty-changes

View File

@@ -6,7 +6,6 @@
(ns app.main.data.workspace.path.state
(:require
[app.common.data :as d]
[app.common.path.shapes-to-path :as upsp]))
(defn get-path-id
@@ -19,11 +18,10 @@
[state & ks]
(let [edit-id (get-in state [:workspace-local :edition])
page-id (:current-page-id state)]
(d/concat
(if edit-id
[:workspace-data :pages-index page-id :objects edit-id]
[:workspace-drawing :object])
ks)))
(into (if edit-id
[:workspace-data :pages-index page-id :objects edit-id]
[:workspace-drawing :object])
ks)))
(defn get-path
"Retrieves the location of the path object and additionaly can pass

View File

@@ -56,13 +56,14 @@
(rx/filter dch/commit-changes?)
(rx/debounce 2000)
(rx/merge stoper forcer))
local-file? #(as-> (:file-id %) event-file-id
(or (nil? event-file-id)
(= event-file-id file-id)))
library-file? #(as-> (:file-id %) event-file-id
(and (some? event-file-id)
(not= event-file-id file-id)))
local-file?
#(as-> (:file-id %) event-file-id
(or (nil? event-file-id)
(= event-file-id file-id)))
library-file?
#(as-> (:file-id %) event-file-id
(and (some? event-file-id)
(not= event-file-id file-id)))
on-dirty
(fn []
@@ -565,6 +566,20 @@
[frame-id]
(ptk/event ::update-frame-thumbnail {:frame-id frame-id}))
(defn update-shape-thumbnail
"An event that is succeptible to be executed out of the main flow, so
it need to correctly handle the situation that there are no page-id
or file-is loaded."
[shape-id thumbnail-data]
(ptk/reify ::update-shape-thumbnail
ptk/WatchEvent
(watch [_ state _]
(when (and (dwc/initialized? state)
(uuid? shape-id))
(rx/of (dch/update-shapes [shape-id]
#(assoc % :thumbnail thumbnail-data)
{:save-undo? false}))))))
(defn- extract-frame-changes
"Process a changes set in a commit to extract the frames that are channging"
[[event [old-objects new-objects]]]

View File

@@ -409,8 +409,10 @@
:shapes [shape-id]}]
;; Careful! the undo changes are concatenated reversed (we undo in reverse order
changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)]
unames (conj unames (:name shape))
changes [(d/concat-vec rchs rch1 rch2)
(d/concat-vec uch1 uchs)]
unames (conj unames (:name shape))
reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data)]
(reduce reducer-fn [unames changes] (d/enumerate children)))

View File

@@ -54,23 +54,23 @@
(ptk/reify ::finalize-editor-state
ptk/WatchEvent
(watch [_ state _]
(let [content (-> (get-in state [:workspace-editor-state id])
(ted/get-editor-current-content))]
(when (dwc/initialized? state)
(let [content (-> (get-in state [:workspace-editor-state id])
(ted/get-editor-current-content))]
(if (ted/content-has-text? content)
(let [content (d/merge (ted/export-content content)
(dissoc (:content shape) :children))]
(rx/merge
(rx/of (update-editor-state shape nil))
(when (and (not= content (:content shape))
(some? (:current-page-id state)))
(rx/of
(dch/update-shapes [id] #(assoc % :content content))
(dwu/commit-undo-transaction)))))
(if (ted/content-has-text? content)
(let [content (d/merge (ted/export-content content)
(dissoc (:content shape) :children))]
(rx/merge
(rx/of (update-editor-state shape nil))
(when (and (not= content (:content shape))
(some? (:current-page-id state)))
(rx/of
(dch/update-shapes [id] #(assoc % :content content))
(dwu/commit-undo-transaction)))))
(when (some? id)
(rx/of (dws/deselect-shape id)
(dwc/delete-shapes #{id}))))))))
(when (some? id)
(rx/of (dws/deselect-shape id)
(dwc/delete-shapes #{id})))))))))
(defn initialize-editor-state
[{:keys [id content] :as shape} decorator]

View File

@@ -25,7 +25,6 @@
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
;; -- Helpers --------------------------------------------------------
;; For each of the 8 handlers gives the multiplier for resize
@@ -123,8 +122,7 @@
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))]
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)]
(reduce (fn [state id]
(update state :workspace-modifiers
@@ -148,20 +146,19 @@
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
id->obj #(get objects %)
get-children (fn [shape] (map id->obj (cp/get-children (:id shape) objects)))
shapes (->> shapes (into [] (remove #(get % :blocked false))))
shapes (->> shapes (mapcat get-children) (concat shapes))
shapes (->> shapes
(remove #(get % :blocked false))
(mapcat (fn [shape]
(->> (cp/get-children (:id shape) objects)
(map #(get objects %)))))
(concat shapes))
update-shape
(fn [modifiers shape]
(let [rotate-modifiers (gsh/rotation-modifiers shape center angle)]
(assoc-in modifiers [(:id shape) :modifiers] rotate-modifiers)))]
(-> state
(update :workspace-modifiers
#(reduce update-shape % shapes))))))))
(update state :workspace-modifiers #(reduce update-shape % shapes)))))))
(defn- apply-modifiers
[ids]
@@ -169,11 +166,11 @@
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat [] children-ids ids)
object-modifiers (get state :workspace-modifiers)
ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
(let [objects (wsh/lookup-page-objects state)
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat-vec children-ids ids)
object-modifiers (get state :workspace-modifiers)
ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
(rx/of (dwu/start-undo-transaction)
(dch/update-shapes
@@ -423,7 +420,10 @@
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))]
;; TODO: looks completly redundant operation because
;; apply-modifiers already finds all children.
ids (d/concat-vec ids (mapcat #(cp/get-children % objects) ids))]
(rx/of (apply-modifiers ids))))))

View File

@@ -168,9 +168,8 @@
(map between-snap))
;; Search the minimum snap
snap-list (-> [] (d/concat lt-snap) (d/concat gt-snap) (d/concat between-snap))
min-snap (reduce best-snap ##Inf snap-list)]
snap-list (d/concat-vec lt-snap gt-snap between-snap)
min-snap (reduce best-snap ##Inf snap-list)]
(if (mth/finite? min-snap) [0 min-snap] nil)))
@@ -291,8 +290,7 @@
(set (keys other)))]
(into {}
(map (fn [key]
[key
(d/concat [] (get matches key []) (get other key []))]))
[key (d/concat-vec (get matches key []) (get other key []))]))
keys)))]
(-> matches

View File

@@ -16,7 +16,8 @@
[app.util.timers :as timers]
[rumext.alpha :as mf]))
(mf/defc editable-select [{:keys [value type options class on-change placeholder on-blur] :as params}]
(mf/defc editable-select
[{:keys [value type options class on-change placeholder on-blur] :as params}]
(let [state (mf/use-state {:id (uuid/next)
:is-open? false
:current-value value
@@ -42,20 +43,23 @@
(when on-blur (on-blur))))
as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item]))
labels-map (into {} (->> options (map as-key-value)))
labels-map (into {} (map as-key-value) options)
value->label (fn [value] (get labels-map value value))
set-value (fn [value]
(swap! state assoc :current-value value)
(when on-change (on-change value)))
set-value
(fn [value]
(swap! state assoc :current-value value)
(when on-change (on-change value)))
handle-change-input (fn [event]
(let [value (-> event dom/get-target dom/get-value)
value (-> (or (d/parse-double value) value)
(math/precision 2))]
(set-value value)))
;; TODO: why this method supposes that all editable select
;; works with numbers?
handle-change-input
(fn [event]
(let [value (-> event dom/get-target dom/get-value)
value (-> (or (d/parse-double value) value)
(math/precision 2))]
(set-value value)))
on-node-load
(fn [node]
@@ -133,7 +137,7 @@
[:div.editable-select {:class class
:ref on-node-load}
[:input.input-text {:value (or (-> @state :current-value value->label) "")
[:input.input-text {:value (or (some-> @state :current-value value->label) "")
:on-change handle-change-input
:on-key-down handle-key-down
:on-focus handle-focus
@@ -149,12 +153,12 @@
:left (:left @state)
:bottom (:bottom @state)}}
(for [[index item] (map-indexed vector options)]
(cond
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
:else (let [[value label] (as-key-value item)]
[:li.checked-element
{:key (str (:id @state) "-" index)
:class (when (= value (-> @state :current-value)) "is-selected")
:on-click (select-item value)}
[:span.check-icon i/tick]
[:span label]])))]]]))
(if (= :separator item)
[:hr {:key (str (:id @state) "-" index)}]
(let [[value label] (as-key-value item)]
[:li.checked-element
{:key (str (:id @state) "-" index)
:class (when (= value (-> @state :current-value)) "is-selected")
:on-click (select-item value)}
[:span.check-icon i/tick]
[:span label]])))]]]))

View File

@@ -15,6 +15,11 @@
[app.util.simple-math :as sm]
[rumext.alpha :as mf]))
(defn num? [val]
(and (number? val)
(not (math/nan? val))
(math/finite? val)))
(mf/defc numeric-input
{::mf/wrap-props false
::mf/forward-ref true}
@@ -25,32 +30,31 @@
wrap-value? (obj/get props "data-wrap")
on-change (obj/get props "onChange")
title (obj/get props "title")
default-val (obj/get props "default" 0)
;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here).
;; So we use the external ref if provided, and the local one if not.
local-ref (mf/use-ref)
ref (or external-ref local-ref)
local-ref (mf/use-ref)
ref (or external-ref local-ref)
value (d/parse-integer value-str 0)
;; This `value` represents the previous value and is used as
;; initil value for the simple math expression evaluation.
value (d/parse-integer value-str default-val)
min-val (cond
(number? min-val-str)
min-val-str
min-val (cond
(number? min-val-str)
min-val-str
(string? min-val-str)
(d/parse-integer min-val-str))
(string? min-val-str)
(d/parse-integer min-val-str))
max-val (cond
(number? max-val-str)
max-val-str
max-val (cond
(number? max-val-str)
max-val-str
(string? max-val-str)
(d/parse-integer max-val-str))
num? (fn [val] (and (number? val)
(not (math/nan? val))
(math/finite? val)))
(string? max-val-str)
(d/parse-integer max-val-str))
parse-value
(mf/use-callback
@@ -82,10 +86,9 @@
(mf/use-callback
(mf/deps on-change update-input value)
(fn [new-value]
(when (and (some? new-value) (not= new-value value) (some? on-change))
(when (and (not= new-value value) (some? on-change))
(on-change new-value))
(when (some? new-value)
(update-input new-value))))
(update-input new-value)))
set-delta
(mf/use-callback
@@ -143,10 +146,10 @@
(mf/use-callback
(mf/deps parse-value apply-value update-input)
(fn [_]
(let [new-value (parse-value)]
(let [new-value (or (parse-value) default-val)]
(if new-value
(apply-value new-value)
(update-input value-str)))))
(update-input new-value)))))
props (-> props
(obj/without ["value" "onChange"])
@@ -159,12 +162,5 @@
(obj/set! "onKeyDown" handle-key-down)
(obj/set! "onBlur" handle-blur))]
(mf/use-effect
(mf/deps value-str)
(fn []
(when-let [input-node (mf/ref-val ref)]
(when-not (dom/active? input-node)
(dom/set-value! input-node value-str)))))
[:> :input props]))

View File

@@ -230,9 +230,12 @@
(mf/defc leave-and-reassign-modal
{::mf/register modal/components
::mf/register-as ::leave-and-reassign}
[{:keys [members profile team accept]}]
[{:keys [team accept]}]
(let [form (fm/use-form :spec ::leave-modal-form :initial {})
members (some->> members (filterv #(not= (:id %) (:id profile))))
members-map (mf/deref refs/dashboard-team-members)
members (vals members-map)
options (into [{:value ""
:label (tr "modals.leave-and-reassign.select-memeber-to-promote")}]
(map #(hash-map :label (:name %) :value (str (:id %))) members))
@@ -290,7 +293,7 @@
on-leaved-success
(fn []
(st/emit! (modal/hide)
(dd/go-to-projects (:default-team-id profile))))
(du/fetch-teams)))
leave-fn
(st/emitf (dd/leave-team (with-meta {} {:on-success on-leaved-success})))
@@ -298,7 +301,13 @@
leave-and-reassign-fn
(fn [member-id]
(let [params {:reassign-to member-id}]
(st/emit! (dd/leave-team (with-meta params {:on-success on-leaved-success})))))
(st/emit! (dd/go-to-projects (:default-team-id profile))
(dd/leave-team (with-meta params {:on-success on-leaved-success})))))
delete-fn
(fn []
(st/emit! (dd/go-to-projects (:default-team-id profile))
(dd/delete-team (with-meta team {:on-success on-leaved-success}))))
on-leave-clicked
(st/emitf (modal/show
@@ -309,15 +318,13 @@
:on-accept leave-fn}))
on-leave-as-owner-clicked
(st/emitf (modal/show
{:type ::leave-and-reassign
:profile profile
:team team
:members members
:accept leave-and-reassign-fn}))
delete-fn
(st/emitf (dd/delete-team (with-meta team {:on-success on-leaved-success})))
(fn []
(st/emit! (dd/fetch-team-members)
(modal/show
{:type ::leave-and-reassign
:profile profile
:team team
:accept leave-and-reassign-fn})))
on-delete-clicked
(st/emitf
@@ -335,14 +342,14 @@
[:li {:on-click on-rename-clicked} (tr "labels.rename")]
(cond
(:is-owner team)
(get-in team [:permissions :is-owner])
[:li {:on-click on-leave-as-owner-clicked} (tr "dashboard.leave-team")]
(> (count members) 1)
[:li {:on-click on-leave-clicked} (tr "dashboard.leave-team")])
(when (:is-owner team)
(when (get-in team [:permissions :is-owner])
[:li {:on-click on-delete-clicked} (tr "dashboard.delete-team")])]))

View File

@@ -124,6 +124,7 @@
(mf/defc team-member
{::mf/wrap [mf/memo]}
[{:keys [team member profile] :as props}]
(let [show? (mf/use-state false)
set-role
@@ -174,8 +175,8 @@
[:span.label (tr "labels.viewer")])
(when (and (not (:is-owner member))
(or (:is-admin team)
(:is-owner team)))
(or (get-in team [:permissions :is-admin])
(get-in team [:permissions :is-owner])))
[:span.icon {:on-click #(reset! show? true)} i/arrow-down])]
[:& dropdown {:show @show?
@@ -191,8 +192,8 @@
[:hr]
[:li {:on-click set-owner} (tr "dashboard.promote-to-owner")]])
[:hr]
(when (and (or (:is-owner team)
(:is-admin team))
(when (and (or (get-in team [:permissions :is-owner])
(get-in team [:permissions :is-admin]))
(not= (:id profile)
(:id member)))
[:li {:on-click delete} (tr "labels.remove")])]]]]))

View File

@@ -192,7 +192,7 @@
h-lines (->> (calculate-distance-lines (:x1 from) (:x2 from) (:x1 to) (:x2 to))
(map (fn [[start end]] [start fixed-y end fixed-y])))
lines (d/concat [] v-lines h-lines)
lines (d/concat-vec v-lines h-lines)
distance-line-stroke (/ distance-line-stroke zoom)]

View File

@@ -6,7 +6,6 @@
(ns app.main.ui.settings.options
(:require
[app.common.data :as d]
[app.common.spec :as us]
[app.main.data.messages :as dm]
[app.main.data.users :as du]
@@ -49,8 +48,8 @@
[:h2 (t locale "labels.language")]
[:div.fields-row
[:& fm/select {:options (d/concat [{:label "Auto (browser)" :value ""}]
i18n/supported-locales)
[:& fm/select {:options (into [{:label "Auto (browser)" :value ""}]
i18n/supported-locales)
:label (t locale "dashboard.select-ui-language")
:default ""
:name :lang}]]

View File

@@ -149,8 +149,7 @@
(defn shape->filters
[shape]
(d/concat
[]
(d/concat-vec
[{:id "BackgroundImageFix" :type :image-fix}]
;; Background blur won't work in current SVG specification

View File

@@ -53,7 +53,7 @@
(fn [index]
(swap! exports (fn [exports]
(let [[before after] (split-at index exports)]
(d/concat [] before (rest after)))))))
(d/concat-vec before (rest after)))))))
on-scale-change
(mf/use-callback

View File

@@ -36,7 +36,7 @@
update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)]
(->> (cp/get-children frame-id objects)
(d/concat [frame-id])
(into [frame-id])
(reduce update-fn objects)))))

View File

@@ -7,7 +7,6 @@
(ns app.main.ui.viewer.shapes
"The main container for a frame in viewer mode"
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
@@ -396,7 +395,7 @@
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
frame-id (:id frame)
modifier-ids (d/concat [frame-id] (cp/get-children frame-id objects))
modifier-ids (into [frame-id] (cp/get-children frame-id objects))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier)

View File

@@ -6,7 +6,6 @@
(ns app.main.ui.workspace.shapes.path.editor
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gsp]
[app.common.path.commands :as upc]
@@ -166,9 +165,9 @@
:zoom zoom}]])
(mf/defc path-snap [{:keys [selected points zoom]}]
(let [ranges (mf/use-memo (mf/deps selected points) #(snap/create-ranges points selected))
(let [ranges (mf/use-memo (mf/deps selected points) #(snap/create-ranges points selected))
snap-matches (snap/get-snap-delta-match selected ranges (/ 1 zoom))
matches (d/concat [] (second (:x snap-matches)) (second (:y snap-matches)))]
matches (concat (second (:x snap-matches)) (second (:y snap-matches)))]
[:g.snap-paths
(for [[from to] matches]

View File

@@ -75,7 +75,7 @@
(mf/deps shape)
(fn [index]
(let [[before after] (split-at index exports)
exports (d/concat [] before (rest after))]
exports (d/concat-vec before (rest after))]
(st/emit! (udw/update-shape (:id shape)
{:exports exports})))))

View File

@@ -16,8 +16,7 @@
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
[app.util.data :as d]
[app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row input-row-v2]]
[app.util.geom.grid :as gg]
[app.util.i18n :as i18n :refer [tr]]
[okulary.core :as l]
@@ -27,90 +26,101 @@
(l/derived :saved-grids refs/workspace-page-options))
(defn- get-size-options []
[{:value :auto :label (tr "workspace.options.grid.auto")}
[{:value nil :label (tr "workspace.options.grid.auto")}
:separator
18 12 10 8 6 4 3 2])
(mf/defc grid-options
[{:keys [grid frame default-grid-params on-change on-remove on-save-grid]}]
(let [size-options (get-size-options)
state (mf/use-state {:show-advanced-options false})
{::mf/wrap [mf/memo]}
[{:keys [shape-id index grid frame-width frame-height default-grid-params]}]
(let [on-change (mf/use-fn (mf/deps shape-id index) #(st/emit! (dw/set-frame-grid shape-id index %)))
on-remove (mf/use-fn (mf/deps shape-id index) #(st/emit! (dw/remove-frame-grid shape-id index)))
on-save-default (mf/use-fn #(st/emit! (dw/set-default-grid (:type %) (:params %))))
size-options (mf/use-memo get-size-options)
state (mf/use-state {:show-advanced-options false})
{:keys [type display params]} grid
toggle-advanced-options
#(swap! state update :show-advanced-options not)
(mf/use-fn #(swap! state update :show-advanced-options not))
handle-toggle-visibility
(fn [_]
(when on-change
(on-change (update grid :display #(if (nil? %) false (not %))))))
handle-remove-grid
(fn [_]
(when on-remove (on-remove)))
(mf/use-fn
(mf/deps grid)
(fn [_]
(on-change (update grid :display #(if (nil? %) false (not %))))))
handle-change-type
(fn [grid-type]
(let [defaults (grid-type default-grid-params)]
(when on-change
(on-change (assoc grid
:type grid-type
:params defaults)))))
(mf/use-fn
(mf/deps grid)
(fn [grid-type]
(let [defaults (grid-type default-grid-params)]
(on-change (assoc grid
:type grid-type
:params defaults)))))
handle-change
(fn [& keys]
(fn [& keys-path]
(fn [value]
(when on-change
(on-change (assoc-in grid keys value)))))
(on-change (assoc-in grid keys-path value))))
;; TODO: remove references to :auto
handle-change-size
(fn [size]
(let [{:keys [margin gutter item-length]} (:params grid)
frame-length (if (= :column (:type grid)) (:width frame) (:height frame))
item-length (if (or (nil? size) (= :auto size))
(-> (gg/calculate-default-item-length frame-length margin gutter)
(mth/precision 2))
item-length)]
(when on-change
(on-change (-> grid
(assoc-in [:params :size] size)
(assoc-in [:params :item-length] item-length))))))
(mf/use-fn
(mf/deps grid)
(fn [size]
(let [{:keys [margin gutter item-length]} (:params grid)
frame-length (if (= :column (:type grid)) frame-width frame-height)
item-length (if (nil? size)
(-> (gg/calculate-default-item-length frame-length margin gutter)
(mth/precision 2))
item-length)]
(-> grid
(update :params assoc :size size :item-length item-length)
(on-change)))))
handle-change-item-length
(fn [item-length]
(let [size (get-in grid [:params :size])
size (if (and (nil? item-length) (or (nil? size) (= :auto size))) 12 size)]
(when on-change
(on-change (-> grid
(assoc-in [:params :size] size)
(assoc-in [:params :item-length] item-length))))))
(mf/use-fn
(mf/deps grid)
(fn [item-length]
(let [item-length (if (zero? item-length) nil item-length)
size (get-in grid [:params :size])
size (if (and (nil? item-length) (nil? size)) 12 size)]
(-> grid
(update :params assoc :size size :item-length item-length)
(on-change)))))
handle-change-color
(fn [color]
(when on-change
(on-change (assoc-in grid [:params :color] color))))
(mf/use-fn
(mf/deps grid)
(fn [color]
(-> grid
(update :params assoc :color color)
(on-change))))
handle-detach-color
(fn []
(when on-change
(on-change (-> grid
(d/dissoc-in [:params :color :id])
(d/dissoc-in [:params :color :file-id])))))
(mf/use-fn
(mf/deps grid)
(fn []
(-> grid
(update-in [:params :color] dissoc :id :file-id)
(on-change))))
handle-use-default
(fn []
(let [params ((:type grid) default-grid-params)
color (or (get-in params [:color :value]) (get-in params [:color :color]))
params (-> params
(assoc-in [:color :color] color)
(update :color dissoc :value))]
(when on-change
(on-change (assoc grid :params params)))))
(mf/use-fn
(mf/deps grid)
(fn []
(let [params ((:type grid) default-grid-params)
color (or (get-in params [:color :value]) (get-in params [:color :color]))
params (-> params
(assoc-in [:color :color] color)
(update :color dissoc :value))]
(when on-change
(on-change (assoc grid :params params))))))
handle-set-as-default
(fn []
(when on-save-grid
(on-save-grid grid)))
(mf/use-fn (mf/deps grid) #(on-save-default grid))
is-default (= (->> grid :params)
(->> grid :type default-grid-params))
@@ -122,21 +132,23 @@
[:button.custom-button {:class (when open? "is-active")
:on-click toggle-advanced-options} i/actions]
[:& select {:class "flex-grow"
:default-value type
:options [{:value :square :label (tr "workspace.options.grid.square")}
{:value :column :label (tr "workspace.options.grid.column")}
{:value :row :label (tr "workspace.options.grid.row")}]
:on-change handle-change-type}]
[:& select
{:class "flex-grow"
:default-value type
:options [{:value :square :label (tr "workspace.options.grid.square")}
{:value :column :label (tr "workspace.options.grid.column")}
{:value :row :label (tr "workspace.options.grid.row")}]
:on-change handle-change-type}]
(if (= type :square)
[:div.input-element.pixels {:title (tr "workspace.options.size")}
[:> numeric-input {:min 1
:value (or (:size params) "")
:no-validate true
:value (:size params)
:on-change (handle-change :params :size)}]]
[:& editable-select {:value (:size params)
:type (when (number? (:size params)) "number" )
:type "number"
:class "input-option"
:min 1
:options size-options
@@ -145,10 +157,9 @@
[:div.grid-option-main-actions
[:button.custom-button {:on-click handle-toggle-visibility} (if display i/eye i/eye-closed)]
[:button.custom-button {:on-click handle-remove-grid} i/minus]]]
[:button.custom-button {:on-click on-remove} i/minus]]]
[:& advanced-options {:visible? open?
:on-close toggle-advanced-options}
[:& advanced-options {:visible? open? :on-close toggle-advanced-options}
[:button.custom-button {:on-click toggle-advanced-options} i/actions]
(when (= :square type)
[:& input-row {:label (tr "workspace.options.grid.params.size")
@@ -190,13 +201,16 @@
:on-change (handle-change :params :type)}])
(when (#{:row :column} type)
[:& input-row {:label (if (= :row type)
(tr "workspace.options.grid.params.height")
(tr "workspace.options.grid.params.width"))
:class "pixels"
:placeholder "Auto"
:value (or (:item-length params) "")
:on-change handle-change-item-length}])
[:& input-row-v2
{:class "pixels"
:label (if (= :row type)
(tr "workspace.options.grid.params.height")
(tr "workspace.options.grid.params.width"))}
[:> numeric-input
{:placeholder "Auto"
:value (or (:item-length params) "")
:default nil
:on-change handle-change-item-length}]])
(when (#{:row :column} type)
[:*
@@ -226,11 +240,9 @@
(mf/defc frame-grid [{:keys [shape]}]
(let [id (:id shape)
default-grid-params (merge dw/default-grid-params (mf/deref workspace-saved-grids))
handle-create-grid #(st/emit! (dw/add-frame-grid id))
handle-remove-grid (fn [index] #(st/emit! (dw/remove-frame-grid id index)))
handle-edit-grid (fn [index] #(st/emit! (dw/set-frame-grid id index %)))
handle-save-grid (fn [grid] (st/emit! (dw/set-default-grid (:type grid) (:params grid))))]
saved-grids (mf/deref workspace-saved-grids)
default-grid-params (mf/use-memo (mf/deps saved-grids) #(merge dw/default-grid-params saved-grids))
handle-create-grid (mf/use-fn (mf/deps id) #(st/emit! (dw/add-frame-grid id)))]
[:div.element-set
[:div.element-set-title
[:span (tr "workspace.options.grid.title")]
@@ -239,12 +251,13 @@
(when (seq (:grids shape))
[:div.element-set-content
(for [[index grid] (map-indexed vector (:grids shape))]
[:& grid-options {:key (str (:id shape) "-" index)
:grid grid
:default-grid-params default-grid-params
:frame shape
:on-change (handle-edit-grid index)
:on-remove (handle-remove-grid index)
:on-save-grid handle-save-grid}])])]))
[:& grid-options {:key (str id "-" index)
:shape-id id
:grid grid
:index index
:frame-width (:width shape)
:frame-height (:height shape)
:default-grid-params default-grid-params
}])])]))

View File

@@ -68,17 +68,19 @@
(def root-attrs text-valign-attrs)
(def paragraph-attrs
(d/concat text-align-attrs
text-direction-attrs))
(d/concat-vec
text-align-attrs
text-direction-attrs))
(def text-attrs
(d/concat text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(d/concat-vec
text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def attrs (d/concat #{} shape-attrs root-attrs paragraph-attrs text-attrs))
(def attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-attrs))
(mf/defc text-align-options
[{:keys [values on-change on-blur] :as props}]
@@ -271,12 +273,12 @@
(fn [_]
(let [setted-values (-> (d/without-nils values)
(select-keys
(d/concat text-font-attrs
text-spacing-attrs
text-transform-attrs)))
typography (merge txt/default-typography setted-values)
typography (generate-typography-name typography)
id (uuid/next)]
(d/concat-vec text-font-attrs
text-spacing-attrs
text-transform-attrs)))
typography (merge txt/default-typography setted-values)
typography (generate-typography-name typography)
id (uuid/next)]
(st/emit! (dwl/add-typography (assoc typography :id id) false))
(run! #(emit-update! % {:typography-ref-id id
:typography-ref-file file-id}) ids)))

View File

@@ -9,6 +9,7 @@
[app.main.ui.components.editable-select :refer [editable-select]]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.components.select :refer [select]]
[app.util.object :as obj]
[rumext.alpha :as mf]))
(mf/defc input-row [{:keys [label options value class min max on-change type placeholder]}]
@@ -27,7 +28,7 @@
[:& editable-select {:value value
:class "input-option"
:options options
:type (when (number? value) "number")
:type "number"
:min min
:max max
:placeholder placeholder
@@ -38,9 +39,28 @@
:class "input-text"
:on-change on-change} ]
[:> numeric-input {:placeholder placeholder
:min min
:max max
:on-change on-change
:value (or value "")}])]])
[:> numeric-input
{:placeholder placeholder
:min min
:max max
:on-change on-change
:value (or value "")}])]])
;; NOTE: (by niwinz) this is a new version of input-row, I didn't
;; touched the original one because it is used in many sites and I
;; don't have intention to refactor all the code right now. We should
;; consider to use the new one and once we have migrated all to the
;; new component, we can proceed to rename it and delete the old one.
(mf/defc input-row-v2
{::mf/wrap-props false}
[props]
(let [label (obj/get props "label")
class (obj/get props "class")
children (obj/get props "children")]
[:div.row-flex.input-row
[:span.element-set-subtitle label]
[:div.input-element {:class class}
children]]))

View File

@@ -183,7 +183,7 @@
(attrs/get-attrs-multi (txt/node-seq content) attrs))))]
:children (let [children (->> (:shapes shape []) (map #(get objects %)))
[new-ids new-values] (get-attrs children objects attr-type)]
[(d/concat ids new-ids) (merge-attrs values new-values)])
[(d/concat-vec ids new-ids) (merge-attrs values new-values)])
[])))]
(reduce extract-attrs [[] []] shapes)))

View File

@@ -82,7 +82,7 @@
on-drop
(mf/use-callback
(mf/deps id)
(mf/deps id index)
(fn [side {:keys [id] :as data}]
(let [index (if (= :bot side) (inc index) index)]
(st/emit! (dw/relocate-page id index)))))

View File

@@ -6,7 +6,6 @@
(ns app.main.ui.workspace.viewport.hooks
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.main.data.shortcuts :as dsc]
@@ -162,10 +161,9 @@
remove-xfm (mapcat #(cp/get-parents % objects))
remove-id? (cond-> (into #{} remove-xfm selected)
@ctrl?
(d/concat (filterv is-group? ids)))
ids (->> ids (filterv (comp not remove-id?)))
(into (filter is-group?) ids))
ids (filterv (comp not remove-id?) ids)
hover-shape (get objects (first ids))]
(reset! hover hover-shape)

View File

@@ -163,12 +163,12 @@
show-candidate? #(check-in-set % distances)
;; Checks the distances between elements for distances that match the set of distances
distance-coincidences (d/concat (get-shapes-match show-candidate? lt-shapes)
(get-shapes-match show-candidate? gt-shapes))
distance-coincidences (d/concat-vec
(get-shapes-match show-candidate? lt-shapes)
(get-shapes-match show-candidate? gt-shapes))
;; Stores the distance candidates to be shown
distance-candidates (d/concat
#{}
distance-candidates (d/concat-set
(map first distance-coincidences)
(filter #(check-in-set % lt-distances) gt-distances)
(filter #(check-in-set % gt-distances) lt-distances))
@@ -194,7 +194,7 @@
(filter #(show-distance? (distance-to-selrect %)))
(map #(vector selrect (:selrect %))))
segments-to-display (d/concat #{} other-shapes-segments selection-segments)]
segments-to-display (d/concat-set other-shapes-segments selection-segments)]
segments-to-display))
(mf/defc shape-distance

View File

@@ -121,7 +121,7 @@
(rx/switch-map #(rx/combine-latest (get-snap :x %)
(get-snap :y %)))
(rx/map (fn [result]
(apply d/concat (seq result))))
(apply d/concat-vec (seq result))))
(rx/subs #(let [rs (filter (fn [[_ snaps _]] (> (count snaps) 0)) %)]
(reset! state rs))))]

View File

@@ -6,7 +6,6 @@
(ns app.main.ui.workspace.viewport.thumbnail-renderer
(:require
[app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.persistence :as dwp]
[app.main.store :as st]
[app.util.dom :as dom]
@@ -80,7 +79,7 @@
"Component in charge of creating thumbnails and storing them"
{::mf/wrap-props false}
[props]
(let [objects (obj/get props "objects")
(let [objects (obj/get props "objects")
background (obj/get props "background")
;; Id of the current frame being rendered
@@ -97,12 +96,9 @@
updates-stream
(mf/use-memo
(fn []
(let [update-events
(->> st/stream
(rx/filter dwp/update-frame-thumbnail?))]
(->> (rx/zip update-events next)
(rx/map first)))))
#(let [update-events (rx/filter dwp/update-frame-thumbnail? st/stream)]
(->> (rx/zip update-events next)
(rx/map first))))
on-thumbnail-data
(mf/use-callback
@@ -111,9 +107,7 @@
(reset! shape-id nil)
(timers/schedule
(fn []
(st/emit! (dwc/update-shapes [@shape-id]
#(assoc % :thumbnail data)
{:save-undo? false}))
(st/emit! (dwp/update-shape-thumbnail @shape-id data))
(rx/push! next :next)))))
on-frame-not-found

View File

@@ -60,14 +60,14 @@
param-list (extract-params cmd [[:x :number]
[:y :number]])]
(d/concat [{:command :move-to
:relative relative
:params (first param-list)}]
(into [{:command :move-to
:relative relative
:params (first param-list)}]
(for [params (rest param-list)]
{:command :line-to
:relative relative
:params params}))))
(for [params (rest param-list)]
{:command :line-to
:relative relative
:params params}))))
(defmethod parse-command "Z" [_]
[{:command :close-path}])
@@ -259,7 +259,7 @@
(update :params merge (quadratic->curve prev-pos (gpt/point params) (upg/calculate-opposite-handler prev-pos prev-qc)))))
result (if (= :elliptical-arc (:command command))
(d/concat result (arc->beziers prev-pos command))
(into result (arc->beziers prev-pos command))
(conj result command))
next-cc (case (:command orig-command)

View File

@@ -304,7 +304,7 @@
[content points]
(let [segments-set (into #{}
(map (fn [{:keys [start end]}] [start end]))
(juxt :start :end)
(get-segments content points))
create-line-command (fn [point other]
@@ -318,7 +318,7 @@
(flatten)
(into []))]
(d/concat content new-content)))
(into content new-content)))
(defn separate-nodes

View File

@@ -28,9 +28,8 @@
(let [token (first tree)
args (rest tree)]
(case token
:opt-expr
(if (empty? args) 0 (interpret (first args) init-value))
(if (empty? args) nil (interpret (first args) init-value))
:expr
(if (index-of "+-*/" (first args))
@@ -87,8 +86,9 @@
(defn expr-eval
[expr init-value]
(s/assert string? expr)
(s/assert number? init-value)
(let [result (parser expr)]
(let [result (parser expr)
init-value (or init-value 0)]
(s/assert number? init-value)
(if-not (insta/failure? result)
(interpret result init-value)
(let [text (:text result)

View File

@@ -630,8 +630,7 @@
(defn find-node-references [node]
(let [current (->> (find-attr-references (:attrs node)) (into #{}))
children (->> (:content node) (map find-node-references) (flatten) (into #{}))]
(-> (d/concat current children)
(vec))))
(vec (into current children))))
(defn find-def-references [defs references]
(loop [result (into #{} references)
@@ -653,7 +652,7 @@
(let [node (get defs to-check)
new-refs (find-node-references node)
pending (concat pending new-refs)]
(recur (d/concat result new-refs)
(recur (into result new-refs)
(conj checked? to-check)
(first pending)
(rest pending))))))

View File

@@ -292,7 +292,7 @@
:file-id (:component-file shape)})
(= :text (:type shape))
(d/concat (get-text-refs (:content shape)))))]
(into (get-text-refs (:content shape)))))]
(->> (get-in file [:data :pages-index])
(vals)

View File

@@ -66,7 +66,7 @@
changed-ids (into #{}
(comp (filter #(not= % uuid/zero))
(filter changes?)
(mapcat #(d/concat [%] (cp/get-children % new-objects))))
(mapcat #(into [%] (cp/get-children % new-objects))))
(set/union (set (keys old-objects))
(set (keys new-objects))))

View File

@@ -22,13 +22,11 @@
(let [points (when-not (:hidden shape) (snap/shape-snap-points shape))
shape-data (->> points (mapv #(vector % (:id shape))))]
(if (= (:id shape) frame-id)
(d/concat
shape-data
;; The grid points are only added by the "root" of the coord-dat
(->> (gg/grid-snap-points shape coord)
(map #(vector % :layout))))
(into shape-data
;; The grid points are only added by the "root" of the coord-dat
(->> (gg/grid-snap-points shape coord)
(map #(vector % :layout))))
shape-data))))
(defn- add-coord-data

View File

@@ -1 +1 @@
1.10.0-beta
1.10.1-beta