diff --git a/CHANGES.md b/CHANGES.md index 8886b953a..a823d5381 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,10 +4,11 @@ ### :sparkles: New features +- Add select layer option to context menu [Taiga #2474](https://tree.taiga.io/project/penpot/us/2474). - Guides [Taiga #290](https://tree.taiga.io/project/penpot/us/290?milestone=307334) - Improve file menu by adding semantically groups [Github #1203](https://github.com/penpot/penpot/issues/1203). - Add update components in bulk option in context menu [Taiga #1975](https://tree.taiga.io/project/penpot/us/1975). -- Create e2e tests for drawing basic fors [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608). +- Create e2e tests for drawing basic shapes [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608). - Create firsts e2e test [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608). ## 1.11.0-beta diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 561be4c62..08bfead57 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -34,11 +34,11 @@ margin: 2px; } - span:first-child { + span { color: $color-gray-60; } - span:last-child { + span.shortcut { color: $color-gray-20; font-size: $fs12; } @@ -57,6 +57,40 @@ } } } + + .sub-menu-item { + display: flex; + justify-content: flex-start; + + &:hover { + background-color: $color-primary-lighter; + } + + span.title { + margin-left: 5px; + } + + .selected-icon { + svg { + width: 10px; + height: 10px; + } + } + + .shape-icon { + margin-left: 3px; + svg { + width: 13px; + height: 13px; + } + } + + .icon-wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + margin: 0; + } + } } .workspace-loader { diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 5763b5e84..8d5901da6 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -150,7 +150,7 @@ ptk/WatchEvent (watch [_ state _] - (let [objects (wsh/lookup-page-objects state)] + (let [objects (wsh/lookup-page-objects state)] (rx/of (dwc/expand-all-parents ids objects)))))) (defn select-all @@ -256,7 +256,7 @@ ;; in the later vector position selected (->> children reverse - (d/seek #(geom/has-point? % position)))] + (d/seek #(geom/has-point? % position)))] (when selected (rx/of (select-shape (:id selected)))))))) @@ -312,8 +312,8 @@ result (prepare-duplicate-change objects page-id unames update-unames! ids-map id delta) result (if (vector? result) result [result])] (recur - (next ids) - (into chgs result))) + (next ids) + (into chgs result))) chgs)))) (defn duplicate-changes-update-indices @@ -384,12 +384,12 @@ (mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! ids-map % delta new-id new-id))) new-frame (-> obj - (assoc :id new-id - :name frame-name - :frame-id uuid/zero - :shapes []) - (geom/move delta) - (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) + (assoc :id new-id + :name frame-name + :frame-id uuid/zero + :shapes []) + (geom/move delta) + (d/update-when :interactions #(cti/remap-interactions % ids-map objects))) fch {:type :add-obj :old-id (:id obj) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index f93a53ae9..35ec0839b 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -152,6 +152,12 @@ (def current-hover (l/derived :hover workspace-local)) +(def context-menu + (l/derived :context-menu workspace-local)) + +(def current-hover-ids + (l/derived :hover-ids context-menu)) + (def editors (l/derived :editors workspace-local)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index cc13bcecb..20b8772f1 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -13,6 +13,7 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shortcuts :as sc] [app.main.refs :as refs] [app.main.store :as st] @@ -33,8 +34,33 @@ (dom/prevent-default event) (dom/stop-propagation event)) + +(mf/defc element-icon + [{:keys [shape] :as props}] + (case (:type shape) + :frame i/artboard + :image i/image + :line i/line + :circle i/circle + :path i/curve + :rect i/box + :text i/text + :group (if (some? (:component-id shape)) + i/component + (if (:masked-group? shape) + i/mask + i/folder)) + :bool (case (:bool-type shape) + :difference i/bool-difference + :exclude i/bool-exclude + :intersection i/bool-intersection + #_:default i/bool-union) + :svg-raw i/file-svg + nil)) + + (mf/defc menu-entry - [{:keys [title shortcut on-click children] :as props}] + [{:keys [title shortcut on-click children selected has-icon? shape] :as props}] (let [submenu-ref (mf/use-ref nil) hovering? (mf/use-ref false) @@ -64,22 +90,33 @@ (when (and (some? dom) (some? submenu-node)) (dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))] - [:li {:ref set-dom-node - :on-click on-click - :on-pointer-enter on-pointer-enter - :on-pointer-leave on-pointer-leave} - [:span.title title] - [:span.shortcut (or shortcut "")] + (if has-icon? + [:li.sub-menu-item {:ref set-dom-node + :on-click on-click + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave} + (when has-icon? + [:span.icon-wrapper + (if selected [:span.selected-icon i/tick] + [:span.selected-icon]) + [:span.shape-icon (element-icon {:shape shape})]]) + [:span.title title]] + [:li {:ref set-dom-node + :on-click on-click + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave} + [:span.title title] + [:span.shortcut (or shortcut "")] - (when (> (count children) 1) - [:span.submenu-icon i/arrow-slide]) + (when (> (count children) 1) + [:span.submenu-icon i/arrow-slide]) - (when (> (count children) 1) - [:ul.workspace-context-menu - {:ref submenu-ref - :style {:display "none" :left 250} - :on-context-menu prevent-default} - children])])) + (when (> (count children) 1) + [:ul.workspace-context-menu + {:ref submenu-ref + :style {:display "none" :left 250} + :on-context-menu prevent-default} + children])]))) (mf/defc menu-separator [] @@ -108,12 +145,21 @@ [:& menu-separator]])) (mf/defc context-menu-layer-position - [] + [{:keys [hover-objs shapes]}] (let [do-bring-forward (st/emitf (dw/vertical-order-selected :up)) do-bring-to-front (st/emitf (dw/vertical-order-selected :top)) do-send-backward (st/emitf (dw/vertical-order-selected :down)) - do-send-to-back (st/emitf (dw/vertical-order-selected :bottom))] + do-send-to-back (st/emitf (dw/vertical-order-selected :bottom)) + select-shapes (fn [id] (st/emitf (dws/select-shape id)))] [:* + (when (> (count hover-objs) 1) + [:& menu-entry {:title (tr "workspace.shape.menu.select-layer")} + (for [object hover-objs] + [:& menu-entry {:title (:name object) + :selected (some #(= object %) shapes) + :on-click (select-shapes (:id object)) + :has-icon? true + :shape object}])]) [:& menu-entry {:title (tr "workspace.shape.menu.forward") :shortcut (sc/get-tooltip :bring-forward) :on-click do-bring-forward}] @@ -392,8 +438,11 @@ [{:keys [mdata] :as props}] (let [{:keys [disable-booleans? disable-flatten?]} mdata shapes (mf/deref refs/selected-objects) + hover-ids (mf/deref refs/current-hover-ids) + hover-objs (mf/deref (refs/objects-by-id hover-ids)) props #js {:shapes shapes + :hover-objs hover-objs :disable-booleans? disable-booleans? :disable-flatten? disable-flatten?}] (when-not (empty? shapes) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 2ca92bb7f..86b744166 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -115,7 +115,7 @@ text-editing? (and edition (= :text (get-in base-objects [edition :type]))) on-click (actions/on-click hover selected edition drawing-path? drawing-tool) - on-context-menu (actions/on-context-menu hover) + on-context-menu (actions/on-context-menu hover hover-ids) on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition) on-drag-enter (actions/on-drag-enter) on-drag-over (actions/on-drag-over) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 9f1fc6b52..54b46e727 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -186,9 +186,9 @@ (dw/start-editing-selected)))))))) (defn on-context-menu - [hover] + [hover hover-ids] (mf/use-callback - (mf/deps @hover) + (mf/deps @hover @hover-ids) (fn [event] (when (or (dom/class? (dom/get-target event) "viewport-controls") (dom/class? (dom/get-target event) "viewport-selrect")) @@ -200,18 +200,19 @@ #(st/emit! (if (some? @hover) (dw/show-shape-context-menu {:position position - :shape @hover}) + :shape @hover + :hover-ids @hover-ids}) (dw/show-context-menu {:position position}))))))))) (defn on-menu-selected [hover hover-ids selected] (mf/use-callback - (mf/deps @hover hover-ids selected) + (mf/deps @hover @hover-ids selected) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) (let [position (dom/get-client-position event)] - (st/emit! (dw/show-shape-context-menu {:position position})))))) + (st/emit! (dw/show-shape-context-menu {:position position :hover-ids @hover-ids})))))) (defn on-mouse-up [disable-paste] @@ -251,58 +252,58 @@ (reset! in-viewport? false)))) (defn on-pointer-down [] - (mf/use-callback - (fn [event] + (mf/use-callback + (fn [event] ;; We need to handle editor related stuff here because ;; handling on editor dom node does not works properly. - (let [target (dom/get-target event) - editor (.closest ^js target ".public-DraftEditor-content")] + (let [target (dom/get-target event) + editor (.closest ^js target ".public-DraftEditor-content")] ;; Capture mouse pointer to detect the movements even if cursor ;; leaves the viewport or the browser itself ;; https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture - (if editor - (.setPointerCapture editor (.-pointerId event)) - (.setPointerCapture target (.-pointerId event))))))) + (if editor + (.setPointerCapture editor (.-pointerId event)) + (.setPointerCapture target (.-pointerId event))))))) (defn on-pointer-up [] - (mf/use-callback - (fn [event] - (let [target (dom/get-target event)] + (mf/use-callback + (fn [event] + (let [target (dom/get-target event)] ; Release pointer on mouse up - (.releasePointerCapture target (.-pointerId event)))))) + (.releasePointerCapture target (.-pointerId event)))))) (defn on-key-down [] - (mf/use-callback - (fn [event] - (let [bevent (.getBrowserEvent ^js event) - key (.-key ^js event) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event) - meta? (kbd/meta? event) - target (dom/get-target event) - editing? (or (some? (.closest ^js target ".public-DraftEditor-content")) - (= "rich-text" (obj/get target "className")) - (= "INPUT" (obj/get target "tagName")) - (= "TEXTAREA" (obj/get target "tagName")))] + (mf/use-callback + (fn [event] + (let [bevent (.getBrowserEvent ^js event) + key (.-key ^js event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + meta? (kbd/meta? event) + target (dom/get-target event) + editing? (or (some? (.closest ^js target ".public-DraftEditor-content")) + (= "rich-text" (obj/get target "className")) + (= "INPUT" (obj/get target "tagName")) + (= "TEXTAREA" (obj/get target "tagName")))] - (when-not (.-repeat bevent) - (st/emit! (ms/->KeyboardEvent :down key shift? ctrl? alt? meta? editing?))))))) + (when-not (.-repeat bevent) + (st/emit! (ms/->KeyboardEvent :down key shift? ctrl? alt? meta? editing?))))))) (defn on-key-up [] - (mf/use-callback - (fn [event] - (let [key (.-key event) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - alt? (kbd/alt? event) - meta? (kbd/meta? event) - target (dom/get-target event) - editing? (or (some? (.closest ^js target ".public-DraftEditor-content")) - (= "rich-text" (obj/get target "className")) - (= "INPUT" (obj/get target "tagName")) - (= "TEXTAREA" (obj/get target "tagName")))] - (st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta? editing?)))))) + (mf/use-callback + (fn [event] + (let [key (.-key event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + alt? (kbd/alt? event) + meta? (kbd/meta? event) + target (dom/get-target event) + editing? (or (some? (.closest ^js target ".public-DraftEditor-content")) + (= "rich-text" (obj/get target "className")) + (= "INPUT" (obj/get target "tagName")) + (= "TEXTAREA" (obj/get target "tagName")))] + (st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta? editing?)))))) (defn on-mouse-move [viewport-ref zoom] (let [last-position (mf/use-var nil)] @@ -386,29 +387,29 @@ :y #(+ % delta-y)}))))))))) (defn on-drag-enter [] - (mf/use-callback - (fn [e] - (when (or (dnd/has-type? e "penpot/shape") - (dnd/has-type? e "penpot/component") - (dnd/has-type? e "Files") - (dnd/has-type? e "text/uri-list") - (dnd/has-type? e "text/asset-id")) - (dom/prevent-default e))))) + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "penpot/shape") + (dnd/has-type? e "penpot/component") + (dnd/has-type? e "Files") + (dnd/has-type? e "text/uri-list") + (dnd/has-type? e "text/asset-id")) + (dom/prevent-default e))))) (defn on-drag-over [] - (mf/use-callback - (fn [e] - (when (or (dnd/has-type? e "penpot/shape") - (dnd/has-type? e "penpot/component") - (dnd/has-type? e "Files") - (dnd/has-type? e "text/uri-list") - (dnd/has-type? e "text/asset-id")) - (dom/prevent-default e))))) + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "penpot/shape") + (dnd/has-type? e "penpot/component") + (dnd/has-type? e "Files") + (dnd/has-type? e "text/uri-list") + (dnd/has-type? e "text/asset-id")) + (dom/prevent-default e))))) (defn on-image-uploaded [] - (mf/use-callback - (fn [image position] - (st/emit! (dw/image-uploaded image position))))) + (mf/use-callback + (fn [image position] + (st/emit! (dw/image-uploaded image position))))) (defn on-drop [file viewport-ref zoom] (let [on-image-uploaded (on-image-uploaded)] @@ -483,19 +484,19 @@ (st/emit! (dw/upload-media-workspace params))))))))) (defn on-paste [disable-paste in-viewport?] - (mf/use-callback - (fn [event] + (mf/use-callback + (fn [event] ;; We disable the paste just after mouse-up of a middle button so when panning won't ;; paste the content into the workspace - (let [tag-name (-> event dom/get-target dom/get-tag-name)] - (when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste)) - (st/emit! (dw/paste-from-event event @in-viewport?))))))) + (let [tag-name (-> event dom/get-target dom/get-tag-name)] + (when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste)) + (st/emit! (dw/paste-from-event event @in-viewport?))))))) (defn on-resize [viewport-ref] - (mf/use-callback - (fn [_] - (let [node (mf/ref-val viewport-ref) - prnt (dom/get-parent node) - size (dom/get-client-size prnt)] + (mf/use-callback + (fn [_] + (let [node (mf/ref-val viewport-ref) + prnt (dom/get-parent node) + size (dom/get-client-size prnt)] ;; We schedule the event so it fires after `initialize-page` event - (timers/schedule #(st/emit! (dw/update-viewport-size size))))))) + (timers/schedule #(st/emit! (dw/update-viewport-size size))))))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index fcf7dc389..1f575ac6e 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3162,6 +3162,10 @@ msgstr "Flip vertical" msgid "workspace.shape.menu.flow-start" msgstr "Flow start" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Select layer" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.forward" msgstr "Bring forward" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index cec3547e5..477b30094 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3175,6 +3175,10 @@ msgstr "Voltear vertical" msgid "workspace.shape.menu.flow-start" msgstr "Inicio de flujo" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Seleccionar capa" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.forward" msgstr "Mover hacia delante"