mirror of
https://github.com/penpot/penpot.git
synced 2026-01-04 04:18:51 -05:00
Compare commits
15 Commits
1.10.0-bet
...
1.10.2-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2d3616171 | ||
|
|
a83e37493a | ||
|
|
384f0a05c6 | ||
|
|
a3016b8400 | ||
|
|
64c456678b | ||
|
|
16ed09a303 | ||
|
|
f8cecfd61f | ||
|
|
8a2a1d6d70 | ||
|
|
4ad34ab5c8 | ||
|
|
33c7847dfc | ||
|
|
7a04f15710 | ||
|
|
b8043a2432 | ||
|
|
ed5de525aa | ||
|
|
8105d9388b | ||
|
|
8151dcc05f |
@@ -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:
|
||||
|
||||
@@ -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}))
|
||||
|
||||
|
||||
|
||||
19
CHANGES.md
19
CHANGES.md
@@ -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
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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"}))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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})]
|
||||
|
||||
@@ -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)})))
|
||||
|
||||
@@ -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
|
||||
|
||||
36
backend/src/app/util/rlimit.clj
Normal file
36
backend/src/app/util/rlimit.clj
Normal 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))
|
||||
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
;; --- Development Stuff
|
||||
|
||||
(defn- run-tests
|
||||
([] (run-tests #"^app.common.tests.*"))
|
||||
([] (run-tests #"^app.common.*-test$"))
|
||||
([o]
|
||||
(repl/refresh)
|
||||
(cond
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]}]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 %))))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 [_]
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
58
common/test/app/common/data_test.clj
Normal file
58
common/test/app/common/data_test.clj
Normal 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] *))))
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]]
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]])))]]]))
|
||||
|
||||
@@ -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]))
|
||||
|
||||
|
||||
@@ -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")])]))
|
||||
|
||||
|
||||
|
||||
@@ -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")])]]]]))
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -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}]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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})))))
|
||||
|
||||
|
||||
@@ -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
|
||||
}])])]))
|
||||
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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]]))
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))))]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.10.0-beta
|
||||
1.10.1-beta
|
||||
|
||||
Reference in New Issue
Block a user