mirror of
https://github.com/penpot/penpot.git
synced 2026-01-10 23:39:04 -05:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b13a6d5d | ||
|
|
6935d54870 | ||
|
|
65e8526ee2 | ||
|
|
202762027f | ||
|
|
d95551e651 | ||
|
|
c96fbfdcd6 | ||
|
|
6e5d64d403 | ||
|
|
3e0c2bf1a1 | ||
|
|
283cdee5d6 | ||
|
|
ab5e01e54a | ||
|
|
373248e304 | ||
|
|
80308ceafa | ||
|
|
f65518f865 | ||
|
|
f155042958 | ||
|
|
1dd23a3f47 | ||
|
|
1194e40222 | ||
|
|
05fac41534 | ||
|
|
3f85e89f62 | ||
|
|
ee0f8ad19a | ||
|
|
b7d7cf233a | ||
|
|
bd208c31e2 | ||
|
|
151dc352c8 | ||
|
|
ccbf17106d | ||
|
|
95c4d95fd3 | ||
|
|
a72c07b657 | ||
|
|
708492afeb | ||
|
|
1305ab3cc6 | ||
|
|
29cc6b4f9c | ||
|
|
cc7f0b145c | ||
|
|
e69c0c3e27 | ||
|
|
a209966427 | ||
|
|
d5abbd4220 | ||
|
|
70a23a14c4 | ||
|
|
93c81ea49c | ||
|
|
ddc41027ab | ||
|
|
4f931fbe6a | ||
|
|
b49a4734ff | ||
|
|
2aaa2f3033 | ||
|
|
202b9f3075 | ||
|
|
be0814cdac | ||
|
|
80d719353c | ||
|
|
fa3fc12594 | ||
|
|
422a9db07b | ||
|
|
a4145a30f5 | ||
|
|
e004671346 | ||
|
|
38e5c161e7 | ||
|
|
a7c1f7ba69 | ||
|
|
e9755d437e | ||
|
|
e5db66351e | ||
|
|
89153eef23 | ||
|
|
b7a8677036 | ||
|
|
9ff2160c77 | ||
|
|
4c77b32171 | ||
|
|
34141ce9af | ||
|
|
58c867885c | ||
|
|
ccb6e25914 | ||
|
|
965d2d4036 | ||
|
|
9f8d7c9e41 | ||
|
|
8d352c1f82 | ||
|
|
faead09174 | ||
|
|
ae3ce1220b | ||
|
|
6e3673136a | ||
|
|
28caa1d47d | ||
|
|
ea6f0abf7c | ||
|
|
45cdfff128 | ||
|
|
8c38e41261 | ||
|
|
3197dfddd9 | ||
|
|
d900516302 | ||
|
|
fa68a25bea | ||
|
|
2cc2d34719 | ||
|
|
4640d043e3 | ||
|
|
bc957893f4 | ||
|
|
b8107ee497 | ||
|
|
6b3a988526 | ||
|
|
5cb39874a2 | ||
|
|
9fc671cc17 | ||
|
|
3fb3b45fdc | ||
|
|
0816adbaec | ||
|
|
1d69941882 | ||
|
|
8f600f334f | ||
|
|
cf55d12991 | ||
|
|
78919df886 | ||
|
|
5d600c6715 | ||
|
|
ea031a2161 | ||
|
|
4d4a04e9aa | ||
|
|
3ec797f56e | ||
|
|
74f11859e4 | ||
|
|
47f80cf3db |
30
CHANGES.md
30
CHANGES.md
@@ -1,6 +1,31 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.6.0 (Unreleased)
|
||||
## 2.6.2
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Increase the height of the right sidebar dropdowns [Taiga #10615](https://tree.taiga.io/project/penpot/issue/10615)
|
||||
- Fix scroll on token themes modal [Taiga #10745](https://tree.taiga.io/project/penpot/issue/10745)
|
||||
- Fix collapsing grouped sets in "edit Theme" closes the dialog [Taiga #10771](https://tree.taiga.io/project/penpot/issue/10771)
|
||||
- Fix unexpected exception on path editor on merge segments when undo stack is empty
|
||||
- Fix pricing CTA to be under a config flag [Taiga #10808](https://tree.taiga.io/project/penpot/issue/10808)
|
||||
- Fix allow moving a main component into another [Taiga #10818](https://tree.taiga.io/project/penpot/issue/10818)
|
||||
- Fix several issues with internal srepl helpers
|
||||
- Fix unexpected exception on template import from libraries
|
||||
- Fix incorrect uuid parsing from different parts of code
|
||||
- Fix update layout on component restore [Taiga #10637](https://tree.taiga.io/project/penpot/issue/10637)
|
||||
- Fix horizontal scroll in viewer [Github #6290](https://github.com/penpot/penpot/issues/6290)
|
||||
- Fix detach component in a particular case [Taiga #10837](https://tree.taiga.io/project/penpot/issue/10837)
|
||||
|
||||
## 2.6.1
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix webhooks not shown in list [Taiga #10763](https://tree.taiga.io/project/penpot/issue/10763)
|
||||
- Fix colorpicker scroll when dropdown displayed [Taiga #10696](https://tree.taiga.io/project/penpot/issue/10696)
|
||||
- Clean internal workspace state on exit or url changed [Taiga #10619](https://tree.taiga.io/project/penpot/issue/10619)
|
||||
|
||||
## 2.6.0
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
@@ -25,6 +50,7 @@
|
||||
- [DESIGN TOKENS] Import and export tokens from a JSON file.
|
||||
- [DESIGN TOKENS] Apply Themes and Sets at document level.
|
||||
- Add more descriptive tooltip to boards for first time users [Taiga #9426](https://tree.taiga.io/project/penpot/us/9426)
|
||||
- First State of a Project Changes Consolidation [Taia #10605](https://tree.taiga.io/project/penpot/us/10605)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
@@ -46,6 +72,8 @@
|
||||
- Fix incorrect handling of background task result (now task rows are properly marked as completed)
|
||||
- Fix available size of resize handler [Taiga #10639](https://tree.taiga.io/project/penpot/issue/10639)
|
||||
- Internal error when install a plugin by penpothub - Try plugin [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
|
||||
- Add character limitation to asset inputs [Taiga #10669](https://tree.taiga.io/project/penpot/issue/10669)
|
||||
- Fix Storybook link 'list of all available icons' wrong path [Taiga #10705](https://tree.taiga.io/project/penpot/issue/10705)
|
||||
|
||||
|
||||
## 2.5.4
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
|
||||
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
@@ -30,7 +30,8 @@ export PENPOT_FLAGS="\
|
||||
enable-access-tokens \
|
||||
enable-tiered-file-data-storage \
|
||||
enable-file-validation \
|
||||
enable-file-schema-validation";
|
||||
enable-file-schema-validation \
|
||||
enable-subscriptions-old";
|
||||
|
||||
# Default deletion delay for devenv
|
||||
export PENPOT_DELETION_DELAY="24h"
|
||||
|
||||
@@ -23,7 +23,8 @@ export PENPOT_FLAGS="\
|
||||
enable-access-tokens \
|
||||
enable-tiered-file-data-storage \
|
||||
enable-file-validation \
|
||||
enable-file-schema-validation";
|
||||
enable-file-schema-validation \
|
||||
enable-subscriptions-old";
|
||||
|
||||
export OPTIONS="
|
||||
-A:jmx-remote -A:dev \
|
||||
|
||||
44
backend/src/app/binfile/cleaner.clj
Normal file
44
backend/src/app/binfile/cleaner.clj
Normal file
@@ -0,0 +1,44 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.binfile.cleaner
|
||||
"A collection of helpers for perform cleaning of artifacts; mainly
|
||||
for recently imported shapes."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn- fix-shape-shadow-color
|
||||
"Some shapes can come with invalid `id` property on shadow colors
|
||||
caused by incorrect uuid parsing bug that should be already fixed;
|
||||
this function removes the invalid id from the data structure."
|
||||
[shape]
|
||||
(let [fix-color
|
||||
(fn [{:keys [id] :as color}]
|
||||
(if (uuid? id)
|
||||
color
|
||||
(if (and (string? id)
|
||||
(re-matches uuid/regex id))
|
||||
(assoc color :id (uuid/uuid id))
|
||||
(dissoc color :id))))
|
||||
|
||||
fix-shadow
|
||||
(fn [shadow]
|
||||
(d/update-when shadow :color fix-color))
|
||||
|
||||
xform
|
||||
(map fix-shadow)]
|
||||
|
||||
(d/update-when shape :shadow
|
||||
(fn [shadows]
|
||||
(into [] xform shadows)))))
|
||||
|
||||
(defn clean-shape-post-decode
|
||||
"A shape procesor that expected to be executed after schema decoding
|
||||
process but before validation."
|
||||
[shape]
|
||||
(-> shape
|
||||
(fix-shape-shadow-color)))
|
||||
@@ -8,12 +8,14 @@
|
||||
"A ZIP based binary file exportation"
|
||||
(:refer-clojure :exclude [read])
|
||||
(:require
|
||||
[app.binfile.cleaner :as bfl]
|
||||
[app.binfile.common :as bfc]
|
||||
[app.binfile.migrations :as bfm]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.files.migrations :as-alias fmg]
|
||||
[app.common.json :as json]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
@@ -594,16 +596,25 @@
|
||||
|
||||
(defn- read-file-components
|
||||
[{:keys [::bfc/input ::file-id ::entries]}]
|
||||
(->> (keep (match-component-entry-fn file-id) entries)
|
||||
(reduce (fn [result {:keys [id entry]}]
|
||||
(let [object (->> (read-entry input entry)
|
||||
(decode-component)
|
||||
(validate-component))]
|
||||
(if (= id (:id object))
|
||||
(assoc result id object)
|
||||
result)))
|
||||
{})
|
||||
(not-empty)))
|
||||
(let [clean-component-post-decode
|
||||
(fn [component]
|
||||
(d/update-when component :objects
|
||||
(fn [objects]
|
||||
(reduce-kv (fn [objects id shape]
|
||||
(assoc objects id (bfl/clean-shape-post-decode shape)))
|
||||
objects
|
||||
objects))))]
|
||||
(->> (keep (match-component-entry-fn file-id) entries)
|
||||
(reduce (fn [result {:keys [id entry]}]
|
||||
(let [object (->> (read-entry input entry)
|
||||
(decode-component)
|
||||
(clean-component-post-decode)
|
||||
(validate-component))]
|
||||
(if (= id (:id object))
|
||||
(assoc result id object)
|
||||
result)))
|
||||
{})
|
||||
(not-empty))))
|
||||
|
||||
(defn- read-file-typographies
|
||||
[{:keys [::bfc/input ::file-id ::entries]}]
|
||||
@@ -631,7 +642,9 @@
|
||||
(reduce (fn [result {:keys [id entry]}]
|
||||
(let [object (->> (read-entry input entry)
|
||||
(decode-shape)
|
||||
(bfl/clean-shape-post-decode)
|
||||
(validate-shape))]
|
||||
|
||||
(if (= id (:id object))
|
||||
(assoc result id object)
|
||||
result)))
|
||||
@@ -733,7 +746,14 @@
|
||||
(assoc :name file-name)
|
||||
(assoc :project-id project-id)
|
||||
(dissoc :options)
|
||||
(bfc/process-file))]
|
||||
(bfc/process-file)
|
||||
|
||||
;; NOTE: this is necessary because when we just
|
||||
;; creating a new file from imported artifact,
|
||||
;; there are no migrations registered on the
|
||||
;; database, so we need to persist all of them, not
|
||||
;; only the applied
|
||||
(vary-meta dissoc ::fmg/migrated))]
|
||||
|
||||
|
||||
(bfm/register-pending-migrations! cfg file)
|
||||
|
||||
@@ -155,10 +155,10 @@
|
||||
[["" {:middleware [[mw/server-timing]
|
||||
[mw/params]
|
||||
[mw/format-response]
|
||||
[mw/parse-request]
|
||||
[mw/errors errors/handle]
|
||||
[session/soft-auth cfg]
|
||||
[actoken/soft-auth cfg]
|
||||
[mw/parse-request]
|
||||
[mw/errors errors/handle]
|
||||
[mw/restrict-methods]]}
|
||||
|
||||
(::mtx/routes cfg)
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
(let [claims (-> {}
|
||||
(into (::session/token-claims request))
|
||||
(into (::actoken/token-claims request)))]
|
||||
|
||||
{:request/path (:path request)
|
||||
:request/method (:method request)
|
||||
:request/params (:params request)
|
||||
@@ -62,7 +61,8 @@
|
||||
::yres/body data}
|
||||
|
||||
(binding [l/*context* (request->context request)]
|
||||
(l/err :hint "restriction error" :data data)
|
||||
(l/err :hint "restriction error"
|
||||
:cause err)
|
||||
{::yres/status 400
|
||||
::yres/body data}))))
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
(= code :invalid-image)
|
||||
(binding [l/*context* (request->context request)]
|
||||
(let [cause (or parent-cause err)]
|
||||
(l/warn :hint "unexpected error on processing image" :cause cause)
|
||||
(l/warn :hint "image process error" :cause cause)
|
||||
{::yres/status 400 ::yres/body data}))
|
||||
|
||||
:else
|
||||
@@ -177,7 +177,7 @@
|
||||
(let [state (.getSQLState ^java.sql.SQLException error)
|
||||
cause (or parent-cause error)]
|
||||
(binding [l/*context* (request->context request)]
|
||||
(l/error :hint "PSQL error"
|
||||
(l/error :hint "postgresql error"
|
||||
:cause cause)
|
||||
(cond
|
||||
(= state "57014")
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
|
||||
(defn- http-handler
|
||||
[cfg {:keys [params ::session/profile-id] :as request}]
|
||||
(let [session-id (some-> params :session-id sm/parse-uuid)]
|
||||
(let [session-id (some-> params :session-id uuid/parse*)]
|
||||
(when-not (uuid? session-id)
|
||||
(ex/raise :type :validation
|
||||
:code :missing-session-id
|
||||
|
||||
@@ -53,11 +53,16 @@
|
||||
(assoc :logger/name logger)
|
||||
(assoc :logger/level level)
|
||||
(dissoc :request/params :value :params :data))]
|
||||
|
||||
(merge
|
||||
{:context (-> (into (sorted-map) ctx)
|
||||
(pp/pprint-str :length 50))
|
||||
:props (pp/pprint-str props :length 50)
|
||||
:hint (or (ex-message cause) @message)
|
||||
:hint (or (when-let [message (ex-message cause)]
|
||||
(if-let [props-hint (:hint props)]
|
||||
(str props-hint ": " message)
|
||||
message))
|
||||
@message)
|
||||
:trace (or (::trace record)
|
||||
(some-> cause (ex/format-throwable :data? false :explain? false :header? false :summary? false)))}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
(contains? cf/flags :login-with-password))
|
||||
(ex/raise :type :restriction
|
||||
:code :login-disabled
|
||||
:hint "login is disabled in this instance"))
|
||||
:hint "login is disabled"))
|
||||
|
||||
(letfn [(check-password [cfg profile password]
|
||||
(if (= (:password profile) "!")
|
||||
@@ -79,7 +79,8 @@
|
||||
:code :wrong-credentials))
|
||||
(when (:is-blocked profile)
|
||||
(ex/raise :type :restriction
|
||||
:code :profile-blocked))
|
||||
:code :profile-blocked
|
||||
:hint "profile is marked as blocked"))
|
||||
(when-not (check-password cfg profile password)
|
||||
(ex/raise :type :validation
|
||||
:code :wrong-credentials))
|
||||
@@ -183,11 +184,11 @@
|
||||
(defn- validate-register-attempt!
|
||||
[cfg params]
|
||||
|
||||
(when (or
|
||||
(not (contains? cf/flags :registration))
|
||||
(not (contains? cf/flags :login-with-password)))
|
||||
(when (or (not (contains? cf/flags :registration))
|
||||
(not (contains? cf/flags :login-with-password)))
|
||||
(ex/raise :type :restriction
|
||||
:code :registration-disabled))
|
||||
:code :registration-disabled
|
||||
:hint "registration disabled"))
|
||||
|
||||
(when (contains? params :invitation-token)
|
||||
(let [invitation (tokens/verify (::setup/props cfg)
|
||||
@@ -201,12 +202,14 @@
|
||||
(when (and (email.blacklist/enabled? cfg)
|
||||
(email.blacklist/contains? cfg (:email params)))
|
||||
(ex/raise :type :restriction
|
||||
:code :email-domain-is-not-allowed))
|
||||
:code :email-domain-is-not-allowed
|
||||
:hint "email domain in blacklist"))
|
||||
|
||||
(when (and (email.whitelist/enabled? cfg)
|
||||
(not (email.whitelist/contains? cfg (:email params))))
|
||||
(ex/raise :type :restriction
|
||||
:code :email-domain-is-not-allowed))
|
||||
:code :email-domain-is-not-allowed
|
||||
:hint "email domain not in whitelist"))
|
||||
|
||||
;; Perform a basic validation of email & password
|
||||
(when (= (str/lower (:email params))
|
||||
@@ -219,13 +222,13 @@
|
||||
(ex/raise :type :restriction
|
||||
:code :email-has-permanent-bounces
|
||||
:email (:email params)
|
||||
:hint "looks like the email has bounce reports"))
|
||||
:hint "email has bounce reports"))
|
||||
|
||||
(when (eml/has-complaint-reports? cfg (:email params))
|
||||
(ex/raise :type :restriction
|
||||
:code :email-has-complaints
|
||||
:email (:email params)
|
||||
:hint "looks like the email has complaint reports")))
|
||||
:hint "email has complaint reports")))
|
||||
|
||||
(defn prepare-register
|
||||
[{:keys [::db/pool] :as cfg} {:keys [email] :as params}]
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
(def ^:private
|
||||
schema:export-binfile
|
||||
[:map {:title "export-binfile"}
|
||||
[:name [:string {:max 250}]]
|
||||
[:file-id ::sm/uuid]
|
||||
[:version {:optional true} ::sm/int]
|
||||
[:include-libraries ::sm/boolean]
|
||||
@@ -78,7 +77,7 @@
|
||||
"Export a penpot file in a binary format."
|
||||
{::doc/added "1.15"
|
||||
::webhooks/event? true
|
||||
::sm/result schema:export-binfile}
|
||||
::sm/params schema:export-binfile}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id version file-id] :as params}]
|
||||
(files/check-read-permissions! pool profile-id file-id)
|
||||
(fn [_]
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
|
||||
(defn get-file-etag
|
||||
[{:keys [::rpc/profile-id]} {:keys [modified-at revn vern permissions]}]
|
||||
(str profile-id "/" revn "/" vern "/"
|
||||
(str profile-id "/" revn "/" vern "/" (hash fmg/available-migrations) "/"
|
||||
(dt/format-instant modified-at :iso)
|
||||
"/"
|
||||
(uri/map->query-string permissions)))
|
||||
@@ -328,7 +328,7 @@
|
||||
|
||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file) (:features params)))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
;; This operation is needed for backward comapatibility with frontends that
|
||||
;; does not support pointer-map resolution mechanism; this just resolves the
|
||||
@@ -490,7 +490,7 @@
|
||||
|
||||
_ (-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file) (:features params)))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
|
||||
(let [page-id (or page-id (-> file :data :pages first))
|
||||
@@ -737,7 +737,7 @@
|
||||
|
||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file) (:features params)))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
|
||||
{:name (:name file)
|
||||
|
||||
@@ -91,9 +91,6 @@
|
||||
:project-id project-id)
|
||||
team-id (:id team)
|
||||
|
||||
;; When we create files, we only need to respect the team
|
||||
;; features, because some features can be enabled
|
||||
;; globally, but the team is still not migrated properly.
|
||||
features (-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params)))
|
||||
|
||||
@@ -107,7 +104,7 @@
|
||||
|
||||
params (-> params
|
||||
(assoc :profile-id profile-id)
|
||||
(assoc :features (set/difference features cfeat/frontend-only-features)))]
|
||||
(assoc :features features))]
|
||||
|
||||
(quotes/check! cfg {::quotes/id ::quotes/files-per-project
|
||||
::quotes/team-id team-id
|
||||
@@ -120,7 +117,7 @@
|
||||
;; to lost team features updating
|
||||
|
||||
;; When newly computed features does not match exactly with
|
||||
;; the features defined on team row, we update it.
|
||||
;; the features defined on team row, we update it
|
||||
(when (not= features (:features team))
|
||||
(let [features (db/create-array conn "text" features)]
|
||||
(db/update! conn :team
|
||||
|
||||
@@ -180,8 +180,7 @@
|
||||
(def ^:private
|
||||
schema:get-file-data-for-thumbnail
|
||||
[:map {:title "get-file-data-for-thumbnail"}
|
||||
[:file-id ::sm/uuid]
|
||||
[:features {:optional true} ::cfeat/features]])
|
||||
[:file-id ::sm/uuid]])
|
||||
|
||||
(def ^:private
|
||||
schema:partial-file
|
||||
@@ -211,8 +210,7 @@
|
||||
(fmg/migrate-file)))]
|
||||
|
||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file) (:features params)))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
{:file-id file-id
|
||||
:revn (:revn file)
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
features (-> (cfeat/get-team-enabled-features cf/flags team)
|
||||
(cfeat/check-client-features! (:features params))
|
||||
(cfeat/check-file-features! (:features file) (:features params)))
|
||||
(cfeat/check-file-features! (:features file)))
|
||||
|
||||
changes (if changes-with-metadata
|
||||
(->> changes-with-metadata (mapcat :changes) vec)
|
||||
|
||||
@@ -209,100 +209,116 @@
|
||||
This method allows send flash notifications to specified target destinations.
|
||||
The message can be a free text or a preconfigured one.
|
||||
|
||||
The destination can be: all, profile-id, team-id, or a coll of them."
|
||||
[{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level]
|
||||
:or {code :generic level :info}
|
||||
:as params}]
|
||||
The destination can be: all, profile-id, team-id, or a coll of them.
|
||||
It also can be:
|
||||
|
||||
{:email \"some@example.com\"}
|
||||
[[:email \"some@example.com\"], ...]
|
||||
|
||||
Command examples:
|
||||
|
||||
(notify! :dest :all :code :maintenance)
|
||||
(notify! :dest :all :code :upgrade-version)
|
||||
"
|
||||
[& {:keys [dest code message level]
|
||||
:or {code :generic level :info}
|
||||
:as params}]
|
||||
|
||||
(when-not (contains? #{:success :error :info :warning} level)
|
||||
(ex/raise :type :assertion
|
||||
:code :incorrect-level
|
||||
:hint (str "level '" level "' not supported")))
|
||||
|
||||
(letfn [(send [dest]
|
||||
(l/inf :hint "sending notification" :dest (str dest))
|
||||
(let [message {:type :notification
|
||||
:code code
|
||||
:level level
|
||||
:version (:full cf/version)
|
||||
:subs-id dest
|
||||
:message message}
|
||||
message (->> (dissoc params :dest :code :message :level)
|
||||
(merge message))]
|
||||
(mbus/pub! msgbus
|
||||
:topic (str dest)
|
||||
:message message)))
|
||||
(let [{:keys [::mbus/msgbus ::db/pool]} main/system
|
||||
|
||||
(resolve-profile [email]
|
||||
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
|
||||
send
|
||||
(fn [dest]
|
||||
(l/inf :hint "sending notification" :dest (str dest))
|
||||
(let [message {:type :notification
|
||||
:code code
|
||||
:level level
|
||||
:version (:full cf/version)
|
||||
:subs-id dest
|
||||
:message message}
|
||||
message (->> (dissoc params :dest :code :message :level)
|
||||
(merge message))]
|
||||
(mbus/pub! msgbus
|
||||
:topic dest
|
||||
:message message)))
|
||||
|
||||
(resolve-team [team-id]
|
||||
(->> (db/query pool :team-profile-rel
|
||||
{:team-id team-id}
|
||||
{:columns [:profile-id]})
|
||||
(map :profile-id)))
|
||||
resolve-profile
|
||||
(fn [email]
|
||||
(some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector))
|
||||
|
||||
(resolve-dest [dest]
|
||||
(cond
|
||||
(= :all dest)
|
||||
[uuid/zero]
|
||||
resolve-team
|
||||
(fn [team-id]
|
||||
(->> (db/query pool :team-profile-rel
|
||||
{:team-id team-id}
|
||||
{:columns [:profile-id]})
|
||||
(map :profile-id)))
|
||||
|
||||
(uuid? dest)
|
||||
[dest]
|
||||
resolve-dest
|
||||
(fn resolve-dest [dest]
|
||||
(cond
|
||||
(= :all dest)
|
||||
[uuid/zero]
|
||||
|
||||
(string? dest)
|
||||
(some-> dest h/parse-uuid resolve-dest)
|
||||
(uuid? dest)
|
||||
[dest]
|
||||
|
||||
(nil? dest)
|
||||
(resolve-dest uuid/zero)
|
||||
(string? dest)
|
||||
(some-> dest h/parse-uuid resolve-dest)
|
||||
|
||||
(map? dest)
|
||||
(sequence (comp
|
||||
(map vec)
|
||||
(mapcat resolve-dest))
|
||||
dest)
|
||||
(nil? dest)
|
||||
[uuid/zero]
|
||||
|
||||
(and (vector? dest)
|
||||
(every? vector? dest))
|
||||
(sequence (comp
|
||||
(map vec)
|
||||
(mapcat resolve-dest))
|
||||
dest)
|
||||
(map? dest)
|
||||
(sequence (comp
|
||||
(map vec)
|
||||
(mapcat resolve-dest))
|
||||
dest)
|
||||
|
||||
(and (vector? dest)
|
||||
(keyword? (first dest)))
|
||||
(let [[op param] dest]
|
||||
(and (vector? dest)
|
||||
(every? vector? dest))
|
||||
(sequence (comp
|
||||
(map vec)
|
||||
(mapcat resolve-dest))
|
||||
dest)
|
||||
|
||||
(and (vector? dest)
|
||||
(keyword? (first dest)))
|
||||
(let [[op param] dest]
|
||||
(cond
|
||||
(= op :email)
|
||||
(cond
|
||||
(= op :email)
|
||||
(cond
|
||||
(and (coll? param)
|
||||
(every? string? param))
|
||||
(sequence (comp
|
||||
(keep resolve-profile)
|
||||
(mapcat identity))
|
||||
param)
|
||||
(and (coll? param)
|
||||
(every? string? param))
|
||||
(sequence (comp
|
||||
(keep resolve-profile)
|
||||
(mapcat identity))
|
||||
param)
|
||||
|
||||
(string? param)
|
||||
(resolve-profile param))
|
||||
(string? param)
|
||||
(resolve-profile param))
|
||||
|
||||
(= op :team-id)
|
||||
(cond
|
||||
(coll? param)
|
||||
(sequence (comp
|
||||
(mapcat resolve-team)
|
||||
(keep h/parse-uuid))
|
||||
param)
|
||||
(= op :team-id)
|
||||
(cond
|
||||
(coll? param)
|
||||
(sequence (comp
|
||||
(mapcat resolve-team)
|
||||
(keep h/parse-uuid))
|
||||
param)
|
||||
|
||||
(uuid? param)
|
||||
(resolve-team param)
|
||||
(uuid? param)
|
||||
(resolve-team param)
|
||||
|
||||
(string? param)
|
||||
(some-> param h/parse-uuid resolve-team))
|
||||
(string? param)
|
||||
(some-> param h/parse-uuid resolve-team))
|
||||
|
||||
(= op :profile-id)
|
||||
(if (coll? param)
|
||||
(sequence (keep h/parse-uuid) param)
|
||||
(resolve-dest param))))))]
|
||||
(= op :profile-id)
|
||||
(if (coll? param)
|
||||
(sequence (keep h/parse-uuid) param)
|
||||
(resolve-dest param))))))]
|
||||
|
||||
(->> (resolve-dest dest)
|
||||
(filter some?)
|
||||
@@ -321,14 +337,23 @@
|
||||
(db/tx-run! main/system fsnap/create-file-snapshot! {:file-id file-id :label label})))
|
||||
|
||||
(defn restore-file-snapshot!
|
||||
[file-id label]
|
||||
(let [file-id (h/parse-uuid file-id)]
|
||||
[file-id & {:keys [label id]}]
|
||||
(let [file-id (h/parse-uuid file-id)
|
||||
snapshot-id (some-> id h/parse-uuid)]
|
||||
(db/tx-run! main/system
|
||||
(fn [{:keys [::db/conn] :as system}]
|
||||
(when-let [snapshot (->> (h/search-file-snapshots conn #{file-id} label)
|
||||
(map :id)
|
||||
(first))]
|
||||
(fsnap/restore-file-snapshot! system file-id (:id snapshot)))))))
|
||||
(cond
|
||||
(uuid? snapshot-id)
|
||||
(fsnap/restore-file-snapshot! system file-id snapshot-id)
|
||||
|
||||
(string? label)
|
||||
(->> (h/search-file-snapshots conn #{file-id} label)
|
||||
(map :id)
|
||||
(first)
|
||||
(fsnap/restore-file-snapshot! system file-id))
|
||||
|
||||
:else
|
||||
(throw (ex-info "snapshot id or label should be provided" {})))))))
|
||||
|
||||
(defn list-file-snapshots!
|
||||
[file-id & {:as _}]
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
(t/is (sto/object? mobj1))
|
||||
(t/is (sto/object? mobj2))
|
||||
(t/is (= 122785 (:size mobj1)))
|
||||
(t/is (= 3302 (:size mobj2)))))))
|
||||
(t/is (= 3299 (:size mobj2)))))))
|
||||
|
||||
(t/deftest media-object-upload
|
||||
(let [prof (th/create-profile* 1)
|
||||
@@ -85,7 +85,7 @@
|
||||
(t/is (sto/object? mobj1))
|
||||
(t/is (sto/object? mobj2))
|
||||
(t/is (= 312043 (:size mobj1)))
|
||||
(t/is (= 3887 (:size mobj2)))))))
|
||||
(t/is (= 3901 (:size mobj2)))))))
|
||||
|
||||
|
||||
(t/deftest media-object-upload-idempotency
|
||||
@@ -163,7 +163,7 @@
|
||||
(t/is (sto/object? mobj1))
|
||||
(t/is (sto/object? mobj2))
|
||||
(t/is (= 122785 (:size mobj1)))
|
||||
(t/is (= 3302 (:size mobj2)))))))
|
||||
(t/is (= 3299 (:size mobj2)))))))
|
||||
|
||||
(t/deftest media-object-upload-command
|
||||
(let [prof (th/create-profile* 1)
|
||||
@@ -200,7 +200,7 @@
|
||||
(t/is (sto/object? mobj1))
|
||||
(t/is (sto/object? mobj2))
|
||||
(t/is (= 312043 (:size mobj1)))
|
||||
(t/is (= 3887 (:size mobj2)))))))
|
||||
(t/is (= 3901 (:size mobj2)))))))
|
||||
|
||||
|
||||
(t/deftest media-object-upload-idempotency-command
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
|
||||
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
(defn undo
|
||||
[stack]
|
||||
(update stack :index dec))
|
||||
(update stack :index #(max 0 (dec %))))
|
||||
|
||||
(defn redo
|
||||
[{index :index items :items :as stack}]
|
||||
|
||||
@@ -85,12 +85,11 @@
|
||||
;; be applied (per example backend can operate in both modes with or
|
||||
;; without migration applied)
|
||||
(def no-migration-features
|
||||
(-> #{"fdata/objects-map"
|
||||
"fdata/pointer-map"
|
||||
"layout/grid"
|
||||
(-> #{"layout/grid"
|
||||
"fdata/shape-data-type"
|
||||
"design-tokens/v1"}
|
||||
(into frontend-only-features)))
|
||||
(into frontend-only-features)
|
||||
(into backend-only-features)))
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::features}
|
||||
@@ -158,7 +157,6 @@
|
||||
team-features (into #{} xf-remove-ephimeral (:features team))]
|
||||
(-> enabled-features
|
||||
(set/intersection no-migration-features)
|
||||
(set/difference frontend-only-features)
|
||||
(set/union team-features))))
|
||||
|
||||
(defn check-client-features!
|
||||
@@ -167,6 +165,8 @@
|
||||
frontend client"
|
||||
[enabled-features client-features]
|
||||
(when (set? client-features)
|
||||
;; Check if client declares support for features enabled on
|
||||
;; backend side
|
||||
(let [not-supported (-> enabled-features
|
||||
(set/difference client-features)
|
||||
(set/difference frontend-only-features)
|
||||
@@ -176,14 +176,6 @@
|
||||
:code :feature-not-supported
|
||||
:feature (first not-supported)
|
||||
:hint (str/ffmt "client declares no support for '%' features"
|
||||
(str/join "," not-supported)))))
|
||||
|
||||
(let [not-supported (set/difference client-features supported-features)]
|
||||
(when (seq not-supported)
|
||||
(ex/raise :type :restriction
|
||||
:code :feature-not-supported
|
||||
:feature (first not-supported)
|
||||
:hint (str/ffmt "backend does not support '%' features requested by client"
|
||||
(str/join "," not-supported))))))
|
||||
|
||||
enabled-features)
|
||||
@@ -194,57 +186,49 @@
|
||||
supported by the current backend"
|
||||
[enabled-features]
|
||||
(let [not-supported (set/difference enabled-features supported-features)]
|
||||
(when (seq not-supported)
|
||||
(when-let [not-supported (first not-supported)]
|
||||
(ex/raise :type :restriction
|
||||
:code :feature-not-supported
|
||||
:feature (first not-supported)
|
||||
:hint (str/ffmt "features '%' not supported"
|
||||
(str/join "," not-supported)))))
|
||||
enabled-features)
|
||||
:feature not-supported
|
||||
:hint (str/ffmt "feature '%' not supported on this backend" not-supported)))
|
||||
enabled-features))
|
||||
|
||||
(defn check-file-features!
|
||||
"Function used for check feature compability between currently
|
||||
enabled features set on backend with the provided featured set by
|
||||
the penpot file"
|
||||
([enabled-features file-features]
|
||||
(check-file-features! enabled-features file-features #{}))
|
||||
([enabled-features file-features client-features]
|
||||
(let [file-features (into #{} xf-remove-ephimeral file-features)
|
||||
;; We should ignore all features that does not match with the
|
||||
;; `no-migration-features` set because we can't enable them
|
||||
;; as-is, because they probably need migrations
|
||||
client-features (set/intersection client-features no-migration-features)]
|
||||
(let [not-supported (-> enabled-features
|
||||
(set/union client-features)
|
||||
(set/difference file-features)
|
||||
;; NOTE: we don't want to raise a feature-mismatch
|
||||
;; exception for features which don't require an
|
||||
;; explicit file migration process or has no real
|
||||
;; effect on file data structure
|
||||
(set/difference no-migration-features))]
|
||||
(when (seq not-supported)
|
||||
(ex/raise :type :restriction
|
||||
:code :file-feature-mismatch
|
||||
:feature (first not-supported)
|
||||
:hint (str/ffmt "enabled features '%' not present in file (missing migration)"
|
||||
(str/join "," not-supported)))))
|
||||
[enabled-features file-features]
|
||||
(let [file-features (into #{} xf-remove-ephimeral file-features)
|
||||
not-supported (-> enabled-features
|
||||
(set/difference file-features)
|
||||
;; NOTE: we don't want to raise a feature-mismatch
|
||||
;; exception for features which don't require an
|
||||
;; explicit file migration process or has no real
|
||||
;; effect on file data structure
|
||||
(set/difference no-migration-features))]
|
||||
|
||||
(check-supported-features! file-features)
|
||||
(when-let [not-supported (first not-supported)]
|
||||
(ex/raise :type :restriction
|
||||
:code :file-feature-mismatch
|
||||
:feature not-supported
|
||||
:hint (str/ffmt "enabled feature '%' not present in file (missing migration)"
|
||||
not-supported)))
|
||||
|
||||
(let [not-supported (-> file-features
|
||||
(set/difference enabled-features)
|
||||
(set/difference client-features)
|
||||
(set/difference backend-only-features)
|
||||
(set/difference frontend-only-features))]
|
||||
(check-supported-features! file-features)
|
||||
|
||||
(when (seq not-supported)
|
||||
(ex/raise :type :restriction
|
||||
:code :file-feature-mismatch
|
||||
:feature (first not-supported)
|
||||
:hint (str/ffmt "file features '%' not enabled"
|
||||
(str/join "," not-supported))))))
|
||||
(let [not-supported (-> file-features
|
||||
(set/difference enabled-features)
|
||||
(set/difference backend-only-features)
|
||||
(set/difference frontend-only-features))]
|
||||
|
||||
enabled-features))
|
||||
;; Check if file has a feature but that feature is not enabled
|
||||
(when-let [not-supported (first not-supported)]
|
||||
(ex/raise :type :restriction
|
||||
:code :file-feature-mismatch
|
||||
:feature not-supported
|
||||
:hint (str/ffmt "file feature '%' not enabled" not-supported))))
|
||||
|
||||
enabled-features))
|
||||
|
||||
(defn check-teams-compatibility!
|
||||
[{source-features :features} {destination-features :features}]
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]
|
||||
@@ -35,9 +36,7 @@
|
||||
|
||||
#?(:cljs (l/set-level! :info))
|
||||
|
||||
(declare ^:private available-migrations)
|
||||
(declare ^:private migration-up-index)
|
||||
(declare ^:private migration-down-index)
|
||||
(declare available-migrations)
|
||||
|
||||
(def version cfd/version)
|
||||
|
||||
@@ -49,7 +48,10 @@
|
||||
[file]
|
||||
(or (nil? (:version file))
|
||||
(not= cfd/version (:version file))
|
||||
(not= available-migrations (:migrations file))))
|
||||
(boolean
|
||||
(->> (:migrations file #{})
|
||||
(set/difference available-migrations)
|
||||
(not-empty)))))
|
||||
|
||||
(def xf:map-name
|
||||
(map :name))
|
||||
@@ -119,9 +121,9 @@
|
||||
(into [] shapes)
|
||||
shapes))))
|
||||
(update-page [page]
|
||||
(update page :objects update-vals update-object))]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate-data "legacy-3"
|
||||
[data _]
|
||||
@@ -172,9 +174,9 @@
|
||||
(fix-empty-points)))
|
||||
|
||||
(update-page [page]
|
||||
(update page :objects update-vals update-object))]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Put the id of the local file in :component-file in instances of
|
||||
;; local components
|
||||
@@ -187,9 +189,9 @@
|
||||
object))
|
||||
|
||||
(update-page [page]
|
||||
(update page :objects update-vals update-object))]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Fixes issues with selrect/points for shapes with width/height =
|
||||
;; 0 (line-like paths)
|
||||
@@ -212,11 +214,11 @@
|
||||
shape))
|
||||
|
||||
(update-container [container]
|
||||
(update container :objects update-vals fix-line-paths))]
|
||||
(update container :objects d/update-vals fix-line-paths))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
;; Remove interactions pointing to deleted frames
|
||||
(defmethod migrate-data "legacy-7"
|
||||
@@ -227,9 +229,9 @@
|
||||
(filterv #(get-in page [:objects (:destination %)]) interactions))))
|
||||
|
||||
(update-page [page]
|
||||
(update page :objects update-vals (partial update-object page)))]
|
||||
(update page :objects d/update-vals (partial update-object page)))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Remove groups without any shape, both in pages and components
|
||||
(defmethod migrate-data "legacy-8"
|
||||
@@ -269,8 +271,8 @@
|
||||
(assoc container :objects objects)))))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals clean-container)
|
||||
(update :components update-vals clean-container))))
|
||||
(update :pages-index d/update-vals clean-container)
|
||||
(d/update-when :components d/update-vals clean-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-9"
|
||||
[data _]
|
||||
@@ -304,7 +306,7 @@
|
||||
[data _]
|
||||
(letfn [(update-page [page]
|
||||
(d/update-in-when page [:objects uuid/zero] dissoc :points :selrect))]
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate-data "legacy-11"
|
||||
[data _]
|
||||
@@ -318,7 +320,7 @@
|
||||
(update page :objects (fn [objects]
|
||||
(update-vals objects (partial update-object objects)))))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate-data "legacy-12"
|
||||
[data _]
|
||||
@@ -328,9 +330,9 @@
|
||||
(assoc :size nil)))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-in-when page [:options :saved-grids] update-vals update-grid))]
|
||||
(d/update-in-when page [:options :saved-grids] d/update-vals update-grid))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; Add rx and ry to images
|
||||
(defmethod migrate-data "legacy-13"
|
||||
@@ -348,9 +350,9 @@
|
||||
(fix-radius)))
|
||||
|
||||
(update-page [page]
|
||||
(update page :objects update-vals update-object))]
|
||||
(update page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate-data "legacy-14"
|
||||
[data _]
|
||||
@@ -380,8 +382,8 @@
|
||||
container))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-16"
|
||||
[data _]
|
||||
@@ -423,11 +425,11 @@
|
||||
(assign-fills)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-17"
|
||||
[data _]
|
||||
@@ -452,11 +454,11 @@
|
||||
(assoc :fills [])))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
;; Remove position-data to solve a bug with the text positioning
|
||||
(defmethod migrate-data "legacy-18"
|
||||
@@ -467,11 +469,11 @@
|
||||
(dissoc :position-data)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-19"
|
||||
[data _]
|
||||
@@ -483,11 +485,11 @@
|
||||
(dissoc :position-data)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-25"
|
||||
[data _]
|
||||
@@ -499,10 +501,10 @@
|
||||
(update :selrect grc/make-rect)
|
||||
(cts/create-shape))))
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-26"
|
||||
[data _]
|
||||
@@ -515,11 +517,11 @@
|
||||
(assoc :transform-inverse (gmt/matrix))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-27"
|
||||
[data _]
|
||||
@@ -546,11 +548,11 @@
|
||||
(dissoc :saved-component-root?))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-28"
|
||||
[data _]
|
||||
@@ -575,8 +577,8 @@
|
||||
(d/update-when container :objects #(update-vals % (partial update-object %))))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-29"
|
||||
[data _]
|
||||
@@ -607,11 +609,11 @@
|
||||
(update :content #(txt/transform-nodes invalid-node? fix-node %)))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-31"
|
||||
[data _]
|
||||
@@ -622,10 +624,10 @@
|
||||
(dissoc :use-for-thumbnail?))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-32"
|
||||
[data _]
|
||||
@@ -640,11 +642,11 @@
|
||||
object)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-33"
|
||||
[data _]
|
||||
@@ -662,9 +664,9 @@
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-34"
|
||||
[data _]
|
||||
@@ -674,10 +676,10 @@
|
||||
(dissoc object :x :y :width :height)
|
||||
object))
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-36"
|
||||
[data _]
|
||||
@@ -687,8 +689,8 @@
|
||||
(dissoc objects nil)
|
||||
objects))))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-37"
|
||||
[data _]
|
||||
@@ -716,11 +718,11 @@
|
||||
shape)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-39"
|
||||
[data _]
|
||||
@@ -738,11 +740,11 @@
|
||||
shape))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-40"
|
||||
[data _]
|
||||
@@ -762,11 +764,11 @@
|
||||
shape))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-41"
|
||||
[data _]
|
||||
@@ -795,11 +797,11 @@
|
||||
shape))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-42"
|
||||
[data _]
|
||||
@@ -812,11 +814,11 @@
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(def ^:private valid-fill?
|
||||
(sm/lazy-validator ::cts/fill))
|
||||
@@ -841,14 +843,11 @@
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
|
||||
(def ^:private valid-shadow?
|
||||
(sm/lazy-validator ::ctss/shadow))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-44"
|
||||
[data _]
|
||||
@@ -861,14 +860,14 @@
|
||||
|
||||
(update-object [object]
|
||||
(let [xform (comp (map fix-shadow)
|
||||
(filter valid-shadow?))]
|
||||
(filter ctss/valid-shadow?))]
|
||||
(d/update-when object :shadow #(into [] xform %))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-45"
|
||||
[data _]
|
||||
@@ -881,9 +880,9 @@
|
||||
:parent-id parent-id)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals fix-shape))]
|
||||
(d/update-when container :objects d/update-vals fix-shape))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-46"
|
||||
[data _]
|
||||
@@ -891,10 +890,10 @@
|
||||
(dissoc object :thumbnail))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-47"
|
||||
[data _]
|
||||
@@ -915,9 +914,9 @@
|
||||
shape)))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals (partial fix-shape page)))]
|
||||
(d/update-when page :objects d/update-vals (partial fix-shape page)))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-page))))
|
||||
(update :pages-index d/update-vals update-page))))
|
||||
|
||||
(defmethod migrate-data "legacy-48"
|
||||
[data _]
|
||||
@@ -929,9 +928,9 @@
|
||||
shape)))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals fix-shape))]
|
||||
(d/update-when page :objects d/update-vals fix-shape))]
|
||||
(-> data
|
||||
(update :pages-index update-vals update-page))))
|
||||
(update :pages-index d/update-vals update-page))))
|
||||
|
||||
;; Remove hide-in-viewer for shapes that are origin or destination of an interaction
|
||||
(defmethod migrate-data "legacy-49"
|
||||
@@ -949,9 +948,9 @@
|
||||
(mapcat :interactions)
|
||||
(map :destination)
|
||||
(set))]
|
||||
(update page :objects update-vals (partial update-object destinations))))]
|
||||
(update page :objects d/update-vals (partial update-object destinations))))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
;; This migration mainly fixes paths with curve-to segments
|
||||
;; without :c1x :c1y :c2x :c2y properties. Additionally, we found a
|
||||
@@ -994,11 +993,11 @@
|
||||
|
||||
update-container
|
||||
(fn [page]
|
||||
(d/update-when page :objects update-vals update-shape))]
|
||||
(d/update-when page :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(def ^:private valid-color?
|
||||
(sm/lazy-validator ::ctc/color))
|
||||
@@ -1018,9 +1017,9 @@
|
||||
shape))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals update-shape))]
|
||||
(d/update-when page :objects d/update-vals update-shape))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
|
||||
(defmethod migrate-data "legacy-53"
|
||||
@@ -1036,15 +1035,15 @@
|
||||
|
||||
(update-shape [shape]
|
||||
(let [xform (comp (map fix-shadow)
|
||||
(filter valid-shadow?))]
|
||||
(filter ctss/valid-shadow?))]
|
||||
(d/update-when shape :shadow #(into [] xform %))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
;; This migration moves page options to the page level
|
||||
(defmethod migrate-data "legacy-55"
|
||||
@@ -1096,11 +1095,11 @@
|
||||
(update :content (partial txt/transform-nodes identity fix-fills)))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
|
||||
(defmethod migrate-data "legacy-57"
|
||||
@@ -1127,7 +1126,7 @@
|
||||
(-> data
|
||||
(update :pages (fn [pages] (into [] (remove nil?) pages)))
|
||||
(update :pages-index dissoc nil)
|
||||
(update :pages-index update-vals update-page))))
|
||||
(update :pages-index d/update-vals update-page))))
|
||||
|
||||
(defmethod migrate-data "legacy-59"
|
||||
[data _]
|
||||
@@ -1138,11 +1137,11 @@
|
||||
(d/update-when shape :touched #(into #{} (map fix-touched) %)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-62"
|
||||
[data _]
|
||||
@@ -1175,7 +1174,7 @@
|
||||
;; so the relevant objects are inside the component
|
||||
(d/update-when component :objects remove-cycles))]
|
||||
|
||||
(update data :components update-vals update-component)))
|
||||
(d/update-when data :components d/update-vals update-component)))
|
||||
|
||||
(defmethod migrate-data "legacy-65"
|
||||
[data _]
|
||||
@@ -1186,14 +1185,14 @@
|
||||
update-page
|
||||
(fn [page]
|
||||
(-> (update-object page)
|
||||
(update :objects update-vals update-object)))]
|
||||
(update :objects d/update-vals update-object)))]
|
||||
|
||||
(-> data
|
||||
(update-object)
|
||||
(d/update-when :pages-index update-vals update-page)
|
||||
(d/update-when :colors update-vals update-object)
|
||||
(d/update-when :typographies update-vals update-object)
|
||||
(d/update-when :components update-vals update-object))))
|
||||
(update :pages-index d/update-vals update-page)
|
||||
(d/update-when :colors d/update-vals update-object)
|
||||
(d/update-when :typographies d/update-vals update-object)
|
||||
(d/update-when :components d/update-vals update-object))))
|
||||
|
||||
(defmethod migrate-data "legacy-66"
|
||||
[data _]
|
||||
@@ -1207,11 +1206,11 @@
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "legacy-67"
|
||||
[data _]
|
||||
@@ -1219,11 +1218,11 @@
|
||||
(d/update-when object :shadow #(into [] (reverse %))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0001-remove-tokens-from-groups"
|
||||
[data _]
|
||||
@@ -1237,8 +1236,55 @@
|
||||
(dissoc :applied-tokens)))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals update-object))]
|
||||
(update data :pages-index update-vals update-page)))
|
||||
(d/update-when page :objects d/update-vals update-object))]
|
||||
|
||||
(update data :pages-index d/update-vals update-page)))
|
||||
|
||||
(defmethod migrate-data "0002-clean-shape-interactions"
|
||||
[data _]
|
||||
(let [decode-fn (sm/decoder ctsi/schema:interaction sm/json-transformer)
|
||||
validate-fn (sm/validator ctsi/schema:interaction)
|
||||
|
||||
xform
|
||||
(comp
|
||||
(map decode-fn)
|
||||
(filter validate-fn))
|
||||
|
||||
update-object
|
||||
(fn [object]
|
||||
(d/update-when object :interactions
|
||||
(fn [interactions]
|
||||
(into [] xform interactions))))
|
||||
|
||||
update-container
|
||||
(fn [container]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0003-fix-root-shape"
|
||||
[data _]
|
||||
(letfn [(update-object [shape]
|
||||
(if (= (:id shape) uuid/zero)
|
||||
(-> shape
|
||||
(assoc :parent-id uuid/zero)
|
||||
(assoc :frame-id uuid/zero)
|
||||
;; We explicitly dissoc them and let the shape-setup
|
||||
;; to regenerate it with valid values.
|
||||
(dissoc :selrect)
|
||||
(dissoc :points)
|
||||
(cts/setup-shape))
|
||||
shape))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container)
|
||||
(d/without-nils))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
@@ -1294,4 +1340,6 @@
|
||||
"legacy-65"
|
||||
"legacy-66"
|
||||
"legacy-67"
|
||||
"0001-remove-tokens-from-groups"]))
|
||||
"0001-remove-tokens-from-groups"
|
||||
"0002-clean-shape-interactions"
|
||||
"0003-fix-root-shape"]))
|
||||
|
||||
@@ -124,7 +124,8 @@
|
||||
;; TODO: deprecate this flag and consolidate the code
|
||||
:export-file-v3
|
||||
:render-wasm-dpr
|
||||
:hide-release-modal})
|
||||
:hide-release-modal
|
||||
:subscriptions-old})
|
||||
|
||||
(def all-flags
|
||||
(set/union email login varia))
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||
(log/set-level! :warn)
|
||||
|
||||
;; Add uuids here to filter logs to only show specific shapes or containers (and all shapes
|
||||
;; contained in them).
|
||||
(def log-shape-ids #{})
|
||||
(def log-container-ids #{})
|
||||
|
||||
@@ -293,6 +295,7 @@
|
||||
|
||||
(declare generate-detach-recursive)
|
||||
(declare generate-advance-nesting-level)
|
||||
(declare generate-detach-immediate)
|
||||
|
||||
(defn generate-detach-instance
|
||||
"Generate changes to remove the links between a shape and all its children
|
||||
@@ -306,60 +309,84 @@
|
||||
(defn- generate-detach-recursive
|
||||
[changes container libraries shape-id first component-root?]
|
||||
(let [shape (ctn/get-shape container shape-id)]
|
||||
(shape-log :trace shape-id container
|
||||
:msg " Processing" :shape-id shape-id)
|
||||
(if (and (ctk/instance-head? shape) (not first))
|
||||
; Subinstances are not detached
|
||||
(cond-> changes
|
||||
component-root?
|
||||
; If the initial shape was component-root, first level subinstances are converted in top instances
|
||||
(pcb/update-shapes [shape-id] #(assoc % :component-root true))
|
||||
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root")
|
||||
(assoc % :component-root true)))
|
||||
|
||||
:always
|
||||
; First level subinstances of a detached component can't have swap-slot
|
||||
(pcb/update-shapes [shape-id] ctk/remove-swap-slot)
|
||||
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> remove swap-slot")
|
||||
(ctk/remove-swap-slot %)))
|
||||
|
||||
(nil? (ctk/get-swap-slot shape))
|
||||
; Near shape-refs need to be advanced one level (except if the head is already swapped)
|
||||
; Near shape-ref of shape and children need to be advanced one level
|
||||
; (except if the head is already swapped)
|
||||
(generate-advance-nesting-level nil container libraries (:id shape)))
|
||||
|
||||
;; Otherwise, detach the shape and all children
|
||||
(let [children-ids (:shapes shape)]
|
||||
(log/trace :msg " -> detach")
|
||||
(reduce #(generate-detach-recursive %1 container libraries %2 false component-root?)
|
||||
(pcb/update-shapes changes [(:id shape)] ctk/detach-shape)
|
||||
children-ids)))))
|
||||
|
||||
(defn- generate-advance-nesting-level
|
||||
[changes file container libraries shape-id]
|
||||
(let [children (cfh/get-children-with-self (:objects container) shape-id)
|
||||
skip-near (fn [changes shape]
|
||||
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
|
||||
(cond-> changes
|
||||
(some? (:shape-ref ref-shape))
|
||||
(pcb/update-shapes [(:id shape)] #(assoc % :shape-ref (:shape-ref ref-shape)))
|
||||
(log/trace :msg " -> advance-nesting-level")
|
||||
(let [detached-ids (atom #{})
|
||||
children (cfh/get-children-with-self (:objects container) shape-id) ;; TODO: this function should be refactored to be a recursive tree traversal.
|
||||
skip-near (fn [changes shape] ;; this way we could shake the tree more easily when detaching shapes
|
||||
(shape-log :trace (:id shape) container ;; and perhaps even allow to recover nested instances that have been
|
||||
:msg " * advancing" :shape-id (:id shape)) ;; swapped and so we can access the main instance again.
|
||||
(if (contains? @detached-ids (:id shape))
|
||||
(do (log/trace :msg " (detached)")
|
||||
changes)
|
||||
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
|
||||
(cond-> changes
|
||||
(some? (:shape-ref ref-shape))
|
||||
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (advanced)")
|
||||
(assoc % :shape-ref (:shape-ref ref-shape))))
|
||||
|
||||
;; When advancing level, the normal touched groups (not swap slots) of the
|
||||
;; ref-shape must be merged into the current shape, because they refer to
|
||||
;; the new referenced shape.
|
||||
(some? ref-shape)
|
||||
(pcb/update-shapes
|
||||
[(:id shape)]
|
||||
#(assoc % :touched
|
||||
(clojure.set/union (:touched shape)
|
||||
(ctk/normal-touched-groups ref-shape))))
|
||||
;; When advancing level, the normal touched groups (not swap slots) of the
|
||||
;; ref-shape must be merged into the current shape, because they refer to
|
||||
;; the new referenced shape.
|
||||
(some? ref-shape)
|
||||
(pcb/update-shapes
|
||||
[(:id shape)]
|
||||
#(do (log/trace :msg " (merge touched)")
|
||||
(assoc % :touched
|
||||
(clojure.set/union (:touched shape)
|
||||
(ctk/normal-touched-groups ref-shape)))))
|
||||
|
||||
;; Swap slot must also be copied if the current shape has not any,
|
||||
;; except if this is the first level subcopy.
|
||||
(and (some? (ctk/get-swap-slot ref-shape))
|
||||
(nil? (ctk/get-swap-slot shape))
|
||||
(not= (:id shape) shape-id))
|
||||
(pcb/update-shapes [(:id shape)] #(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape)))
|
||||
;; Swap slot must also be copied if the current shape has not any,
|
||||
;; except if this is the first level subcopy.
|
||||
(and (some? (ctk/get-swap-slot ref-shape))
|
||||
(nil? (ctk/get-swap-slot shape))
|
||||
(not= (:id shape) shape-id))
|
||||
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (got swap-slot)")
|
||||
(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape))))
|
||||
|
||||
;; If we can't get the ref-shape (e.g. it's in an external library not linked),
|
||||
;: we can't do a suitable advance. So it's better to detach the shape
|
||||
(nil? ref-shape)
|
||||
(pcb/update-shapes [(:id shape)] ctk/detach-shape))))]
|
||||
;; If we can't get the ref-shape (e.g. it's in an external library not linked),
|
||||
;: we can't do a suitable advance. So it's better to detach the shape and all its
|
||||
;; children (and add to detached-ids so they are not processed again).
|
||||
(nil? ref-shape)
|
||||
(generate-detach-immediate container (:id shape) detached-ids)))))]
|
||||
|
||||
(reduce skip-near changes children)))
|
||||
|
||||
(defn- generate-detach-immediate
|
||||
[changes container shape-id detached-ids]
|
||||
(let [shape-and-children (cfh/get-children-ids-with-self (:objects container) shape-id)]
|
||||
(log/trace :msg " (cannot advance; detach shape and children)")
|
||||
(swap! detached-ids #(into % shape-and-children))
|
||||
(pcb/update-shapes changes shape-and-children ctk/detach-shape)))
|
||||
|
||||
(defn prepare-restore-component
|
||||
([changes library-data component-id current-page]
|
||||
(let [component (ctkl/get-deleted-component library-data component-id)
|
||||
|
||||
@@ -390,14 +390,22 @@
|
||||
(register! :merge (mu/-merge))
|
||||
(register! :union (mu/-union))
|
||||
|
||||
(def uuid-rx
|
||||
#"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
|
||||
|
||||
(defn parse-uuid
|
||||
(defn- parse-uuid
|
||||
[s]
|
||||
(if (string? s)
|
||||
(some->> (re-matches uuid-rx s) uuid/uuid)
|
||||
s))
|
||||
(if (uuid? s)
|
||||
s
|
||||
(if (str/empty? s)
|
||||
nil
|
||||
(try
|
||||
(uuid/parse s)
|
||||
(catch #?(:clj Exception :cljs :default) _cause
|
||||
s)))))
|
||||
|
||||
(defn- encode-uuid
|
||||
[v]
|
||||
(if (uuid? v)
|
||||
(str v)
|
||||
v))
|
||||
|
||||
(register!
|
||||
{:type ::uuid
|
||||
@@ -409,8 +417,8 @@
|
||||
:gen/gen (sg/uuid)
|
||||
:decode/string parse-uuid
|
||||
:decode/json parse-uuid
|
||||
:encode/string str
|
||||
:encode/json str
|
||||
:encode/string encode-uuid
|
||||
:encode/json encode-uuid
|
||||
::oapi/type "string"
|
||||
::oapi/format "uuid"}})
|
||||
|
||||
@@ -856,7 +864,7 @@
|
||||
choices))]
|
||||
{:pred pred
|
||||
:type-properties
|
||||
{:title "contains"
|
||||
{:title "contains any"
|
||||
:description "contains predicate"}}))})
|
||||
|
||||
(register!
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
(let [smallest (-> params :shrunk :smallest vec)]
|
||||
(println)
|
||||
(println "Condition failed with the following params:")
|
||||
(println "Seed:" (:seed params))
|
||||
(println)
|
||||
(pp/pprint smallest)))
|
||||
|
||||
|
||||
@@ -543,14 +543,23 @@
|
||||
;; We can always move the children to the parent they already have.
|
||||
;; But if we are pasting, those are new items, so it is considered a change
|
||||
no-changes?
|
||||
(and (->> children (every? #(= parent-id (:parent-id %))))
|
||||
(and (every? #(= parent-id (:parent-id %)) children)
|
||||
(not pasting?))
|
||||
all-main?
|
||||
(->> children (every? #(ctk/main-instance? %)))]
|
||||
(every? ctk/main-instance? children)
|
||||
|
||||
any-main-descendant
|
||||
(some
|
||||
(fn [shape]
|
||||
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
|
||||
children)]
|
||||
|
||||
(if (or no-changes?
|
||||
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
|
||||
;; If we are moving into a variant-container, all the items should be main
|
||||
(or all-main? (not (ctk/is-variant-container? parent)))))
|
||||
(or all-main? (not (ctk/is-variant-container? parent)))
|
||||
;; If we are moving into a main component, no descendant can be main
|
||||
(or (nil? any-main-descendant) (not (ctk/main-instance? parent)))))
|
||||
[parent-id (get-frame parent-id)]
|
||||
(recur (:parent-id parent) objects children pasting? libraries))))))
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
[:interactions {:optional true}
|
||||
[:vector {:gen/max 2} ::ctsi/interaction]]
|
||||
[:shadow {:optional true}
|
||||
[:vector {:gen/max 1} ::ctss/shadow]]
|
||||
[:vector {:gen/max 1} ctss/schema:shadow]]
|
||||
[:blur {:optional true} ::ctsb/blur]
|
||||
[:grow-type {:optional true}
|
||||
[::sm/one-of grow-types]]
|
||||
|
||||
@@ -109,13 +109,27 @@
|
||||
(def check-animation!
|
||||
(sm/check-fn schema:animation))
|
||||
|
||||
(def schema:interaction-attrs
|
||||
[:map {:title "InteractionAttrs"}
|
||||
[:action-type {:optional true} [::sm/one-of action-types]]
|
||||
[:event-type {:optional true} [::sm/one-of event-types]]
|
||||
[:destination {:optional true} [:maybe ::sm/uuid]]
|
||||
[:preserve-scroll {:optional true} :boolean]
|
||||
[:animation {:optional true} schema:animation]
|
||||
[:overlay-position {:optional true} ::gpt/point]
|
||||
[:overlay-pos-type {:optional true} [::sm/one-of overlay-positioning-types]]
|
||||
[:close-click-outside {:optional true} :boolean]
|
||||
[:background-overlay {:optional true} :boolean]
|
||||
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]
|
||||
[:url {:optional true} :string]])
|
||||
|
||||
(def schema:navigate-interaction
|
||||
[:map
|
||||
[:action-type [:= :navigate]]
|
||||
[:event-type [::sm/one-of event-types]]
|
||||
[:destination {:optional true} [:maybe ::sm/uuid]]
|
||||
[:preserve-scroll {:optional true} :boolean]
|
||||
[:animation {:optional true} ::animation]])
|
||||
[:animation {:optional true} schema:animation]])
|
||||
|
||||
(def schema:open-overlay-interaction
|
||||
[:map
|
||||
@@ -126,7 +140,7 @@
|
||||
[:destination {:optional true} [:maybe ::sm/uuid]]
|
||||
[:close-click-outside {:optional true} :boolean]
|
||||
[:background-overlay {:optional true} :boolean]
|
||||
[:animation {:optional true} ::animation]
|
||||
[:animation {:optional true} schema:animation]
|
||||
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
||||
(def schema:toggle-overlay-interaction
|
||||
@@ -138,7 +152,7 @@
|
||||
[:destination {:optional true} [:maybe ::sm/uuid]]
|
||||
[:close-click-outside {:optional true} :boolean]
|
||||
[:background-overlay {:optional true} :boolean]
|
||||
[:animation {:optional true} ::animation]
|
||||
[:animation {:optional true} schema:animation]
|
||||
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
||||
(def schema:close-overlay-interaction
|
||||
@@ -146,7 +160,7 @@
|
||||
[:action-type [:= :close-overlay]]
|
||||
[:event-type [::sm/one-of event-types]]
|
||||
[:destination {:optional true} [:maybe ::sm/uuid]]
|
||||
[:animation {:optional true} ::animation]
|
||||
[:animation {:optional true} schema:animation]
|
||||
[:position-relative-to {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
||||
(def schema:prev-scren-interaction
|
||||
@@ -161,21 +175,21 @@
|
||||
[:url :string]])
|
||||
|
||||
(def schema:interaction
|
||||
[:multi {:dispatch :action-type
|
||||
:title "Interaction"
|
||||
:gen/gen (sg/one-of (sg/generator schema:navigate-interaction)
|
||||
(sg/generator schema:open-overlay-interaction)
|
||||
(sg/generator schema:close-overlay-interaction)
|
||||
(sg/generator schema:toggle-overlay-interaction)
|
||||
(sg/generator schema:prev-scren-interaction)
|
||||
(sg/generator schema:open-url-interaction))
|
||||
:decode/json #(update % :action-type keyword)}
|
||||
[:navigate schema:navigate-interaction]
|
||||
[:open-overlay schema:open-overlay-interaction]
|
||||
[:toggle-overlay schema:toggle-overlay-interaction]
|
||||
[:close-overlay schema:close-overlay-interaction]
|
||||
[:prev-screen schema:prev-scren-interaction]
|
||||
[:open-url schema:open-url-interaction]])
|
||||
[:and {:title "Interaction"
|
||||
:gen/gen (sg/one-of (sg/generator schema:navigate-interaction)
|
||||
(sg/generator schema:open-overlay-interaction)
|
||||
(sg/generator schema:close-overlay-interaction)
|
||||
(sg/generator schema:toggle-overlay-interaction)
|
||||
(sg/generator schema:prev-scren-interaction)
|
||||
(sg/generator schema:open-url-interaction))}
|
||||
schema:interaction-attrs
|
||||
[:multi {:dispatch :action-type}
|
||||
[:navigate schema:navigate-interaction]
|
||||
[:open-overlay schema:open-overlay-interaction]
|
||||
[:toggle-overlay schema:toggle-overlay-interaction]
|
||||
[:close-overlay schema:close-overlay-interaction]
|
||||
[:prev-screen schema:prev-scren-interaction]
|
||||
[:open-url schema:open-url-interaction]]])
|
||||
|
||||
(sm/register! ::interaction schema:interaction)
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
[:hidden :boolean]
|
||||
[:color ::ctc/color]])
|
||||
|
||||
(sm/register! ::shadow schema:shadow)
|
||||
|
||||
(def check-shadow
|
||||
(sm/check-fn schema:shadow))
|
||||
|
||||
(def valid-shadow?
|
||||
(sm/validator schema:shadow))
|
||||
|
||||
|
||||
@@ -17,9 +17,14 @@
|
||||
java.util.UUID
|
||||
java.nio.ByteBuffer)))
|
||||
|
||||
(def regex
|
||||
#"^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]$")
|
||||
|
||||
(defn uuid
|
||||
"Creates an UUID instance from string, expectes valid uuid strings,
|
||||
the existense of validation is implementation detail"
|
||||
the existense of validation is implementation detail.
|
||||
|
||||
UNSAFE: this can accept invalid uuids or incomplete uuids"
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/uuid s)))
|
||||
@@ -27,8 +32,21 @@
|
||||
(defn parse
|
||||
"Parse string uuid representation into proper UUID instance, validates input"
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/parse-uuid s)))
|
||||
(if (and (string? s) ^boolean (re-matches regex s))
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (uuid s))
|
||||
|
||||
(let [message (str "invalid string '" s "' for uuid")]
|
||||
(throw #?(:clj (IllegalArgumentException. message)
|
||||
:cljs (js/Error. message))))))
|
||||
|
||||
(defn parse*
|
||||
"Exception safe version of `parse`."
|
||||
[s]
|
||||
(try
|
||||
(parse s)
|
||||
(catch #?(:clj Exception :cljs :default) _cause
|
||||
nil)))
|
||||
|
||||
(defn next
|
||||
[]
|
||||
|
||||
@@ -156,10 +156,13 @@ http {
|
||||
}
|
||||
|
||||
location / {
|
||||
location ~ ^/github/penpot-files/(?<template_file>[a-zA-Z0-9\-\_\.]+) {
|
||||
proxy_pass https://raw.githubusercontent.com/penpot/penpot-files/main/$template_file;
|
||||
location ~ ^/github/penpot-files/(.+)$ {
|
||||
rewrite ^/github/penpot-files/(.+) /penpot/penpot-files/refs/heads/main/$1 break;
|
||||
proxy_pass https://raw.githubusercontent.com;
|
||||
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_set_header User-Agent "curl/7.74.0";
|
||||
proxy_hide_header Cookies;
|
||||
proxy_set_header User-Agent "curl/8.5.0";
|
||||
proxy_set_header Host "raw.githubusercontent.com";
|
||||
proxy_set_header Accept "*/*";
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
|
||||
@@ -87,7 +87,7 @@ services:
|
||||
networks:
|
||||
- penpot
|
||||
|
||||
labels:
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
|
||||
# ## HTTPS: example of labels for the case where penpot will be exposed to the
|
||||
|
||||
@@ -135,10 +135,13 @@ http {
|
||||
}
|
||||
|
||||
location / {
|
||||
location ~ ^/github/penpot-files/(?<template_file>[a-zA-Z0-9\-\_\.]+) {
|
||||
proxy_pass https://raw.githubusercontent.com/penpot/penpot-files/main/$template_file;
|
||||
location ~ ^/github/penpot-files/(.+)$ {
|
||||
rewrite ^/github/penpot-files/(.+) /penpot/penpot-files/refs/heads/main/$1 break;
|
||||
proxy_pass https://raw.githubusercontent.com;
|
||||
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_set_header User-Agent "curl/7.74.0";
|
||||
proxy_hide_header Cookies;
|
||||
proxy_set_header User-Agent "curl/8.5.0";
|
||||
proxy_set_header Host "raw.githubusercontent.com";
|
||||
proxy_set_header Accept "*/*";
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
|
||||
@@ -151,9 +151,20 @@ Postgres database and another one for the assets uploaded by your users (images
|
||||
clips). There may be more volumes if you enable other features, as explained in the file
|
||||
itself.
|
||||
|
||||
## Configure the proxy
|
||||
## Configure the proxy and HTTPS
|
||||
|
||||
Your host configuration needs to make a proxy to http://localhost:9001.
|
||||
We strongly recommend to use Penpot under HTTPS/SSL, which will require specific server configurations for DNS and SSL certificates.
|
||||
Besides, your host configuration needs to make a proxy to http://localhost:9001.
|
||||
|
||||
<p class="advice">
|
||||
If you plan to serve Penpot under different domain than `localhost` without HTTPS,
|
||||
you need to disable the `secure` flag on cookies, with the `disable-secure-session-cookies` flag.
|
||||
This is a configuration NOT recommended for production environments; as some browser APIs do
|
||||
not work properly under non-https environments, this unsecure configuration
|
||||
may limit the usage of Penpot; as an example, the clipboard does not work with HTTP.
|
||||
</p>
|
||||
|
||||
Below, you can see three examples with three different proxys:
|
||||
|
||||
### Example with NGINX
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
---
|
||||
title: 1.1 Recommended Settings
|
||||
title: 1.1 Recommended storage
|
||||
---
|
||||
|
||||
# Recommended settings
|
||||
# Recommended storage
|
||||
|
||||
To self-host Penpot, you’ll need a server with the following specifications:
|
||||
Disk requirements depend on your usage, with the primary factors being database storage and user-uploaded files.
|
||||
|
||||
* **CPU:** 1-2 CPUs
|
||||
* **RAM:** 4 GiB of RAM
|
||||
* **Disk Space:** Disk requirements depend on your usage. Disk usage primarily involves the database and any files uploaded by users.
|
||||
As a rule of thumb, start with a **minimum** database size of **50GB** to **100GB** with elastic sizing capability — this configuration should adequately support up to 10 editors. For environments with **more than 10 users**, we recommend adding approximately **5GB** of capacity per additional editor.
|
||||
|
||||
This setup should be sufficient for a smooth experience with typical usage (your mileage may vary).
|
||||
Keep in mind that database size doesn't grow strictly proportionally with user count, as it depends heavily on how Penpot is used and the complexity of files created. Most organizations begin with this baseline and elastic sizing approach, then monitor usage patterns monthly until resource requirements stabilize.
|
||||
|
||||
@@ -848,7 +848,7 @@ title: Shortcuts
|
||||
</table>
|
||||
|
||||
<h2 id="viewer-section"> View mode </h2>
|
||||
<p>The View mode is the area to present and share designs and play the proptotype interactions. <a href="/user-guide/the-interface/#interface-viewmode">More about the View mode</a>.</p>
|
||||
<p>The View mode is the area to present and share designs and play the prototype interactions. <a href="/user-guide/the-interface/#interface-viewmode">More about the View mode</a>.</p>
|
||||
|
||||
<h3 id="generic-viewer">Generic</h3>
|
||||
<table cellspacing="0" cellpadding="1" border="1" width="100%">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9",
|
||||
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9",
|
||||
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
|
||||
@@ -511,9 +511,7 @@ test.describe("Tokens: Sets Tab", () => {
|
||||
// Creates nesting by renaming set with double click
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "light-renamed" })
|
||||
.click({ button: "right" });
|
||||
await expect(tokenContextMenuForSet).toBeVisible();
|
||||
await tokenContextMenuForSet.getByText("Rename").click();
|
||||
.dblclick();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "nested/light");
|
||||
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
@@ -558,6 +556,45 @@ test.describe("Tokens: Sets Tab", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("User can create & edit sets and set groups with an identical name", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokenThemesSetsSidebar, tokenContextMenuForSet } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabButton = tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
|
||||
await createSet(tokenThemesSetsSidebar, "core/colors");
|
||||
await createSet(tokenThemesSetsSidebar, "core");
|
||||
await assertSetsList(tokenThemesSetsSidebar, ["core", "colors", "core"]);
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "core" })
|
||||
.nth(0)
|
||||
.dblclick();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "core-group-renamed");
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core-group-renamed",
|
||||
"colors",
|
||||
"core",
|
||||
]);
|
||||
|
||||
await page.keyboard.press(`ControlOrMeta+z`);
|
||||
await assertSetsList(tokenThemesSetsSidebar, ["core", "colors", "core"]);
|
||||
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "core" })
|
||||
.nth(1)
|
||||
.dblclick();
|
||||
await changeSetInput(tokenThemesSetsSidebar, "core-set-renamed");
|
||||
await assertSetsList(tokenThemesSetsSidebar, [
|
||||
"core",
|
||||
"colors",
|
||||
"core-set-renamed",
|
||||
]);
|
||||
});
|
||||
|
||||
test("Fold/Unfold set", async ({ page }) => {
|
||||
const { tokenThemesSetsSidebar, tokenSetGroupItems } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
@@ -239,15 +239,15 @@
|
||||
(str (:last-id file)))
|
||||
|
||||
(lookupShape [_ shape-id]
|
||||
(clj->js (fb/lookup-shape file (uuid/uuid shape-id))))
|
||||
(clj->js (fb/lookup-shape file (uuid/parse shape-id))))
|
||||
|
||||
(updateObject [_ id new-obj]
|
||||
(let [old-obj (fb/lookup-shape file (uuid/uuid id))
|
||||
(let [old-obj (fb/lookup-shape file (uuid/parse id))
|
||||
new-obj (d/deep-merge old-obj (parse-data new-obj))]
|
||||
(set! file (fb/update-object file old-obj new-obj))))
|
||||
|
||||
(deleteObject [_ id]
|
||||
(set! file (fb/delete-object file (uuid/uuid id))))
|
||||
(set! file (fb/delete-object file (uuid/parse id))))
|
||||
|
||||
(getId [_]
|
||||
(:id file))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
[file ^string page-id]
|
||||
|
||||
;; Better to expose the api as a promise to be consumed from JS
|
||||
(let [page-id (uuid/uuid page-id)
|
||||
(let [page-id (uuid/parse page-id)
|
||||
file-data (.-file file)
|
||||
data (get-in file-data [:data :pages-index page-id])]
|
||||
(p/create
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
[app.main.data.profile :as dp]
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.errors]
|
||||
[app.main.features :as feat]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui :as ui]
|
||||
@@ -67,7 +66,6 @@
|
||||
(watch [_ _ stream]
|
||||
(rx/merge
|
||||
(rx/of (ev/initialize)
|
||||
(feat/initialize)
|
||||
(dp/refresh-profile))
|
||||
|
||||
;; Watch for profile deletion events
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.features :as features]
|
||||
[app.main.worker :as uw]
|
||||
[app.util.time :as dt]
|
||||
[beicon.v2.core :as rx]
|
||||
@@ -182,8 +181,8 @@
|
||||
(let [file-id (or file-id (:current-file-id state))
|
||||
uchg (vec undo-changes)
|
||||
rchg (vec redo-changes)
|
||||
features (features/get-team-enabled-features state)
|
||||
permissions (:permissions state)]
|
||||
features (get state :features)
|
||||
permissions (get state :permissions)]
|
||||
|
||||
;; Prevent commit changes by a viewer team member (it really should never happen)
|
||||
(when (:can-edit permissions)
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"Retrieves the mentions in the content as an array of uuids"
|
||||
[content]
|
||||
(->> (re-seq r-mentions content)
|
||||
(mapv (fn [[_ _ id]] (uuid/uuid id)))))
|
||||
(mapv (fn [[_ _ id]] (uuid/parse id)))))
|
||||
|
||||
(defn update-mentions
|
||||
"Updates the params object with the mentiosn"
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.persistence :as-alias dps]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
@@ -73,7 +72,7 @@
|
||||
(st/emit! (ntf/hide)))
|
||||
|
||||
(defn handle-notification
|
||||
[{:keys [message code level] :as params}]
|
||||
[{:keys [message code] :as params}]
|
||||
(ptk/reify ::show-notification
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
@@ -81,9 +80,6 @@
|
||||
:upgrade-version
|
||||
(rx/of (ntf/dialog
|
||||
:content (tr "notifications.by-code.upgrade-version")
|
||||
:controls :inline-actions
|
||||
:type :inline
|
||||
:level level
|
||||
:accept {:label (tr "labels.refresh")
|
||||
:callback force-reload!}
|
||||
:tag :notification))
|
||||
@@ -91,16 +87,14 @@
|
||||
:maintenance
|
||||
(rx/of (ntf/dialog
|
||||
:content (tr "notifications.by-code.maintenance")
|
||||
:controls :inline-actions
|
||||
:type level
|
||||
:accept {:label (tr "labels.accept")
|
||||
:callback hide-notifications!}
|
||||
:tag :notification))
|
||||
|
||||
(rx/of (ntf/dialog
|
||||
:content message
|
||||
:controls :close
|
||||
:type level
|
||||
:accept {:label (tr "labels.close")
|
||||
:callback hide-notifications!}
|
||||
:tag :notification))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -112,7 +106,7 @@
|
||||
(ptk/reify ::show-shared-dialog
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
(let [features (get state :features)
|
||||
file (dsh/lookup-file state)
|
||||
data (get file :data)]
|
||||
|
||||
@@ -169,8 +163,8 @@
|
||||
(ptk/reify ::export-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
team-id (:current-team-id state)]
|
||||
(let [features (get state :features)
|
||||
team-id (get state :current-team-id)]
|
||||
(->> (rx/from files)
|
||||
(rx/mapcat
|
||||
(fn [file]
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.websocket :as dws]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.sse :as sse]
|
||||
@@ -57,8 +56,7 @@
|
||||
(rx/filter (fn [{:keys [topic] :as msg}]
|
||||
(or (= topic uuid/zero)
|
||||
(= topic profile-id))))
|
||||
(rx/map process-message)
|
||||
(rx/ignore)))
|
||||
(rx/map process-message)))
|
||||
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
@@ -497,7 +495,7 @@
|
||||
base-name (tr "dashboard.new-file-prefix")
|
||||
name (or name
|
||||
(cfh/generate-unique-name base-name unames :immediate-suffix? true))
|
||||
features (-> (features/get-team-enabled-features state)
|
||||
features (-> (get state :features)
|
||||
(set/difference cfeat/frontend-only-features))
|
||||
params (-> params
|
||||
(assoc :name name)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
@@ -47,7 +46,7 @@
|
||||
(ptk/reify ::export-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
(let [features (get state :features)
|
||||
team-id (:current-team-id state)
|
||||
evname (if (= format :legacy-zip)
|
||||
"export-standard-files"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
(def ^:private schema:notification
|
||||
[:map {:title "Notification"}
|
||||
[:level [::sm/one-of #{:success :error :info :warning}]]
|
||||
[:level {:optional true} [::sm/one-of #{:success :error :info :warning}]]
|
||||
[:status {:optional true}
|
||||
[::sm/one-of #{:visible :hide}]]
|
||||
[:position {:optional true}
|
||||
@@ -129,15 +129,11 @@
|
||||
:timeout timeout})))
|
||||
|
||||
(defn dialog
|
||||
[& {:keys [content controls actions accept cancel position tag level links]
|
||||
:or {controls :none position :floating level :info}}]
|
||||
[& {:keys [content accept cancel tag links]}]
|
||||
(show (d/without-nils
|
||||
{:content content
|
||||
:level level
|
||||
:links links
|
||||
:position position
|
||||
:controls controls
|
||||
:actions actions
|
||||
:type :inline
|
||||
:accept accept
|
||||
:cancel cancel
|
||||
:links links
|
||||
:tag tag})))
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
(let [permissions (get team :permissions)
|
||||
features (get team :features)]
|
||||
(rx/of #(assoc % :permissions permissions)
|
||||
(features/initialize (or features #{}))
|
||||
(features/initialize features)
|
||||
(fetch-members team-id))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
@@ -199,7 +199,7 @@
|
||||
(ptk/reify ::webhooks-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:team-id team-id] assoc :webhooks webhooks))))
|
||||
(update-in state [:teams team-id] assoc :webhooks webhooks))))
|
||||
|
||||
(defn fetch-webhooks
|
||||
[]
|
||||
@@ -255,12 +255,12 @@
|
||||
(dm/assert! (string? name))
|
||||
(ptk/reify ::create-team
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(watch [it _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
features (features/get-enabled-features state)
|
||||
params {:name name :features features}]
|
||||
features features/global-enabled-features
|
||||
params {:name name :features features}]
|
||||
(->> (rp/cmd! :create-team (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/map team-created)
|
||||
@@ -272,11 +272,11 @@
|
||||
[{:keys [name emails role] :as params}]
|
||||
(ptk/reify ::create-team-with-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(watch [it _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
features (features/get-enabled-features state)
|
||||
features features/global-enabled-features
|
||||
params {:name name
|
||||
:emails emails
|
||||
:role role
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [team-id (:id team)
|
||||
team {:members users}]
|
||||
team (assoc team :members users)]
|
||||
(-> state
|
||||
(assoc :share-links share-links)
|
||||
(assoc :current-team-id team-id)
|
||||
@@ -248,7 +248,7 @@
|
||||
|
||||
(defn fetch-comments
|
||||
[{:keys [thread-id]}]
|
||||
(dm/assert! (uuid thread-id))
|
||||
(assert (uuid? thread-id))
|
||||
(letfn [(fetched [comments state]
|
||||
(update state :comments assoc thread-id (d/index-by :id comments)))]
|
||||
(ptk/reify ::retrieve-comments
|
||||
@@ -413,7 +413,7 @@
|
||||
(watch [_ state _]
|
||||
(let [params (rt/get-params state)
|
||||
index (some-> params :index parse-long)
|
||||
page-id (some-> params :page-id parse-uuid)
|
||||
page-id (some-> params :page-id uuid/parse)
|
||||
|
||||
total (count (get-in state [:viewer :pages page-id :frames]))]
|
||||
|
||||
|
||||
@@ -205,30 +205,29 @@
|
||||
(d/index-by :id))))))
|
||||
|
||||
(defn- fetch-libraries
|
||||
[file-id]
|
||||
[file-id features]
|
||||
(ptk/reify ::fetch-libries
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)]
|
||||
(->> (rp/cmd! :get-file-libraries {:file-id file-id})
|
||||
(rx/mapcat
|
||||
(fn [libraries]
|
||||
(rx/concat
|
||||
(rx/of (libraries-fetched file-id libraries))
|
||||
(rx/merge
|
||||
(->> (rx/from libraries)
|
||||
(rx/merge-map
|
||||
(fn [{:keys [id synced-at]}]
|
||||
(->> (rp/cmd! :get-file {:id id :features features})
|
||||
(rx/map #(assoc % :synced-at synced-at :library-of file-id)))))
|
||||
(rx/mapcat resolve-file)
|
||||
(rx/map library-resolved))
|
||||
(->> (rx/from libraries)
|
||||
(rx/map :id)
|
||||
(rx/mapcat (fn [file-id]
|
||||
(rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"})))
|
||||
(rx/map dwl/library-thumbnails-fetched)))
|
||||
(rx/of (check-libraries-synchronozation file-id libraries))))))))))
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-file-libraries {:file-id file-id})
|
||||
(rx/mapcat
|
||||
(fn [libraries]
|
||||
(rx/concat
|
||||
(rx/of (libraries-fetched file-id libraries))
|
||||
(rx/merge
|
||||
(->> (rx/from libraries)
|
||||
(rx/merge-map
|
||||
(fn [{:keys [id synced-at]}]
|
||||
(->> (rp/cmd! :get-file {:id id :features features})
|
||||
(rx/map #(assoc % :synced-at synced-at :library-of file-id)))))
|
||||
(rx/mapcat resolve-file)
|
||||
(rx/map library-resolved))
|
||||
(->> (rx/from libraries)
|
||||
(rx/map :id)
|
||||
(rx/mapcat (fn [file-id]
|
||||
(rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"})))
|
||||
(rx/map dwl/library-thumbnails-fetched)))
|
||||
(rx/of (check-libraries-synchronozation file-id libraries)))))))))
|
||||
|
||||
(defn- workspace-initialized
|
||||
[file-id]
|
||||
@@ -246,28 +245,16 @@
|
||||
(fbs/fix-broken-shapes)))))
|
||||
|
||||
(defn- bundle-fetched
|
||||
[{:keys [features file thumbnails]}]
|
||||
[{:keys [file file-id thumbnails] :as bundle}]
|
||||
(ptk/reify ::bundle-fetched
|
||||
IDeref
|
||||
(-deref [_]
|
||||
{:features features
|
||||
:file file
|
||||
:thumbnails thumbnails})
|
||||
(-deref [_] bundle)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [file-id (:id file)]
|
||||
(-> state
|
||||
(assoc :thumbnails thumbnails)
|
||||
(update :files assoc file-id file))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
file-id (:id file)]
|
||||
(rx/of (dwn/initialize team-id file-id)
|
||||
(dwsl/initialize-shape-layout)
|
||||
(fetch-libraries file-id))))))
|
||||
(-> state
|
||||
(assoc :thumbnails thumbnails)
|
||||
(update :files assoc file-id file)))))
|
||||
|
||||
(defn zoom-to-frame
|
||||
[]
|
||||
@@ -296,46 +283,30 @@
|
||||
|
||||
(defn- fetch-bundle
|
||||
"Multi-stage file bundle fetch coordinator"
|
||||
[file-id]
|
||||
[file-id features]
|
||||
(ptk/reify ::fetch-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
render-wasm? (contains? features "render-wasm/v1")
|
||||
stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
|
||||
team-id (:current-team-id state)]
|
||||
|
||||
(->> (rx/concat
|
||||
;; Firstly load wasm module if it is enabled and fonts
|
||||
(rx/merge
|
||||
(if ^boolean render-wasm?
|
||||
(->> (rx/from @wasm/module)
|
||||
(rx/ignore))
|
||||
(rx/empty))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::df/fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
(rx/of (df/fetch-fonts team-id)))
|
||||
|
||||
;; Then fetch file and thumbnails
|
||||
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
|
||||
(get-file-object-thumbnails file-id))
|
||||
(rx/take 1)
|
||||
(rx/mapcat
|
||||
(fn [[file thumbnails]]
|
||||
(->> (resolve-file file)
|
||||
(rx/map (fn [file]
|
||||
{:file file
|
||||
:features features
|
||||
:thumbnails thumbnails})))))
|
||||
(rx/map bundle-fetched)))
|
||||
(watch [_ _ stream]
|
||||
(let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)]
|
||||
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
|
||||
(get-file-object-thumbnails file-id))
|
||||
(rx/take 1)
|
||||
(rx/mapcat
|
||||
(fn [[file thumbnails]]
|
||||
(->> (resolve-file file)
|
||||
(rx/map (fn [file]
|
||||
{:file file
|
||||
:file-id file-id
|
||||
:features features
|
||||
:thumbnails thumbnails})))))
|
||||
(rx/map bundle-fetched)
|
||||
(rx/take-until stopper-s))))))
|
||||
|
||||
(defn initialize-workspace
|
||||
[file-id]
|
||||
[team-id file-id]
|
||||
(assert (uuid? team-id) "expected valud uuid for `team-id`")
|
||||
(assert (uuid? file-id) "expected valud uuid for `file-id`")
|
||||
|
||||
(ptk/reify ::initialize-workspace
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
@@ -347,31 +318,58 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(log/debug :hint "initialize-workspace" :file-id (dm/str file-id))
|
||||
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
|
||||
rparams (rt/get-params state)]
|
||||
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
|
||||
rparams (rt/get-params state)
|
||||
features (features/get-enabled-features state team-id)
|
||||
render-wasm? (contains? features "render-wasm/v1")]
|
||||
|
||||
(log/debug :hint "initialize-workspace"
|
||||
:team-id (dm/str team-id)
|
||||
:file-id (dm/str file-id))
|
||||
|
||||
(->> (rx/merge
|
||||
(rx/of (ntf/hide)
|
||||
(dcmt/retrieve-comment-threads file-id)
|
||||
(dcmt/fetch-profiles)
|
||||
(fetch-bundle file-id))
|
||||
(rx/concat
|
||||
;; Fetch all essential data that should be loaded before the file
|
||||
(rx/merge
|
||||
(if ^boolean render-wasm?
|
||||
(->> (rx/from @wasm/module)
|
||||
(rx/ignore))
|
||||
(rx/empty))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::df/fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
|
||||
(rx/of (ntf/hide)
|
||||
(dcmt/retrieve-comment-threads file-id)
|
||||
(dcmt/fetch-profiles)
|
||||
(df/fetch-fonts team-id)))
|
||||
|
||||
;; Once the essential data is fetched, lets proceed to
|
||||
;; fetch teh file bunldle
|
||||
(rx/of (fetch-bundle file-id features)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::bundle-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat (fn [{:keys [file]}]
|
||||
(rx/of (dpj/initialize-project (:project-id file))
|
||||
(-> (workspace-initialized file-id)
|
||||
(with-meta {:file-id file-id}))))))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [file]}]
|
||||
(rx/of (dpj/initialize-project (:project-id file))
|
||||
(dwn/initialize team-id file-id)
|
||||
(dwsl/initialize-shape-layout)
|
||||
(fetch-libraries file-id features)
|
||||
(-> (workspace-initialized file-id)
|
||||
(with-meta {:team-id team-id
|
||||
:file-id file-id}))))))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dps/persistence-notification))
|
||||
(rx/take 1)
|
||||
(rx/map dwc/set-workspace-visited))
|
||||
|
||||
(when-let [component-id (some-> rparams :component-id parse-uuid)]
|
||||
(when-let [component-id (some-> rparams :component-id uuid/parse)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
@@ -384,7 +382,7 @@
|
||||
(rx/take 1)
|
||||
(rx/map zoom-to-frame)))
|
||||
|
||||
(when-let [comment-id (some-> rparams :comment-id parse-uuid)]
|
||||
(when-let [comment-id (some-> rparams :comment-id uuid/parse)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
@@ -410,7 +408,7 @@
|
||||
(unchecked-set ug/global "name" name)))))
|
||||
|
||||
(defn finalize-workspace
|
||||
[file-id]
|
||||
[_team-id file-id]
|
||||
(ptk/reify ::finalize-workspace
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
@@ -425,12 +423,12 @@
|
||||
:workspace-tokens
|
||||
:workspace-undo)
|
||||
(update :workspace-global dissoc :read-only?)
|
||||
(assoc-in [:workspace-global :options-mode] :design)))
|
||||
(assoc-in [:workspace-global :options-mode] :design)
|
||||
(update :files d/update-vals #(dissoc % :data))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (:current-project-id state)]
|
||||
|
||||
(rx/of (dwn/finalize file-id)
|
||||
(dpj/finalize-project project-id)
|
||||
(dwsl/finalize-shape-layout)
|
||||
@@ -444,14 +442,13 @@
|
||||
(ptk/reify ::reload-current-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/of (initialize-workspace file-id))))))
|
||||
(let [file-id (:current-file-id state)
|
||||
team-id (:current-team-id state)]
|
||||
(rx/of (initialize-workspace team-id file-id))))))
|
||||
|
||||
;; Make this event callable through dynamic resolution
|
||||
(defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file))
|
||||
|
||||
|
||||
|
||||
(def ^:private xf:collect-file-media
|
||||
"Resolve and collect all file media on page objects"
|
||||
(comp (map second)
|
||||
@@ -488,18 +485,25 @@
|
||||
(defn initialize-page
|
||||
[file-id page-id]
|
||||
(assert (uuid? file-id) "expected valid uuid for `file-id`")
|
||||
(assert (uuid? page-id) "expected valid uuid for `page-id`")
|
||||
|
||||
(ptk/reify ::initialize-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(if-let [page (dsh/lookup-page state file-id page-id)]
|
||||
(rx/concat (rx/of (initialize-page* file-id page-id page)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes))
|
||||
(let [profile (:profile state)
|
||||
props (get profile :props)]
|
||||
(when (not (:workspace-visited props))
|
||||
(rx/of (select-frame-tool file-id page-id)))))
|
||||
(rx/concat
|
||||
(rx/of (initialize-page* file-id page-id page)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes))
|
||||
(let [profile (:profile state)
|
||||
props (get profile :props)]
|
||||
(when (not (:workspace-visited props))
|
||||
(rx/of (select-frame-tool file-id page-id)))))
|
||||
|
||||
;; NOTE: this redirect is necessary for cases where user
|
||||
;; explicitly passes an non-existing page-id on the url
|
||||
;; params, so on check it we can detect that there are no data
|
||||
;; for the page and redirect user to an existing page
|
||||
(rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true))))))
|
||||
|
||||
(defn finalize-page
|
||||
@@ -1410,7 +1414,7 @@
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
selected (->> (dsh/lookup-selected state)
|
||||
(cfh/clean-loops objects))
|
||||
features (-> (features/get-team-enabled-features state)
|
||||
features (-> (get state :features)
|
||||
(set/difference cfeat/frontend-only-features))
|
||||
|
||||
file-id (:current-file-id state)
|
||||
@@ -1648,9 +1652,10 @@
|
||||
objects (dsh/lookup-page-objects state)]
|
||||
(when-let [shape (get objects selected)]
|
||||
(let [props (cts/extract-props shape)
|
||||
features (-> (features/get-team-enabled-features state)
|
||||
features (-> (get state :features)
|
||||
(set/difference cfeat/frontend-only-features))
|
||||
version (-> (dsh/lookup-file state) :version)
|
||||
version (-> (dsh/lookup-file state)
|
||||
(get :version))
|
||||
|
||||
copy-data {:type :copied-props
|
||||
:features features
|
||||
@@ -1784,8 +1789,8 @@
|
||||
(ptk/reify ::paste-transit-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
features (features/get-team-enabled-features state)]
|
||||
(let [file-id (:current-file-id state)
|
||||
features (get state :features)]
|
||||
|
||||
(when-not (paste-data-valid? pdata)
|
||||
(ex/raise :type :validation
|
||||
@@ -1856,7 +1861,7 @@
|
||||
(ptk/reify ::paste-transit-props
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
(let [features (get state :features)
|
||||
selected (dsh/lookup-selected state)]
|
||||
|
||||
(when (paste-data-valid? pdata)
|
||||
@@ -2440,13 +2445,6 @@
|
||||
(js/console.log "Copies no ref" (count copies-no-ref) (clj->js copies-no-ref))
|
||||
(js/console.log "Childs no ref" (count childs-no-ref) (clj->js childs-no-ref))))))
|
||||
|
||||
(defn set-shape-ref
|
||||
[id shape-ref]
|
||||
(ptk/reify ::set-shape-ref
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (update-shape (uuid/uuid id) {:shape-ref (uuid/uuid shape-ref)})))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Exports
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -586,8 +586,13 @@
|
||||
ldata (dsh/lookup-file-data state library-id)
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(cll/generate-restore-component ldata component-id library-id page objects))]
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
(cll/generate-restore-component ldata component-id library-id page objects))
|
||||
|
||||
frames
|
||||
(->> changes :redo-changes (keep :frame-id))]
|
||||
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update {:ids frames}))))))
|
||||
|
||||
|
||||
(defn restore-components
|
||||
@@ -1398,7 +1403,7 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)]
|
||||
(let [features (get state :features)]
|
||||
(rx/concat
|
||||
(rx/merge
|
||||
(->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id})
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
(->> (rx/from initmsg)
|
||||
(rx/map dws/send))
|
||||
|
||||
|
||||
;; Subscribe to notifications of the subscription
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dws/message))
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.persistence :as dwp]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.thumbnails :as th]
|
||||
@@ -97,7 +98,8 @@
|
||||
(ptk/reify ::restore-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(let [file-id (:current-file-id state)
|
||||
team-id (:current-team-id state)]
|
||||
(rx/concat
|
||||
(rx/of ::dwp/force-persist
|
||||
(dw/remove-layout-flag :document-history))
|
||||
@@ -106,7 +108,7 @@
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/tap #(th/clear-queue!))
|
||||
(rx/map #(dw/initialize-workspace file-id)))
|
||||
(rx/map #(dw/initialize-workspace team-id file-id)))
|
||||
(case origin
|
||||
:version
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-pin-version"}))
|
||||
@@ -200,21 +202,23 @@
|
||||
|
||||
(ptk/reify ::restore-version-from-plugins
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"})
|
||||
::dwp/force-persist)
|
||||
(watch [_ state _]
|
||||
(let [file (dsh/lookup-file state file-id)
|
||||
team-id (or (:team-id file) (:current-file-id state))]
|
||||
(rx/concat
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"})
|
||||
::dwp/force-persist)
|
||||
|
||||
;; FIXME: we should abstract this
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/map #(dw/initialize-workspace file-id)))
|
||||
;; FIXME: we should abstract this
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/map #(dw/initialize-workspace team-id file-id)))
|
||||
|
||||
(->> (rx/of 1)
|
||||
(rx/tap resolve)
|
||||
(rx/ignore))))))
|
||||
(->> (rx/of 1)
|
||||
(rx/tap resolve)
|
||||
(rx/ignore)))))))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"A thin, frontend centric abstraction layer and collection of
|
||||
helpers for `app.common.features` namespace."
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.logging :as log]
|
||||
[app.config :as cf]
|
||||
[app.main.store :as st]
|
||||
[app.render-wasm :as wasm]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
@@ -26,38 +26,32 @@
|
||||
(cfeat/get-enabled-features cf/flags))
|
||||
|
||||
(defn get-enabled-features
|
||||
[state]
|
||||
(-> (get state :features-runtime #{})
|
||||
(set/intersection cfeat/no-migration-features)
|
||||
(set/union global-enabled-features)))
|
||||
|
||||
(defn get-team-enabled-features
|
||||
[state]
|
||||
(let [runtime-features (:features-runtime state #{})
|
||||
team-features (->> (:features-team state #{})
|
||||
(into #{} cfeat/xf-remove-ephimeral))]
|
||||
"An explicit lookup of enabled features for the current team"
|
||||
[state team-id]
|
||||
(let [team (dm/get-in state [:teams team-id])]
|
||||
(-> global-enabled-features
|
||||
(set/union runtime-features)
|
||||
(set/union (get state :features-runtime #{}))
|
||||
(set/intersection cfeat/no-migration-features)
|
||||
(set/union team-features))))
|
||||
|
||||
(def features-ref
|
||||
(l/derived get-team-enabled-features st/state =))
|
||||
(set/union (get team :features)))))
|
||||
|
||||
(defn active-feature?
|
||||
"Given a state and feature, check if feature is enabled"
|
||||
"Given a state and feature, check if feature is enabled."
|
||||
[state feature]
|
||||
(assert (contains? cfeat/supported-features feature) "not supported feature")
|
||||
(or (contains? (get state :features-runtime) feature)
|
||||
(if (contains? cfeat/no-migration-features feature)
|
||||
(or (contains? global-enabled-features feature)
|
||||
(contains? (get state :features-team) feature))
|
||||
(contains? (get state :features-team state) feature))))
|
||||
(assert (contains? cfeat/supported-features feature) "feature not supported")
|
||||
(let [runtime-features (get state :features-runtime)
|
||||
enabled-features (get state :features)]
|
||||
(or (contains? runtime-features feature)
|
||||
(if (contains? cfeat/no-migration-features feature)
|
||||
(or (contains? global-enabled-features feature)
|
||||
(contains? enabled-features feature))
|
||||
(contains? enabled-features feature)))))
|
||||
|
||||
(def ^:private features-ref
|
||||
(l/derived (l/key :features) st/state))
|
||||
|
||||
(defn use-feature
|
||||
"A react hook that checks if feature is currently enabled"
|
||||
[feature]
|
||||
(assert (contains? cfeat/supported-features feature) "Not supported feature")
|
||||
(let [enabled-features (mf/deref features-ref)]
|
||||
(contains? enabled-features feature)))
|
||||
|
||||
@@ -71,14 +65,16 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assert (contains? cfeat/supported-features feature) "not supported feature")
|
||||
(update state :features-runtime (fn [features]
|
||||
(if (contains? features feature)
|
||||
(do
|
||||
(log/trc :hint "feature disabled" :feature feature)
|
||||
(disj features feature))
|
||||
(do
|
||||
(log/trc :hint "feature enabled" :feature feature)
|
||||
(conj features feature))))))))
|
||||
(-> state
|
||||
(update :features-runtime (fn [features]
|
||||
(if (contains? features feature)
|
||||
(do
|
||||
(log/trc :hint "feature disabled" :feature feature)
|
||||
(disj features feature))
|
||||
(do
|
||||
(log/trc :hint "feature enabled" :feature feature)
|
||||
(conj features feature)))))
|
||||
(update :features-runtime set/intersection cfeat/no-migration-features)))))
|
||||
|
||||
(defn enable-feature
|
||||
[feature]
|
||||
@@ -90,46 +86,28 @@
|
||||
state
|
||||
(do
|
||||
(log/trc :hint "feature enabled" :feature feature)
|
||||
(update state :features-runtime (fnil conj #{}) feature))))))
|
||||
(-> state
|
||||
(update :features-runtime (fnil conj #{}) feature)
|
||||
(update :features-runtime set/intersection cfeat/no-migration-features)))))))
|
||||
|
||||
(defn initialize
|
||||
([] (initialize #{}))
|
||||
([team-features]
|
||||
(assert (set? team-features) "expected a set of features")
|
||||
(assert (every? string? team-features) "expected a set of strings")
|
||||
[features]
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [features (-> global-enabled-features
|
||||
(set/union (get state :features-runtime #{}))
|
||||
(set/union features))]
|
||||
(assoc state :features features)))
|
||||
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [runtime-features (get state :features/runtime #{})
|
||||
team-features (into #{}
|
||||
cfeat/xf-supported-features
|
||||
team-features)]
|
||||
(-> state
|
||||
(assoc :features-runtime runtime-features)
|
||||
(assoc :features-team team-features))))
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [features (get state :features)]
|
||||
(if (contains? features "render-wasm/v1")
|
||||
(wasm/initialize true)
|
||||
(wasm/initialize false))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when *assert*
|
||||
(->> (rx/from cfeat/no-migration-features)
|
||||
;; text editor v2 isn't enabled by default even in devenv
|
||||
;; wasm render v1 isn't enabled by default even in devenv
|
||||
(rx/filter #(not (or (contains? cfeat/backend-only-features %)
|
||||
(= "text-editor/v2" %)
|
||||
(= "render-wasm/v1" %)
|
||||
(= "design-tokens/v1" %))))
|
||||
(rx/observe-on :async)
|
||||
(rx/map enable-feature))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [features (get-team-enabled-features state)]
|
||||
(if (contains? features "render-wasm/v1")
|
||||
(wasm/initialize true)
|
||||
(wasm/initialize false))
|
||||
|
||||
(log/inf :hint "initialized"
|
||||
:enabled (str/join "," features)
|
||||
:runtime (str/join "," (:features-runtime state))))))))
|
||||
(log/inf :hint "initialized"
|
||||
:enabled (str/join "," features)
|
||||
:runtime (str/join "," (:features-runtime state)))))))
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.main.ui
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.team :as dtm]
|
||||
@@ -69,7 +70,7 @@
|
||||
(mf/defc dashboard-legacy-redirect*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [section team-id project-id search-term plugin-url template-url]}]
|
||||
[{:keys [section team-id project-id search-term plugin-url template]}]
|
||||
(let [section (case section
|
||||
:dashboard-legacy-search
|
||||
:dashboard-search
|
||||
@@ -97,7 +98,7 @@
|
||||
:project-id project-id
|
||||
:search-term search-term
|
||||
:plugin plugin-url
|
||||
:template-url template-url}]
|
||||
:template template}]
|
||||
(st/emit! (rt/nav section (d/without-nils params)))))
|
||||
|
||||
[:> loader*
|
||||
@@ -212,11 +213,11 @@
|
||||
:dashboard-webhooks
|
||||
:dashboard-settings)
|
||||
(let [params (get params :query)
|
||||
team-id (some-> params :team-id uuid)
|
||||
project-id (some-> params :project-id uuid)
|
||||
team-id (some-> params :team-id uuid/parse*)
|
||||
project-id (some-> params :project-id uuid/parse*)
|
||||
search-term (some-> params :search-term)
|
||||
plugin-url (some-> params :plugin)
|
||||
template-url (some-> params :template)]
|
||||
template (some-> params :template)]
|
||||
[:?
|
||||
#_[:& app.main.ui.releases/release-notes-modal {:version "2.5"}]
|
||||
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
|
||||
@@ -243,13 +244,13 @@
|
||||
:search-term search-term
|
||||
:plugin-url plugin-url
|
||||
:project-id project-id
|
||||
:template-url template-url}]]])
|
||||
:template template}]]])
|
||||
|
||||
:workspace
|
||||
(let [params (get params :query)
|
||||
team-id (some-> params :team-id uuid)
|
||||
file-id (some-> params :file-id uuid)
|
||||
page-id (some-> params :page-id uuid)
|
||||
team-id (some-> params :team-id uuid/parse*)
|
||||
file-id (some-> params :file-id uuid/parse*)
|
||||
page-id (some-> params :page-id uuid/parse*)
|
||||
layout (some-> params :layout keyword)]
|
||||
[:? {}
|
||||
(when (cf/external-feature-flag "onboarding-03" "test")
|
||||
@@ -276,15 +277,15 @@
|
||||
:viewer
|
||||
(let [params (get params :query)
|
||||
index (some-> (:index params) parse-long)
|
||||
share-id (some-> (:share-id params) parse-uuid)
|
||||
share-id (some-> (:share-id params) uuid/parse*)
|
||||
section (or (some-> (:section params) keyword)
|
||||
:interactions)
|
||||
|
||||
file-id (some-> (:file-id params) parse-uuid)
|
||||
page-id (some-> (:page-id params) parse-uuid)
|
||||
file-id (some-> (:file-id params) uuid/parse*)
|
||||
page-id (some-> (:page-id params) uuid/parse*)
|
||||
imode (or (some-> (:interactions-mode params) keyword)
|
||||
:show-on-click)
|
||||
frame-id (some-> (:frame-id params) parse-uuid)
|
||||
frame-id (some-> (:frame-id params) uuid/parse*)
|
||||
share (:share params)]
|
||||
|
||||
[:? {}
|
||||
@@ -300,9 +301,9 @@
|
||||
|
||||
|
||||
:workspace-legacy
|
||||
(let [project-id (some-> params :path :project-id uuid)
|
||||
file-id (some-> params :path :file-id uuid)
|
||||
page-id (some-> params :query :page-id uuid)
|
||||
(let [project-id (some-> params :path :project-id uuid/parse*)
|
||||
file-id (some-> params :path :file-id uuid/parse*)
|
||||
page-id (some-> params :query :page-id uuid/parse*)
|
||||
layout (some-> params :query :layout keyword)]
|
||||
|
||||
[:> workspace-legacy-redirect*
|
||||
@@ -321,18 +322,18 @@
|
||||
:dashboard-legacy-team-invitations
|
||||
:dashboard-legacy-team-webhooks
|
||||
:dashboard-legacy-team-settings)
|
||||
(let [team-id (some-> params :path :team-id uuid)
|
||||
project-id (some-> params :path :project-id uuid)
|
||||
(let [team-id (some-> params :path :team-id uuid/parse*)
|
||||
project-id (some-> params :path :project-id uuid/parse*)
|
||||
search-term (some-> params :query :search-term)
|
||||
plugin-url (some-> params :query :plugin)
|
||||
template-url (some-> params :template)]
|
||||
template (some-> params :template)]
|
||||
[:> dashboard-legacy-redirect*
|
||||
{:team-id team-id
|
||||
:section section
|
||||
:project-id project-id
|
||||
:search-term search-term
|
||||
:plugin-url plugin-url
|
||||
:template-url template-url}])
|
||||
:template template}])
|
||||
|
||||
:viewer-legacy
|
||||
(let [{:keys [query-params path-params]} route
|
||||
@@ -370,6 +371,6 @@
|
||||
(if edata
|
||||
[:> static/exception-page* {:data edata :route route}]
|
||||
[:> error-boundary* {:fallback static/internal-error*}
|
||||
[:& notifications/current-notification]
|
||||
[:> notifications/current-notification*]
|
||||
(when route
|
||||
[:> page* {:route route :profile profile}])])]]))
|
||||
|
||||
@@ -487,7 +487,7 @@
|
||||
(dom/stop-propagation event)
|
||||
(let [id (-> (dom/get-current-target event)
|
||||
(dom/get-data "user-id")
|
||||
(uuid/uuid))
|
||||
(uuid/parse))
|
||||
|
||||
user (d/seek #(= (:id %) id) members)]
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
@@ -93,6 +94,7 @@
|
||||
:default-value value
|
||||
:on-key-up on-key-up
|
||||
:on-double-click on-dbl-click
|
||||
:max-length max-input-length
|
||||
:on-blur cancel-edition}]
|
||||
|
||||
[:span {:class (stl/css :editable-label-close)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
[item item item]))
|
||||
|
||||
(mf/defc select
|
||||
[{:keys [default-value options class dropdown-class is-open? on-change on-pointer-enter-option on-pointer-leave-option disabled]}]
|
||||
[{:keys [default-value options class dropdown-class is-open? on-change on-pointer-enter-option on-pointer-leave-option disabled data-direction]}]
|
||||
(let [label-index (mf/with-memo [options]
|
||||
(into {} (map as-key-value) options))
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
[:span {:class (stl/css :current-label)} current-label]
|
||||
[:span {:class (stl/css :dropdown-button)} i/arrow]
|
||||
[:& dropdown {:show is-open? :on-close close-dropdown}
|
||||
[:ul {:ref dropdown-element* :data-direction @dropdown-direction*
|
||||
[:ul {:ref dropdown-element* :data-direction (or data-direction @dropdown-direction*)
|
||||
:class (dm/str dropdown-class " " (stl/css :custom-select-dropdown))}
|
||||
(for [[index item] (d/enumerate options)]
|
||||
(if (= :separator item)
|
||||
|
||||
@@ -34,12 +34,10 @@
|
||||
[app.main.ui.workspace.plugins]
|
||||
[app.plugins.register :as preg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
@@ -211,17 +209,22 @@
|
||||
(swap! storage/session dissoc :plugin-url))))))
|
||||
|
||||
(defn use-templates-import
|
||||
[can-edit? template-url project]
|
||||
[can-edit? template project]
|
||||
(let [project-id (get project :id)
|
||||
team-id (get project :team-id)]
|
||||
(mf/with-layout-effect [can-edit? template-url project-id team-id]
|
||||
(when (and (some? template-url)
|
||||
(mf/with-layout-effect [can-edit? template project-id team-id]
|
||||
(when (and (some? template)
|
||||
(some? project-id)
|
||||
(some? team-id))
|
||||
(if can-edit?
|
||||
(let [valid-url? (and (str/ends-with? template-url ".penpot")
|
||||
(str/starts-with? template-url cf/templates-uri))
|
||||
template-name (when valid-url? (subs template-url (count cf/templates-uri)))
|
||||
(let [valid-url? (str/ends-with? template ".penpot")
|
||||
|
||||
;; Backwards compatibility, ideally the template should be only the .penpot file name, not the full url
|
||||
template-name (if (str/starts-with? template "http")
|
||||
(subs template (count cf/templates-uri))
|
||||
template)
|
||||
|
||||
template-url (str "/github/penpot-files/" template-name)
|
||||
on-import #(st/emit! (dpj/fetch-files project-id)
|
||||
(dd/fetch-recent-files team-id)
|
||||
(dd/fetch-projects team-id)
|
||||
@@ -230,30 +233,22 @@
|
||||
:name template-name
|
||||
:url template-url}))]
|
||||
(if valid-url?
|
||||
(do
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "install-template-from-link" :name template-name :url template-url}))
|
||||
(->> (http/send! {:method :get
|
||||
:uri template-url
|
||||
:response-type :blob
|
||||
:omit-default-headers true})
|
||||
(rx/subs!
|
||||
(fn [result]
|
||||
(if (or (< (:status result) 200) (>= (:status result) 300))
|
||||
(st/emit! (notif/error (tr "dashboard.import.error")))
|
||||
(st/emit! (modal/show
|
||||
{:type :import
|
||||
:project-id project-id
|
||||
:entries [{:name template-name :uri (wapi/create-uri (:body result))}]
|
||||
:on-finish-import on-import})))))))
|
||||
(st/emit!
|
||||
(ptk/event ::ev/event {::ev/name "install-template-from-link" :name template-name :url template-url})
|
||||
(modal/show
|
||||
{:type :import
|
||||
:project-id project-id
|
||||
:entries [{:name template-name :uri template-url}]
|
||||
:on-finish-import on-import}))
|
||||
(st/emit! (notif/error (tr "dashboard.import.bad-url")))))
|
||||
(st/emit! (notif/error (tr "dashboard.import.no-perms"))))
|
||||
|
||||
(binding [storage/*sync* true]
|
||||
(swap! storage/session dissoc :template-url))))))
|
||||
(swap! storage/session dissoc :template))))))
|
||||
|
||||
(mf/defc dashboard*
|
||||
{::mf/props :obj}
|
||||
[{:keys [profile project-id team-id search-term plugin-url template-url section]}]
|
||||
[{:keys [profile project-id team-id search-term plugin-url template section]}]
|
||||
(let [team (mf/deref refs/team)
|
||||
projects (mf/deref refs/projects)
|
||||
|
||||
@@ -263,7 +258,7 @@
|
||||
(filterv #(= team-id (:team-id %)))))
|
||||
|
||||
can-edit? (dm/get-in team [:permissions :can-edit])
|
||||
template-url (or template-url (:template-url storage/session))
|
||||
template (or template (:template storage/session))
|
||||
plugin-url (or plugin-url (:plugin-url storage/session))
|
||||
|
||||
default-project
|
||||
@@ -289,7 +284,7 @@
|
||||
(events/unlistenByKey key))))
|
||||
|
||||
(use-plugin-register plugin-url team-id (:id default-project))
|
||||
(use-templates-import can-edit? template-url default-project)
|
||||
(use-templates-import can-edit? template default-project)
|
||||
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
[:> modal-container*]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.media :as cm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.modal :as modal]
|
||||
@@ -121,7 +122,7 @@
|
||||
(fn [event]
|
||||
(let [id (-> (dom/get-current-target event)
|
||||
(dom/get-data "id")
|
||||
(parse-uuid))
|
||||
(uuid/parse))
|
||||
item (get fonts id)]
|
||||
(on-upload* item))))
|
||||
|
||||
@@ -132,7 +133,7 @@
|
||||
(let [target (dom/get-current-target event)
|
||||
id (-> target
|
||||
(dom/get-data "id")
|
||||
(parse-uuid))
|
||||
(uuid/parse))
|
||||
name (dom/get-value target)]
|
||||
(when-not (str/blank? name)
|
||||
(swap! fonts* df/rename-and-regroup id name installed-fonts)))))
|
||||
@@ -143,7 +144,7 @@
|
||||
(let [target (dom/get-current-target event)
|
||||
id (-> target
|
||||
(dom/get-data "id")
|
||||
(parse-uuid))
|
||||
(uuid/parse))
|
||||
name (dom/get-value target)]
|
||||
(swap! fonts* update id assoc :font-family-tmp name))))
|
||||
|
||||
@@ -153,7 +154,7 @@
|
||||
(fn [event]
|
||||
(let [id (-> (dom/get-current-target event)
|
||||
(dom/get-data "id")
|
||||
(parse-uuid))]
|
||||
(uuid/parse))]
|
||||
(swap! fonts* dissoc id))))
|
||||
|
||||
on-upload-all
|
||||
@@ -344,7 +345,7 @@
|
||||
(fn [event]
|
||||
(let [id (-> (dom/get-current-target event)
|
||||
(dom/get-data "id")
|
||||
(parse-uuid))
|
||||
(uuid/parse))
|
||||
options {:type :confirm
|
||||
:title (tr "modals.delete-font-variant.title")
|
||||
:message (tr "modals.delete-font-variant.message")
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.features :as features]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.refs :as refs]
|
||||
@@ -29,7 +28,7 @@
|
||||
[app.main.ui.dashboard.file-menu :refer [file-menu*]]
|
||||
[app.main.ui.dashboard.import :refer [use-import-file]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.placeholder :refer [empty-placeholder loading-placeholder]]
|
||||
[app.main.ui.dashboard.placeholder :refer [empty-grid-placeholder* loading-placeholder*]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as i]
|
||||
@@ -60,7 +59,7 @@
|
||||
(->> (wrk/ask! {:cmd :thumbnails/generate-for-file
|
||||
:revn revn
|
||||
:file-id file-id
|
||||
:features (features/get-team-enabled-features @st/state)})
|
||||
:features (get @st/state :features)})
|
||||
(rx/mapcat (fn [{:keys [fonts] :as result}]
|
||||
(->> (fonts/render-font-styles fonts)
|
||||
(rx/map (fn [styles]
|
||||
@@ -90,14 +89,16 @@
|
||||
|
||||
(mf/with-effect [file-id revn visible? thumbnail-id]
|
||||
(when (and visible? (not thumbnail-id))
|
||||
(->> (ask-for-thumbnail file-id revn)
|
||||
(rx/subs! (fn [thumbnail-id]
|
||||
(st/emit! (dd/set-file-thumbnail file-id thumbnail-id)))
|
||||
(fn [cause]
|
||||
(log/error :hint "unable to render thumbnail"
|
||||
:file-if file-id
|
||||
:revn revn
|
||||
:message (ex-message cause)))))))
|
||||
(let [subscription
|
||||
(->> (ask-for-thumbnail file-id revn)
|
||||
(rx/subs! (fn [thumbnail-id]
|
||||
(st/emit! (dd/set-file-thumbnail file-id thumbnail-id)))
|
||||
(fn [cause]
|
||||
(log/error :hint "unable to render thumbnail"
|
||||
:file-if file-id
|
||||
:revn revn
|
||||
:message (ex-message cause)))))]
|
||||
(partial rx/dispose! subscription))))
|
||||
|
||||
[:div {:class (stl/css :grid-item-th)
|
||||
:style {:background-color bg-color}
|
||||
@@ -512,7 +513,7 @@
|
||||
:ref node-ref}
|
||||
(cond
|
||||
(nil? files)
|
||||
[:& loading-placeholder]
|
||||
[:> loading-placeholder*]
|
||||
|
||||
(seq files)
|
||||
(for [[index slice] (d/enumerate (partition-all limit files))]
|
||||
@@ -529,12 +530,13 @@
|
||||
:can-edit can-edit}])])
|
||||
|
||||
:else
|
||||
[:& empty-placeholder
|
||||
[:> empty-grid-placeholder*
|
||||
{:limit limit
|
||||
:can-edit can-edit
|
||||
:create-fn create-fn
|
||||
:origin origin
|
||||
:project-id project-id
|
||||
:team-id team-id
|
||||
:on-finish-import on-finish-import}])]))
|
||||
|
||||
(mf/defc line-grid-row
|
||||
@@ -646,7 +648,7 @@
|
||||
:on-drop on-drop}
|
||||
(cond
|
||||
(nil? files)
|
||||
[:& loading-placeholder]
|
||||
[:> loading-placeholder*]
|
||||
|
||||
(seq files)
|
||||
[:& line-grid-row {:files files
|
||||
@@ -657,10 +659,11 @@
|
||||
:limit limit}]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder
|
||||
{:dragging? @dragging?
|
||||
[:> empty-grid-placeholder*
|
||||
{:is-dragging @dragging?
|
||||
:limit limit
|
||||
:can-edit can-edit
|
||||
:create-fn create-fn
|
||||
:project-id project-id
|
||||
:team-id team-id
|
||||
:on-finish-import on-finish-import}])]))
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.errors :as errors]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
@@ -162,29 +161,32 @@
|
||||
|
||||
(defn- analyze-entries
|
||||
[state entries]
|
||||
(->> (uw/ask-many!
|
||||
{:cmd :analyze-import
|
||||
:files entries
|
||||
:features @features/features-ref})
|
||||
(rx/mapcat #(rx/delay emit-delay (rx/of %)))
|
||||
(rx/filter some?)
|
||||
(rx/subs!
|
||||
(fn [message]
|
||||
(swap! state update-with-analyze-result message)))))
|
||||
(let [features (get @st/state :features)]
|
||||
(->> (uw/ask-many!
|
||||
{:cmd :analyze-import
|
||||
:files entries
|
||||
:features features})
|
||||
(rx/mapcat #(rx/delay emit-delay (rx/of %)))
|
||||
(rx/filter some?)
|
||||
(rx/subs!
|
||||
(fn [message]
|
||||
(swap! state update-with-analyze-result message))))))
|
||||
|
||||
(defn- import-files
|
||||
[state project-id entries]
|
||||
(st/emit! (ptk/data-event ::ev/event {::ev/name "import-files"
|
||||
:num-files (count entries)}))
|
||||
(->> (uw/ask-many!
|
||||
{:cmd :import-files
|
||||
:project-id project-id
|
||||
:files entries
|
||||
:features @features/features-ref})
|
||||
(rx/filter (comp uuid? :file-id))
|
||||
(rx/subs!
|
||||
(fn [message]
|
||||
(swap! state update-entry-status message)))))
|
||||
|
||||
(let [features (get @st/state :features)]
|
||||
(->> (uw/ask-many!
|
||||
{:cmd :import-files
|
||||
:project-id project-id
|
||||
:files entries
|
||||
:features features})
|
||||
(rx/filter (comp uuid? :file-id))
|
||||
(rx/subs!
|
||||
(fn [message]
|
||||
(swap! state update-entry-status message))))))
|
||||
|
||||
(mf/defc import-entry*
|
||||
{::mf/props :obj
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
(ns app.main.ui.dashboard.placeholder
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.import :as udi]
|
||||
@@ -16,50 +15,92 @@
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc empty-placeholder-projects*
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [on-create on-finish-import project-id] :as props}]
|
||||
(mf/defc empty-project-placeholder*
|
||||
{::mf/private true}
|
||||
[{:keys [on-create on-finish-import project-id]}]
|
||||
(let [file-input (mf/use-ref nil)
|
||||
on-add-library (mf/use-fn
|
||||
(fn [_]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "explore-libraries-click"
|
||||
::ev/origin "dashboard"
|
||||
:section "empty-placeholder-projects"}))
|
||||
(dom/open-new-window "https://penpot.app/penpothub/libraries-templates")))
|
||||
on-import-files (mf/use-fn #(dom/click (mf/ref-val file-input)))]
|
||||
|
||||
on-add-library
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "explore-libraries-click"
|
||||
::ev/origin "dashboard"
|
||||
:section "empty-placeholder-projects"}))
|
||||
(dom/open-new-window "https://penpot.app/penpothub/libraries-templates")))
|
||||
|
||||
on-import
|
||||
(mf/use-fn #(dom/click (mf/ref-val file-input)))]
|
||||
|
||||
[:div {:class (stl/css :empty-project-container)}
|
||||
[:div {:class (stl/css :empty-project-card) :on-click on-create :title (tr "dashboard.add-file")}
|
||||
[:div {:class (stl/css :empty-project-card-title)} (tr "dashboard.empty-project.create")]
|
||||
[:div {:class (stl/css :empty-project-card-subtitle)} (tr "dashboard.empty-project.start")]]
|
||||
[:div {:class (stl/css :empty-project-card)
|
||||
:on-click on-create
|
||||
:title (tr "dashboard.add-file")}
|
||||
[:div {:class (stl/css :empty-project-card-title)}
|
||||
(tr "dashboard.empty-project.create")]
|
||||
[:div {:class (stl/css :empty-project-card-subtitle)}
|
||||
(tr "dashboard.empty-project.start")]]
|
||||
|
||||
[:div {:class (stl/css :empty-project-card) :on-click on-import-files :title (tr "dashboard.empty-project.import")}
|
||||
[:div {:class (stl/css :empty-project-card-title)} (tr "dashboard.empty-project.import")]
|
||||
[:div {:class (stl/css :empty-project-card-subtitle)} (tr "dashboard.empty-project.import-penpot")]]
|
||||
[:div {:class (stl/css :empty-project-card)
|
||||
:on-click on-import
|
||||
:title (tr "dashboard.empty-project.import")}
|
||||
[:div {:class (stl/css :empty-project-card-title)}
|
||||
(tr "dashboard.empty-project.import")]
|
||||
[:div {:class (stl/css :empty-project-card-subtitle)}
|
||||
(tr "dashboard.empty-project.import-penpot")]]
|
||||
|
||||
[:div {:class (stl/css :empty-project-card) :on-click on-add-library :title (tr "dashboard.empty-project.go-to-libraries")}
|
||||
[:div {:class (stl/css :empty-project-card-title)} (tr "dashboard.empty-project.add-library")]
|
||||
[:div {:class (stl/css :empty-project-card-subtitle)} (tr "dashboard.empty-project.explore")]]
|
||||
[:div {:class (stl/css :empty-project-card)
|
||||
:on-click on-add-library
|
||||
:title (tr "dashboard.empty-project.go-to-libraries")}
|
||||
[:div {:class (stl/css :empty-project-card-title)}
|
||||
(tr "dashboard.empty-project.add-library")]
|
||||
[:div {:class (stl/css :empty-project-card-subtitle)}
|
||||
(tr "dashboard.empty-project.explore")]]
|
||||
|
||||
[:& udi/import-form {:ref file-input
|
||||
:project-id project-id
|
||||
:on-finish-import on-finish-import}]]))
|
||||
|
||||
(mf/defc empty-placeholder
|
||||
[{:keys [dragging? limit origin create-fn can-edit project-id on-finish-import]}]
|
||||
(defn- make-has-other-files-or-projects-ref
|
||||
"Return a ref that resolves to true or false if there are at least some
|
||||
file or some project (a part of the default) exists; this determines
|
||||
if we need to show a complete placeholder or the small one."
|
||||
[team-id]
|
||||
(l/derived (fn [state]
|
||||
(or (let [projects (get state :projects)]
|
||||
(some (fn [[_ project]]
|
||||
(and (= (:team-id project) team-id)
|
||||
(not (:is-default project))))
|
||||
projects))
|
||||
(let [files (get state :files)]
|
||||
(some (fn [[_ file]]
|
||||
(= (:team-id file) team-id))
|
||||
files))))
|
||||
st/state))
|
||||
|
||||
(mf/defc empty-grid-placeholder*
|
||||
[{:keys [is-dragging limit origin create-fn can-edit team-id project-id on-finish-import]}]
|
||||
(let [on-click
|
||||
(mf/use-fn
|
||||
(mf/deps create-fn)
|
||||
(fn [_]
|
||||
(create-fn "dashboard:empty-folder-placeholder")))
|
||||
show-text (mf/use-state nil)
|
||||
on-mouse-enter (mf/use-fn #(reset! show-text true))
|
||||
on-mouse-leave (mf/use-fn #(reset! show-text nil))]
|
||||
|
||||
show-text* (mf/use-state nil)
|
||||
show-text? (deref show-text*)
|
||||
|
||||
on-mouse-enter (mf/use-fn #(reset! show-text* true))
|
||||
on-mouse-leave (mf/use-fn #(reset! show-text* nil))
|
||||
|
||||
has-other* (mf/with-memo [team-id]
|
||||
(make-has-other-files-or-projects-ref team-id))
|
||||
has-other? (mf/deref has-other*)]
|
||||
|
||||
(cond
|
||||
(true? dragging?)
|
||||
(true? is-dragging)
|
||||
[:ul
|
||||
{:class (stl/css :grid-row :no-wrap)
|
||||
:style {:grid-template-columns (str "repeat(" limit ", 1fr)")}}
|
||||
@@ -79,22 +120,24 @@
|
||||
:tag-name "span"}])]
|
||||
|
||||
:else
|
||||
(if (cf/external-feature-flag "add-file-02" "test")
|
||||
[:> empty-placeholder-projects* {:on-create on-click :on-finish-import on-finish-import :project-id project-id}]
|
||||
(if-not has-other?
|
||||
[:> empty-project-placeholder*
|
||||
{:on-create on-click
|
||||
:on-finish-import on-finish-import
|
||||
:project-id project-id}]
|
||||
[:div {:class (stl/css :grid-empty-placeholder)}
|
||||
(if (cf/external-feature-flag "add-file-01" "test")
|
||||
[:button {:class (stl/css :create-new)
|
||||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}
|
||||
(if @show-text (tr "dashboard.empty-project.create") i/add)]
|
||||
[:button {:class (stl/css :create-new)
|
||||
:on-click on-click}
|
||||
i/add])]))))
|
||||
[:button {:class (stl/css :create-new)
|
||||
:on-click on-click
|
||||
:on-mouse-enter on-mouse-enter
|
||||
:on-mouse-leave on-mouse-leave}
|
||||
(if show-text?
|
||||
(tr "dashboard.empty-project.create")
|
||||
i/add)]]))))
|
||||
|
||||
(mf/defc loading-placeholder
|
||||
(mf/defc loading-placeholder*
|
||||
[]
|
||||
[:> loader* {:width 32
|
||||
:title (tr "labels.loading")
|
||||
:class (stl/css :placeholder-loader)}
|
||||
[:span {:class (stl/css :placeholder-text)} (tr "dashboard.loading-files")]])
|
||||
[:span {:class (stl/css :placeholder-text)}
|
||||
(tr "dashboard.loading-files")]])
|
||||
|
||||
@@ -371,6 +371,7 @@
|
||||
show-team-hero?
|
||||
can-invite))}
|
||||
(for [{:keys [id] :as project} projects]
|
||||
;; FIXME: refactor this, looks inneficient
|
||||
(let [files (when recent-map
|
||||
(->> (vals recent-map)
|
||||
(filterv #(= id (:project-id %)))
|
||||
|
||||
@@ -284,7 +284,6 @@
|
||||
(let [team-id (-> (dom/get-current-target event)
|
||||
(dom/get-data "value")
|
||||
(uuid/parse))]
|
||||
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
|
||||
|
||||
handle-select-default
|
||||
@@ -963,13 +962,14 @@
|
||||
(dom/open-new-window "https://penpot.app/pricing")))]
|
||||
|
||||
[:*
|
||||
[:button {:class (stl/css :upgrade-plan-section)
|
||||
:on-click on-power-up-click}
|
||||
[:div {:class (stl/css :penpot-free)}
|
||||
[:span (tr "dashboard.upgrade-plan.penpot-free")]
|
||||
[:span {:class (stl/css :no-limits)} (tr "dashboard.upgrade-plan.no-limits")]]
|
||||
[:div {:class (stl/css :power-up)}
|
||||
(tr "dashboard.upgrade-plan.power-up")]]
|
||||
(when (contains? cf/flags :subscriptions-old)
|
||||
[:button {:class (stl/css :upgrade-plan-section)
|
||||
:on-click on-power-up-click}
|
||||
[:div {:class (stl/css :penpot-free)}
|
||||
[:span (tr "dashboard.upgrade-plan.penpot-free")]
|
||||
[:span {:class (stl/css :no-limits)} (tr "dashboard.upgrade-plan.no-limits")]]
|
||||
[:div {:class (stl/css :power-up)}
|
||||
(tr "dashboard.upgrade-plan.power-up")]])
|
||||
(when (and team profile)
|
||||
[:& comments-section
|
||||
{:profile profile
|
||||
|
||||
@@ -1045,7 +1045,7 @@
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
|
||||
(mf/with-effect [team]
|
||||
(mf/with-effect []
|
||||
(st/emit! (dtm/fetch-webhooks)))
|
||||
|
||||
[:*
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as IconStories from "./icon.stories"
|
||||
|
||||
# Iconography
|
||||
|
||||
See the [list of all available icons](?path=/story/foundations-icons--all-icons).
|
||||
See the [list of all available icons](?path=/story/foundations-assets-icon--all).
|
||||
|
||||
## Variants
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
[:class {:optional true} :string]
|
||||
[:variant {:optional true}
|
||||
[:maybe [:enum "default" "error"]]]
|
||||
[:accept-label {:optional true} :string]
|
||||
[:cancel-label {:optional true} :string]
|
||||
[:on-accept {:optional true} [:fn fn?]]
|
||||
[:on-cancel {:optional true} [:fn fn?]]])
|
||||
[:accept-label {:optional true} [:maybe :string]]
|
||||
[:cancel-label {:optional true} [:maybe :string]]
|
||||
[:on-accept {:optional true} [:maybe [:fn fn?]]]
|
||||
[:on-cancel {:optional true} [:maybe [:fn fn?]]]])
|
||||
|
||||
(mf/defc actionable*
|
||||
{::mf/schema schema:actionable}
|
||||
@@ -45,9 +45,13 @@
|
||||
|
||||
[:> :aside props
|
||||
[:div {:class (stl/css :notification-message)} children]
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click on-cancel}
|
||||
cancel-label]
|
||||
[:> button* {:variant (if (= variant "default") "primary" "destructive")
|
||||
:on-click on-accept}
|
||||
accept-label]]))
|
||||
|
||||
(when cancel-label
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click on-cancel}
|
||||
cancel-label])
|
||||
|
||||
(when accept-label
|
||||
[:> button* {:variant (if (= variant "default") "primary" "destructive")
|
||||
:on-click on-accept}
|
||||
accept-label])]))
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
.text-row {
|
||||
@extend .attr-row;
|
||||
height: unset;
|
||||
min-height: $s-32;
|
||||
:global(.attr-value) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ref:notification
|
||||
(def ^:private ref:notification
|
||||
(l/derived :notification st/state))
|
||||
|
||||
(mf/defc current-notification
|
||||
(mf/defc current-notification*
|
||||
[]
|
||||
(let [notification (mf/deref ref:notification)
|
||||
on-close (mf/use-fn #(st/emit! (ntf/hide)))
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
(binding [storage/*sync* true]
|
||||
(when (some? template)
|
||||
(swap! storage/session assoc
|
||||
:template-url template))
|
||||
:template template))
|
||||
(when (some? plugin)
|
||||
(swap! storage/session assoc
|
||||
:plugin-url plugin))))
|
||||
|
||||
@@ -202,9 +202,8 @@
|
||||
cancel-text])
|
||||
[:button {:on-click on-click} button-text]]]]))
|
||||
|
||||
(mf/defc request-access
|
||||
{::mf/props :obj}
|
||||
[{:keys [file-id team-id is-default workspace?]}]
|
||||
(mf/defc request-access*
|
||||
[{:keys [file-id team-id is-default is-workspace]}]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
requested* (mf/use-state {:sent false :already-requested false})
|
||||
requested (deref requested*)
|
||||
@@ -227,11 +226,11 @@
|
||||
|
||||
on-request-access
|
||||
(mf/use-fn
|
||||
(mf/deps file-id team-id workspace?)
|
||||
(mf/deps file-id team-id is-workspace)
|
||||
(fn []
|
||||
(let [params (if (some? file-id)
|
||||
{:file-id file-id
|
||||
:is-viewer (not workspace?)}
|
||||
:is-viewer (not is-workspace)}
|
||||
{:team-id team-id})
|
||||
mdata {:on-success on-success
|
||||
:on-error on-error}]
|
||||
@@ -240,7 +239,7 @@
|
||||
|
||||
[:*
|
||||
(if (some? file-id)
|
||||
(if workspace?
|
||||
(if is-workspace
|
||||
[:div {:class (stl/css :workspace)}
|
||||
[:div {:class (stl/css :workspace-left)}
|
||||
i/logo-icon
|
||||
@@ -341,7 +340,7 @@
|
||||
[:div {:class (stl/css :sign-info)}
|
||||
[:button {:on-click handle-retry} (tr "labels.retry")]]]))
|
||||
|
||||
(mf/defc service-unavailable
|
||||
(mf/defc service-unavailable*
|
||||
[]
|
||||
(let [on-click (mf/use-fn #(st/emit! (rt/assign-exception nil)))]
|
||||
[:> error-container* {}
|
||||
@@ -350,58 +349,55 @@
|
||||
[:div {:class (stl/css :sign-info)}
|
||||
[:button {:on-click on-click} (tr "labels.retry")]]]))
|
||||
|
||||
(defn generate-report
|
||||
(defn- generate-report
|
||||
[data]
|
||||
(try
|
||||
(let [team-id (:current-team-id @st/state)
|
||||
profile-id (:profile-id @st/state)
|
||||
|
||||
trace (:app.main.errors/trace data)
|
||||
instance (:app.main.errors/instance data)
|
||||
content (with-out-str
|
||||
(println "Hint: " (or (:hint data) (ex-message instance) "--"))
|
||||
(println "Prof ID:" (str (or profile-id "--")))
|
||||
(println "Team ID:" (str (or team-id "--")))
|
||||
instance (:app.main.errors/instance data)]
|
||||
(with-out-str
|
||||
(println "Hint: " (or (:hint data) (ex-message instance) "--"))
|
||||
(println "Prof ID:" (str (or profile-id "--")))
|
||||
(println "Team ID:" (str (or team-id "--")))
|
||||
|
||||
(when-let [file-id (:file-id data)]
|
||||
(println "File ID:" (str file-id)))
|
||||
(when-let [file-id (:file-id data)]
|
||||
(println "File ID:" (str file-id)))
|
||||
|
||||
(println)
|
||||
(println)
|
||||
|
||||
(println "Data:")
|
||||
(loop [data data]
|
||||
(-> (d/without-qualified data)
|
||||
(dissoc :explain)
|
||||
(d/update-when :data (constantly "(...)"))
|
||||
(pp/pprint {:level 8 :length 10}))
|
||||
(println "Data:")
|
||||
(loop [data data]
|
||||
(-> (d/without-qualified data)
|
||||
(dissoc :explain)
|
||||
(d/update-when :data (constantly "(...)"))
|
||||
(pp/pprint {:level 8 :length 10}))
|
||||
|
||||
(println)
|
||||
(println)
|
||||
|
||||
(when-let [explain (:explain data)]
|
||||
(print explain))
|
||||
(when-let [explain (:explain data)]
|
||||
(print explain))
|
||||
|
||||
(when (and (= :server-error (:type data))
|
||||
(contains? data :data))
|
||||
(recur (:data data))))
|
||||
(when (and (= :server-error (:type data))
|
||||
(contains? data :data))
|
||||
(recur (:data data))))
|
||||
|
||||
(println "Trace:")
|
||||
(println trace)
|
||||
(println)
|
||||
(println "Trace:")
|
||||
(println trace)
|
||||
(println)
|
||||
|
||||
(println "Last events:")
|
||||
(pp/pprint @st/last-events {:length 200})
|
||||
(println "Last events:")
|
||||
(pp/pprint @st/last-events {:length 200})
|
||||
|
||||
(println))]
|
||||
(wapi/create-blob content "text/plain"))
|
||||
(println)))
|
||||
(catch :default cause
|
||||
(.error js/console "error on generating report.txt" cause)
|
||||
nil)))
|
||||
|
||||
(mf/defc internal-error*
|
||||
{::mf/props :obj}
|
||||
[{:keys [data on-reset] :as props}]
|
||||
[{:keys [on-reset report] :as props}]
|
||||
(let [report-uri (mf/use-ref nil)
|
||||
report (mf/use-memo (mf/deps data) #(generate-report data))
|
||||
on-reset (or on-reset #(st/emit! (rt/assign-exception nil)))
|
||||
|
||||
on-download
|
||||
@@ -413,8 +409,8 @@
|
||||
|
||||
(mf/with-effect [report]
|
||||
(when (some? report)
|
||||
|
||||
(let [uri (wapi/create-uri report)]
|
||||
(let [report (wapi/create-blob report "text/plain")
|
||||
uri (wapi/create-uri report)]
|
||||
(mf/set-ref-val! report-uri uri)
|
||||
(fn []
|
||||
(wapi/revoke-uri uri)))))
|
||||
@@ -455,6 +451,38 @@
|
||||
(rx/of default)
|
||||
(rx/throw cause)))))))
|
||||
|
||||
(mf/defc exception-section*
|
||||
{::mf/private true}
|
||||
[{:keys [data route] :as props}]
|
||||
(let [type (get data :type)
|
||||
report (mf/with-memo [data]
|
||||
(generate-report data))
|
||||
props (mf/spread-props props {:report report})]
|
||||
|
||||
(mf/with-effect [data route report]
|
||||
(let [params (:query-params route)
|
||||
params (u/map->query-string params)]
|
||||
(st/emit! (ptk/data-event ::ev/event
|
||||
{::ev/name "exception-page"
|
||||
:type (get data :type :unknown)
|
||||
:hint (get data :hint)
|
||||
:path (get route :path)
|
||||
:report report
|
||||
:params params}))))
|
||||
(case type
|
||||
:not-found
|
||||
[:> not-found* {}]
|
||||
|
||||
:authentication
|
||||
[:> not-found* {}]
|
||||
|
||||
:bad-gateway
|
||||
[:> bad-gateway* props]
|
||||
|
||||
:service-unavailable
|
||||
[:> service-unavailable*]
|
||||
|
||||
[:> internal-error* props])))
|
||||
|
||||
(mf/defc exception-page*
|
||||
{::mf/props :obj}
|
||||
@@ -477,42 +505,23 @@
|
||||
|
||||
request-access?
|
||||
(and
|
||||
(or (= (:type data) :not-found)
|
||||
(= (:type data) :authentication))
|
||||
(or (= type :not-found)
|
||||
(= type :authentication))
|
||||
(or workspace? dashboard? view?)
|
||||
(or (:file-id info)
|
||||
(:team-id info)))]
|
||||
|
||||
(mf/with-effect [type path params]
|
||||
(st/emit! (ptk/data-event ::ev/event
|
||||
{::ev/name "exception-page"
|
||||
:type type
|
||||
:path path
|
||||
:params (u/map->query-string params)})))
|
||||
|
||||
(mf/with-effect [params info]
|
||||
(when-not (:loaded info)
|
||||
(->> (load-info params)
|
||||
(rx/subs! (partial reset! info*)))))
|
||||
(rx/subs! (partial reset! info*)
|
||||
(partial reset! info* {:loaded true})))))
|
||||
|
||||
(when loaded?
|
||||
(if request-access?
|
||||
[:& request-access {:file-id (:file-id info)
|
||||
:team-id (:team-id info)
|
||||
:is-default (:team-default info)
|
||||
:workspace? workspace?}]
|
||||
[:> request-access* {:file-id (:file-id info)
|
||||
:team-id (:team-id info)
|
||||
:is-default (:team-default info)
|
||||
:is-workspace workspace?}]
|
||||
[:> exception-section* props]))))
|
||||
|
||||
(case (:type data)
|
||||
:not-found
|
||||
[:> not-found* {}]
|
||||
|
||||
:authentication
|
||||
[:> not-found* {}]
|
||||
|
||||
:bad-gateway
|
||||
[:> bad-gateway* props]
|
||||
|
||||
:service-unavailable
|
||||
[:& service-unavailable]
|
||||
|
||||
[:> internal-error* props])))))
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
padding-right: 0 $s-8 $s-40 $s-8;
|
||||
transition: transform 400ms ease 300ms;
|
||||
z-index: $z-index-2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dc]
|
||||
[app.main.data.event :as ev]
|
||||
@@ -104,7 +105,7 @@
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
checked? (dom/checked? target)
|
||||
page-id (parse-uuid (dom/get-data target "page-id"))
|
||||
page-id (uuid/parse (dom/get-data target "page-id"))
|
||||
dif-pages? (not= page-id (first (:pages options)))
|
||||
no-one-page (< 1 (count (:pages options)))
|
||||
should-change? (or ^boolean no-one-page
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.persistence :as dps]
|
||||
[app.main.data.plugins :as dpl]
|
||||
[app.main.data.workspace :as dw]
|
||||
@@ -45,9 +46,10 @@
|
||||
(mf/defc workspace-content*
|
||||
{::mf/private true}
|
||||
[{:keys [file layout page wglobal]}]
|
||||
|
||||
(let [palete-size (mf/use-state nil)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
page-id (:id page)
|
||||
page-id (get page :id)
|
||||
|
||||
{:keys [vport] :as wlocal} (mf/deref refs/workspace-local)
|
||||
{:keys [options-mode]} wglobal
|
||||
@@ -120,10 +122,46 @@
|
||||
:overlay true
|
||||
:file-loading true}])
|
||||
|
||||
(defn- make-team-ref
|
||||
[team-id]
|
||||
(l/derived (fn [state]
|
||||
(let [teams (get state :teams)]
|
||||
(get teams team-id)))
|
||||
st/state))
|
||||
|
||||
(defn- make-file-ref
|
||||
[file-id]
|
||||
(l/derived (fn [state]
|
||||
;; NOTE: for ensure ordering of execution, we need to
|
||||
;; wait the file initialization completly success until
|
||||
;; mark this file availablea and unlock the rendering
|
||||
;; of the following components
|
||||
(when (= (get state :current-file-id) file-id)
|
||||
(let [files (get state :files)
|
||||
file (get files file-id)]
|
||||
(-> file
|
||||
(dissoc :data)
|
||||
(assoc ::has-data (contains? file :data))))))
|
||||
st/state))
|
||||
|
||||
(defn- make-page-ref
|
||||
[file-id page-id]
|
||||
(l/derived (fn [state]
|
||||
(let [current-page-id (get state :current-page-id)]
|
||||
;; NOTE: for ensure ordering of execution, we need to
|
||||
;; wait the page initialization completly success until
|
||||
;; mark this file availablea and unlock the rendering
|
||||
;; of the following components
|
||||
(when (= current-page-id page-id)
|
||||
(dsh/lookup-page state file-id page-id))))
|
||||
st/state))
|
||||
|
||||
(mf/defc workspace-page*
|
||||
{::mf/private true}
|
||||
[{:keys [page-id file-id file layout wglobal]}]
|
||||
(let [page (mf/deref refs/workspace-page)]
|
||||
(let [page-ref (mf/with-memo [file-id page-id]
|
||||
(make-page-ref file-id page-id))
|
||||
page (mf/deref page-ref)]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [focus-out #(st/emit! (dw/workspace-focus-lost))
|
||||
@@ -133,8 +171,7 @@
|
||||
(mf/with-effect [file-id page-id]
|
||||
(st/emit! (dw/initialize-page file-id page-id))
|
||||
(fn []
|
||||
(when page-id
|
||||
(st/emit! (dw/finalize-page file-id page-id)))))
|
||||
(st/emit! (dw/finalize-page file-id page-id))))
|
||||
|
||||
(if (some? page)
|
||||
[:> workspace-content* {:file file
|
||||
@@ -143,18 +180,9 @@
|
||||
:layout layout}]
|
||||
[:> workspace-loader*])))
|
||||
|
||||
(def ^:private ref:file-without-data
|
||||
(l/derived (fn [file]
|
||||
(-> file
|
||||
(dissoc :data)
|
||||
(assoc ::has-data (contains? file :data))))
|
||||
refs/file
|
||||
=))
|
||||
|
||||
(mf/defc workspace*
|
||||
{::mf/props :obj
|
||||
::mf/wrap [mf/memo]}
|
||||
[{:keys [project-id file-id page-id layout-name]}]
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [team-id project-id file-id page-id layout-name]}]
|
||||
|
||||
(let [file-id (hooks/use-equal-memo file-id)
|
||||
page-id (hooks/use-equal-memo page-id)
|
||||
@@ -162,8 +190,15 @@
|
||||
layout (mf/deref refs/workspace-layout)
|
||||
wglobal (mf/deref refs/workspace-global)
|
||||
|
||||
team (mf/deref refs/team)
|
||||
file (mf/deref ref:file-without-data)
|
||||
team-ref (mf/with-memo [team-id]
|
||||
(make-team-ref team-id))
|
||||
file-ref (mf/with-memo [file-id]
|
||||
(make-file-ref file-id))
|
||||
|
||||
team (mf/deref team-ref)
|
||||
file (mf/deref file-ref)
|
||||
|
||||
file-loaded? (get file ::has-data)
|
||||
|
||||
file-name (:name file)
|
||||
permissions (:permissions team)
|
||||
@@ -187,14 +222,14 @@
|
||||
(when file-name
|
||||
(dom/set-html-title (tr "title.workspace" file-name))))
|
||||
|
||||
(mf/with-effect [file-id]
|
||||
(st/emit! (dw/initialize-workspace file-id))
|
||||
(mf/with-effect [team-id file-id]
|
||||
(st/emit! (dw/initialize-workspace team-id file-id))
|
||||
(fn []
|
||||
(st/emit! ::dps/force-persist
|
||||
(dw/finalize-workspace file-id))))
|
||||
(dw/finalize-workspace team-id file-id))))
|
||||
|
||||
(mf/with-effect [file page-id]
|
||||
(when-not page-id
|
||||
(mf/with-effect [file-id page-id file-loaded?]
|
||||
(when (and file-loaded? (not page-id))
|
||||
(st/emit! (dcm/go-to-workspace :file-id file-id ::rt/replace true))))
|
||||
|
||||
[:> (mf/provider ctx/current-project-id) {:value project-id}
|
||||
@@ -208,8 +243,7 @@
|
||||
:style {:background-color background-color
|
||||
:touch-action "none"}}
|
||||
[:> context-menu*]
|
||||
|
||||
(if (::has-data file)
|
||||
(if (and file-loaded? page-id)
|
||||
[:> workspace-page*
|
||||
{:page-id page-id
|
||||
:file-id file-id
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
.colorpicker {
|
||||
border-radius: $br-8;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.colorpicker-tabs {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as mdc]
|
||||
@@ -62,7 +63,7 @@
|
||||
(if (or (= event "recent")
|
||||
(= event "file"))
|
||||
(keyword event)
|
||||
(parse-uuid event)))))
|
||||
(uuid/parse event)))))
|
||||
|
||||
valid-color?
|
||||
(mf/use-fn
|
||||
@@ -124,6 +125,7 @@
|
||||
[:div {:class (stl/css :select-wrapper)}
|
||||
[:& select
|
||||
{:class (stl/css :shadow-type-select)
|
||||
:data-direction "up"
|
||||
:default-value (or (d/name selected) "recent")
|
||||
:options options
|
||||
:on-change on-library-change}]]
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
(fn [event]
|
||||
(let [library-id (some-> (dom/get-current-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(uuid/parse))]
|
||||
(reset! selected library-id)
|
||||
(st/emit! (dwl/link-file-to-library file-id library-id)))))
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
(fn [event]
|
||||
(let [library-id (some-> (dom/get-current-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(uuid/parse))]
|
||||
(when (= library-id @selected)
|
||||
(reset! selected :file))
|
||||
(st/emit! (dwl/unlink-file-from-library file-id library-id)
|
||||
@@ -451,7 +451,7 @@
|
||||
(when-not updating?
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(uuid/parse))]
|
||||
(st/emit!
|
||||
(dwl/set-updating-library true)
|
||||
(dwl/sync-file file-id library-id))))))]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as mdc]
|
||||
@@ -87,7 +88,7 @@
|
||||
value (dom/get-attribute node "data-palette")]
|
||||
(on-select (if (or (= "file" value) (= "recent" value))
|
||||
(keyword value)
|
||||
(parse-uuid value))))))
|
||||
(uuid/parse value))))))
|
||||
|
||||
on-select-text-palette-menu
|
||||
(mf/use-fn
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
@@ -220,6 +221,7 @@
|
||||
:on-blur input-blur
|
||||
:on-key-down input-key-down
|
||||
:auto-focus true
|
||||
:max-length max-input-length
|
||||
:default-value (cfh/merge-path-item (:path color) (:name color))}]
|
||||
|
||||
[:div {:title (if (= (:name color) default-name)
|
||||
|
||||
@@ -251,14 +251,14 @@
|
||||
(mf/deps index update-interaction)
|
||||
(fn [event]
|
||||
(let [value event
|
||||
value (when (not= value "") (uuid/uuid value))]
|
||||
value (when (not= value "") (uuid/parse value))]
|
||||
(update-interaction index #(ctsi/set-destination % value)))))
|
||||
|
||||
change-position-relative-to
|
||||
(mf/use-fn
|
||||
(mf/deps index update-interaction)
|
||||
(fn [event]
|
||||
(let [value (uuid/uuid event)]
|
||||
(let [value (uuid/parse event)]
|
||||
(update-interaction index #(ctsi/set-position-relative-to % value)))))
|
||||
|
||||
change-preserve-scroll
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
.custom-select-dropdown {
|
||||
@extend .dropdown-wrapper;
|
||||
margin-top: $s-2;
|
||||
max-height: 70vh;
|
||||
width: $s-252;
|
||||
.dropdown-element {
|
||||
@extend .dropdown-element-base;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.text :as txt]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.fonts :as fts]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
@@ -483,6 +484,7 @@
|
||||
:type "text"
|
||||
:ref name-input-ref
|
||||
:default-value (:name typography)
|
||||
:max-length max-input-length
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-name-blur}]
|
||||
|
||||
@@ -615,6 +617,7 @@
|
||||
:type "text"
|
||||
:ref name-input-ref
|
||||
:default-value (:name typography)
|
||||
:max-length max-input-length
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-name-blur}]]
|
||||
[:div
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
(mf/deps on-pin-snapshot)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (-> (dom/get-data node "id") uuid/uuid)]
|
||||
id (-> (dom/get-data node "id") uuid/parse)]
|
||||
(when on-pin-snapshot (on-pin-snapshot id)))))
|
||||
|
||||
handle-restore-snapshot
|
||||
@@ -161,7 +161,7 @@
|
||||
(mf/deps on-restore-snapshot)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (-> (dom/get-data node "id") uuid/uuid)]
|
||||
id (-> (dom/get-data node "id") uuid/parse)]
|
||||
(when on-restore-snapshot (on-restore-snapshot id)))))
|
||||
|
||||
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
border: $s-1 solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent);
|
||||
border-radius: $s-8;
|
||||
overflow-y: auto;
|
||||
max-height: $s-452;
|
||||
}
|
||||
|
||||
.sets-count-empty-button {
|
||||
|
||||
@@ -65,6 +65,11 @@
|
||||
(st/emit! (ptk/data-event ::ev/event {::ev/name "create-token-set" :name name})
|
||||
(dt/create-token-set name))))
|
||||
|
||||
(defn group-edition-id
|
||||
"Prefix editing groups `edition-id` so it can be differentiated from sets with the same id."
|
||||
[edition-id]
|
||||
(str "group-" edition-id))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; COMPONENTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -166,16 +171,18 @@
|
||||
{:position (dom/get-client-position event)
|
||||
:is-group true
|
||||
:id id
|
||||
:edition-id (group-edition-id id)
|
||||
:path tree-path})))))
|
||||
|
||||
on-collapse-click
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(on-toggle-collapse tree-path)))
|
||||
|
||||
on-double-click
|
||||
(mf/use-fn (mf/deps id) #(on-start-edition id))
|
||||
(mf/use-fn (mf/deps id) #(on-start-edition (group-edition-id id)))
|
||||
|
||||
on-checkbox-click
|
||||
(mf/use-fn
|
||||
@@ -267,6 +274,7 @@
|
||||
{:position (dom/get-client-position event)
|
||||
:is-group false
|
||||
:id id
|
||||
:edition-id id
|
||||
:path tree-path})))))
|
||||
|
||||
on-double-click
|
||||
@@ -398,7 +406,7 @@
|
||||
:is-active (is-token-set-group-active path)
|
||||
:is-selected false
|
||||
:is-draggable is-draggable
|
||||
:is-editing (= edition-id id)
|
||||
:is-editing (= edition-id (group-edition-id id))
|
||||
:is-collapsed (collapsed? path)
|
||||
:on-select on-select
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
(mf/defc menu*
|
||||
{::mf/private true}
|
||||
[{:keys [is-group id path]}]
|
||||
[{:keys [is-group id edition-id path]}]
|
||||
(let [create-set-at-path
|
||||
(mf/use-fn (mf/deps path) #(st/emit! (dt/start-token-set-creation path)))
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(fn []
|
||||
(st/emit! (dt/start-token-set-edition id))))
|
||||
(st/emit! (dt/start-token-set-edition edition-id))))
|
||||
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
(mf/defc token-set-context-menu*
|
||||
[]
|
||||
(let [{:keys [position is-group id path]}
|
||||
(let [{:keys [position is-group id edition-id path]}
|
||||
(mf/deref ref:token-sets-context-menu)
|
||||
|
||||
position-top
|
||||
@@ -78,4 +78,5 @@
|
||||
:on-context-menu prevent-default}
|
||||
[:> menu* {:is-group is-group
|
||||
:id id
|
||||
:edition-id edition-id
|
||||
:path path}]]]))
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.workspace.tokens.errors :as wte]
|
||||
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
|
||||
[app.main.ui.workspace.tokens.token :as wtt]
|
||||
@@ -268,11 +267,11 @@
|
||||
(cond
|
||||
(and single-set?
|
||||
(= :json-format/legacy json-format))
|
||||
(ctob/decode-single-set-legacy-json (ctob/ensure-tokens-lib (deref refs/tokens-lib)) file-name json-data)
|
||||
(ctob/decode-single-set-legacy-json (ctob/ensure-tokens-lib nil) file-name json-data)
|
||||
|
||||
(and single-set?
|
||||
(= :json-format/dtcg json-format))
|
||||
(ctob/decode-single-set-json (ctob/ensure-tokens-lib (deref refs/tokens-lib)) file-name json-data)
|
||||
(ctob/decode-single-set-json (ctob/ensure-tokens-lib nil) file-name json-data)
|
||||
|
||||
(= :json-format/legacy json-format)
|
||||
(ctob/decode-legacy-json (ctob/ensure-tokens-lib nil) json-data)
|
||||
|
||||
@@ -468,10 +468,7 @@
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(let [point (gpt/point (.-clientX event) (.-clientY event))
|
||||
viewport-coord (uwvv/point->viewport point)
|
||||
asset-id (-> (dnd/get-data event "text/asset-id") uuid/uuid)
|
||||
asset-name (dnd/get-data event "text/asset-name")
|
||||
asset-type (dnd/get-data event "text/asset-type")]
|
||||
viewport-coord (uwvv/point->viewport point)]
|
||||
(cond
|
||||
(dnd/has-type? event "penpot/shape")
|
||||
(let [shape (dnd/get-data event "penpot/shape")
|
||||
@@ -516,25 +513,6 @@
|
||||
(assoc params :blobs (map wapi/data-uri->blob data)))]
|
||||
(st/emit! (dwm/upload-media-workspace params)))
|
||||
|
||||
;; Will trigger when the user drags an SVG asset from the assets panel
|
||||
(and (dnd/has-type? event "text/asset-id") (= asset-type "image/svg+xml"))
|
||||
(let [path (cfg/resolve-file-media {:id asset-id})
|
||||
params {:file-id (:id file)
|
||||
:position viewport-coord
|
||||
:uris [path]
|
||||
:name asset-name
|
||||
:mtype asset-type}]
|
||||
(st/emit! (dwm/upload-media-workspace params)))
|
||||
|
||||
;; Will trigger when the user drags an image from the assets SVG
|
||||
(dnd/has-type? event "text/asset-id")
|
||||
(let [params {:file-id (:id file)
|
||||
:object-id asset-id
|
||||
:name asset-name}]
|
||||
(st/emit! (dwm/clone-media-object
|
||||
(with-meta params
|
||||
{:on-success #(st/emit! (dwm/image-uploaded % viewport-coord))}))))
|
||||
|
||||
;; Will trigger when the user drags a file from their file explorer into the viewport
|
||||
;; Or the user pastes an image
|
||||
;; Or the user uploads an image using the image tool
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
[]
|
||||
(let [worker (uw/init cf/worker-uri err/on-error)]
|
||||
(uw/ask! worker {:cmd :configure
|
||||
:key :public-uri
|
||||
:val cf/public-uri})
|
||||
:config {:public-uri cf/public-uri
|
||||
:build-data cf/build-date
|
||||
:version cf/version}})
|
||||
|
||||
(set! instance worker)))
|
||||
|
||||
(defn ask!
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[app.main.data.exports.files :as exports.files]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.versions :as dwv]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.worker :as uw]
|
||||
@@ -237,7 +236,7 @@
|
||||
|
||||
:else
|
||||
(let [file (u/locate-file id)
|
||||
features (features/get-team-enabled-features @st/state)
|
||||
features (:features @st/state)
|
||||
team-id (:current-team-id @st/state)
|
||||
format (case format
|
||||
"zip" :legacy-zip
|
||||
|
||||
@@ -969,7 +969,7 @@
|
||||
|
||||
:else
|
||||
(let [file-id (:current-file-id @st/state)
|
||||
library-id (uuid/uuid library-id)]
|
||||
library-id (uuid/parse library-id)]
|
||||
(->> st/stream
|
||||
(rx/filter (ptk/type? ::dwl/attach-library-finished))
|
||||
(rx/take 1)
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
(u/display-not-valid :getShapeById shape-id)
|
||||
|
||||
:else
|
||||
(let [shape-id (uuid/uuid shape-id)
|
||||
(let [shape-id (uuid/parse shape-id)
|
||||
shape (u/locate-shape file-id id shape-id)]
|
||||
(when (some? shape)
|
||||
(shape/shape-proxy plugin-id file-id id shape-id)))))
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
(defn parse-id
|
||||
[id]
|
||||
(when id (uuid/uuid id)))
|
||||
(when id (uuid/parse id)))
|
||||
|
||||
(defn parse-keyword
|
||||
[kw]
|
||||
|
||||
@@ -432,7 +432,7 @@
|
||||
(let [id (obj/get self "$id")
|
||||
value (mapv #(shadow-defaults (parser/parse-shadow %)) value)]
|
||||
(cond
|
||||
(not (sm/validate [:vector ::ctss/shadow] value))
|
||||
(not (sm/validate [:vector ctss/schema:shadow] value))
|
||||
(u/display-not-valid :shadows value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (features/initialize (or features #{}))))))
|
||||
(rx/of (features/initialize features)))))
|
||||
|
||||
(defn- fetch-team
|
||||
[& {:keys [file-id]}]
|
||||
@@ -98,7 +98,7 @@
|
||||
(ptk/reify ::fetch-objects-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)]
|
||||
(let [features (get state :features)]
|
||||
(->> (rx/zip
|
||||
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
|
||||
(repo/cmd! :get-page {:file-id file-id
|
||||
@@ -237,7 +237,7 @@
|
||||
(ptk/reify ::fetch-components-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)]
|
||||
(let [features (get state :features)]
|
||||
(->> (repo/cmd! :get-file {:id file-id :features features})
|
||||
(rx/map (fn [file] #(assoc % :file file))))))))
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
|
||||
(defn ^:export init
|
||||
[]
|
||||
(st/emit! (features/initialize))
|
||||
(init-ui))
|
||||
|
||||
(defn reinit
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
[headers]
|
||||
(into {} (map vec) (seq (.entries ^js headers))))
|
||||
|
||||
(def default-headers
|
||||
(defn default-headers
|
||||
[]
|
||||
{"x-frontend-version" (:full cfg/version)})
|
||||
|
||||
(defn fetch
|
||||
@@ -74,7 +75,7 @@
|
||||
|
||||
headers (cond-> headers
|
||||
(not omit-default-headers)
|
||||
(d/merge default-headers))
|
||||
(merge (default-headers)))
|
||||
|
||||
headers (-update-headers body headers)
|
||||
|
||||
|
||||
@@ -104,7 +104,9 @@
|
||||
|
||||
(defn send!
|
||||
[ws msg]
|
||||
(-send ws (t/encode-str msg)))
|
||||
(if *assert*
|
||||
(-send ws (t/encode-str msg {:type :json-verbose}))
|
||||
(-send ws (t/encode-str msg))))
|
||||
|
||||
(defn close!
|
||||
[ws]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user