Compare commits

...

35 Commits

Author SHA1 Message Date
Andrey Antukh
c2b13a6d5d 📚 Update changelog 2025-04-29 14:46:15 +02:00
Andrey Antukh
6935d54870 Merge branch 'main' into staging 2025-04-28 08:43:54 +02:00
Andrey Antukh
65e8526ee2 Merge tag '2.6.2-RC4' 2025-04-28 08:43:04 +02:00
Andrés Moya
202762027f 🐛 Handle swapped nested instances when detaching 2025-04-25 10:00:14 +02:00
Andrés Moya
d95551e651 🔧 Add debug traces to detach copy operation 2025-04-25 10:00:14 +02:00
Xaviju
c96fbfdcd6 📚 Update tokens changelog for 2.6.2 (#6364)
Co-authored-by: Xavier Julian <xaviju@proton.me>
2025-04-24 13:29:56 +02:00
Alejandro Alonso
6e5d64d403 Merge pull request #6362 from penpot/niwinz-staging-bugfixes-2
🐛 Add migration for fix old broken root shapes (file migration)
2025-04-24 09:28:04 +02:00
Andrey Antukh
3e0c2bf1a1 🐛 Add migration for fix root shape 2025-04-24 09:17:33 +02:00
Andrey Antukh
283cdee5d6 Ensure consistency on using d/update-vals on file migrations 2025-04-24 08:55:54 +02:00
Andrey Antukh
ab5e01e54a Ensure we don't leave :components with nil on file data
after aplying migrations
2025-04-24 08:53:30 +02:00
Alejandro Alonso
373248e304 Merge pull request #6360 from penpot/niwinz-staging-bugfixes-2
🐛 Fix issues on file data migration handling
2025-04-24 07:30:50 +02:00
Andrey Antukh
80308ceafa 🐛 Make http cache aware of missing file data migrations 2025-04-23 18:15:33 +02:00
Andrey Antukh
f65518f865 🐛 Fix incorrect migration application after binfile import 2025-04-23 18:10:52 +02:00
Alejandro Alonso
f155042958 Merge pull request #6345 from penpot/niwinz-staging-add-interaction-cleaning
🐛 Add migration for decoding and cleaning shape interactions
2025-04-23 08:08:31 +02:00
Andrey Antukh
1dd23a3f47 🐛 Invalidate http cache on apply migrations to file on read operation 2025-04-23 07:57:56 +02:00
Andrey Antukh
1194e40222 🐛 Properly dispose rx subscription on grid thumbnail component 2025-04-22 21:39:57 +02:00
Andrey Antukh
05fac41534 🐛 Remove feature checking from get-file-data-for-thumbnail rpc method
The prev code has feature resolution race condition and it
in reallity does not need that check.
2025-04-22 21:38:40 +02:00
Andrey Antukh
3f85e89f62 🐛 Send frontend version on worker http requests 2025-04-22 21:26:51 +02:00
Alonso Torres
ee0f8ad19a 🐛 Fix horizontal scroll in viewer (#6347) 2025-04-22 21:03:45 +02:00
Andrey Antukh
b7d7cf233a Fix shadow colors on import penpot files 2025-04-22 19:58:10 +02:00
Alonso Torres
bd208c31e2 🐛 Fix update layout on component restore (#6348) 2025-04-22 18:46:21 +02:00
Andrey Antukh
151dc352c8 Don't register shadow schema
It is not really necessary, we can use the
schema var directly.
2025-04-22 17:21:52 +02:00
Andrey Antukh
ccbf17106d 🐛 Add migration for decoding and cleaning shape interactions 2025-04-22 15:04:22 +02:00
Andrey Antukh
95c4d95fd3 📎 Use d/update-vals instead of update-vals on migrations 2025-04-22 15:01:33 +02:00
Andrey Antukh
a72c07b657 Merge pull request #6309 from penpot/niwinz-staging-bugfixes-2
🐛 Several bugfixes
2025-04-22 09:15:02 +02:00
Andrey Antukh
708492afeb 💄 Add mainly cosmetic changes to dashboard placeholder components 2025-04-17 09:20:35 +02:00
Andrey Antukh
1305ab3cc6 🐛 Fix issue with empty placeholder on team change 2025-04-17 09:20:34 +02:00
Andrey Antukh
29cc6b4f9c Print the current seed on test.check fail 2025-04-17 09:20:34 +02:00
Andrey Antukh
cc7f0b145c 🐛 Make shape interaction properly decode on binfile import 2025-04-17 09:20:34 +02:00
Andrey Antukh
e69c0c3e27 Make schema uuid parsing fns private 2025-04-17 09:20:34 +02:00
Andrey Antukh
a209966427 🐛 Don't use schema uuid parsing function on websocket ns 2025-04-17 09:20:34 +02:00
Andrey Antukh
d5abbd4220 📎 Add missing entries on the changelog 2025-04-17 09:20:32 +02:00
Pablo Alba
70a23a14c4 🐛 Fix allow moving a main component into another 2025-04-16 22:54:30 +02:00
Marina López
93c81ea49c 🐛 Fix pricing CTA to be under a config flag (#6304) 2025-04-16 17:17:47 +02:00
andrés gonzález
e004671346 📚 Update Recommended storage info (#6275) 2025-04-11 14:02:35 +02:00
27 changed files with 519 additions and 286 deletions

View File

@@ -1,12 +1,21 @@
# CHANGELOG
## 2.6.2 (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

View File

@@ -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"

View File

@@ -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 \

View 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)))

View File

@@ -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)

View File

@@ -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

View File

@@ -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)))

View File

@@ -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,7 +210,6 @@
(fmg/migrate-file)))]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file)))
{:file-id file-id

View File

@@ -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"]))

View File

@@ -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))

View File

@@ -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)

View File

@@ -390,20 +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]
(try
(uuid/parse s)
(catch #?(:clj Exception :cljs :default) _cause
s)))
(if (uuid? s)
s
(if (str/empty? s)
nil
(try
(uuid/parse s)
(catch #?(:clj Exception :cljs :default) _cause
s)))))
(defn encode-uuid
(defn- encode-uuid
[v]
(when (uuid? v)
(str v)))
(if (uuid? v)
(str v)
v))
(register!
{:type ::uuid
@@ -862,7 +864,7 @@
choices))]
{:pred pred
:type-properties
{:title "contains"
{:title "contains any"
:description "contains predicate"}}))})
(register!

View File

@@ -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)))

View File

@@ -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))))))

View File

@@ -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]]

View File

@@ -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)

View File

@@ -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))

View File

@@ -1,13 +1,11 @@
---
title: 1.1 Recommended Settings
title: 1.1 Recommended storage
---
# Recommended settings
# Recommended storage
To self-host Penpot, youll 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.

View File

@@ -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

View File

@@ -28,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]
@@ -89,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}
@@ -511,7 +513,7 @@
:ref node-ref}
(cond
(nil? files)
[:& loading-placeholder]
[:> loading-placeholder*]
(seq files)
(for [[index slice] (d/enumerate (partition-all limit files))]
@@ -528,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
@@ -645,7 +648,7 @@
:on-drop on-drop}
(cond
(nil? files)
[:& loading-placeholder]
[:> loading-placeholder*]
(seq files)
[:& line-grid-row {:files files
@@ -656,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}])]))

View File

@@ -8,7 +8,6 @@
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.event :as ev]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.dashboard.import :as udi]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
@@ -16,51 +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))
files (mf/deref refs/files)]
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)")}}
@@ -80,18 +120,24 @@
:tag-name "span"}])]
:else
(if (= (count files) 0)
[:> 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)}
[: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)]]))))
(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")]])

View File

@@ -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 %)))

View File

@@ -962,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

View File

@@ -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 {

View File

@@ -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"))

View File

@@ -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)

View File

@@ -42,12 +42,11 @@
:http-body body})))
(defn- request-data-for-thumbnail
[file-id revn features]
[file-id revn]
(let [path "api/rpc/command/get-file-data-for-thumbnail"
params {:file-id file-id
:revn revn
:strip-frames-with-thumbnails true
:features features}
:strip-frames-with-thumbnails true}
request {:method :get
:uri (u/join cf/public-uri path)
:credentials "include"
@@ -86,6 +85,6 @@
nil)))
(defmethod impl/handler :thumbnails/generate-for-file
[{:keys [file-id revn features] :as message} _]
(->> (request-data-for-thumbnail file-id revn features)
[{:keys [file-id revn] :as message} _]
(->> (request-data-for-thumbnail file-id revn)
(rx/map render-thumbnail)))