diff --git a/CHANGES.md b/CHANGES.md index d2072c307..508f9a635 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,11 @@ - 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 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 ## 2.6.1 diff --git a/backend/src/app/http/websocket.clj b/backend/src/app/http/websocket.clj index bcedf31ce..a93fd33cb 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/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index b6f1a49c4..23d32e3e7 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -390,20 +390,22 @@ (register! :merge (mu/-merge)) (register! :union (mu/-union)) -(def uuid-rx - #"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") - -(defn parse-uuid +(defn- parse-uuid [s] - (try - (uuid/parse s) - (catch #?(:clj Exception :cljs :default) _cause - s))) + (if (uuid? s) + s + (if (str/empty? s) + nil + (try + (uuid/parse s) + (catch #?(:clj Exception :cljs :default) _cause + s))))) -(defn encode-uuid +(defn- encode-uuid [v] - (when (uuid? v) - (str v))) + (if (uuid? v) + (str v) + v)) (register! {:type ::uuid diff --git a/common/src/app/common/schema/test.cljc b/common/src/app/common/schema/test.cljc index 7fa774dd1..c3b38b0a2 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 23373b152..bfb6d09e2 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -479,7 +479,7 @@ ;; 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?)) ;; When pasting frames, children have the frames and their children @@ -489,7 +489,13 @@ ;; Are all the top-children a main-instance of a component? all-main? - (->> top-children (every? #(ctk/main-instance? %))) + (every? ctk/main-instance? top-children) + + any-main-descendant + (some + (fn [shape] + (some ctk/main-instance? (cfh/get-children-with-self objects (:id shape)))) + children) ;; Are all the top-children a main-instance of a cutted component? all-comp-cut? @@ -501,6 +507,8 @@ (every? :deleted)))] (if (or no-changes? (and (not (invalid-structure-for-component? objects parent children pasting? libraries)) + ;; If we are moving into a main component, no descendant can be main + (or (nil? any-main-descendant) (not (ctk/main-instance? parent))) ;; If we are moving into a variant-container, all the items should be main ;; so if we are pasting, only allow main instances that are cut-and-pasted (or (not (ctk/is-variant-container? parent)) diff --git a/common/src/app/common/types/shape/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc index 111bba5cb..2547f5528 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/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 21d4c8581..3fd373103 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] @@ -511,7 +511,7 @@ :ref node-ref} (cond (nil? files) - [:& loading-placeholder] + [:> loading-placeholder*] (seq files) (for [[index slice] (d/enumerate (partition-all limit files))] @@ -528,12 +528,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 +646,7 @@ :on-drop on-drop} (cond (nil? files) - [:& loading-placeholder] + [:> loading-placeholder*] (seq files) [:& line-grid-row {:files files @@ -656,10 +657,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 ce6177dac..8d6f36956 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 3aaa590f8..cf013de2a 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 %)))