Compare commits

...

50 Commits
2.6.1 ... 2.6.2

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
Alejandro Alonso
ddc41027ab Merge pull request #6316 from penpot/palba-fix-instanciate-component
🐛 Fix error while drag an drop a component to the canvas
2025-04-16 13:15:07 +02:00
Pablo Alba
4f931fbe6a 🐛 Fix error while drag an drop a component to the canvas 2025-04-16 13:05:56 +02:00
Andrey Antukh
b49a4734ff 🐛 Fix srepl helper for restore file snapshots 2025-04-15 11:03:50 +02:00
Alejandro Alonso
2aaa2f3033 🐛 Fix template import (#6299) 2025-04-15 10:39:22 +02:00
Alejandro Alonso
202b9f3075 Merge pull request #6284 from penpot/niwinz-staging-several-bugfixes
🐛 Several bugfixes and enhacements
2025-04-15 10:33:59 +02:00
Andrey Antukh
be0814cdac Improve internal error reporting 2025-04-14 13:26:12 +02:00
Andrey Antukh
80d719353c Make auth data available before request parsing
For properly report profile-id
2025-04-14 09:23:41 +02:00
Andrey Antukh
fa3fc12594 Sanitize uuid on the rest of code 2025-04-14 09:23:29 +02:00
Andrey Antukh
422a9db07b Sanitize uuid parsing on legacy zip import code 2025-04-14 09:13:35 +02:00
Andrey Antukh
a4145a30f5 🐛 Fix uuid encode/decode on schema 2025-04-14 09:13:34 +02:00
andrés gonzález
e004671346 📚 Update Recommended storage info (#6275) 2025-04-11 14:02:35 +02:00
Andrey Antukh
38e5c161e7 Sanitize plugins uuid parsing 2025-04-11 13:21:26 +02:00
Andrey Antukh
a7c1f7ba69 🐛 Fix incorrect undo handling on path edition 2025-04-11 08:54:02 +02:00
Florian Schroedl
e9755d437e 🐛 Fix sets and set groups with same name cannot be renamed 2025-04-10 13:27:49 +02:00
Eva Marco
e5db66351e 🐛 Fix scroll on token themes modal (#6251)
* 🐛 Fix scroll on token themes modal

* 🐛 Fix collapse set group error
2025-04-10 10:25:08 +02:00
ºelhombretecla
89153eef23 🎉 Increase height presets dropdown (#6185)
* 🎉 Add new measures dropdown height

* 🎉 Add enhancement to CHANGES.md
2025-04-10 10:01:52 +02:00
62 changed files with 834 additions and 520 deletions

View File

@@ -1,5 +1,22 @@
# CHANGELOG
## 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
@@ -58,6 +75,7 @@
- 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
### :heart: Community contributions (Thank you!)

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

@@ -155,9 +155,9 @@
[["" {:middleware [[mw/server-timing]
[mw/params]
[mw/format-response]
[mw/parse-request]
[session/soft-auth cfg]
[actoken/soft-auth cfg]
[mw/parse-request]
[mw/errors errors/handle]
[mw/restrict-methods]]}

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

@@ -337,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 _}]

View File

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

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,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!

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

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

View File

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

View File

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

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

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

View File

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

View 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

View File

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

View File

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

View File

@@ -369,7 +369,7 @@
(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)
@@ -382,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)
@@ -2445,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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
(defn parse-id
[id]
(when id (uuid/uuid id)))
(when id (uuid/parse id)))
(defn parse-keyword
[kw]

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

@@ -43,14 +43,14 @@
(defn read-json-key
[m]
(or (sm/parse-uuid m)
(or (uuid/parse m)
(json/read-kebab-key m)))
(defn read-json-val
[m]
(cond
(and (string? m)
(re-matches sm/uuid-rx m))
(re-matches uuid/regex m))
(uuid/uuid m)
(and (string? m)
@@ -521,8 +521,8 @@
id (resolve old-id)
path (get-in node [:attrs :penpot:path] "")
type (parser/get-type content)
main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] "")))
main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] "")))
main-instance-id (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-id] "")))
main-instance-page (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-page] "")))
data (-> (parser/parse-data type content)
(assoc :path path)
(assoc :id id)
@@ -547,12 +547,12 @@
old-id (parser/get-id node)
id (resolve old-id)
path (get-in node [:attrs :penpot:path] "")
main-instance-id (resolve (uuid (get-in node [:attrs :penpot:main-instance-id] "")))
main-instance-page (resolve (uuid (get-in node [:attrs :penpot:main-instance-page] "")))
main-instance-id (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-id] "")))
main-instance-page (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-page] "")))
main-instance-x (-> (get-in node [:attrs :penpot:main-instance-x] "") (d/parse-double))
main-instance-y (-> (get-in node [:attrs :penpot:main-instance-y] "") (d/parse-double))
main-instance-parent (resolve (uuid (get-in node [:attrs :penpot:main-instance-parent] "")))
main-instance-frame (resolve (uuid (get-in node [:attrs :penpot:main-instance-frame] "")))
main-instance-parent (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-parent] "")))
main-instance-frame (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-frame] "")))
type (parser/get-type content)
data (-> (parser/parse-data type content)

View File

@@ -20,9 +20,6 @@
(def url-regex
#"url\(#([^\)]*)\)")
(def uuid-regex
#"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}")
(def uuid-regex-prefix
#"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-")
@@ -84,7 +81,7 @@
(defn get-id
[node]
(let [attr-id (get-in node [:attrs :id])
id (when (string? attr-id) (re-find uuid-regex attr-id))]
id (when (string? attr-id) (re-find uuid/regex attr-id))]
(when (some? id)
(uuid/uuid id))))
@@ -189,10 +186,10 @@
[m]
(letfn [(convert [value]
(cond
(and (string? value) (re-matches uuid-regex value))
(and (string? value) (re-matches uuid/regex value))
(uuid/uuid value)
(and (keyword? value) (re-matches uuid-regex (d/name value)))
(and (keyword? value) (re-matches uuid/regex (d/name value)))
(uuid/uuid (d/name value))
(vector? value)
@@ -429,11 +426,11 @@
(defn add-library-refs
[props node]
(let [stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/uuid)
stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/uuid)
component-id (get-meta node :component-id uuid/uuid)
component-file (get-meta node :component-file uuid/uuid)
shape-ref (get-meta node :shape-ref uuid/uuid)
(let [stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/parse)
stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/parse)
component-id (get-meta node :component-id uuid/parse)
component-file (get-meta node :component-file uuid/parse)
shape-ref (get-meta node :shape-ref uuid/parse)
component-root? (get-meta node :component-root str->bool)
main-instance? (get-meta node :main-instance str->bool)
touched (get-meta node :touched parse-touched)]
@@ -463,8 +460,8 @@
[props node svg-data]
(let [fill (:fill svg-data)
fill-color-ref-id (get-meta node :fill-color-ref-id uuid/uuid)
fill-color-ref-file (get-meta node :fill-color-ref-file uuid/uuid)
fill-color-ref-id (get-meta node :fill-color-ref-id uuid/parse)
fill-color-ref-file (get-meta node :fill-color-ref-file uuid/parse)
meta-fill-color (get-meta node :fill-color)
meta-fill-opacity (get-meta node :fill-opacity)
meta-fill-color-gradient (if (str/starts-with? meta-fill-color "url#fill-color-gradient")
@@ -627,7 +624,7 @@
(let [attrs (-> node :attrs remove-penpot-prefix)]
{:id (uuid/next)
:name (-> attrs :name)
:starting-frame (-> attrs :starting-frame uuid)}))
:starting-frame (-> attrs :starting-frame uuid/parse)}))
(defn parse-flows [node]
(let [flows-node (get-data node :penpot:flows)]
@@ -638,7 +635,7 @@
id (uuid/next)]
[id
{:id id
:frame-id (when (:frame-id attrs) (-> attrs :frame-id uuid))
:frame-id (when (:frame-id attrs) (-> attrs :frame-id uuid/parse))
:axis (-> attrs :axis keyword)
:position (-> attrs :position d/parse-double)}]))
@@ -775,8 +772,8 @@
(parse-gradient node (get-meta fill-node :fill-color)))
:fill-image (when fill-image-id
(get images fill-image-id))
:fill-color-ref-file (get-meta fill-node :fill-color-ref-file uuid/uuid)
:fill-color-ref-id (get-meta fill-node :fill-color-ref-id uuid/uuid)
:fill-color-ref-file (get-meta fill-node :fill-color-ref-file uuid/parse)
:fill-color-ref-id (get-meta fill-node :fill-color-ref-id uuid/parse)
:fill-opacity (get-meta fill-node :fill-opacity d/parse-double)})))
(mapv d/without-nils)
(filterv #(not= (:fill-color %) "none")))]
@@ -800,8 +797,8 @@
(parse-gradient node (get-meta stroke-node :stroke-color)))
:stroke-image (when stroke-image-id
(get images stroke-image-id))
:stroke-color-ref-file (get-meta stroke-node :stroke-color-ref-file uuid/uuid)
:stroke-color-ref-id (get-meta stroke-node :stroke-color-ref-id uuid/uuid)
:stroke-color-ref-file (get-meta stroke-node :stroke-color-ref-file uuid/parse)
:stroke-color-ref-id (get-meta stroke-node :stroke-color-ref-id uuid/parse)
:stroke-opacity (get-meta stroke-node :stroke-opacity d/parse-double)
:stroke-style (get-meta stroke-node :stroke-style keyword)
:stroke-width (get-meta stroke-node :stroke-width d/parse-double)
@@ -993,7 +990,7 @@
align-self
justify-self
shapes]} (-> cell-node :attrs remove-penpot-prefix)
id (uuid/uuid id)]
id (uuid/parse id)]
[id (d/without-nils
{:id id
:area-name area-name
@@ -1006,7 +1003,7 @@
:justify-self (keyword justify-self)
:shapes (if (and (some? shapes) (d/not-empty? shapes))
(->> (str/split shapes " ")
(mapv uuid/uuid))
(mapv uuid/parse))
[])})])))
(into {}))))
@@ -1154,7 +1151,7 @@
(assoc :delay (get-meta node :delay d/parse-double))
(ctsi/has-destination interaction)
(assoc :destination (get-meta node :destination uuid/uuid)
(assoc :destination (get-meta node :destination uuid/parse)
:preserve-scroll (get-meta node :preserve-scroll str->bool))
(ctsi/has-url interaction)

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

View File

@@ -179,7 +179,7 @@
[state name]
(let [objects (dsh/lookup-page-objects state)
result (or (d/seek (fn [shape] (= name (:name shape))) (vals objects))
(get objects (uuid/uuid name)))]
(get objects (uuid/parse name)))]
result))
(defn ^:export dump-object
@@ -222,12 +222,12 @@
(defn ^:export select-by-object-id
[object-id]
(let [[_ page-id shape-id _] (str/split object-id #"/")]
(st/emit! (dcm/go-to-workspace :page-id (uuid/uuid page-id)))
(st/emit! (dws/select-shape (uuid/uuid shape-id)))))
(st/emit! (dcm/go-to-workspace :page-id (uuid/parse page-id)))
(st/emit! (dws/select-shape (uuid/parse shape-id)))))
(defn ^:export select-by-id
[shape-id]
(st/emit! (dws/select-shape (uuid/uuid shape-id))))
(st/emit! (dws/select-shape (uuid/parse shape-id))))
(defn dump-tree'
([state] (dump-tree' state false false false))
@@ -255,7 +255,7 @@
file (dsh/lookup-file state)
libraries (get state :files)
shape-id (if (some? shape-id)
(uuid/uuid shape-id)
(uuid/parse shape-id)
(first (dsh/lookup-selected state)))]
(if (some? shape-id)
(ctf/dump-subtree file page-id shape-id libraries {:show-ids show-ids
@@ -369,7 +369,7 @@
(let [file (dsh/lookup-file @st/state)
libraries (get @st/state :files)]
(try
(->> (if-let [shape-id (some-> shape-id parse-uuid)]
(->> (if-let [shape-id (some-> shape-id uuid/parse)]
(let [page (dm/get-in file [:data :pages-index (get @st/state :current-page-id)])]
(cfv/validate-shape shape-id file page libraries))
(cfv/validate-file file libraries))
@@ -426,6 +426,15 @@
[]
(st/emit! (dw/find-components-norefs)))
(defn- set-shape-ref*
[id shape-ref]
(ptk/reify ::set-shape-ref
ptk/WatchEvent
(watch [_ _ _]
(let [shape-id (uuid/parse id)
shape-ref (uuid/parse shape-ref)]
(rx/of (dw/update-shape shape-id {:shape-ref shape-ref}))))))
(defn ^:export set-shape-ref
[id shape-ref]
(st/emit! (dw/set-shape-ref id shape-ref)))
(st/emit! (set-shape-ref* id shape-ref)))