diff --git a/CHANGES.md b/CHANGES.md index 0d6109fd61..111d5b9a53 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -45,6 +45,13 @@ - Fix tooltips for some alignment options are truncated on design tab [Taiga #5040](https://tree.taiga.io/project/penpot/issue/5040) - Fix horizontal margins drag don't always start from place [Taiga #5020](https://tree.taiga.io/project/penpot/issue/5020) - Fix multiplayer username sometimes is not displayed correctly [Taiga #4400](https://tree.taiga.io/project/penpot/issue/4400) +- Show warning when trying to invite a user that is already in members [Taiga #4147](https://tree.taiga.io/project/penpot/issue/4147) +- Fix problem with text out of borders when changing from auto-width to fixed [Taiga #4308](https://tree.taiga.io/project/penpot/issue/4308) +- Fix header not showing when exiting fullscreen mode in viewer [Taiga #4244](https://tree.taiga.io/project/penpot/issue/4244) +- Fix visual problem in select options [Taiga #5028](https://tree.taiga.io/project/penpot/issue/5028) +- Forbid empty names for assets [Taiga #5056](https://tree.taiga.io/project/penpot/issue/5056) +- Select children after ungroup action [Taiga #4917](https://tree.taiga.io/project/penpot/issue/4917) +- Fix problem with guides not showing when moving over nested frames [Taiga #4905](https://tree.taiga.io/project/penpot/issue/4905) ### :heart: Community contributions by (Thank you!) - To @ondrejkonec: for contributing to the code with: diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 864167f1b2..1c7bc9b8ea 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -382,10 +382,11 @@ (defn update-group-selrect [group children] - (let [shape-center (gco/center-shape group) - ;; Points for every shape inside the group + (let [;; Points for every shape inside the group points (->> children (mapcat :points)) + shape-center (gco/center-points points) + ;; Fixed problem with empty groups. Should not happen (but it does) points (if (empty? points) (:points group) points) diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 0cc126ade8..384b748edc 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -97,12 +97,11 @@ fill: $color-gray-20; } - .error { - background-color: #ffd9e0; + .error, + .warning { width: 100%; display: flex; .icon { - background-color: $color-danger; text-align: center; padding: 5px; svg { @@ -118,6 +117,22 @@ font-size: $fs12; } } + + .error { + background-color: #ffd9e0; + + .icon { + background-color: $color-danger; + } + } + + .warning { + background-color: #ffeaca; + + .icon { + background-color: $color-warning; + } + } } .dashboard-team-members, diff --git a/frontend/resources/styles/main/partials/forms.scss b/frontend/resources/styles/main/partials/forms.scss index 04affd6d5f..d45d719e82 100644 --- a/frontend/resources/styles/main/partials/forms.scss +++ b/frontend/resources/styles/main/partials/forms.scss @@ -271,6 +271,9 @@ textarea { &.invalid { border: 1px solid $color-danger; } + &.caution { + border: 1px solid $color-warning; + } .text { display: inline-block; diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 6c77948fab..ef3608c281 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -195,6 +195,10 @@ } .input-select { + /* This padding is so the text won't overlap the arrow*/ + padding-right: 1rem; + overflow: hidden; + text-overflow: ellipsis; color: $color-gray-10; &:focus { diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 9213cd9388..7d62461098 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -300,6 +300,13 @@ (update [_ state] (update-in state [:viewer-local :fullscreen?] not)))) +(defn exit-fullscreen + [] + (ptk/reify ::exit-fullscreen + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:viewer-local :fullscreen?] false)))) + (defn set-viewport-size [{:keys [size]}] (ptk/reify ::set-viewport-size diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index de927b33f2..d04beadc90 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -7,6 +7,7 @@ (ns app.main.data.workspace.groups (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] @@ -195,6 +196,11 @@ (keep :id)) selected) + child-ids + (into (d/ordered-set) + (mapcat #(dm/get-in objects [% :shapes])) + selected) + changes {:redo-changes (vec (mapcat :redo-changes changes-list)) :undo-changes (vec (mapcat :undo-changes changes-list)) :origin it} @@ -203,7 +209,8 @@ (rx/of (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) (ptk/data-event :layout/update parents) - (dwu/commit-undo-transaction undo-id)))))) + (dwu/commit-undo-transaction undo-id) + (dws/select-shapes child-ids)))))) (def mask-group (ptk/reify ::mask-group diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 78aaad5c9c..6f23a04879 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -176,10 +176,11 @@ (ptk/reify ::rename-color ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) - object (get-in data [:colors id]) - new-object (assoc object :name new-name)] - (do-update-color it state new-object file-id))))) + (when (and (some? new-name) (not= "" new-name)) + (let [data (get state :workspace-data) + object (get-in data [:colors id]) + new-object (assoc object :name new-name)] + (do-update-color it state new-object file-id)))))) (defn delete-color [{:keys [id] :as params}] @@ -211,14 +212,15 @@ (ptk/reify ::rename-media ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) - [path name] (cph/parse-path-name new-name) - object (get-in data [:media id]) - new-object (assoc object :path path :name name) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/update-media new-object))] - (rx/of (dch/commit-changes changes)))))) + (when (and (some? new-name) (not= "" new-name)) + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name new-name) + object (get-in data [:media id]) + new-object (assoc object :path path :name name) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-media new-object))] + (rx/of (dch/commit-changes changes))))))) (defn delete-media @@ -281,11 +283,12 @@ (ptk/reify ::rename-typography ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) - [path name] (cph/parse-path-name new-name) - object (get-in data [:typographies id]) - new-object (assoc object :path path :name name)] - (do-update-tipography it state new-object file-id))))) + (when (and (some? new-name) (not= "" new-name)) + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name new-name) + object (get-in data [:typographies id]) + new-object (assoc object :path path :name name)] + (do-update-tipography it state new-object file-id)))))) (defn delete-typography [id] @@ -342,27 +345,28 @@ (ptk/reify ::rename-component ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) - [path name] (cph/parse-path-name new-name) + (when (and (some? new-name) (not= "" new-name)) + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name new-name) - update-fn - (fn [component] - ;; NOTE: we need to ensure the component exists, - ;; because there are small possibilities of race - ;; conditions with component deletion. - (when component - (-> component - (assoc :path path) - (assoc :name name) - (update :objects - ;; Give the same name to the root shape - #(assoc-in % [id :name] name))))) + update-fn + (fn [component] + ;; NOTE: we need to ensure the component exists, + ;; because there are small possibilities of race + ;; conditions with component deletion. + (when component + (-> component + (assoc :path path) + (assoc :name name) + (update :objects + ;; Give the same name to the root shape + #(assoc-in % [id :name] name))))) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/update-component id update-fn))] + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-component id update-fn))] - (rx/of (dch/commit-changes changes)))))) + (rx/of (dch/commit-changes changes))))))) (defn duplicate-component "Create a new component copied from the one with the given id." diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index fab3f476cd..55e63f9e43 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -251,7 +251,7 @@ (into [] (distinct) (conj coll item))) (mf/defc multi-input - [{:keys [form label class name trim valid-item-fn on-submit] :as props}] + [{:keys [form label class name trim valid-item-fn caution-item-fn on-submit] :as props}] (let [form (or form (mf/use-ctx form-ctx)) input-name (get props :name) touched? (get-in @form [:touched input-name]) @@ -309,7 +309,9 @@ (on-submit form)) (when (not (str/empty? @value)) (reset! value "") - (swap! items conj-dedup {:text val :valid (valid-item-fn val)})))) + (swap! items conj-dedup {:text val + :valid (valid-item-fn val) + :caution (caution-item-fn val)})))) (and (kbd/backspace? event) (str/empty? @value)) @@ -361,6 +363,7 @@ [:div.selected-item {:key (:text item) :tab-index "0" :on-key-down (partial manage-key-down item)} - [:span.around {:class (when-not (:valid item) "invalid")} + [:span.around {:class (dom/classnames "invalid" (not (:valid item)) + "caution" (:caution item))} [:span.text (:text item)] [:span.icon {:on-click #(remove-item! item)} i/cross]]])])])) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index da8d7c0e17..3d6250d3b0 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -39,7 +39,9 @@ go-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks))) invite-member (mf/use-fn (mf/deps team) - #(st/emit! (modal/show {:type :invite-members :team team :origin :team}))) + #(st/emit! (modal/show {:type :invite-members + :team team + :origin :team}))) members-section? (= section :dashboard-team-members) settings-section? (= section :dashboard-team-settings) @@ -98,7 +100,10 @@ {::mf/register modal/components ::mf/register-as :invite-members} [{:keys [team origin]}] - (let [perms (:permissions team) + (let [members-map (mf/deref refs/dashboard-team-members) + + perms (:permissions team) + roles (mf/use-memo (mf/deps perms) #(get-available-roles perms)) initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)})) form (fm/use-form :spec ::invite-member-form @@ -111,6 +116,9 @@ (modal/hide) (dd/fetch-team-invitations))) + current-data-emails (into #{} (dm/get-in @form [:clean-data :emails])) + current-members-emails (into #{} (map (comp :email second)) members-map) + on-error (fn [{:keys [type code] :as error}] (cond @@ -148,17 +156,23 @@ [:div.error [:span.icon i/msg-error] [:span.text @error-text]]) + + (when (some current-data-emails current-members-emails) + [:div.warning + [:span.icon i/msg-warning] + [:span.text (tr "modals.invite-member.repeated-invitation")]]) + [:div.form-row [:p.label (tr "onboarding.choice.team-up.roles")] [:& fm/select {:name :role :options roles}]] + [:div.form-row - - [:& fm/multi-input {:type "email" :name :emails :auto-focus? true :trim true :valid-item-fn us/parse-email + :caution-item-fn current-members-emails :label (tr "modals.invite-member.emails") :on-submit on-submit}]] diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 4b7de5729c..723b927f37 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -5,6 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.viewer + (:import goog.events.EventType) (:require [app.common.colors :as clr] [app.common.data :as d] @@ -34,6 +35,7 @@ [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] [app.util.dom :as dom] [app.util.dom.normalize-wheel :as nw] + [app.util.globals :as globals] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.webapi :as wapi] @@ -329,7 +331,13 @@ (dom/stop-propagation event) (if shift? (dom/set-h-scroll-pos! viewer-section new-scroll-pos) - (dom/set-scroll-pos! viewer-section new-scroll-pos)))))))] + (dom/set-scroll-pos! viewer-section new-scroll-pos))))))) + + on-exit-fullscreen + (mf/use-callback + (fn [] + (when (not (dom/fullscreen?)) + (st/emit! (dv/exit-fullscreen)))))] (hooks/use-shortcuts ::viewer sc/shortcuts) (when (nil? page) @@ -348,11 +356,19 @@ (mf/with-effect [] (dom/set-html-theme-color clr/gray-50 "dark") - (let [key1 (events/listen js/window "click" on-click) - key2 (events/listen (mf/ref-val viewer-section-ref) "wheel" on-wheel #js {"passive" false})] + (let [events + [(events/listen globals/window EventType.CLICK on-click) + (events/listen (mf/ref-val viewer-section-ref) EventType.WHEEL on-wheel #js {"passive" false})]] + + (doseq [event dom/fullscreen-events] + (.addEventListener globals/document event on-exit-fullscreen false)) + (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2)))) + (doseq [key events] + (events/unlistenByKey key)) + + (doseq [event dom/fullscreen-events] + (.removeEventListener globals/document event on-exit-fullscreen))))) (mf/use-effect (fn [] diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index a6433818b3..14ef7aaceb 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -234,10 +234,11 @@ ;; When we have a text with grow-type :auto-height or :auto-height we need to check the correct height ;; otherwise the center alignment will break - tr-shape (when text-modifier (dwt/apply-text-modifier shape text-modifier)) - shape (cond-> shape - (and (some? text-modifier) (#{:auto-height :auto-width} (:grow-type shape))) - (assoc :width (:width tr-shape) :height (:height tr-shape))) + shape + (if (some? text-modifier) + (let [{:keys [width height]} (dwt/apply-text-modifier shape text-modifier)] + (assoc shape :width width :height height)) + shape) shape (hooks/use-equal-memo shape) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index f89f6438f0..6009b16133 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -1156,7 +1156,7 @@ rename-color (fn [name] - (st/emit! (dwl/update-color (assoc color :name name) file-id))) + (st/emit! (dwl/rename-color file-id (:id color) name))) edit-color (fn [new-color] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 6b1952b14b..3bbd9bd1c7 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -134,12 +134,14 @@ ;; STREAMS move-stream (mf/use-memo #(rx/subject)) - frame-parent (mf/use-memo + guide-frame (mf/use-memo (mf/deps @hover-ids base-objects) (fn [] - (let [parent (get base-objects (last @hover-ids))] - (when (= :frame (:type parent)) - parent)))) + (let [parent-id + (->> @hover-ids + (d/seek (partial cph/root-frame? base-objects)))] + (when (some? parent-id) + (get base-objects parent-id))))) zoom (d/check-num zoom 1) drawing-tool (:tool drawing) @@ -494,7 +496,7 @@ [:& guides/viewport-guides {:zoom zoom :vbox vbox - :hover-frame frame-parent + :hover-frame guide-frame :disabled-guides? disabled-guides? :modifiers modifiers}]) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index d6f0645463..8328f9025f 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -368,12 +368,22 @@ (when (some? node) (.blur node))) +;; List of dom events for different browsers to detect the exit of fullscreen mode +(def fullscreen-events + ["fullscreenchange" "mozfullscreenchange" "MSFullscreenChange" "webkitfullscreenchange"]) + (defn fullscreen? [] (cond (obj/in? globals/document "webkitFullscreenElement") (boolean (.-webkitFullscreenElement globals/document)) + (obj/in? globals/document "mozFullScreen") + (boolean (.-mozFullScreen globals/document)) + + (obj/in? globals/document "msFullscreenElement") + (boolean (.-msFullscreenElement globals/document)) + (obj/in? globals/document "fullscreenElement") (boolean (.-fullscreenElement globals/document)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ea6bdb4acf..75e81ef1ee 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1821,6 +1821,9 @@ msgstr "Send invitation" msgid "modals.invite-member.emails" msgstr "Emails, comma separated" +msgid "modals.invite-member.repeated-invitation" +msgstr "Some emails are from current team members. Their invitations will not be sent." + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-team-member.title" msgstr "Invite members to the team" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index eadd234a9c..19b30a5751 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1904,6 +1904,9 @@ msgstr "Enviar invitacion" msgid "modals.invite-member.emails" msgstr "Emails, separados por coma" +msgid "modals.invite-member.repeated-invitation" +msgstr "Algunas direcciones de correo ya se encuentran entre los miembros. Estas invitaciones no serĂ¡n enviadas." + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-team-member.title" msgstr "Invitar a miembros al equipo"