diff --git a/CHANGES.md b/CHANGES.md index f41128a23..c0850bc64 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -99,6 +99,17 @@ - Fix assets right click button for multiple selection [Taiga #5545](https://tree.taiga.io/project/penpot/issue/5545) - Fix problem with precision in resizes [Taiga #5623](https://tree.taiga.io/project/penpot/issue/5623) - Fix absolute positioned layouts not showing flex properties [Taiga #5630](https://tree.taiga.io/project/penpot/issue/5630) +- Fix text gradient handlers [Taiga #4047](https://tree.taiga.io/project/penpot/issue/4047) +- Fix when user deletes one file during import it is impossible to finish importing of second file [Taiga #5656](https://tree.taiga.io/project/penpot/issue/5656) +- Fix export multiple images when only one of them has export settings [Taiga #5649](https://tree.taiga.io/project/penpot/issue/5649) +- Fix error when a user different than the thread creator edits a comment [Taiga #5647](https://tree.taiga.io/project/penpot/issue/5647) +- Fix unnecessary button [Taiga #3312](https://tree.taiga.io/project/penpot/issue/3312) +- Fix copy color information in several formats [Taiga #4723](https://tree.taiga.io/project/penpot/issue/4723) +- Fix dropdown width [Taiga #5541](https://tree.taiga.io/project/penpot/issue/5541) +- Fix enable comment mode and insert image keeps on comment mode [Taiga #5678](https://tree.taiga.io/project/penpot/issue/5678) +- Fix enable undo just after using pencil [Taiga #5674](https://tree.taiga.io/project/penpot/issue/5674) +- Fix 400 error when user changes password [Taiga #5643](https://tree.taiga.io/project/penpot/issue/5643) +- Fix cannot undo layer styles [Taiga #5676](https://tree.taiga.io/project/penpot/issue/5676) ### :arrow_up: Deps updates diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index e91529a3b..b8352f622 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -468,8 +468,8 @@ {::doc/added "1.15"} [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id ::rpc/request-at id share-id content] :as params}] (db/with-atomic [conn pool] - (let [{:keys [thread-id] :as comment} (get-comment conn id ::db/for-update? true) - {:keys [file-id page-id owner-id] :as thread} (get-comment-thread conn thread-id ::db/for-update? true)] + (let [{:keys [thread-id owner-id] :as comment} (get-comment conn id ::db/for-update? true) + {:keys [file-id page-id] :as thread} (get-comment-thread conn thread-id ::db/for-update? true)] (files/check-comment-permissions! conn profile-id file-id share-id) diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 56b352ffc..c79e4c773 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -133,7 +133,8 @@ (def schema:update-profile-password [:map {:title "update-profile-password"} [:password [::sm/word-string {:max 500}]] - [:old-password [::sm/word-string {:max 500}]]]) + ;; Social registered users don't have old-password + [:old-password {:optional true} [:maybe [::sm/word-string {:max 500}]]]]) (sv/defmethod ::update-profile-password {:doc/added "1.0" diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 7460bb9d7..bf74b198b 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -776,6 +776,12 @@ [key (delay (generator-fn key))])) keys)) +(defn opacity-to-hex [opacity] + (let [opacity (* opacity 255) + value (mth/round opacity)] + (.. value + (toString 16) + (padStart 2 "0")))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; String Functions diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 192e82c9d..a3c207d0f 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -316,6 +316,22 @@ &:hover { border: 1px solid $color-gray-20; } + + &.no-check { + .custom-select-dropdown { + width: 100%; + min-width: unset; + .check-icon { + display: none; + } + li.checked-element { + padding-left: 0.5rem; + &.is-selected { + background-color: $color-primary; + } + } + } + } } .opened { border: 1px solid $color-primary; diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index f408a8fc4..2aa2eb5e4 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -416,6 +416,12 @@ span.element-name { .page-name { padding: 8px; margin-top: 8px; + color: #e3e3e3; + font-size: 0.875rem; + max-width: 90%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .icon-search { margin-top: 8px; diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index b1890d5ec..9112fe1a9 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -60,6 +60,15 @@ white-space: nowrap; } + span.pages-title { + color: #e3e3e3; + font-size: 0.875rem; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + span.tool-badge { border: 1px solid $color-primary; border-radius: $br2; diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index 225ddae8b..46bf3c752 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -322,7 +322,7 @@ (rx/concat (rx/of (partial fetched-comments comments)) - (->> (rx/from (map :file-id comments)) + (->> (rx/from (into #{} (map :file-id) comments)) (rx/merge-map #(rp/cmd! :get-profiles-for-file-comments {:file-id %})) (rx/reduce #(merge %1 (d/index-by :id %2)) {}) (rx/map #(partial fetched-users %)))))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 9e042aa9b..92d2e8bb1 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -337,7 +337,8 @@ [:map {:closed true} [:password-1 :string] [:password-2 :string] - [:password-old :string]]) + ;; Social registered users don't have old-password + [:password-old {:optional true} [:maybe :string]]]) (defn update-password [data] diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 5aecdc0b9..ec5270b25 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -129,7 +129,7 @@ (let [objects (wsh/lookup-page-objects state) edition (get-in state [:workspace-local :edition]) drawing (get state :workspace-drawing)] - (when-not (and (or (some? edition) (not-empty drawing)) + (when-not (and (or (some? edition) (some? (:object drawing))) (not (ctl/grid-layout? objects edition))) (let [undo (:workspace-undo state) items (:items undo) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 9e1c8f0ee..1ccdc27d1 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -182,11 +182,19 @@ (rx/of (shapes-changes-persisted-finished)))))) (rx/catch (fn [cause] - (rx/concat - (if (= :authentication (:type cause)) - (rx/empty) - (rx/of (rt/assign-exception cause))) - (rx/throw cause))))))))) + (cond + (= :authentication (:type cause)) + (rx/throw cause) + + (instance? js/TypeError cause) + (->> (rx/timer 2000) + (rx/map (fn [_] + (persist-changes file-id file-revn changes pending-commits)))) + + :else + (rx/concat + (rx/of (rt/assign-exception cause)) + (rx/throw cause)))))))))) ;; Event to be thrown after the changes have been persisted (defn shapes-changes-persisted-finished diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index ea60fa7b8..7bd072645 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -66,7 +66,8 @@ (defmethod ptk/handle-error :default [error] - (ts/schedule #(st/emit! (rt/assign-exception (::instance error)))) + (when-let [cause (::instance error)] + (ts/schedule #(st/emit! (rt/assign-exception cause)))) (print-group! "Unhandled Error" (fn [] (print-trace! error) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index baf740baa..a15e5c6b6 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -145,6 +145,7 @@ (mf/with-effect [theme] (dom/set-html-theme-color theme)) + [:& (mf/provider ctx/current-route) {:value route} [:& (mf/provider ctx/current-profile) {:value profile} (if edata diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 0b0cdff15..fe4651d8b 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -214,7 +214,11 @@ :select-on-focus true :on-change on-change}] [:div.buttons - [:input.btn-primary {:type "button" :value "Post" :on-click on-submit*}] + [:input.btn-primary {:type "button" + :value "Post" + :on-click on-submit* + :disabled (or (fm/all-spaces? @content) + (str/empty-or-nil? @content))}] [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]]])) (mf/defc comment-item diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 3356f736e..64f4e4103 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -481,3 +481,13 @@ (cond-> errors (all-spaces? (get data field)) (assoc field {:message error-msg})))) + +(defn validate-not-all-spaces + [field error-msg] + (fn [errors data] + (let [value (get data field)] + (cond-> errors + (and + (all-spaces? value) + (> (count value) 0)) + (assoc field {:message error-msg}))))) diff --git a/frontend/src/app/main/ui/dashboard/comments.cljs b/frontend/src/app/main/ui/dashboard/comments.cljs index 2da270779..dc92961dd 100644 --- a/frontend/src/app/main/ui/dashboard/comments.cljs +++ b/frontend/src/app/main/ui/dashboard/comments.cljs @@ -22,17 +22,12 @@ (mf/defc comments-section [{:keys [profile team]}] - - (mf/use-effect - (mf/deps team) - (fn [] - (st/emit! (dcm/retrieve-unread-comment-threads (:id team))))) - (let [show-dropdown? (mf/use-state false) show-dropdown (mf/use-fn #(reset! show-dropdown? true)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) threads-map (mf/deref refs/comment-threads) users (mf/deref refs/current-team-comments-users) + team-id (:id team) tgroups (->> (vals threads-map) (sort-by :modified-at) @@ -46,6 +41,11 @@ (st/emit! (-> (dwcm/navigate thread) (with-meta {::ev/origin "dashboard"})))))] + (mf/use-effect + (mf/deps team-id) + (fn [] + (st/emit! (dcm/retrieve-unread-comment-threads team-id)))) + (mf/use-effect (mf/deps @show-dropdown?) (fn [] diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 1aa8fbc73..bb26e8d0a 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -337,18 +337,18 @@ (st/emit! (modal/hide)) (when on-finish-import (on-finish-import)))) + files (->> (:files @state) (filterv (comp not :deleted?))) + num-importing (+ - (->> @state :files (filter #(= (:status %) :importing)) count) + (->> files (filter #(= (:status %) :importing)) count) (:importing-templates @state)) - warning-files (->> @state :files (filter #(and (= (:status %) :import-finish) (d/not-empty? (:errors %)))) count) - success-files (->> @state :files (filter #(and (= (:status %) :import-finish) (empty? (:errors %)))) count) - pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0) + warning-files (->> files (filter #(and (= (:status %) :import-finish) (d/not-empty? (:errors %)))) count) + success-files (->> files (filter #(and (= (:status %) :import-finish) (empty? (:errors %)))) count) + pending-analysis? (> (->> files (filter #(= (:status %) :analyzing)) count) 0) pending-import? (> num-importing 0) - files (->> (:files @state) (filterv (comp not :deleted?))) - ;; pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0) - ;; files (->> (:files @state) (filterv (comp not :deleted?))) + valid-files? (or (some? template) (> (+ (->> files (filterv (fn [x] (not= (:status x) :analyze-error))) count)) 0))] @@ -400,7 +400,7 @@ [:div.modal-footer [:div.action-buttons - (when (or (= :analyzing (:status @state)) pending-import?) + (when (= :analyzing (:status @state)) [:input.cancel-button {:type "button" :value (tr "labels.cancel") diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index f04262ffa..50f13b185 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -71,7 +71,8 @@ [{:keys [locale] :as props}] (let [initial (mf/use-memo (constantly {:password-old nil})) form (fm/use-form :spec ::password-form - :validators [(fm/validate-not-empty :password-1 (tr "auth.password-not-empty")) + :validators [(fm/validate-not-all-spaces :password-old (tr "auth.password-not-empty")) + (fm/validate-not-empty :password-1 (tr "auth.password-not-empty")) (fm/validate-not-empty :password-2 (tr "auth.password-not-empty")) password-equality] :initial initial)] diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs index 0292569ed..a3bc2fd1d 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs @@ -28,19 +28,25 @@ (seq (:fills shape))))) (mf/defc fill-block + {::mf/wrap-props false} [{:keys [objects shape]}] - (let [color-format (mf/use-state :hex) - color (shape->color shape)] + (let [format* (mf/use-state :hex) + format (deref format*) + + color (shape->color shape) + on-change (mf/use-fn #(reset! format* %))] [:div.attributes-fill-block - [:& color-row {:color color - :format @color-format - :on-change-format #(reset! color-format %) - :copy-data (css/get-shape-properties-css objects {:fills [shape]} properties)}]])) + [:& color-row + {:color color + :format format + :on-change-format on-change + :copy-data (css/get-shape-properties-css objects {:fills [shape]} properties)}]])) (mf/defc fill-panel + {::mf/wrap-props false} [{:keys [shapes]}] - (let [shapes (->> shapes (filter has-fill?))] + (let [shapes (filter has-fill? shapes)] (when (seq shapes) [:div.attributes-block [:div.attributes-block-title @@ -48,9 +54,9 @@ [:div.attributes-fill-blocks (for [shape shapes] - (if (seq (:fills shape)) - (for [value (:fills shape [])] - [:& fill-block {:key (str "fill-block-" (:id shape) value) - :shape value}]) - [:& fill-block {:key (str "fill-block-only" (:id shape)) - :shape shape}]))]]))) + (if (seq (:fills shape)) + (for [value (:fills shape [])] + [:& fill-block {:key (str "fill-block-" (:id shape) value) + :shape value}]) + [:& fill-block {:key (str "fill-block-only" (:id shape)) + :shape shape}]))]]))) diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 33eaecfaf..dbb3acab8 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -31,7 +31,10 @@ file-id (mf/use-ctx ctx/current-file-id) on-click - (mf/use-fn #(dom/click (mf/ref-val ref))) + (mf/use-fn + (fn [] + (st/emit! :interrupt dw/clear-edition-mode) + (dom/click (mf/ref-val ref)))) on-selected (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs index 191051640..879c49173 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs @@ -26,18 +26,20 @@ (mf/defc exports-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "page-id" "file-id"]))]} [{:keys [ids type values page-id file-id] :as props}] - (let [exports (:exports values []) + (let [exports (:exports values []) - state (mf/deref refs/export) - in-progress? (:in-progress state) + state (mf/deref refs/export) + in-progress? (:in-progress state) - sname (when (seqable? exports) - (let [shapes (wsh/lookup-shapes @st/state ids) - sname (-> shapes first :name) - suffix (-> exports first :suffix)] - (cond-> sname - (and (= 1 (count exports)) (some? suffix)) - (str suffix)))) + shapes-with-exports (->> (wsh/lookup-shapes @st/state ids) + (filter #(pos? (count (:exports %))))) + + sname (when (seqable? exports) + (let [sname (-> shapes-with-exports first :name) + suffix (-> exports first :suffix)] + (cond-> sname + (and (= 1 (count exports)) (some? suffix)) + (str suffix)))) scale-enabled? (mf/use-callback @@ -50,7 +52,22 @@ (fn [event] (dom/prevent-default event) (if (= :multiple type) - (st/emit! (de/show-workspace-export-dialog {:selected (reverse ids)})) + ;; I can select multiple shapes all of them with no export settings and one of them with only one + ;; In that situation we must export it directly + (if (and (= 1 (count shapes-with-exports)) (= 1 (-> shapes-with-exports first :exports count))) + (let [shape (-> shapes-with-exports first) + export (-> shape :exports first) + sname (:name shape) + suffix (:suffix export) + defaults {:page-id page-id + :file-id file-id + :name sname + :object-id (:id (first shapes-with-exports))}] + (cond-> sname + (some? suffix) + (str suffix)) + (st/emit! (de/request-simple-export {:export (merge export defaults)}))) + (st/emit! (de/show-workspace-export-dialog {:selected (reverse ids)}))) ;; In other all cases we only allowed to have a single ;; shape-id because multiple shape-ids are handled @@ -182,4 +199,4 @@ :disabled in-progress?} (if in-progress? (tr "workspace.options.exporting-object") - (tr "workspace.options.export-object" (c (count ids))))])])) + (tr "workspace.options.export-object" (c (count shapes-with-exports))))])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs index 90234e91c..6ac870eca 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs @@ -62,6 +62,7 @@ :selected-blend-mode value :option-highlighted? false :preview-complete? true) + (st/emit! (dw/unset-preview-blend-mode ids)) (on-change :blend-mode value))) handle-blend-mode-enter @@ -152,7 +153,7 @@ [:div.element-set-content [:div.row-flex [:& select - {:class "flex-grow" + {:class "flex-grow no-check" :default-value selected-blend-mode :options options :on-change handle-change-blend-mode diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index c016b0cc1..88a001c27 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -269,7 +269,7 @@ [:div#sitemap.tool-window {:ref parent-ref :style #js {"--height" (str size "px")}} [:div.tool-window-bar - [:span (tr "workspace.sidebar.sitemap")] + [:span.pages-title (tr "workspace.sidebar.sitemap")] (if workspace-read-only? [:div.view-only-mode (tr "labels.view-only")] [:div.add-page {:on-click create} i/close]) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index 428aa3fb5..30f49882e 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -7,6 +7,7 @@ (ns app.util.color "Color conversion utils." (:require + [app.common.data :as d] [app.util.i18n :as i18n :refer [tr]] [app.util.object :as obj] [app.util.strings :as ust] @@ -150,6 +151,24 @@ :else "transparent"))) +(defn color->format->background [{:keys [color opacity gradient]} format] + (let [opacity (or opacity 1)] + (cond + (and gradient (not= :multiple gradient)) + (gradient->css gradient) + + (not= color :multiple) + (case format + :rgba (let [[r g b] (hex->rgb color)] + (str/fmt "rgba(%s, %s, %s, %s)" r g b opacity)) + + :hsla (let [[h s l] (hex->hsl color)] + (str/fmt "hsla(%s, %s, %s, %s)" h (* 100 s) (* 100 l) opacity)) + + :hex (str color (str/upper (d/opacity-to-hex opacity)))) + + :else "transparent"))) + (defn multiple? [{:keys [id file-id value color gradient]}] (or (= value :multiple) (= color :multiple)