diff --git a/CHANGES.md b/CHANGES.md index ff6096035c..e8fb2aa482 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,21 @@ # CHANGELOG +## 2.6.2 (Unreleased) + +### :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) + ## 2.6.1 ### :bug: Bugs fixed @@ -58,6 +74,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!) diff --git a/backend/scripts/repl b/backend/scripts/repl index 6da57ebcc1..558a68b955 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -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" diff --git a/backend/scripts/start-dev b/backend/scripts/start-dev index 9fe2ccb1b4..dae3af23a7 100755 --- a/backend/scripts/start-dev +++ b/backend/scripts/start-dev @@ -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 \ diff --git a/backend/src/app/binfile/cleaner.clj b/backend/src/app/binfile/cleaner.clj new file mode 100644 index 0000000000..1f4d29ea1f --- /dev/null +++ b/backend/src/app/binfile/cleaner.clj @@ -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))) diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj index dc6bf7b80b..07ba4618dd 100644 --- a/backend/src/app/binfile/v3.clj +++ b/backend/src/app/binfile/v3.clj @@ -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) diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 0668a4a0b2..4cb0aea3e0 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -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]]} diff --git a/backend/src/app/http/websocket.clj b/backend/src/app/http/websocket.clj index bcedf31cea..a93fd33cb3 100644 --- a/backend/src/app/http/websocket.clj +++ b/backend/src/app/http/websocket.clj @@ -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 diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 39b3d6c029..117a432ab8 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -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))) diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj index f4c5e3d140..2455807dd8 100644 --- a/backend/src/app/rpc/commands/files_thumbnails.clj +++ b/backend/src/app/rpc/commands/files_thumbnails.clj @@ -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 diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 6914ff54dd..e55a0bfdc2 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -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 _}] diff --git a/common/src/app/common/data/undo_stack.cljc b/common/src/app/common/data/undo_stack.cljc index 61117cb2ee..dbcae0db14 100644 --- a/common/src/app/common/data/undo_stack.cljc +++ b/common/src/app/common/data/undo_stack.cljc @@ -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}] diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 734b3e4299..d307756f05 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -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"])) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index cba12a9cde..dbc39e5041 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -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)) diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index 6d89efd89e..64eaa1f9b9 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -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! diff --git a/common/src/app/common/schema/test.cljc b/common/src/app/common/schema/test.cljc index 7fa774dd15..c3b38b0a2b 100644 --- a/common/src/app/common/schema/test.cljc +++ b/common/src/app/common/schema/test.cljc @@ -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))) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 372b0ce1b0..6b714fb465 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -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)))))) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 5033965dba..fc70ff61e1 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -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]] diff --git a/common/src/app/common/types/shape/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc index 111bba5cbc..2547f5528a 100644 --- a/common/src/app/common/types/shape/interactions.cljc +++ b/common/src/app/common/types/shape/interactions.cljc @@ -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) diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index 1b37dd3e98..c00a1ce829 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -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)) + diff --git a/common/src/app/common/uuid.cljc b/common/src/app/common/uuid.cljc index 707770fcd9..9b21f8f796 100644 --- a/common/src/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -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 [] diff --git a/docker/devenv/files/nginx.conf b/docker/devenv/files/nginx.conf index 1777f8a88e..c24c2c7685 100644 --- a/docker/devenv/files/nginx.conf +++ b/docker/devenv/files/nginx.conf @@ -156,10 +156,13 @@ http { } location / { - location ~ ^/github/penpot-files/(?[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; diff --git a/docker/images/files/nginx.conf b/docker/images/files/nginx.conf index 72e231c261..38278d2875 100644 --- a/docker/images/files/nginx.conf +++ b/docker/images/files/nginx.conf @@ -135,10 +135,13 @@ http { } location / { - location ~ ^/github/penpot-files/(?[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; diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js index 9988fe1ead..73e8488316 100644 --- a/frontend/playwright/ui/specs/tokens.spec.js +++ b/frontend/playwright/ui/specs/tokens.spec.js @@ -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); diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 05be149ac8..36e22caef7 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -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)) diff --git a/frontend/src/app/libs/render.cljs b/frontend/src/app/libs/render.cljs index 26e6cfe5d6..7ece057c01 100644 --- a/frontend/src/app/libs/render.cljs +++ b/frontend/src/app/libs/render.cljs @@ -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 diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index 0d7677f821..e4205f9bd7 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -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" diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index dbd96a2900..c18444623a 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -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]))] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 3e1fcba1ba..01dc099c8a 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 2f73ce76cc..9c98fcd939 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -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 diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index f67fb755a3..143dec77a1 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -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 diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 2955543540..1aa3c833e3 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -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)] diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 438597de7d..ad42a3738b 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -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*] diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index bcda5f4b0a..c03aa5bebf 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -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") diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index fd5804760b..450bcd0f33 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -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}])])) diff --git a/frontend/src/app/main/ui/dashboard/placeholder.cljs b/frontend/src/app/main/ui/dashboard/placeholder.cljs index ce6177dacc..8d6f369561 100644 --- a/frontend/src/app/main/ui/dashboard/placeholder.cljs +++ b/frontend/src/app/main/ui/dashboard/placeholder.cljs @@ -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")]]) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 3aaa590f81..cf013de2ab 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -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 %))) diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 46dd0d270a..0a6d07c8b9 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -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 diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index f2a5df0005..57886b1e30 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -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)))) diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index ebe78c50d7..d81d5959ac 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -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]))))) diff --git a/frontend/src/app/main/ui/viewer.scss b/frontend/src/app/main/ui/viewer.scss index 7e475a227b..31194da31b 100644 --- a/frontend/src/app/main/ui/viewer.scss +++ b/frontend/src/app/main/ui/viewer.scss @@ -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 { diff --git a/frontend/src/app/main/ui/viewer/share_link.cljs b/frontend/src/app/main/ui/viewer/share_link.cljs index ddda62a0fb..1db0b902b0 100644 --- a/frontend/src/app/main/ui/viewer/share_link.cljs +++ b/frontend/src/app/main/ui/viewer/share_link.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index cb703ef5a9..4996eb9d14 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 0c6a8b8feb..40de66b24a 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -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))))))] diff --git a/frontend/src/app/main/ui/workspace/palette.cljs b/frontend/src/app/main/ui/workspace/palette.cljs index 431f97935c..ff13a57b13 100644 --- a/frontend/src/app/main/ui/workspace/palette.cljs +++ b/frontend/src/app/main/ui/workspace/palette.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs index 22e2421fc7..ae2193a23b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss index 2660148083..22d3b7914b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss @@ -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; diff --git a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs index 323d60a433..a4173adabc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs @@ -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))))) diff --git a/frontend/src/app/main/ui/workspace/tokens/modals/themes.scss b/frontend/src/app/main/ui/workspace/tokens/modals/themes.scss index 9277b720be..f7e15d6abc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.scss +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.scss @@ -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 { diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index dc8f5364dc..0ff9f15831 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs index b4b4f5015e..029244de5b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs @@ -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}]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index b22a8bda79..42d23a2bff 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -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 diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index a269d2ab52..bd430ce729 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -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) diff --git a/frontend/src/app/plugins/page.cljs b/frontend/src/app/plugins/page.cljs index 373a9b93c5..ba7713f42b 100644 --- a/frontend/src/app/plugins/page.cljs +++ b/frontend/src/app/plugins/page.cljs @@ -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))))) diff --git a/frontend/src/app/plugins/parser.cljs b/frontend/src/app/plugins/parser.cljs index 0ce6aad418..caaa697f74 100644 --- a/frontend/src/app/plugins/parser.cljs +++ b/frontend/src/app/plugins/parser.cljs @@ -13,7 +13,7 @@ (defn parse-id [id] - (when id (uuid/uuid id))) + (when id (uuid/parse id))) (defn parse-keyword [kw] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 5b1fe0467d..f05981d1d4 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -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")) diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index 47f3823668..98177688cd 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -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) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 950c98ffba..f2ea47fabe 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -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) diff --git a/frontend/src/app/worker/import/parser.cljs b/frontend/src/app/worker/import/parser.cljs index 27ad5f1149..8bf5158eee 100644 --- a/frontend/src/app/worker/import/parser.cljs +++ b/frontend/src/app/worker/import/parser.cljs @@ -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) diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index 658429c198..cca294cb0e 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -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))) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 4d635b99b5..e934f851b5 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -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)))