mirror of
https://github.com/penpot/penpot.git
synced 2025-04-14 07:51:35 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
4561a87450
70 changed files with 655 additions and 416 deletions
24
CHANGES.md
24
CHANGES.md
|
@ -28,14 +28,32 @@
|
|||
- Add the ability to disable google fonts provider with the `disable-google-fonts-provider` flag
|
||||
- Add the ability to disable dashboard templates section with the `disable-dashboard-templates-section` flag
|
||||
- Add the ability to use the registration whitelist with OICD [Github #3348](https://github.com/penpot/penpot/issues/3348)
|
||||
- Add support for local caching of google fonts (this avoids exposing
|
||||
the final user IP to goolge and reduces the amount of request sent
|
||||
to google)
|
||||
- Add support for local caching of google fonts (this avoids exposing the final user IP to
|
||||
goolge and reduces the amount of request sent to google)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix files can be opened from multiple urls [Taiga #5310](https://tree.taiga.io/project/penpot/issue/5310)
|
||||
- Fix asset color item was created from the selected layer [Taiga #5180](https://tree.taiga.io/project/penpot/issue/5180)
|
||||
- Fix unpublish more than one library at the same time [Taiga #5532](https://tree.taiga.io/project/penpot/issue/5532)
|
||||
- Fix drag projects on dahsboard [Taiga #5531](https://tree.taiga.io/project/penpot/issue/5531)
|
||||
- Fix allow team name to be all blank [Taiga #5527](https://tree.taiga.io/project/penpot/issue/5527)
|
||||
- Fix search font visualitation [Taiga #5523](https://tree.taiga.io/project/penpot/issue/5523)
|
||||
- Fix create and account only with spaces [Taiga #5518](https://tree.taiga.io/project/penpot/issue/5518)
|
||||
- Fix context menu outside screen [Taiga #5524](https://tree.taiga.io/project/penpot/issue/5524)
|
||||
- Fix graphic item rename on assets pannel [Taiga #5556](https://tree.taiga.io/project/penpot/issue/5556)
|
||||
- Fix component and media name validation on assets panel [Taiga #5555](https://tree.taiga.io/project/penpot/issue/5555)
|
||||
- Fix problem with selection shortcuts [Taiga #5492](https://tree.taiga.io/project/penpot/issue/5492)
|
||||
- Fix issue with paths line to curve and concurrent editing [Taiga #5191](https://tree.taiga.io/project/penpot/issue/5191)
|
||||
- Fix problems with locked layers [Taiga #5139](https://tree.taiga.io/project/penpot/issue/5139)
|
||||
- Fix export from shared prototype [Taiga #5565](https://tree.taiga.io/project/penpot/issue/5565)
|
||||
- Fix email change: validation error displaying even after both fields are identical [Taiga #5514](https://tree.taiga.io/project/penpot/issue/5514)
|
||||
- Fix scroll on viewer comment list [Taiga #5563](https://tree.taiga.io/project/penpot/issue/5563)
|
||||
- Fix context menu z-index [Taiga #5561](https://tree.taiga.io/project/penpot/issue/5561)
|
||||
- Fix select all checkbox on shared link config [Taiga #5566](https://tree.taiga.io/project/penpot/issue/5566)
|
||||
- Fix validation on full name input on account creation [Taiga #5516](https://tree.taiga.io/project/penpot/issue/5516)
|
||||
- Fix validation on team name input [Taiga #5510](https://tree.taiga.io/project/penpot/issue/5510)
|
||||
|
||||
|
||||
### :arrow_up: Deps updates
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
org.clojure/clojure {:mvn/version "1.11.1"}
|
||||
org.clojure/core.async {:mvn/version "1.6.673"}
|
||||
|
||||
com.github.luben/zstd-jni {:mvn/version "1.5.2-5"}
|
||||
com.github.luben/zstd-jni {:mvn/version "1.5.5-4"}
|
||||
|
||||
io.prometheus/simpleclient {:mvn/version "0.16.0"}
|
||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
|
||||
|
@ -17,7 +17,7 @@
|
|||
|
||||
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
|
||||
|
||||
io.lettuce/lettuce-core {:mvn/version "6.2.2.RELEASE"}
|
||||
io.lettuce/lettuce-core {:mvn/version "6.2.4.RELEASE"}
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
|
||||
funcool/yetti
|
||||
|
@ -26,8 +26,8 @@
|
|||
:git/url "https://github.com/funcool/yetti.git"
|
||||
:exclusions [org.slf4j/slf4j-api]}
|
||||
|
||||
com.github.seancorfield/next.jdbc {:mvn/version "1.3.847"}
|
||||
metosin/reitit-core {:mvn/version "0.5.18"}
|
||||
com.github.seancorfield/next.jdbc {:mvn/version "1.3.883"}
|
||||
metosin/reitit-core {:mvn/version "0.6.0"}
|
||||
|
||||
org.postgresql/postgresql {:mvn/version "42.6.0"}
|
||||
|
||||
|
@ -35,12 +35,12 @@
|
|||
|
||||
io.whitfin/siphash {:mvn/version "2.0.0"}
|
||||
|
||||
buddy/buddy-hashers {:mvn/version "1.8.158"}
|
||||
buddy/buddy-sign {:mvn/version "3.4.333"}
|
||||
buddy/buddy-hashers {:mvn/version "2.0.167"}
|
||||
buddy/buddy-sign {:mvn/version "3.5.351"}
|
||||
|
||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.5"}
|
||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.6"}
|
||||
|
||||
org.jsoup/jsoup {:mvn/version "1.15.3"}
|
||||
org.jsoup/jsoup {:mvn/version "1.16.1"}
|
||||
org.im4java/im4java
|
||||
{:git/tag "1.4.0-penpot-2"
|
||||
:git/sha "e2b3e16"
|
||||
|
@ -49,14 +49,14 @@
|
|||
org.lz4/lz4-java {:mvn/version "1.8.0"}
|
||||
|
||||
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
|
||||
integrant/integrant {:mvn/version "0.8.0"}
|
||||
integrant/integrant {:mvn/version "0.8.1"}
|
||||
|
||||
dawran6/emoji {:mvn/version "0.1.5"}
|
||||
markdown-clj/markdown-clj {:mvn/version "1.11.4"}
|
||||
|
||||
;; Pretty Print specs
|
||||
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
|
||||
software.amazon.awssdk/s3 {:mvn/version "2.19.29"}
|
||||
software.amazon.awssdk/s3 {:mvn/version "2.20.96"}
|
||||
}
|
||||
|
||||
:paths ["src" "resources" "target/classes"]
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
(def default-params
|
||||
{:alg :argon2id
|
||||
:memory (* 32768 2)
|
||||
:iterations 5
|
||||
:memory (* 32768 2) ;; 64 MiB
|
||||
:iterations 7
|
||||
:parallelism (px/get-available-processors)})
|
||||
|
||||
(defn derive-password
|
||||
|
|
|
@ -25,8 +25,7 @@
|
|||
[app.tokens :as tokens]
|
||||
[app.util.json :as json]
|
||||
[app.util.time :as dt]
|
||||
[buddy.core.keys :as keys]
|
||||
[buddy.sign.jws :as jws]
|
||||
[buddy.sign.jwk :as jwk]
|
||||
[buddy.sign.jwt :as jwt]
|
||||
[clojure.set :as set]
|
||||
[clojure.spec.alpha :as s]
|
||||
|
@ -109,7 +108,7 @@
|
|||
(defn- process-oidc-jwks
|
||||
[keys]
|
||||
(reduce (fn [result {:keys [kid] :as kdata}]
|
||||
(let [pkey (ex/try! (keys/jwk->public-key kdata))]
|
||||
(let [pkey (ex/try! (jwk/public-key kdata))]
|
||||
(if (ex/exception? pkey)
|
||||
(do
|
||||
(l/warn :hint "unable to create public key"
|
||||
|
@ -392,7 +391,7 @@
|
|||
(defn- get-user-info
|
||||
[{:keys [provider]} tdata]
|
||||
(try
|
||||
(let [{:keys [kid alg] :as theader} (jws/decode-header (:token/id tdata))]
|
||||
(let [{:keys [kid alg] :as theader} (jwt/decode-header (:token/id tdata))]
|
||||
(when-let [key (if (str/starts-with? (name alg) "hs")
|
||||
(:client-secret provider)
|
||||
(get-in provider [:jwks kid]))]
|
||||
|
@ -425,8 +424,12 @@
|
|||
code (get params :code)
|
||||
state (tokens/verify props {:token state :iss :oauth})
|
||||
tdata (fetch-access-token cfg code)
|
||||
info (or (get-user-info cfg tdata)
|
||||
(fetch-user-info cfg tdata))
|
||||
info (case (cf/get :oidc-user-info-source)
|
||||
:token (get-user-info cfg tdata)
|
||||
:userinfo (fetch-user-info cfg tdata)
|
||||
(or (get-user-info cfg tdata)
|
||||
(fetch-user-info cfg tdata)))
|
||||
|
||||
info (process-user-info provider tdata info)]
|
||||
|
||||
(l/trace :hint "user info" :info info)
|
||||
|
|
|
@ -146,6 +146,7 @@
|
|||
(s/def ::google-client-id ::us/string)
|
||||
(s/def ::google-client-secret ::us/string)
|
||||
(s/def ::oidc-client-id ::us/string)
|
||||
(s/def ::oidc-user-info-source ::us/keyword)
|
||||
(s/def ::oidc-client-secret ::us/string)
|
||||
(s/def ::oidc-base-uri ::us/string)
|
||||
(s/def ::oidc-token-uri ::us/string)
|
||||
|
@ -242,6 +243,7 @@
|
|||
::google-client-secret
|
||||
::oidc-client-id
|
||||
::oidc-client-secret
|
||||
::oidc-user-info-source
|
||||
::oidc-base-uri
|
||||
::oidc-token-uri
|
||||
::oidc-auth-uri
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as l]
|
||||
[app.common.spec :as us]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
|
@ -19,9 +19,7 @@
|
|||
[app.rpc.climit :as-alias climit]
|
||||
[app.rpc.doc :as-alias doc]
|
||||
[app.rpc.helpers :as rph]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[clojure.spec.alpha :as s]))
|
||||
[app.util.services :as sv]))
|
||||
|
||||
(defn- event->row [event]
|
||||
[(uuid/next)
|
||||
|
@ -52,25 +50,23 @@
|
|||
(when (seq events)
|
||||
(db/insert-multi! pool :audit-log event-columns events))))
|
||||
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::type ::us/string)
|
||||
(s/def ::props (s/map-of ::us/keyword any?))
|
||||
(s/def ::timestamp dt/instant?)
|
||||
(s/def ::context (s/map-of ::us/keyword any?))
|
||||
(def schema:event
|
||||
[:map {:title "Event"}
|
||||
[:name [:string {:max 250}]]
|
||||
[:type [:string {:max 250}]]
|
||||
[:props
|
||||
[:map-of :keyword :any]]
|
||||
[:context {:optional true}
|
||||
[:map-of :keyword :any]]])
|
||||
|
||||
(s/def ::event
|
||||
(s/keys :req-un [::type ::name ::props ::timestamp]
|
||||
:opt-un [::context]))
|
||||
|
||||
(s/def ::events (s/every ::event))
|
||||
|
||||
(s/def ::push-audit-events
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::events]))
|
||||
(def schema:push-audit-events
|
||||
[:map {:title "push-audit-events"}
|
||||
[:events [:vector schema:event]]])
|
||||
|
||||
(sv/defmethod ::push-audit-events
|
||||
{::climit/id :submit-audit-events-by-profile
|
||||
::climit/key-fn ::rpc/profile-id
|
||||
::sm/params schema:push-audit-events
|
||||
::audit/skip true
|
||||
::doc/added "1.17"}
|
||||
[{:keys [::db/pool] :as cfg} params]
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
(:require
|
||||
[app.auth :as auth]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.spec :as us]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
|
@ -26,18 +27,13 @@
|
|||
[app.tokens :as tokens]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::fullname ::us/not-empty-string)
|
||||
(s/def ::lang ::us/string)
|
||||
(s/def ::path ::us/string)
|
||||
(s/def ::password ::us/not-empty-string)
|
||||
(s/def ::old-password ::us/not-empty-string)
|
||||
(s/def ::theme ::us/string)
|
||||
(s/def ::invitation-token ::us/not-empty-string)
|
||||
(s/def ::token ::us/not-empty-string)
|
||||
(def schema:password
|
||||
[::sm/word-string {:max 500}])
|
||||
|
||||
(def schema:token
|
||||
[::sm/word-string {:max 1000}])
|
||||
|
||||
;; ---- COMMAND: login with password
|
||||
|
||||
|
@ -101,22 +97,22 @@
|
|||
(rph/with-meta {::audit/props (audit/profile->props profile)
|
||||
::audit/profile-id (:id profile)}))))))
|
||||
|
||||
(s/def ::login-with-password
|
||||
(s/keys :req-un [::email ::password]
|
||||
:opt-un [::invitation-token]))
|
||||
(def schema:login-with-password
|
||||
[:map {:title "login-with-password"}
|
||||
[:email ::sm/email]
|
||||
[:password schema:password]
|
||||
[:invitation-token {:optional true} schema:token]])
|
||||
|
||||
(sv/defmethod ::login-with-password
|
||||
"Performs authentication using penpot password."
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"}
|
||||
::doc/added "1.15"
|
||||
::sm/params schema:login-with-password}
|
||||
[cfg params]
|
||||
(login-with-password cfg params))
|
||||
|
||||
;; ---- COMMAND: Logout
|
||||
|
||||
(s/def ::logout
|
||||
(s/keys :opt [::rpc/profile-id]))
|
||||
|
||||
(sv/defmethod ::logout
|
||||
"Clears the authentication cookie and logout the current session."
|
||||
{::rpc/auth false
|
||||
|
@ -141,13 +137,15 @@
|
|||
(update-password conn))
|
||||
nil)))
|
||||
|
||||
(s/def ::token ::us/not-empty-string)
|
||||
(s/def ::recover-profile
|
||||
(s/keys :req-un [::token ::password]))
|
||||
(def schema:recover-profile
|
||||
[:map {:title "recover-profile"}
|
||||
[:token schema:token]
|
||||
[:password schema:password]])
|
||||
|
||||
(sv/defmethod ::recover-profile
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"}
|
||||
::doc/added "1.15"
|
||||
::sm/params schema:recover-profile}
|
||||
[cfg params]
|
||||
(recover-profile cfg params))
|
||||
|
||||
|
@ -228,13 +226,16 @@
|
|||
(with-meta {:token token}
|
||||
{::audit/profile-id uuid/zero})))
|
||||
|
||||
(s/def ::prepare-register-profile
|
||||
(s/keys :req-un [::email ::password]
|
||||
:opt-un [::invitation-token]))
|
||||
(def schema:prepare-register-profile
|
||||
[:map {:title "prepare-register-profile"}
|
||||
[:email ::sm/email]
|
||||
[:password schema:password]
|
||||
[:invitation-token {:optional true} schema:token]])
|
||||
|
||||
(sv/defmethod ::prepare-register-profile
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"}
|
||||
::doc/added "1.15"
|
||||
::sm/params schema:prepare-register-profile}
|
||||
[cfg params]
|
||||
(prepare-register cfg params))
|
||||
|
||||
|
@ -244,7 +245,7 @@
|
|||
"Create the profile entry on the database with limited set of input
|
||||
attrs (all the other attrs are filled with default values)."
|
||||
[conn {:keys [email] :as params}]
|
||||
(us/assert! ::us/email email)
|
||||
(dm/assert! ::sm/email email)
|
||||
(let [id (or (:id params) (uuid/next))
|
||||
props (-> (audit/extract-utm-params params)
|
||||
(merge (:props params))
|
||||
|
@ -391,12 +392,16 @@
|
|||
{::audit/replace-props (audit/profile->props profile)
|
||||
::audit/profile-id (:id profile)})))))
|
||||
|
||||
(s/def ::register-profile
|
||||
(s/keys :req-un [::token ::fullname]))
|
||||
|
||||
(def schema:register-profile
|
||||
[:map {:title "register-profile"}
|
||||
[:token schema:token]
|
||||
[:fullname [::sm/word-string {:max 100}]]])
|
||||
|
||||
(sv/defmethod ::register-profile
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"}
|
||||
::doc/added "1.15"
|
||||
::sm/params schema:register-profile}
|
||||
[{:keys [::db/pool] :as cfg} params]
|
||||
(db/with-atomic [conn pool]
|
||||
(-> (assoc cfg ::db/conn conn)
|
||||
|
@ -448,12 +453,15 @@
|
|||
(create-recovery-token)
|
||||
(send-email-notification conn))))))
|
||||
|
||||
(s/def ::request-profile-recovery
|
||||
(s/keys :req-un [::email]))
|
||||
|
||||
(def schema:request-profile-recovery
|
||||
[:map {:title "request-profile-recovery"}
|
||||
[:email ::sm/email]])
|
||||
|
||||
(sv/defmethod ::request-profile-recovery
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"}
|
||||
::doc/added "1.15"
|
||||
::sm/params schema:request-profile-recovery}
|
||||
[cfg params]
|
||||
(request-profile-recovery cfg params))
|
||||
|
||||
|
|
|
@ -512,6 +512,7 @@
|
|||
[:map {:title "GetPage"}
|
||||
[:file-id ::sm/uuid]
|
||||
[:page-id {:optional true} ::sm/uuid]
|
||||
[:share-id {:optional true} ::sm/uuid]
|
||||
[:object-id {:optional true} ::sm/uuid]
|
||||
[:features {:optional true} ::features]])
|
||||
|
||||
|
@ -527,14 +528,12 @@
|
|||
Mainly used for rendering purposes."
|
||||
{::doc/added "1.17"
|
||||
::sm/params ::get-page}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id share-id] :as params}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(check-read-permissions! conn profile-id file-id)
|
||||
|
||||
(binding [pmap/*load-fn* (partial load-pointer conn file-id)]
|
||||
(get-page conn params))))
|
||||
|
||||
|
||||
(let [perms (get-permissions conn profile-id file-id share-id)]
|
||||
(check-read-permissions! perms)
|
||||
(binding [pmap/*load-fn* (partial load-pointer conn file-id)]
|
||||
(get-page conn params)))))
|
||||
|
||||
;; --- COMMAND QUERY: get-team-shared-files
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/not-empty-string)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::share-id ::us/uuid)
|
||||
(s/def ::style valid-style)
|
||||
(s/def ::team-id ::us/uuid)
|
||||
(s/def ::weight valid-weight)
|
||||
|
@ -47,7 +48,8 @@
|
|||
(s/keys :req [::rpc/profile-id]
|
||||
:opt-un [::team-id
|
||||
::file-id
|
||||
::project-id])
|
||||
::project-id
|
||||
::share-id])
|
||||
(fn [o]
|
||||
(or (contains? o :team-id)
|
||||
(contains? o :file-id)
|
||||
|
@ -55,7 +57,7 @@
|
|||
|
||||
(sv/defmethod ::get-font-variants
|
||||
{::doc/added "1.18"}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id file-id project-id] :as params}]
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id file-id project-id share-id] :as params}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(cond
|
||||
(uuid? team-id)
|
||||
|
@ -74,11 +76,12 @@
|
|||
|
||||
(uuid? file-id)
|
||||
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
|
||||
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})]
|
||||
(files/check-read-permissions! conn profile-id file-id)
|
||||
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
|
||||
perms (files/get-permissions conn profile-id file-id share-id)]
|
||||
(files/check-read-permissions! perms)
|
||||
(db/query conn :team-font-variant
|
||||
{:team-id (:team-id project)
|
||||
:deleted-at nil})))))
|
||||
{:team-id (:team-id project)
|
||||
:deleted-at nil})))))
|
||||
|
||||
|
||||
(declare create-font-variant)
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
[app.tokens :as tokens]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(declare check-profile-existence!)
|
||||
|
@ -41,7 +40,7 @@
|
|||
(def schema:profile
|
||||
[:map {:title "Profile"}
|
||||
[:id ::sm/uuid]
|
||||
[:fullname :string]
|
||||
[:fullname [::sm/word-string {:max 250}]]
|
||||
[:email ::sm/email]
|
||||
[:is-active {:optional true} :boolean]
|
||||
[:is-blocked {:optional true} :boolean]
|
||||
|
@ -82,12 +81,15 @@
|
|||
|
||||
;; --- MUTATION: Update Profile (own)
|
||||
|
||||
(def schema:update-profile
|
||||
[:map {:title "update-profile"}
|
||||
[:fullname [::sm/word-string {:max 250}]]
|
||||
[:lang {:optional true} [:string {:max 5}]]
|
||||
[:theme {:optional true} [:string {:max 250}]]])
|
||||
|
||||
(sv/defmethod ::update-profile
|
||||
{::doc/added "1.0"
|
||||
::sm/params [:map {:title "UpdateProfileParams"}
|
||||
[:fullname {:min 1} :string]
|
||||
[:lang {:optional true} :string]
|
||||
[:theme {:optional true} :string]]
|
||||
::sm/params schema:update-profile
|
||||
::sm/result schema:profile}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id fullname lang theme] :as params}]
|
||||
|
||||
|
@ -128,11 +130,14 @@
|
|||
(declare update-profile-password!)
|
||||
(declare invalidate-profile-session!)
|
||||
|
||||
(def schema:update-profile-password
|
||||
[:map {:title "update-profile-password"}
|
||||
[:password [::sm/word-string {:max 500}]]
|
||||
[:old-password [::sm/word-string {:max 500}]]])
|
||||
|
||||
(sv/defmethod ::update-profile-password
|
||||
{:doc/added "1.0"
|
||||
::sm/params [:map {:title "UpdateProfilePasswordParams"}
|
||||
[:password :string]
|
||||
[:old-password :string]]
|
||||
::sm/params schema:update-profile-password
|
||||
::sm/result :nil}
|
||||
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id password] :as params}]
|
||||
|
@ -178,10 +183,13 @@
|
|||
(declare upload-photo)
|
||||
(declare update-profile-photo)
|
||||
|
||||
(def schema:update-profile-photo
|
||||
[:map {:title "update-profile-photo"}
|
||||
[:file ::media/upload]])
|
||||
|
||||
(sv/defmethod ::update-profile-photo
|
||||
{:doc/added "1.1"
|
||||
::sm/params [:map {:title "UpdateProfilePhotoParams"}
|
||||
[:file ::media/upload]]
|
||||
::sm/params schema:update-profile-photo
|
||||
::sm/result :nil}
|
||||
[cfg {:keys [::rpc/profile-id file] :as params}]
|
||||
;; Validate incoming mime type
|
||||
|
@ -239,11 +247,13 @@
|
|||
(declare ^:private request-email-change!)
|
||||
(declare ^:private change-email-immediately!)
|
||||
|
||||
(s/def ::request-email-change
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::email]))
|
||||
(def schema:request-email-change
|
||||
[:map {:title "request-email-change"}
|
||||
[:email ::sm/email]])
|
||||
|
||||
(sv/defmethod ::request-email-change
|
||||
{::doc/added "1.0"
|
||||
::sm/params schema:request-email-change}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id email] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [profile (db/get-by-id conn :profile profile-id)
|
||||
|
@ -304,12 +314,13 @@
|
|||
|
||||
;; --- MUTATION: Update Profile Props
|
||||
|
||||
(s/def ::props map?)
|
||||
(s/def ::update-profile-props
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::props]))
|
||||
(def schema:update-profile-props
|
||||
[:map {:title "update-profile-props"}
|
||||
[:props [:map-of :keyword :any]]])
|
||||
|
||||
(sv/defmethod ::update-profile-props
|
||||
{::doc/added "1.0"
|
||||
::sm/params schema:update-profile-props}
|
||||
[{:keys [::db/pool]} {:keys [::rpc/profile-id props]}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [profile (get-profile conn profile-id ::db/for-update? true)
|
||||
|
@ -329,15 +340,12 @@
|
|||
|
||||
(filter-props props))))
|
||||
|
||||
|
||||
;; --- MUTATION: Delete Profile
|
||||
|
||||
(declare ^:private get-owned-teams-with-participants)
|
||||
|
||||
(s/def ::delete-profile
|
||||
(s/keys :req [::rpc/profile-id]))
|
||||
|
||||
(sv/defmethod ::delete-profile
|
||||
{::doc/added "1.0"}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [teams (get-owned-teams-with-participants conn profile-id)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[app.common.pages :as cp]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.spec :as us]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
|
@ -414,6 +415,14 @@
|
|||
(println
|
||||
(us/pretty-explain data))
|
||||
|
||||
(= :params-validation (:code data))
|
||||
(app.common.pprint/pprint
|
||||
(sm/humanize-data (::sm/explain data)))
|
||||
|
||||
(= :data-validation (:code data))
|
||||
(app.common.pprint/pprint
|
||||
(sm/humanize-data (::sm/explain data)))
|
||||
|
||||
(= :service-error (:type data))
|
||||
(print-error! (.getCause ^Throwable error))
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@
|
|||
params {::th/type :push-audit-events
|
||||
::rpc/profile-id (:id prof)
|
||||
:events [{:name "navigate"
|
||||
:props {:project-id proj-id
|
||||
:team-id team-id
|
||||
:props {:project-id (str proj-id)
|
||||
:team-id (str team-id)
|
||||
:route "dashboard-files"}
|
||||
:context {:engine "blink"}
|
||||
:profile-id (:id prof)
|
||||
|
@ -71,8 +71,8 @@
|
|||
params {::th/type :push-audit-events
|
||||
::rpc/profile-id (:id prof)
|
||||
:events [{:name "navigate"
|
||||
:props {:project-id proj-id
|
||||
:team-id team-id
|
||||
:props {:project-id (str proj-id)
|
||||
:team-id (str team-id)
|
||||
:route "dashboard-files"}
|
||||
:context {:engine "blink"}
|
||||
:profile-id uuid/zero
|
||||
|
@ -91,6 +91,8 @@
|
|||
(t/is (= 1 (count rows)))
|
||||
(t/is (= (:id prof) (:profile-id row)))
|
||||
(t/is (= "navigate" (:name row)))
|
||||
(t/is (= "frontend" (:source row)))))))
|
||||
(t/is (= "frontend" (:source row))))
|
||||
|
||||
)))
|
||||
|
||||
|
||||
|
|
|
@ -252,6 +252,7 @@
|
|||
:components-v2 true
|
||||
:changes changes}
|
||||
out (th/command! params)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(:result out)))]
|
||||
|
||||
|
@ -278,7 +279,7 @@
|
|||
[{:type :add-obj
|
||||
:page-id page-id
|
||||
:id shid
|
||||
:parent-id uuid/zero
|
||||
:parent-id uuid/zero
|
||||
:frame-id uuid/zero
|
||||
:components-v2 true
|
||||
:obj {:id shid
|
||||
|
@ -286,7 +287,7 @@
|
|||
:frame-id uuid/zero
|
||||
:parent-id uuid/zero
|
||||
:type :image
|
||||
:metadata {:id (:id fmo1)}}}])
|
||||
:metadata {:id (:id fmo1) :width 200 :height 200 :mtype "image/jpeg"}}}])
|
||||
|
||||
;; Check that reference storage objects on filemediaobjects
|
||||
;; are the same because of deduplication feature.
|
||||
|
|
|
@ -278,7 +278,7 @@
|
|||
(let [error (:error out)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :validation))
|
||||
(t/is (th/ex-of-code? error :spec-validation))))
|
||||
(t/is (th/ex-of-code? error :params-validation))))
|
||||
|
||||
;; try correct register
|
||||
(let [data {::th/type :register-profile
|
||||
|
|
|
@ -1,40 +1,37 @@
|
|||
{:deps
|
||||
{org.clojure/clojure {:mvn/version "1.11.1"}
|
||||
org.clojure/data.json {:mvn/version "2.4.0"}
|
||||
org.clojure/tools.cli {:mvn/version "1.0.214"}
|
||||
org.clojure/tools.cli {:mvn/version "1.0.219"}
|
||||
org.clojure/clojurescript {:mvn/version "1.11.60"}
|
||||
org.clojure/test.check {:mvn/version "1.1.1"}
|
||||
org.clojure/data.fressian {:mvn/version "1.0.0"}
|
||||
|
||||
;; Logging
|
||||
org.apache.logging.log4j/log4j-api {:mvn/version "2.19.0"}
|
||||
org.apache.logging.log4j/log4j-core {:mvn/version "2.19.0"}
|
||||
org.apache.logging.log4j/log4j-web {:mvn/version "2.19.0"}
|
||||
org.apache.logging.log4j/log4j-jul {:mvn/version "2.19.0"}
|
||||
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.19.0"}
|
||||
org.slf4j/slf4j-api {:mvn/version "2.0.6"}
|
||||
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.26"}
|
||||
org.apache.logging.log4j/log4j-api {:mvn/version "2.20.0"}
|
||||
org.apache.logging.log4j/log4j-core {:mvn/version "2.20.0"}
|
||||
org.apache.logging.log4j/log4j-web {:mvn/version "2.20.0"}
|
||||
org.apache.logging.log4j/log4j-jul {:mvn/version "2.20.0"}
|
||||
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.20.0"}
|
||||
org.slf4j/slf4j-api {:mvn/version "2.0.7"}
|
||||
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.30"}
|
||||
|
||||
selmer/selmer {:mvn/version "1.12.55"}
|
||||
selmer/selmer {:mvn/version "1.12.58"}
|
||||
criterium/criterium {:mvn/version "0.4.6"}
|
||||
|
||||
metosin/jsonista {:mvn/version "0.3.7"}
|
||||
metosin/malli {:mvn/version "0.11.0"}
|
||||
|
||||
expound/expound {:mvn/version "0.9.0"}
|
||||
com.cognitect/transit-clj {:mvn/version "1.0.329"}
|
||||
com.cognitect/transit-clj {:mvn/version "1.0.333"}
|
||||
com.cognitect/transit-cljs {:mvn/version "0.8.280"}
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
|
||||
funcool/cuerdas {:mvn/version "2022.06.16-403"}
|
||||
funcool/promesa
|
||||
{:git/tag "11.0-alpha13"
|
||||
:git/sha "f6cab38"
|
||||
:git/url "https://github.com/funcool/promesa.git"}
|
||||
funcool/promesa {:mvn/version "11.0.671"}
|
||||
funcool/datoteka {:mvn/version "3.0.66"
|
||||
:exclusions [funcool/promesa]}
|
||||
|
||||
lambdaisland/uri {:mvn/version "1.13.95"
|
||||
lambdaisland/uri {:mvn/version "1.15.125"
|
||||
:exclusions [org.clojure/data.json]}
|
||||
|
||||
frankiesardo/linked {:mvn/version "1.3.0"}
|
||||
|
@ -44,7 +41,7 @@
|
|||
|
||||
;; exception printing
|
||||
fipp/fipp {:mvn/version "0.6.26"}
|
||||
io.aviso/pretty {:mvn/version "1.3"}
|
||||
io.aviso/pretty {:mvn/version "1.4.4"}
|
||||
environ/environ {:mvn/version "1.2.0"}}
|
||||
:paths ["src" "target/classes"]
|
||||
:aliases
|
||||
|
|
|
@ -50,12 +50,15 @@
|
|||
|
||||
(defn update-curve-to
|
||||
[command h1 h2]
|
||||
(-> command
|
||||
(assoc :command :curve-to)
|
||||
(assoc-in [:params :c1x] (:x h1))
|
||||
(assoc-in [:params :c1y] (:y h1))
|
||||
(assoc-in [:params :c2x] (:x h2))
|
||||
(assoc-in [:params :c2y] (:y h2))))
|
||||
(let [params {:x (-> command :params :x)
|
||||
:y (-> command :params :y)
|
||||
:c1x (:x h1)
|
||||
:c1y (:y h1)
|
||||
:c2x (:x h2)
|
||||
:c2y (:y h2)}]
|
||||
(-> command
|
||||
(assoc :command :curve-to)
|
||||
(assoc :params params))))
|
||||
|
||||
(defn make-curve-to
|
||||
[to h1 h2]
|
||||
|
|
|
@ -282,7 +282,9 @@
|
|||
(def! ::email
|
||||
{:type ::email
|
||||
:pred (fn [s]
|
||||
(and (string? s) (re-seq email-re s)))
|
||||
(and (string? s)
|
||||
(< (count s) 250)
|
||||
(re-seq email-re s)))
|
||||
:type-properties
|
||||
{:title "email"
|
||||
:description "string with valid email address"
|
||||
|
@ -464,6 +466,7 @@
|
|||
(def! ::word-string
|
||||
{:type ::word-string
|
||||
:pred #(and (string? %) (not (str/blank? %)))
|
||||
:property-pred (m/-min-max-pred count)
|
||||
:type-properties
|
||||
{:title "string"
|
||||
:description "string"
|
||||
|
|
|
@ -260,7 +260,11 @@
|
|||
|
||||
(let [frame-ids (cond->> (all-frames-by-position objects position)
|
||||
(some? excluded)
|
||||
(remove excluded))
|
||||
(remove excluded)
|
||||
|
||||
:always
|
||||
(remove #(or (dm/get-in objects [% :hidden])
|
||||
(dm/get-in objects [% :blocked]))))
|
||||
|
||||
frame-set (set frame-ids)]
|
||||
|
||||
|
@ -276,7 +280,10 @@
|
|||
"Search the top nested frame in a list of ids"
|
||||
[objects ids]
|
||||
|
||||
(let [frame-ids (->> ids (filter #(cph/frame-shape? objects %)))
|
||||
(let [frame-ids (->> ids
|
||||
(filter #(cph/frame-shape? objects %))
|
||||
(remove #(or (dm/get-in objects [% :hidden])
|
||||
(dm/get-in objects [% :blocked]))))
|
||||
frame-set (set frame-ids)]
|
||||
(loop [current-id (first frame-ids)]
|
||||
(let [current-shape (get objects current-id)
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
(s/def ::name ::us/string)
|
||||
(s/def ::object-id ::us/uuid)
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::share-id ::us/uuid)
|
||||
(s/def ::profile-id ::us/uuid)
|
||||
(s/def ::scale ::us/number)
|
||||
(s/def ::suffix ::us/string)
|
||||
|
@ -35,7 +36,8 @@
|
|||
(s/def ::wait ::us/boolean)
|
||||
|
||||
(s/def ::export
|
||||
(s/keys :req-un [::page-id ::file-id ::object-id ::type ::suffix ::scale ::name]))
|
||||
(s/keys :req-un [::page-id ::file-id ::object-id ::type ::suffix ::scale ::name]
|
||||
:opt-un [::share-id]))
|
||||
|
||||
(s/def ::exports
|
||||
(s/coll-of ::export :kind vector? :min-count 1))
|
||||
|
@ -89,7 +91,6 @@
|
|||
proc (-> (rd/render export on-progress)
|
||||
(p/then (constantly resource))
|
||||
(p/catch on-error))]
|
||||
|
||||
(if wait
|
||||
(p/then proc #(assoc exchange :response/body (dissoc % :path)))
|
||||
(assoc exchange :response/body (dissoc resource :path)))))
|
||||
|
@ -188,6 +189,7 @@
|
|||
(process-partition [[part1 :as part]]
|
||||
{:file-id (:file-id part1)
|
||||
:page-id (:page-id part1)
|
||||
:share-id (:share-id part1)
|
||||
:name (:name part1)
|
||||
:token token
|
||||
:type (:type part1)
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
(s/def ::type #{:jpeg :png :pdf :svg})
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::share-id ::us/uuid)
|
||||
(s/def ::scale ::us/number)
|
||||
(s/def ::token ::us/string)
|
||||
(s/def ::filename ::us/string)
|
||||
|
||||
(s/def ::object
|
||||
(s/keys :req-un [::id ::name ::suffix ::filename]))
|
||||
(s/keys :req-un [::id ::name ::suffix ::filename]
|
||||
:opt-un [::share-id]))
|
||||
|
||||
(s/def ::objects
|
||||
(s/coll-of ::object :min-count 1))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
[promesa.core :as p]))
|
||||
|
||||
(defn render
|
||||
[{:keys [file-id page-id token scale type objects] :as params} on-object]
|
||||
[{:keys [file-id page-id share-id token scale type objects] :as params} on-object]
|
||||
(letfn [(prepare-options [uri]
|
||||
#js {:screen #js {:width bw/default-viewport-width
|
||||
:height bw/default-viewport-height}
|
||||
|
@ -48,9 +48,9 @@
|
|||
;; take the screnshot of requested objects, one by one
|
||||
(p/run! (partial render-object page) objects)
|
||||
nil))]
|
||||
|
||||
(p/let [params {:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:object-id (mapv :id objects)
|
||||
:route "objects"}
|
||||
uri (-> (cf/get :public-uri)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
[promesa.core :as p]))
|
||||
|
||||
(defn render
|
||||
[{:keys [file-id page-id token scale type objects] :as params} on-object]
|
||||
[{:keys [file-id page-id share-id token scale type objects] :as params} on-object]
|
||||
(letfn [(prepare-options [uri]
|
||||
#js {:screen #js {:width bw/default-viewport-width
|
||||
:height bw/default-viewport-height}
|
||||
|
@ -31,6 +31,7 @@
|
|||
(prepare-uri [base-uri object-id]
|
||||
(let [params {:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:object-id object-id
|
||||
:route "objects"}]
|
||||
(-> base-uri
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
:height height}))
|
||||
|
||||
(defn render
|
||||
[{:keys [page-id file-id objects token scale type]} on-object]
|
||||
[{:keys [page-id file-id share-id objects token scale type]} on-object]
|
||||
(letfn [(convert-to-ppm [pngpath]
|
||||
(let [ppmpath (str/concat pngpath "origin.ppm")]
|
||||
(l/trace :fn :convert-to-ppm :path ppmpath)
|
||||
|
@ -338,9 +338,9 @@
|
|||
;; take the screnshot of requested objects, one by one
|
||||
(p/run! (partial render-object page) objects)
|
||||
nil))]
|
||||
|
||||
(p/let [params {:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:render-embed true
|
||||
:object-id (mapv :id objects)
|
||||
:route "objects"}
|
||||
|
|
|
@ -246,7 +246,7 @@
|
|||
pointer-events: auto;
|
||||
|
||||
.thread-groups {
|
||||
height: 100%;
|
||||
height: calc(100% - 34px);
|
||||
overflow-y: scroll;
|
||||
hr {
|
||||
border: 0;
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
font-size: $fs14;
|
||||
background-color: $color-white;
|
||||
display: flex;
|
||||
min-width: 1000px;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
min-height: 97px;
|
||||
align-items: center;
|
||||
|
|
|
@ -324,7 +324,7 @@
|
|||
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
|
||||
z-index: 12;
|
||||
top: 30px;
|
||||
left: 6px;
|
||||
left: -151px;
|
||||
width: 155px;
|
||||
|
||||
hr {
|
||||
|
|
|
@ -466,20 +466,27 @@
|
|||
|
||||
.dashboard-templates-section {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 285px;
|
||||
height: 228px;
|
||||
transition: bottom 300ms;
|
||||
|
||||
pointer-events: none;
|
||||
&.collapsed {
|
||||
bottom: -228px;
|
||||
transition: bottom 300ms;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
pointer-events: all;
|
||||
width: fit-content;
|
||||
top: -56px;
|
||||
right: -28px;
|
||||
text-align: right;
|
||||
height: 56px;
|
||||
position: absolute;
|
||||
button {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
@ -529,6 +536,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: all;
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
@ -550,6 +558,7 @@
|
|||
}
|
||||
|
||||
.content {
|
||||
pointer-events: all;
|
||||
background-color: $color-white;
|
||||
width: 200%;
|
||||
height: 229px;
|
||||
|
|
|
@ -355,10 +355,12 @@ span.element-name {
|
|||
.pages-tool-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 32px;
|
||||
margin-top: 8px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
|
||||
&.search {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
.search-box {
|
||||
border: 1px solid $color-gray-20;
|
||||
border-radius: $br4;
|
||||
|
@ -403,6 +405,67 @@ span.element-name {
|
|||
margin: 0 2px 0 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.page-name {
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.icon-search {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.focus-title {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 16px 1fr auto;
|
||||
grid-column-gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
.back-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
fill: $color-gray-20;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.focus-name {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.focus-mode {
|
||||
color: $color-primary;
|
||||
border: 1px solid $color-primary;
|
||||
border-radius: $br3;
|
||||
font-size: $fs10;
|
||||
text-transform: uppercase;
|
||||
padding: 0px 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.active-filters {
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
flex-shrink: 0;
|
||||
padding: $size-2;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
|
@ -136,52 +137,6 @@
|
|||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
& .focus-title {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-column-gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
& .back-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
& svg {
|
||||
fill: $color-white;
|
||||
transform: rotate(180deg);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .focus-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
& .focus-mode {
|
||||
color: $color-primary;
|
||||
border: 1px solid $color-primary;
|
||||
border-radius: $br3;
|
||||
font-size: $fs10;
|
||||
text-transform: uppercase;
|
||||
padding: 0px 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.assets-bar .tool-window {
|
||||
|
|
|
@ -182,8 +182,8 @@
|
|||
right: 0;
|
||||
top: 50px;
|
||||
width: 256px;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
height: calc(100vh - 48px);
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
{:exports (vec exports)})))))))
|
||||
|
||||
(defn show-viewer-export-dialog
|
||||
[{:keys [shapes page-id file-id exports]}]
|
||||
[{:keys [shapes page-id file-id share-id exports]}]
|
||||
(ptk/reify ::show-viewer-export-dialog
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -87,8 +87,9 @@
|
|||
(assoc :file-id file-id)
|
||||
(assoc :object-id (:id shape))
|
||||
(assoc :shape (dissoc shape :exports))
|
||||
(assoc :name (:name shape))))]
|
||||
(rx/of (modal/show :export-shapes {:exports (vec exports)}))))))
|
||||
(assoc :name (:name shape))
|
||||
(cond-> share-id (assoc :share-id share-id))))]
|
||||
(rx/of (modal/show :export-shapes {:exports (vec exports)})))))) #_TODO
|
||||
|
||||
(defn show-workspace-export-frames-dialog
|
||||
[frames]
|
||||
|
|
|
@ -938,7 +938,10 @@
|
|||
(rx/of (dwe/start-edition-mode id))
|
||||
|
||||
(:group :bool :frame)
|
||||
(rx/of (dws/select-shapes (into (d/ordered-set) shapes)))
|
||||
(let [shapes-ids (into (d/ordered-set)
|
||||
(remove #(dm/get-in objects [% :hidden]))
|
||||
shapes)]
|
||||
(rx/of (dws/select-shapes shapes-ids)))
|
||||
|
||||
:svg-raw
|
||||
nil
|
||||
|
|
|
@ -63,20 +63,22 @@
|
|||
|
||||
;; Add & select the created shape to the workspace
|
||||
(rx/concat
|
||||
(if (= :text (:type shape))
|
||||
(if (or (= :text (:type shape)) (= :frame (:type shape)))
|
||||
(rx/of (dwu/start-undo-transaction (:id shape)))
|
||||
(rx/empty))
|
||||
|
||||
(rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)}))
|
||||
|
||||
(if (= :frame (:type shape))
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
:page-id page-id
|
||||
:rect (:selrect shape)
|
||||
:include-frames? true
|
||||
:full-frame? true})
|
||||
(rx/map #(cph/clean-loops objects %))
|
||||
(rx/map #(dwsh/move-shapes-into-frame (:id shape) %)))
|
||||
(rx/concat
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
:page-id page-id
|
||||
:rect (:selrect shape)
|
||||
:include-frames? true
|
||||
:full-frame? true})
|
||||
(rx/map #(cph/clean-loops objects %))
|
||||
(rx/map #(dwsh/move-shapes-into-frame (:id shape) %)))
|
||||
(rx/of (dwu/commit-undo-transaction (:id shape))))
|
||||
(rx/empty)))))
|
||||
|
||||
;; Delay so the mouse event can read the drawing state
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||
|
@ -184,21 +185,22 @@
|
|||
|
||||
(defn rename-media
|
||||
[id new-name]
|
||||
(dm/assert! (uuid? id))
|
||||
(dm/assert! (string? new-name))
|
||||
(dm/verify! (uuid? id))
|
||||
(dm/verify! (string? new-name))
|
||||
(ptk/reify ::rename-media
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(when (and (some? new-name) (not= "" new-name))
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cph/parse-path-name new-name)
|
||||
object (get-in data [:media id])
|
||||
new-object (assoc object :path path :name name)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-media new-object))]
|
||||
(rx/of (dch/commit-changes changes)))))))
|
||||
|
||||
(let [new-name (str/trim new-name)]
|
||||
(if (str/empty? new-name)
|
||||
(rx/empty)
|
||||
(let [[path name] (cph/parse-path-name new-name)
|
||||
data (get state :workspace-data)
|
||||
object (get-in data [:media id])
|
||||
new-object (assoc object :path path :name name)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-media new-object))]
|
||||
(rx/of (dch/commit-changes changes))))))))
|
||||
|
||||
(defn delete-media
|
||||
[{:keys [id] :as params}]
|
||||
|
@ -340,24 +342,20 @@
|
|||
(defn rename-component
|
||||
"Rename the component with the given id, in the current file library."
|
||||
[id new-name]
|
||||
(dm/assert! (uuid? id))
|
||||
(dm/assert! (string? new-name))
|
||||
(dm/verify! (uuid? id))
|
||||
(dm/verify! (string? new-name))
|
||||
(ptk/reify ::rename-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(when (and (some? new-name) (not= "" new-name))
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cph/parse-path-name new-name)
|
||||
components-v2 (features/active-feature? state :components-v2)
|
||||
(let [new-name (str/trim new-name)]
|
||||
(if (str/empty? new-name)
|
||||
(rx/empty)
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cph/parse-path-name new-name)
|
||||
components-v2 (features/active-feature? state :components-v2)
|
||||
|
||||
update-fn
|
||||
(fn [component]
|
||||
;; NOTE: we need to ensure the component exists,
|
||||
;; because there are small possibilities of race
|
||||
;; conditions with component deletion.
|
||||
;;
|
||||
;; FIXME: this race conditon should be handled in pcb/update-component
|
||||
(when component
|
||||
update-fn
|
||||
(fn [component]
|
||||
(cond-> component
|
||||
:always
|
||||
(assoc :path path
|
||||
|
@ -366,13 +364,13 @@
|
|||
(not components-v2)
|
||||
(update :objects
|
||||
;; Give the same name to the root shape
|
||||
#(assoc-in % [id :name] name)))))
|
||||
#(assoc-in % [id :name] name))))
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-component id update-fn))]
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/update-component id update-fn))]
|
||||
|
||||
(rx/of (dch/commit-changes changes)))))))
|
||||
(rx/of (dch/commit-changes changes))))))))
|
||||
|
||||
(defn rename-component-and-main-instance
|
||||
[component-id name]
|
||||
|
|
|
@ -249,7 +249,7 @@
|
|||
(lookup uuid/zero))
|
||||
|
||||
toselect (->> (cph/get-immediate-children objects (:id parent))
|
||||
(into (d/ordered-set) (comp (remove :blocked) (map :id))))]
|
||||
(into (d/ordered-set) (comp (remove :hidden) (remove :blocked) (map :id))))]
|
||||
|
||||
(rx/of (select-shapes toselect))))))
|
||||
|
||||
|
|
|
@ -143,12 +143,14 @@
|
|||
(cond-> (ctl/grid-layout? objects frame-id)
|
||||
(pcb/update-shapes [frame-id] ctl/assign-cells))))))
|
||||
|
||||
(defn move-shapes-into-frame [frame-id shapes]
|
||||
(defn move-shapes-into-frame
|
||||
[frame-id shapes]
|
||||
(ptk/reify ::move-shapes-into-frame
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
shapes (->> shapes (remove #(dm/get-in objects [% :blocked])))
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))
|
||||
changes (prepare-move-shapes-into-frame changes
|
||||
|
|
|
@ -196,7 +196,7 @@
|
|||
(-> (update-in [:svg-attrs :style] dissoc :mix-blend-mode)
|
||||
(assoc :blend-mode (-> (get-in shape [:svg-attrs :style :mix-blend-mode]) assert-valid-blend-mode)))))
|
||||
|
||||
(defn create-raw-svg [name frame-id svg-data {:keys [tag attrs] :as data}]
|
||||
(defn create-raw-svg [name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(let [{:keys [x y width height offset-x offset-y]} svg-data]
|
||||
(-> {:id (uuid/next)
|
||||
:type :svg-raw
|
||||
|
@ -206,7 +206,6 @@
|
|||
:height height
|
||||
:x x
|
||||
:y y
|
||||
:hidden (= tag :defs)
|
||||
:content (cond-> data
|
||||
(map? data) (update :attrs usvg/clean-attrs))}
|
||||
(assoc :svg-attrs attrs)
|
||||
|
@ -437,6 +436,7 @@
|
|||
children (cond->> (:content element-data)
|
||||
(contains? usvg/parent-tags tag)
|
||||
(mapv #(usvg/inherit-attributes attrs %)))]
|
||||
|
||||
[shape children]))))))
|
||||
|
||||
(defn create-svg-children
|
||||
|
@ -537,7 +537,8 @@
|
|||
root-shape (create-svg-root frame-id parent-id svg-data)
|
||||
root-id (:id root-shape)
|
||||
|
||||
;; In penpot groups have the size of their children. To respect the imported svg size and empty space let's create a transparent shape as background to respect the imported size
|
||||
;; In penpot groups have the size of their children. To respect the imported
|
||||
;; svg size and empty space let's create a transparent shape as background to respect the imported size
|
||||
base-background-shape {:tag :rect
|
||||
:attrs {:x (str vb-x)
|
||||
:y (str vb-y)
|
||||
|
@ -588,6 +589,7 @@
|
|||
|
||||
[new-shape new-children]
|
||||
(create-svg-shapes svg-data position objects frame-id parent-id selected true)
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/add-object new-shape))
|
||||
|
|
|
@ -56,7 +56,9 @@
|
|||
(mf/defc recovery-form
|
||||
[{:keys [params] :as props}]
|
||||
(let [form (fm/use-form :spec ::recovery-form
|
||||
:validators [password-equality]
|
||||
:validators [password-equality
|
||||
(fm/validate-not-empty :password-1 (tr "auth.password-not-empty"))
|
||||
(fm/validate-not-empty :password-2 (tr "auth.password-not-empty"))]
|
||||
:initial params)]
|
||||
[:& fm/form {:on-submit on-submit
|
||||
:form form}
|
||||
|
|
|
@ -87,7 +87,8 @@
|
|||
[{:keys [params on-success-callback] :as props}]
|
||||
(let [initial (mf/use-memo (mf/deps params) (constantly params))
|
||||
form (fm/use-form :spec ::register-form
|
||||
:validators [validate]
|
||||
:validators [validate
|
||||
(fm/validate-not-empty :password (tr "auth.password-not-empty"))]
|
||||
:initial initial)
|
||||
submitted? (mf/use-state false)
|
||||
|
||||
|
@ -219,6 +220,8 @@
|
|||
(mf/defc register-validate-form
|
||||
[{:keys [params on-success-callback] :as props}]
|
||||
(let [form (fm/use-form :spec ::register-validate-form
|
||||
:validators [(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))]
|
||||
:initial params)
|
||||
submitted? (mf/use-state false)
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
on-key-down (gobj/get props "on-key-down")
|
||||
id (gobj/get props "id")
|
||||
klass (gobj/get props "klass")
|
||||
key (gobj/get props "key")
|
||||
key (gobj/get props "unique-key")
|
||||
data-test (gobj/get props "data-test")]
|
||||
[:li {:id id
|
||||
:class klass
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
(def use-form fm/use-form)
|
||||
|
||||
(mf/defc input
|
||||
[{:keys [label help-icon disabled form hint trim children data-test] :as props}]
|
||||
[{:keys [label help-icon disabled form hint trim children data-test on-change-value] :as props}]
|
||||
(let [input-type (get props :type "text")
|
||||
input-name (get props :name)
|
||||
more-classes (get props :class)
|
||||
|
@ -57,6 +57,8 @@
|
|||
:else
|
||||
help-icon)
|
||||
|
||||
on-change-value (or on-change-value (constantly nil))
|
||||
|
||||
klass (str more-classes " "
|
||||
(dom/classnames
|
||||
:focus @focus?
|
||||
|
@ -80,7 +82,8 @@
|
|||
on-change (fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(swap! form assoc-in [:touched input-name] true)
|
||||
(fm/on-input-change form input-name value trim)))
|
||||
(fm/on-input-change form input-name value trim)
|
||||
(on-change-value name value)))
|
||||
|
||||
on-blur
|
||||
(fn [_]
|
||||
|
@ -135,7 +138,6 @@
|
|||
(string? hint)
|
||||
[:span.hint hint])]]))
|
||||
|
||||
|
||||
(mf/defc textarea
|
||||
[{:keys [label disabled form hint trim] :as props}]
|
||||
(let [input-name (get props :name)
|
||||
|
@ -222,7 +224,7 @@
|
|||
(for [item options]
|
||||
[:> :option (clj->js (cond-> {:key (:value item) :value (:value item)}
|
||||
(:disabled item) (assoc :disabled "disabled")
|
||||
(:hidden item) (assoc :style {:display "none"})))
|
||||
(:hidden item) (assoc :style {:display "none"})))
|
||||
(:label item)])]
|
||||
|
||||
[:div.input-container {:class (dom/classnames :disabled disabled :focus @focus?)}
|
||||
|
@ -237,7 +239,7 @@
|
|||
[{:keys [name options form trim on-change-value] :as props}]
|
||||
(let [form (or form (mf/use-ctx form-ctx))
|
||||
value (get-in @form [:data name] "")
|
||||
on-change-value (or on-change-value (constantly nil))
|
||||
on-change-value (or on-change-value (constantly nil))
|
||||
on-change (fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-value)]
|
||||
(swap! form assoc-in [:touched name] true)
|
||||
|
@ -409,3 +411,31 @@
|
|||
"caution" (:caution item))}
|
||||
[:span.text (:text item)]
|
||||
[:span.icon {:on-click #(remove-item! item)} i/cross]]])])]))
|
||||
|
||||
;; --- Validators
|
||||
|
||||
(defn all-spaces?
|
||||
[value]
|
||||
(let [trimmed (str/trim value)]
|
||||
(str/empty? trimmed)))
|
||||
|
||||
(def max-length-allowed 250)
|
||||
(def max-uri-length-allowed 2048)
|
||||
|
||||
(defn max-length?
|
||||
[value length]
|
||||
(> (count value) length))
|
||||
|
||||
(defn validate-length
|
||||
[field length errors-msg ]
|
||||
(fn [errors data]
|
||||
(cond-> errors
|
||||
(max-length? (get data field) length)
|
||||
(assoc field {:message errors-msg}))))
|
||||
|
||||
(defn validate-not-empty
|
||||
[field error-msg]
|
||||
(fn [errors data]
|
||||
(cond-> errors
|
||||
(all-spaces? (get data field))
|
||||
(assoc field {:message error-msg}))))
|
||||
|
|
|
@ -142,7 +142,11 @@
|
|||
#(st/emit! (dd/set-file-shared (assoc file :is-shared true)))
|
||||
|
||||
del-shared
|
||||
#(st/emit! (dd/set-file-shared (assoc file :is-shared false)))
|
||||
(mf/use-fn
|
||||
(mf/deps files)
|
||||
(fn [_]
|
||||
(run! #(st/emit! (dd/set-file-shared (assoc % :is-shared false))) files)))
|
||||
|
||||
|
||||
on-add-shared
|
||||
(fn [event]
|
||||
|
@ -216,23 +220,23 @@
|
|||
|
||||
(when current-team
|
||||
(let [sub-options (concat (vec (for [project current-projects]
|
||||
{:option-name (get-project-name project)
|
||||
:id (get-project-id project)
|
||||
:option-handler (on-move (:id current-team)
|
||||
(:id project))}))
|
||||
(when (seq other-teams)
|
||||
[{:option-name (tr "dashboard.move-to-other-team")
|
||||
:id "move-to-other-team"
|
||||
:sub-options
|
||||
(for [team other-teams]
|
||||
{:option-name (get-team-name team)
|
||||
:id (get-project-id team)
|
||||
:sub-options
|
||||
(for [sub-project (:projects team)]
|
||||
{:option-name (get-project-name sub-project)
|
||||
:id (get-project-id sub-project)
|
||||
:option-handler (on-move (:id team)
|
||||
(:id sub-project))})})}]))
|
||||
{:option-name (get-project-name project)
|
||||
:id (get-project-id project)
|
||||
:option-handler (on-move (:id current-team)
|
||||
(:id project))}))
|
||||
(when (seq other-teams)
|
||||
[{:option-name (tr "dashboard.move-to-other-team")
|
||||
:id "move-to-other-team"
|
||||
:sub-options
|
||||
(for [team other-teams]
|
||||
{:option-name (get-team-name team)
|
||||
:id (get-project-id team)
|
||||
:sub-options
|
||||
(for [sub-project (:projects team)]
|
||||
{:option-name (get-project-name sub-project)
|
||||
:id (get-project-id sub-project)
|
||||
:option-handler (on-move (:id team)
|
||||
(:id sub-project))})})}]))
|
||||
|
||||
options (if multi?
|
||||
[{:option-name (tr "dashboard.duplicate-multi" file-count)
|
||||
|
|
|
@ -495,17 +495,20 @@
|
|||
(mf/use-fn
|
||||
(mf/deps selected-project)
|
||||
(fn [e]
|
||||
(when (dnd/has-type? e "penpot/files")
|
||||
(dom/prevent-default e)
|
||||
(when-not (or (dnd/from-child? e)
|
||||
(cond
|
||||
(dnd/has-type? e "penpot/files")
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(when-not (or (dnd/from-child? e)
|
||||
(dnd/broken-event? e))
|
||||
(when (not= selected-project project-id)
|
||||
(reset! dragging? true))))
|
||||
(when (not= selected-project project-id)
|
||||
(reset! dragging? true))))
|
||||
|
||||
(when (or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file"))
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? true))))
|
||||
(or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file"))
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? true)))))
|
||||
|
||||
on-drag-over
|
||||
(mf/use-fn
|
||||
|
@ -531,19 +534,22 @@
|
|||
(mf/use-fn
|
||||
(mf/deps files selected-files)
|
||||
(fn [e]
|
||||
(when (or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file"))
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? false)
|
||||
(import-files (.-files (.-dataTransfer e))))
|
||||
(cond
|
||||
(dnd/has-type? e "penpot/files")
|
||||
(do
|
||||
(reset! dragging? false)
|
||||
(when (not= selected-project project-id)
|
||||
(let [data {:ids (into #{} (keys selected-files))
|
||||
:project-id project-id}
|
||||
mdata {:on-success on-drop-success}]
|
||||
(st/emit! (dd/move-files (with-meta data mdata))))))
|
||||
|
||||
(when (dnd/has-type? e "penpot/files")
|
||||
(reset! dragging? false)
|
||||
(when (not= selected-project project-id)
|
||||
(let [data {:ids (into #{} (keys selected-files))
|
||||
:project-id project-id}
|
||||
mdata {:on-success on-drop-success}]
|
||||
(st/emit! (dd/move-files (with-meta data mdata))))))))]
|
||||
(or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file"))
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? false)
|
||||
(import-files (.-files (.-dataTransfer e)))))))]
|
||||
|
||||
[:div.dashboard-grid {:on-drag-enter on-drag-enter
|
||||
:on-drag-over on-drag-over
|
||||
|
|
|
@ -243,6 +243,7 @@
|
|||
(when (kbd/enter? event)
|
||||
(team-selected (:default-team-id profile) event)))
|
||||
:id "teams-selector-default-team"
|
||||
:unique-key "default-team"
|
||||
:klass "team-name"}
|
||||
[:span.team-icon i/logo-icon]
|
||||
[:span.team-text (tr "dashboard.your-penpot")]
|
||||
|
@ -256,7 +257,7 @@
|
|||
(team-selected (:id team-item) event)))
|
||||
:id (str "teams-selector-" (:id team-item))
|
||||
:klass "team-name"
|
||||
:key (dm/str (:id team-item))}
|
||||
:unique-key (dm/str (:id team-item))}
|
||||
[:span.team-icon
|
||||
[:img {:src (cf/resolve-team-photo-url team-item)
|
||||
:alt (:name team-item)}]]
|
||||
|
@ -270,7 +271,7 @@
|
|||
(on-create-clicked event)))
|
||||
:id "teams-selector-create-team"
|
||||
:klass "team-name action"
|
||||
:key "teams-selector-create-team"}
|
||||
:unique-key "teams-selector-create-team"}
|
||||
[:span.team-icon.new-team i/close]
|
||||
[:span.team-text (tr "dashboard.create-new-team")]]]))
|
||||
|
||||
|
@ -364,16 +365,16 @@
|
|||
(when (kbd/enter? event)
|
||||
(go-members)))
|
||||
:id "teams-options-members"
|
||||
:key "teams-options-members"
|
||||
:data-test "team-members"}
|
||||
:unique-key "teams-options-members"
|
||||
:data-test "team-members"}
|
||||
(tr "labels.members")]
|
||||
[:& dropdown-menu-item {:on-click go-invitations
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(go-invitations)))
|
||||
:id "teams-options-invitations"
|
||||
:key "teams-options-invitations"
|
||||
:data-test "team-invitations"}
|
||||
:unique-key "teams-options-invitations"
|
||||
:data-test "team-invitations"}
|
||||
(tr "labels.invitations")]
|
||||
|
||||
(when (contains? cf/flags :webhooks)
|
||||
|
@ -382,7 +383,7 @@
|
|||
(when (kbd/enter? event)
|
||||
(go-webhooks)))
|
||||
:id "teams-options-webhooks"
|
||||
:key "teams-options-webhooks"}
|
||||
:unique-key "teams-options-webhooks"}
|
||||
(tr "labels.webhooks")])
|
||||
|
||||
[:& dropdown-menu-item {:on-click go-settings
|
||||
|
@ -390,8 +391,8 @@
|
|||
(when (kbd/enter? event)
|
||||
(go-settings)))
|
||||
:id "teams-options-settings"
|
||||
:key "teams-options-settings"
|
||||
:data-test "team-settings"}
|
||||
:unique-key "teams-options-settings"
|
||||
:data-test "team-settings"}
|
||||
(tr "labels.settings")]
|
||||
|
||||
[:hr]
|
||||
|
@ -401,8 +402,8 @@
|
|||
(when (kbd/enter? event)
|
||||
(on-rename-clicked)))
|
||||
:id "teams-options-rename"
|
||||
:key "teams-options-rename"
|
||||
:data-test "rename-team"}
|
||||
:unique-key "teams-options-rename"
|
||||
:data-test "rename-team"}
|
||||
(tr "labels.rename")])
|
||||
|
||||
(cond
|
||||
|
@ -412,7 +413,7 @@
|
|||
(when (kbd/enter? event)
|
||||
(leave-and-close)))
|
||||
:id "teams-options-leave-team"
|
||||
:key "teams-options-leave-team"}
|
||||
:unique-key "teams-options-leave-team"}
|
||||
(tr "dashboard.leave-team")]
|
||||
|
||||
|
||||
|
@ -422,8 +423,8 @@
|
|||
(when (kbd/enter? event)
|
||||
(on-leave-as-owner-clicked)))
|
||||
:id "teams-options-leave-team"
|
||||
:key "teams-options-leave-team"
|
||||
:data-test "leave-team"}
|
||||
:unique-key "teams-options-leave-team"
|
||||
:data-test "leave-team"}
|
||||
(tr "dashboard.leave-team")]
|
||||
|
||||
(> (count members) 1)
|
||||
|
@ -432,7 +433,7 @@
|
|||
(when (kbd/enter? event)
|
||||
(on-leave-clicked)))
|
||||
:id "teams-options-leave-team"
|
||||
:key "teams-options-leave-team"}
|
||||
:unique-key "teams-options-leave-team"}
|
||||
(tr "dashboard.leave-team")])
|
||||
|
||||
|
||||
|
@ -442,9 +443,9 @@
|
|||
(when (kbd/enter? event)
|
||||
(on-delete-clicked)))
|
||||
:id "teams-options-delete-team"
|
||||
:key "teams-options-delete-team"
|
||||
:unique-key "teams-options-delete-team"
|
||||
:klass "warning"
|
||||
:data-test "delete-team"}
|
||||
:data-test "delete-team"}
|
||||
(tr "dashboard.delete-team")])]))
|
||||
|
||||
|
||||
|
|
|
@ -658,7 +658,8 @@
|
|||
(let [initial (mf/use-memo (fn [] (or (some-> webhook (update :uri str))
|
||||
{:is-active false :mtype "application/json"})))
|
||||
form (fm/use-form :spec ::webhook-form
|
||||
:initial initial)
|
||||
:initial initial
|
||||
:validators [(fm/validate-length :uri fm/max-uri-length-allowed (tr "team.webhooks.max-length"))])
|
||||
on-success
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
|
@ -750,8 +751,7 @@
|
|||
[:& fm/input {:type "checkbox"
|
||||
:form form
|
||||
:name :is-active
|
||||
:label (tr "dashboard.webhooks.active")}]
|
||||
]
|
||||
:label (tr "dashboard.webhooks.active")}]]
|
||||
[:div.explain (tr "dashboard.webhooks.active.explain")]]]
|
||||
|
||||
[:div.modal-footer
|
||||
|
|
|
@ -66,10 +66,12 @@
|
|||
(on-create-submit form))))
|
||||
|
||||
(mf/defc team-form-modal {::mf/register modal/components
|
||||
::mf/register-as :team-form}
|
||||
::mf/register-as :team-form}
|
||||
[{:keys [team] :as props}]
|
||||
(let [initial (mf/use-memo (fn [] (or team {})))
|
||||
form (fm/use-form :spec ::team-form
|
||||
:validators [(fm/validate-not-empty :name (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :name fm/max-length-allowed (tr "auth.name.too-long"))]
|
||||
:initial initial)]
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.team-form-modal
|
||||
|
|
|
@ -54,7 +54,9 @@
|
|||
::mf/register-as :onboarding-team}
|
||||
[]
|
||||
(let [form (fm/use-form :spec ::team-form
|
||||
:initial {})
|
||||
:initial {}
|
||||
:validators [(fm/validate-not-empty :name (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :name fm/max-length-allowed (tr "auth.name.too-long"))])
|
||||
on-submit
|
||||
(mf/use-callback
|
||||
(fn [form _]
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
(str/blank? name)
|
||||
(assoc :name {:message (tr "dashboard.access-tokens.errors-required-name")}))))
|
||||
|
||||
|
||||
(def initial-data
|
||||
{:name "" :expiration-date "never"})
|
||||
|
||||
|
@ -53,7 +52,9 @@
|
|||
(let [form (fm/use-form
|
||||
:initial initial-data
|
||||
:spec ::access-token-form
|
||||
:validators [name-validator])
|
||||
:validators [name-validator
|
||||
(fm/validate-not-empty :name (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :name fm/max-length-allowed (tr "auth.name.too-long"))])
|
||||
created (mf/deref token-created-ref)
|
||||
created? (mf/use-state false)
|
||||
locale (mf/deref i18n/locale)
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
(ns app.main.ui.settings.change-email
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dma]
|
||||
[app.common.spec :as us]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.modal :as modal]
|
||||
|
@ -29,7 +31,8 @@
|
|||
email-2 (:email-2 data)]
|
||||
(cond-> errors
|
||||
(and email-1 email-2 (not= email-1 email-2))
|
||||
(assoc :email-2 {:message (tr "errors.email-invalid-confirmation")}))))
|
||||
(assoc :email-2 {:message (tr "errors.email-invalid-confirmation")
|
||||
:code :different-emails}))))
|
||||
|
||||
(s/def ::email-change-form
|
||||
(s/keys :req-un [::email-1 ::email-2]))
|
||||
|
@ -81,7 +84,17 @@
|
|||
on-submit
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(partial on-submit profile))]
|
||||
(partial on-submit profile))
|
||||
|
||||
on-email-change
|
||||
(mf/use-callback
|
||||
(fn [_ _]
|
||||
(let [different-emails-error? (= (dma/get-in @form [:errors :email-2 :code]) :different-emails)
|
||||
email-1 (dma/get-in @form [:clean-data :email-1])
|
||||
email-2 (dma/get-in @form [:clean-data :email-2])]
|
||||
(println "different-emails-error?" (and different-emails-error? (= email-1 email-2)))
|
||||
(when (and different-emails-error? (= email-1 email-2))
|
||||
(swap! form d/dissoc-in [:errors :email-2])))))]
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.change-email-modal.form-container
|
||||
|
@ -105,12 +118,14 @@
|
|||
[:& fm/input {:type "email"
|
||||
:name :email-1
|
||||
:label (tr "modals.change-email.new-email")
|
||||
:trim true}]]
|
||||
:trim true
|
||||
:on-change-value on-email-change}]]
|
||||
[:div.fields-row
|
||||
[:& fm/input {:type "email"
|
||||
:name :email-2
|
||||
:label (tr "modals.change-email.confirm-email")
|
||||
:trim true}]]]]
|
||||
:trim true
|
||||
:on-change-value on-email-change}]]]]
|
||||
|
||||
[:div.modal-footer
|
||||
[:div.action-buttons {:data-test "change-email-submit"}
|
||||
|
|
|
@ -28,8 +28,9 @@
|
|||
(mf/defc feedback-form
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
form (fm/use-form :spec ::feedback-form)
|
||||
|
||||
form (fm/use-form :spec ::feedback-form
|
||||
:validators [(fm/validate-length :subject fm/max-length-allowed (tr "auth.name.too-long"))
|
||||
(fm/validate-not-empty :subject (tr "auth.name.not-all-space"))])
|
||||
loading (mf/use-state false)
|
||||
|
||||
on-succes
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
{:message (tr "errors.wrong-old-password")})
|
||||
:email-as-password
|
||||
(swap! form assoc-in [:errors :password-1]
|
||||
{:message (tr "errors.email-as-password")})
|
||||
{:message (tr "errors.email-as-password")})
|
||||
|
||||
(let [msg (tr "generic.error")]
|
||||
(st/emit! (dm/error msg)))))
|
||||
|
@ -71,8 +71,10 @@
|
|||
[{:keys [locale] :as props}]
|
||||
(let [initial (mf/use-memo (constantly {:password-old nil}))
|
||||
form (fm/use-form :spec ::password-form
|
||||
:validators [password-equality]
|
||||
:initial initial)]
|
||||
:validators [(fm/validate-not-empty :password-1 (tr "auth.password-not-empty"))
|
||||
(fm/validate-not-empty :password-2 (tr "auth.password-not-empty"))
|
||||
password-equality]
|
||||
:initial initial)]
|
||||
[:& fm/form {:class "password-form"
|
||||
:on-submit on-submit
|
||||
:form form}
|
||||
|
@ -105,7 +107,7 @@
|
|||
(mf/defc password-page
|
||||
[{:keys [locale]}]
|
||||
(mf/use-effect
|
||||
#(dom/set-html-title (tr "title.settings.password")))
|
||||
#(dom/set-html-title (tr "title.settings.password")))
|
||||
|
||||
[:section.dashboard-settings.form-container
|
||||
[:div.form-container
|
||||
|
|
|
@ -42,7 +42,10 @@
|
|||
(mf/defc profile-form
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
form (fm/use-form :spec ::profile-form :initial profile)]
|
||||
form (fm/use-form :spec ::profile-form
|
||||
:initial profile
|
||||
:validators [(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))
|
||||
(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))])]
|
||||
|
||||
[:& fm/form {:on-submit on-submit
|
||||
:form form
|
||||
|
|
|
@ -215,7 +215,7 @@
|
|||
(mf/defc viewer
|
||||
[{:keys [params data]}]
|
||||
|
||||
(let [{:keys [page-id section index interactions-mode]} params
|
||||
(let [{:keys [page-id share-id section index interactions-mode]} params
|
||||
{:keys [file users project permissions]} data
|
||||
|
||||
allowed (or
|
||||
|
@ -519,7 +519,8 @@
|
|||
:size size
|
||||
:index index
|
||||
:viewer-pagination viewer-pagination
|
||||
:interactions-mode interactions-mode}]
|
||||
:interactions-mode interactions-mode
|
||||
:share-id share-id}]
|
||||
|
||||
|
||||
[:& (mf/provider ctx/current-zoom) {:value zoom}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
(dom/add-class! layout "force-visible"))))))
|
||||
|
||||
(mf/defc viewport
|
||||
[{:keys [local file page frame index viewer-pagination size]}]
|
||||
[{:keys [local file page frame index viewer-pagination size share-id]}]
|
||||
(let [inspect-svg-container-ref (mf/use-ref nil)
|
||||
on-mouse-wheel
|
||||
(fn [event]
|
||||
|
@ -76,4 +76,5 @@
|
|||
[:& right-sidebar {:frame frame
|
||||
:selected (:selected local)
|
||||
:page page
|
||||
:file file}]]))
|
||||
:file file
|
||||
:share-id share-id}]]))
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
:text [:layout :text :shadow :blur :stroke :layout-flex-item]})
|
||||
|
||||
(mf/defc attributes
|
||||
[{:keys [page-id file-id shapes frame from libraries]}]
|
||||
[{:keys [page-id file-id shapes frame from libraries share-id]}]
|
||||
(let [shapes (hooks/use-equal-memo shapes)
|
||||
shapes (mf/with-memo [shapes]
|
||||
(mapv #(gsh/translate-to-frame % frame) shapes))
|
||||
|
@ -66,4 +66,5 @@
|
|||
{:shapes shapes
|
||||
:type type
|
||||
:page-id page-id
|
||||
:file-id file-id}]]))
|
||||
:file-id file-id
|
||||
:share-id share-id}]]))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
(mf/defc exports
|
||||
{::mf/wrap [#(mf/memo % =)]}
|
||||
[{:keys [shapes page-id file-id type] :as props}]
|
||||
[{:keys [shapes page-id file-id share-id type] :as props}]
|
||||
(let [exports (mf/use-state [])
|
||||
xstate (mf/deref refs/export)
|
||||
vstate (mf/deref refs/viewer-data)
|
||||
|
@ -40,15 +40,17 @@
|
|||
:exports @exports
|
||||
:filename filename
|
||||
:page-id page-id
|
||||
:file-id file-id}))
|
||||
:file-id file-id
|
||||
:share-id share-id}))
|
||||
|
||||
;; In other all cases we only allowed to have a single
|
||||
;; shape-id because multiple shape-ids are handled
|
||||
;; separately by the export-modal.
|
||||
(let [defaults {:page-id page-id
|
||||
:file-id file-id
|
||||
:name filename
|
||||
:object-id (-> shapes first :id)}
|
||||
(let [defaults (-> {:page-id page-id
|
||||
:file-id file-id
|
||||
:name filename
|
||||
:object-id (-> shapes first :id)}
|
||||
(cond-> share-id (assoc :share-id share-id)))
|
||||
exports (mapv #(merge % defaults) @exports)]
|
||||
(if (= 1 (count exports))
|
||||
(st/emit! (de/request-simple-export {:export (first exports)}))
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
:data local})))))
|
||||
|
||||
(mf/defc right-sidebar
|
||||
[{:keys [frame page file selected shapes page-id file-id from]
|
||||
[{:keys [frame page file selected shapes page-id file-id share-id from]
|
||||
:or {from :inspect}}]
|
||||
(let [expanded (mf/use-state false)
|
||||
section (mf/use-state :info #_:code)
|
||||
|
@ -89,7 +89,8 @@
|
|||
:frame frame
|
||||
:shapes shapes
|
||||
:from from
|
||||
:libraries libraries}]]
|
||||
:libraries libraries
|
||||
:share-id share-id}]]
|
||||
|
||||
[:& tabs-element {:id :code :title (tr "inspect.tabs.code")}
|
||||
[:& code {:frame frame
|
||||
|
|
|
@ -28,31 +28,36 @@
|
|||
(defn prepare-params
|
||||
[{:keys [pages who-comment who-inspect]}]
|
||||
|
||||
{:pages pages
|
||||
:who-comment who-comment
|
||||
:who-inspect who-inspect})
|
||||
{:pages pages
|
||||
:who-comment who-comment
|
||||
:who-inspect who-inspect})
|
||||
|
||||
(mf/defc share-link-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :share-link}
|
||||
[{:keys [file page]}]
|
||||
(let [current-page page
|
||||
slinks (mf/deref refs/share-links)
|
||||
router (mf/deref refs/router)
|
||||
route (mf/deref refs/route)
|
||||
zoom-type (mf/deref refs/viewer-zoom-type)
|
||||
slinks (mf/deref refs/share-links)
|
||||
router (mf/deref refs/router)
|
||||
route (mf/deref refs/route)
|
||||
zoom-type (mf/deref refs/viewer-zoom-type)
|
||||
|
||||
link (mf/use-state nil)
|
||||
confirm (mf/use-state false)
|
||||
open-ops (mf/use-state false)
|
||||
link (mf/use-state nil)
|
||||
confirm (mf/use-state false)
|
||||
open-ops (mf/use-state false)
|
||||
|
||||
opts (mf/use-state
|
||||
{:pages-mode "current"
|
||||
:all-pages false
|
||||
:pages #{(:id page)}
|
||||
:who-comment "team"
|
||||
:who-inspect "team"})
|
||||
opts* (mf/use-state
|
||||
{:pages-mode "current"
|
||||
:all-pages false
|
||||
:pages #{(:id page)}
|
||||
:who-comment "team"
|
||||
:who-inspect "team"})
|
||||
|
||||
opts (deref opts*)
|
||||
|
||||
selected-pages (:pages opts)
|
||||
file-pages (->> (get-in file [:data :pages])
|
||||
(map #(get-in file [:data :pages-index %])))
|
||||
|
||||
close
|
||||
(fn [event]
|
||||
|
@ -63,7 +68,7 @@
|
|||
toggle-all
|
||||
(fn []
|
||||
(reset! confirm false)
|
||||
(swap! opts
|
||||
(swap! opts*
|
||||
(fn [state]
|
||||
(if (= true (:all-pages state))
|
||||
(-> state
|
||||
|
@ -74,23 +79,29 @@
|
|||
(assoc :pages (into #{} (get-in file [:data :pages]))))))))
|
||||
|
||||
mark-checked-page
|
||||
(fn [event id]
|
||||
(let [target (dom/get-target event)
|
||||
checked? (.-checked ^js target)
|
||||
dif-pages? (not= id (first (:pages @opts)))
|
||||
no-one-page (< 1 (count (:pages @opts)))
|
||||
(mf/use-fn
|
||||
(mf/deps selected-pages)
|
||||
(fn [event id]
|
||||
(let [target (dom/get-target event)
|
||||
not-checked? (.-checked ^js target)
|
||||
dif-pages? (not= id (first selected-pages))
|
||||
no-one-page (< 1 (count selected-pages))
|
||||
should-change (or no-one-page dif-pages?)]
|
||||
(when should-change
|
||||
(reset! confirm false)
|
||||
(swap! opts update :pages
|
||||
(fn [pages]
|
||||
(if checked?
|
||||
(conj pages id)
|
||||
(disj pages id)))))))
|
||||
(swap! opts*
|
||||
(fn [state]
|
||||
(let [actual-pages (:pages state)
|
||||
updated-pages (if not-checked?
|
||||
(conj actual-pages id)
|
||||
(disj actual-pages id))]
|
||||
(-> state
|
||||
(assoc :pages updated-pages)
|
||||
(assoc :all-pages (= (count updated-pages) (count file-pages)))))))))))
|
||||
|
||||
create-link
|
||||
(fn [_]
|
||||
(let [params (prepare-params @opts)
|
||||
(let [params (prepare-params opts)
|
||||
params (assoc params :file-id (:id file))]
|
||||
(st/emit! (dc/create-share-link params)
|
||||
(ptk/event ::ev/event {::ev/name "create-shared-link"
|
||||
|
@ -111,7 +122,7 @@
|
|||
|
||||
delete-link
|
||||
(fn [_]
|
||||
(let [params (prepare-params @opts)
|
||||
(let [params (prepare-params opts)
|
||||
slink (d/seek #(= (:flags %) (:flags params)) slinks)]
|
||||
(reset! confirm false)
|
||||
(st/emit! (dc/delete-share-link slink))))
|
||||
|
@ -127,13 +138,13 @@
|
|||
value (keyword value)]
|
||||
(reset! confirm false)
|
||||
(if (= type :comment)
|
||||
(swap! opts assoc :who-comment (d/name value))
|
||||
(swap! opts assoc :who-inspect (d/name value)))))]
|
||||
(swap! opts* assoc :who-comment (d/name value))
|
||||
(swap! opts* assoc :who-inspect (d/name value)))))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps file slinks @opts)
|
||||
(mf/deps file slinks opts)
|
||||
(fn []
|
||||
(let [{:keys [pages who-comment who-inspect] :as params} (prepare-params @opts)
|
||||
(let [{:keys [pages who-comment who-inspect] :as params} (prepare-params opts)
|
||||
slink (d/seek #(and (= (:who-inspect %) who-inspect) (= (:who-comment %) who-comment) (= (:pages %) pages)) slinks)
|
||||
href (when slink
|
||||
(let [pparams (:path-params route)
|
||||
|
@ -204,10 +215,10 @@
|
|||
[:div.title (tr "common.share-link.manage-ops")]]
|
||||
(when @open-ops
|
||||
[:*
|
||||
(let [all-selected? (:all-pages @opts)
|
||||
(let [all-selected? (:all-pages opts)
|
||||
pages (->> (get-in file [:data :pages])
|
||||
(map #(get-in file [:data :pages-index %])))
|
||||
selected (:pages @opts)]
|
||||
selected selected-pages]
|
||||
|
||||
[:*
|
||||
[:div.view-mode
|
||||
|
@ -253,7 +264,7 @@
|
|||
(tr "common.share-link.permissions-can-comment")]
|
||||
[:div.items
|
||||
[:select.input-select {:on-change (partial on-who-change :comment)
|
||||
:value (:who-comment @opts)}
|
||||
:value (:who-comment opts)}
|
||||
[:option {:value "team"} (tr "common.share-link.team-members")]
|
||||
[:option {:value "all"} (tr "common.share-link.all-users")]]]]
|
||||
[:div.inspect-mode
|
||||
|
@ -262,7 +273,7 @@
|
|||
(tr "common.share-link.permissions-can-inspect")]
|
||||
[:div.items
|
||||
[:select.input-select {:on-change (partial on-who-change :inspect)
|
||||
:value (:who-inspect @opts)}
|
||||
:value (:who-inspect opts)}
|
||||
[:option {:value "team"} (tr "common.share-link.team-members")]
|
||||
[:option {:value "all"} (tr "common.share-link.all-users")]]]]])]]]))
|
||||
|
||||
|
|
|
@ -133,6 +133,8 @@
|
|||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! ::dwt/finalize-editor-state)
|
||||
(st/emit! (dwt/initialize-editor-state shape default-decorator))
|
||||
(reset! blurred true)))
|
||||
|
||||
on-focus
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
(let [input (mf/ref-val input-ref)]
|
||||
(dom/select-text! input)
|
||||
nil)))
|
||||
|
||||
|
||||
(if ^boolean new-css-system
|
||||
[:div {:class (dom/classnames (css :asset-list-item) true
|
||||
(css :selected) (contains? selected (:id color))
|
||||
|
@ -503,6 +503,7 @@
|
|||
(st/emit! (dw/set-assets-section-open file-id :colors true)
|
||||
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
|
||||
:asset-type "color"}))
|
||||
;; FIXME: replace interop with dom helpers
|
||||
(modal/show! :colorpicker
|
||||
{:x (.-clientX event)
|
||||
:y (.-clientY event)
|
||||
|
@ -592,7 +593,7 @@
|
|||
[:& cmm/asset-section-block {:role :title-button}
|
||||
(when-not read-only?
|
||||
[:button {:class (dom/classnames (css :assets-btn) true)
|
||||
:on-click add-color-clicked}
|
||||
:on-click add-color-clicked}
|
||||
i/add-refactor])])
|
||||
|
||||
(when local?
|
||||
|
@ -618,4 +619,4 @@
|
|||
:on-rename-group on-rename-group
|
||||
:on-ungroup on-ungroup
|
||||
:colors colors
|
||||
:selected-full selected-full}]]]))
|
||||
:selected-full selected-full}]]]))
|
||||
|
|
|
@ -390,7 +390,7 @@
|
|||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! state (fn [state]
|
||||
(assoc state :renaming (:component-id state))))))
|
||||
(assoc state :renaming (:object-id state))))))
|
||||
cancel-rename
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
|
|
|
@ -123,6 +123,8 @@
|
|||
(mf/deps last-path)
|
||||
(constantly {:asset-name last-path}))
|
||||
form (fm/use-form :spec ::name-group-form
|
||||
:validators [(fm/validate-not-empty :name (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :name fm/max-length-allowed (tr "auth.name.too-long"))]
|
||||
:initial initial)
|
||||
|
||||
create? (empty? path)
|
||||
|
|
|
@ -93,13 +93,13 @@
|
|||
:space-between i/align-content-row-between
|
||||
:stretch nil))
|
||||
|
||||
(case val
|
||||
:start i/align-content-row-start
|
||||
:end i/align-content-row-end
|
||||
:center i/align-content-row-center
|
||||
:space-around i/align-content-row-around
|
||||
:space-between i/align-content-row-between
|
||||
:stretch nil)
|
||||
(case val
|
||||
:start i/align-content-row-start
|
||||
:end i/align-content-row-end
|
||||
:center i/align-content-row-center
|
||||
:space-around i/align-content-row-around
|
||||
:space-between i/align-content-row-between
|
||||
:stretch nil)
|
||||
|
||||
:align-self
|
||||
(if is-col?
|
||||
|
@ -275,8 +275,8 @@
|
|||
{:placeholder "--"
|
||||
:on-change (partial on-change :simple :p1)
|
||||
:on-focus #(do
|
||||
(dom/select-target %)
|
||||
(select-paddings true false true false))
|
||||
(dom/select-target %)
|
||||
(select-paddings true false true false))
|
||||
:value p1}]]
|
||||
|
||||
[:div.padding-item.tooltip.tooltip-bottom-left
|
||||
|
@ -354,7 +354,7 @@
|
|||
i/auto-gap]
|
||||
[:> numeric-input {:no-validate true
|
||||
:placeholder "--"
|
||||
:on-focus (fn [event]
|
||||
:on-focus (fn [event]
|
||||
(select-gap :row-gap)
|
||||
(reset! gap-selected? :row-gap)
|
||||
(dom/select-target event))
|
||||
|
@ -638,12 +638,12 @@
|
|||
(if (and (not multiple) (:layout values))
|
||||
[:div.title-actions
|
||||
#_[:div.layout-btns
|
||||
[:button {:on-click set-flex
|
||||
:class (dom/classnames
|
||||
:active (= :flex layout-type))} "Flex"]
|
||||
[:button {:on-click set-grid
|
||||
:class (dom/classnames
|
||||
:active (= :grid layout-type))} "Grid"]]
|
||||
[:button {:on-click set-flex
|
||||
:class (dom/classnames
|
||||
:active (= :flex layout-type))} "Flex"]
|
||||
[:button {:on-click set-grid
|
||||
:class (dom/classnames
|
||||
:active (= :grid layout-type))} "Grid"]]
|
||||
[:button.remove-layout {:on-click on-remove-layout} i/minus]]
|
||||
|
||||
[:button.add-page {:on-click #(on-add-layout :flex)} i/close])]]
|
||||
|
|
|
@ -192,6 +192,8 @@
|
|||
(if (= align-self value)
|
||||
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil}))
|
||||
(st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value}))))
|
||||
|
||||
is-absolute? (:layout-item-absolute values)
|
||||
|
||||
is-col? (every? ctl/col? selection-parents)
|
||||
|
||||
|
@ -219,6 +221,8 @@
|
|||
|
||||
on-change-position
|
||||
(fn [value]
|
||||
(when (= value :static)
|
||||
(st/emit! (dwsl/update-layout-child ids {:layout-item-z-index nil})))
|
||||
(st/emit! (dwsl/update-layout-child ids {:layout-item-absolute (= value :absolute)})))
|
||||
|
||||
on-change-z-index
|
||||
|
@ -254,6 +258,8 @@
|
|||
{:placeholder "--"
|
||||
:on-focus #(dom/select-target %)
|
||||
:on-change #(on-change-z-index %)
|
||||
:nillable true
|
||||
:disabled (not is-absolute?)
|
||||
:value (:layout-item-z-index values)}]]]])
|
||||
|
||||
(when (not (:layout-item-absolute values))
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
|
||||
ids (into
|
||||
(d/ordered-set)
|
||||
(remove #(dm/get-in objects [% :blocked]))
|
||||
(ctt/sort-z-index objects ids {:bottom-frames? mod?}))
|
||||
|
||||
grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type])))
|
||||
|
|
|
@ -149,7 +149,9 @@
|
|||
text-pos-x (if (:use-for-thumbnail? frame) 15 0)]
|
||||
|
||||
(when (not (:hidden frame))
|
||||
[:g.frame-title {:id (dm/str "frame-title-" (:id frame)) :transform (vwu/title-transform frame zoom)}
|
||||
[:g.frame-title {:id (dm/str "frame-title-" (:id frame))
|
||||
:transform (vwu/title-transform frame zoom)
|
||||
:pointer-events (when (:blocked frame) "none")}
|
||||
(when (:use-for-thumbnail? frame)
|
||||
[:svg {:x 0
|
||||
:y -9
|
||||
|
|
|
@ -95,16 +95,17 @@
|
|||
state))
|
||||
|
||||
(mf/defc object-svg
|
||||
[{:keys [page-id file-id object-id render-embed?]}]
|
||||
[{:keys [page-id file-id share-id object-id render-embed?]}]
|
||||
(let [components-v2 (feat/use-feature :components-v2)
|
||||
fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id object-id components-v2)
|
||||
(mf/deps file-id page-id share-id object-id components-v2)
|
||||
(fn []
|
||||
(let [features (cond-> #{} components-v2 (conj "components/v2"))]
|
||||
(->> (rx/zip
|
||||
(repo/cmd! :get-font-variants {:file-id file-id})
|
||||
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
|
||||
(repo/cmd! :get-page {:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:object-id object-id
|
||||
:features features}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
|
@ -135,16 +136,17 @@
|
|||
:render-embed? render-embed?}])))
|
||||
|
||||
(mf/defc objects-svg
|
||||
[{:keys [page-id file-id object-ids render-embed?]}]
|
||||
[{:keys [page-id file-id share-id object-ids render-embed?]}]
|
||||
(let [components-v2 (feat/use-feature :components-v2)
|
||||
fetch-state (mf/use-fn
|
||||
(mf/deps file-id page-id components-v2)
|
||||
(mf/deps file-id page-id share-id components-v2)
|
||||
(fn []
|
||||
(let [features (cond-> #{} components-v2 (conj "components/v2"))]
|
||||
(->> (rx/zip
|
||||
(repo/cmd! :get-font-variants {:file-id file-id})
|
||||
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
|
||||
(repo/cmd! :get-page {:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:features features}))
|
||||
(rx/tap (fn [[fonts]]
|
||||
(when (seq fonts)
|
||||
|
@ -164,6 +166,7 @@
|
|||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::share-id ::us/uuid)
|
||||
(s/def ::object-id
|
||||
(s/or :single ::us/uuid
|
||||
:multiple (s/coll-of ::us/uuid)))
|
||||
|
@ -171,24 +174,25 @@
|
|||
|
||||
(s/def ::render-objects
|
||||
(s/keys :req-un [::file-id ::page-id ::object-id]
|
||||
:opt-un [::render-embed]))
|
||||
:opt-un [::render-embed ::share-id]))
|
||||
|
||||
(defn- render-objects
|
||||
[params]
|
||||
(let [{:keys [file-id
|
||||
page-id
|
||||
render-embed]
|
||||
render-embed
|
||||
share-id]
|
||||
:as params}
|
||||
(us/conform ::render-objects params)
|
||||
|
||||
[type object-id] (:object-id params)]
|
||||
|
||||
(case type
|
||||
:single
|
||||
(mf/html
|
||||
[:& object-svg
|
||||
{:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:object-id object-id
|
||||
:render-embed? render-embed}])
|
||||
|
||||
|
@ -197,6 +201,7 @@
|
|||
[:& objects-svg
|
||||
{:file-id file-id
|
||||
:page-id page-id
|
||||
:share-id share-id
|
||||
:object-ids (into #{} object-id)
|
||||
:render-embed? render-embed}]))))
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
|
||||
:curve-to
|
||||
(let [{:keys [c1x c1y c2x c2y]} params]
|
||||
(join-params c1x c1y c2x c2y x y))
|
||||
(join-params (or c1x x) (or c1y y) (or c2x x) (or c2y y) x y))
|
||||
|
||||
(:smooth-curve-to :quadratic-bezier-curve-to)
|
||||
(let [{:keys [cx cy]} params]
|
||||
|
|
|
@ -53,6 +53,14 @@ msgstr "Full Name"
|
|||
msgid "auth.login-here"
|
||||
msgstr "Login here"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/onboarding/team_choice.cljs, src/app/main/ui/settings/access_tokens.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/settings/profile.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
|
||||
msgid "auth.name.too-long"
|
||||
msgstr "The name must contain at most 250 characters."
|
||||
|
||||
#: src/app/main/ui/settings/team-form.cljs, src/app/main/ui/auth/register.cljs, src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/onboarding/team_choice.cljs, src/app/main/ui/settings/access_tokens.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/settings/profile.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
|
||||
msgid "auth.name.not-all-space"
|
||||
msgstr "The name must contain some character other than space."
|
||||
|
||||
#: src/app/main/ui/auth/login.cljs
|
||||
msgid "auth.login-submit"
|
||||
msgstr "Login"
|
||||
|
@ -113,6 +121,10 @@ msgstr "Password"
|
|||
msgid "auth.password-length-hint"
|
||||
msgstr "At least 8 characters"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs
|
||||
msgid "auth.password-not-empty"
|
||||
msgstr "Password must contain some character other than space."
|
||||
|
||||
msgid "auth.privacy-policy"
|
||||
msgstr "Privacy policy"
|
||||
|
||||
|
@ -804,6 +816,10 @@ msgstr "No webhooks created so far."
|
|||
msgid "dashboard.webhooks.update.success"
|
||||
msgstr "Webhook updated successfully."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
msgid "team.webhooks.max-length"
|
||||
msgstr "The webhook name must contain at most 2048 characters."
|
||||
|
||||
#: src/app/main/ui/settings.cljs
|
||||
msgid "dashboard.your-account-title"
|
||||
msgstr "Your account"
|
||||
|
|
|
@ -56,6 +56,14 @@ msgstr "Nombre completo"
|
|||
msgid "auth.login-here"
|
||||
msgstr "Inicia sesión aquí"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/onboarding/team_choice.cljs, src/app/main/ui/settings/access_tokens.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/settings/profile.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
|
||||
msgid "auth.name.too-long"
|
||||
msgstr "El nombre debe contener como máximo 250 caracteres."
|
||||
|
||||
#: src/app/main/ui/settings/team-form.cljs, src/app/main/ui/auth/register.cljs, src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/onboarding/team_choice.cljs, src/app/main/ui/settings/access_tokens.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/settings/profile.cljs, src/app/main/ui/workspace/sidebar/assets.cljs
|
||||
msgid "auth.name.not-all-space"
|
||||
msgstr "El nombre debe contener algún carácter diferente de espacio"
|
||||
|
||||
#: src/app/main/ui/auth/login.cljs
|
||||
msgid "auth.login-submit"
|
||||
msgstr "Entrar"
|
||||
|
@ -118,6 +126,10 @@ msgstr "Contraseña"
|
|||
msgid "auth.password-length-hint"
|
||||
msgstr "8 caracteres como mínimo"
|
||||
|
||||
#: src/app/main/ui/auth/register.cljs
|
||||
msgid "auth.password-not-empty"
|
||||
msgstr "La contraseña debe contener algún caracter diferente de espacio"
|
||||
|
||||
msgid "auth.privacy-policy"
|
||||
msgstr "Política de privacidad"
|
||||
|
||||
|
@ -820,6 +832,10 @@ msgstr "No hay ningún webhook aún."
|
|||
msgid "dashboard.webhooks.update.success"
|
||||
msgstr "Webhook modificado con éxito."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
msgid "team.webhooks.max-length"
|
||||
msgstr "El nombre del webhook debe contener como máximo 2048 caracteres."
|
||||
|
||||
#: src/app/main/ui/settings.cljs
|
||||
msgid "dashboard.your-account-title"
|
||||
msgstr "Tu cuenta"
|
||||
|
|
Loading…
Add table
Reference in a new issue