From 1f2f70fcd4ab0c1942c8dbb13419c4a988154b2e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 31 Jan 2024 10:10:18 +0100 Subject: [PATCH] :sparkles: New menu entry for change theme --- common/src/app/common/geom/shapes/path.cljc | 27 +- .../app/main/ui/workspace/left_header.cljs | 718 +---------------- .../app/main/ui/workspace/left_header.scss | 94 --- .../src/app/main/ui/workspace/main_menu.cljs | 747 ++++++++++++++++++ .../src/app/main/ui/workspace/main_menu.scss | 101 +++ frontend/translations/en.po | 6 + frontend/translations/es.po | 6 + 7 files changed, 889 insertions(+), 810 deletions(-) create mode 100644 frontend/src/app/main/ui/workspace/main_menu.cljs create mode 100644 frontend/src/app/main/ui/workspace/main_menu.scss diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index 14571d65a..d3a00953d 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -351,21 +351,32 @@ [from-p move-p command-pts] (case (:command command) - :move-to [to-p to-p [to-p]] - :close-path [move-p move-p [move-p]] - :line-to [to-p move-p [from-p to-p]] + :move-to [to-p to-p (when to-p [to-p])] + :close-path [move-p move-p (when move-p [move-p])] + :line-to [to-p move-p (when (and from-p to-p) [from-p to-p])] :curve-to [to-p move-p (let [c1 (command->point command :c1) c2 (command->point command :c2) curve [from-p to-p c1 c2]] - (into [from-p to-p] - (->> (curve-extremities curve) - (map #(curve-values curve %)))))] + (when (and from-p to-p c1 c2) + (into [from-p to-p] + (->> (curve-extremities curve) + (map #(curve-values curve %))))))] [to-p move-p []])] (recur (apply conj points command-pts) from-p move-p (next content))) - points))] - (grc/points->rect extremities))) + points)) + + ;; We haven't found any extremes so we turn the commands to points + extremities + (if (empty? extremities) + (->> content (keep command->point)) + extremities)] + + ;; If no points are returned we return an empty rect. + (if (d/not-empty? extremities) + (grc/points->rect extremities) + (grc/make-rect)))) (defn move-content [content move-vec] (let [dx (:x move-vec) diff --git a/frontend/src/app/main/ui/workspace/left_header.cljs b/frontend/src/app/main/ui/workspace/left_header.cljs index f8c1bf53c..0c1cd4930 100644 --- a/frontend/src/app/main/ui/workspace/left_header.cljs +++ b/frontend/src/app/main/ui/workspace/left_header.cljs @@ -8,727 +8,28 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] - [app.common.files.helpers :as cfh] - [app.common.uuid :as uuid] - [app.config :as cf] - [app.main.data.common :as dcm] - [app.main.data.events :as ev] - [app.main.data.exports :as de] [app.main.data.modal :as modal] - [app.main.data.shortcuts :as scd] [app.main.data.workspace :as dw] [app.main.data.workspace.colors :as dc] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.shortcuts :as sc] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]] [app.main.ui.context :as ctx] - [app.main.ui.hooks.resize :as r] [app.main.ui.icons :as i] + [app.main.ui.workspace.main-menu :as main-menu] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.router :as rt] [cuerdas.core :as str] - [potok.v2.core :as ptk] [rumext.v2 :as mf])) -;; --- Header menu and submenus - -(mf/defc help-info-menu - {::mf/wrap-props false - ::mf/wrap [mf/memo]} - [{:keys [layout on-close]}] - (let [nav-to-helpc-center - (mf/use-fn #(dom/open-new-window "https://help.penpot.app")) - - nav-to-community - (mf/use-fn #(dom/open-new-window "https://community.penpot.app")) - - nav-to-youtube - (mf/use-fn #(dom/open-new-window "https://www.youtube.com/c/Penpot")) - - nav-to-templates - (mf/use-fn #(dom/open-new-window "https://penpot.app/libraries-templates")) - - nav-to-github - (mf/use-fn #(dom/open-new-window "https://github.com/penpot/penpot")) - - nav-to-terms - (mf/use-fn #(dom/open-new-window "https://penpot.app/terms")) - - nav-to-feedback - (mf/use-fn #(st/emit! (rt/nav-new-window* {:rname :settings-feedback}))) - - show-shortcuts - (mf/use-fn - (mf/deps layout) - (fn [] - (when (contains? layout :collapse-left-sidebar) - (st/emit! (dw/toggle-layout-flag :collapse-left-sidebar))) - - (st/emit! - (-> (dw/toggle-layout-flag :shortcuts) - (vary-meta assoc ::ev/origin "workspace-header"))))) - - show-release-notes - (mf/use-fn - (fn [event] - (let [version (:main cf/version)] - (st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version})) - (if (and (kbd/alt? event) (kbd/mod? event)) - (st/emit! (modal/show {:type :onboarding})) - (st/emit! (modal/show {:type :release-notes :version version}))))))] - - [:& dropdown-menu {:show true - :on-close on-close - :list-class (stl/css-case :sub-menu true - :help-info true)} - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-helpc-center - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-helpc-center event))) - :id "file-menu-help-center"} - [:span {:class (stl/css :item-name)} (tr "labels.help-center")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-community - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-community event))) - :id "file-menu-community"} - [:span {:class (stl/css :item-name)} (tr "labels.community")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-youtube - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-youtube event))) - :id "file-menu-youtube"} - [:span {:class (stl/css :item-name)} (tr "labels.tutorials")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click show-release-notes - :on-key-down (fn [event] - (when (kbd/enter? event) - (show-release-notes event))) - :id "file-menu-release-notes"} - [:span {:class (stl/css :item-name)} (tr "labels.release-notes")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-templates - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-templates event))) - :id "file-menu-templates"} - [:span {:class (stl/css :item-name)} (tr "labels.libraries-and-templates")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-github - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-github event))) - :id "file-menu-github"} - [:span {:class (stl/css :item-name)} (tr "labels.github-repo")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-terms - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-terms event))) - :id "file-menu-terms"} - [:span {:class (stl/css :item-name)} (tr "auth.terms-of-service")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click show-shortcuts - :on-key-down (fn [event] - (when (kbd/enter? event) - (show-shortcuts event))) - :id "file-menu-shortcuts"} - [:span {:class (stl/css :item-name)} (tr "label.shortcuts")] - [:span {:class (stl/css :shortcut)} - - (for [sc (scd/split-sc (sc/get-tooltip :show-shortcuts))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - (when (contains? cf/flags :user-feedback) - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click nav-to-feedback - :on-key-down (fn [event] - (when (kbd/enter? event) - (nav-to-feedback event))) - :id "file-menu-feedback"} - [:span {:class (stl/css-case :feedback true - :item-name true)} (tr "labels.give-feedback")]])])) - -(mf/defc preferences-menu - {::mf/wrap-props false - ::mf/wrap [mf/memo]} - [{:keys [layout toggle-flag on-close]}] - (let [show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))] - - [:& dropdown-menu {:show true - :list-class (stl/css-case :sub-menu true - :preferences true) - :on-close on-close} - [:> dropdown-menu-item* {:on-click toggle-flag - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "scale-text" - :id "file-menu-scale-text"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :scale-text) - (tr "workspace.header.menu.disable-scale-content") - (tr "workspace.header.menu.enable-scale-content"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-scale-text))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:on-click toggle-flag - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "snap-guides" - :id "file-menu-snap-guides"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :snap-guides) - (tr "workspace.header.menu.disable-snap-guides") - (tr "workspace.header.menu.enable-snap-guides"))] - [:span {:class (stl/css :shortcut)} - - (for [sc (scd/split-sc (sc/get-tooltip :toggle-snap-guide))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:on-click toggle-flag - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "snap-grid" - :id "file-menu-snap-grid"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :snap-grid) - (tr "workspace.header.menu.disable-snap-grid") - (tr "workspace.header.menu.enable-snap-grid"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-snap-grid))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:on-click toggle-flag - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "dynamic-alignment" - :id "file-menu-dynamic-alignment"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :dynamic-alignment) - (tr "workspace.header.menu.disable-dynamic-alignment") - (tr "workspace.header.menu.enable-dynamic-alignment"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-alignment))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:on-click toggle-flag - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "snap-pixel-grid" - :id "file-menu-pixel-grid"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :snap-pixel-grid) - (tr "workspace.header.menu.disable-snap-pixel-grid") - (tr "workspace.header.menu.enable-snap-pixel-grid"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :snap-pixel-grid))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:on-click show-nudge-options - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (show-nudge-options event))) - :data-test "snap-pixel-grid" - :id "file-menu-nudge"} - [:span {:class (stl/css :item-name)} (tr "modals.nudge-title")]]])) - -(mf/defc view-menu - {::mf/wrap-props false - ::mf/wrap [mf/memo]} - [{:keys [layout toggle-flag on-close]}] - (let [read-only? (mf/use-ctx ctx/workspace-read-only?) - - toggle-color-palette - (mf/use-fn - (fn [] - (r/set-resize-type! :bottom) - (st/emit! (dw/remove-layout-flag :textpalette) - (-> (dw/toggle-layout-flag :colorpalette) - (vary-meta assoc ::ev/origin "workspace-menu"))))) - - toggle-text-palette - (mf/use-fn - (fn [] - (r/set-resize-type! :bottom) - (st/emit! (dw/remove-layout-flag :colorpalette) - (-> (dw/toggle-layout-flag :textpalette) - (vary-meta assoc ::ev/origin "workspace-menu")))))] - - [:& dropdown-menu {:show true - :list-class (stl/css-case :sub-menu true - :view true) - :on-close on-close} - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-flag - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "rules" - :id "file-menu-rules"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :rules) - (tr "workspace.header.menu.hide-rules") - (tr "workspace.header.menu.show-rules"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-rules))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-flag - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "display-grid" - :id "file-menu-grid"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :display-grid) - (tr "workspace.header.menu.hide-grid") - (tr "workspace.header.menu.show-grid"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-grid))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - - (when-not ^boolean read-only? - [:* - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-color-palette - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-color-palette event))) - :id "file-menu-color-palette"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :colorpalette) - (tr "workspace.header.menu.hide-palette") - (tr "workspace.header.menu.show-palette"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-colorpalette))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-text-palette - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-text-palette event))) - :id "file-menu-text-palette"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :textpalette) - (tr "workspace.header.menu.hide-textpalette") - (tr "workspace.header.menu.show-textpalette"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-textpalette))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]]]) - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-flag - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "display-artboard-names" - :id "file-menu-artboards"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :display-artboard-names) - (tr "workspace.header.menu.hide-artboard-names") - (tr "workspace.header.menu.show-artboard-names"))]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-flag - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "show-pixel-grid" - :id "file-menu-pixel-grid"} - [:span {:class (stl/css :item-name)} - (if (contains? layout :show-pixel-grid) - (tr "workspace.header.menu.hide-pixel-grid") - (tr "workspace.header.menu.show-pixel-grid"))] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :show-pixel-grid))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click toggle-flag - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-flag event))) - :data-test "hide-ui" - :id "file-menu-hide-ui"} - [:span {:class (stl/css :item-name)} - (tr "workspace.shape.menu.hide-ui")] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :hide-ui))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]]])) - -(mf/defc edit-menu - {::mf/wrap-props false - ::mf/wrap [mf/memo]} - [{:keys [on-close]}] - (let [select-all (mf/use-fn #(st/emit! (dw/select-all))) - undo (mf/use-fn #(st/emit! dwc/undo)) - redo (mf/use-fn #(st/emit! dwc/redo))] - [:& dropdown-menu {:show true - :list-class (stl/css-case :sub-menu true - :edit true) - :on-close on-close} - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click select-all - :on-key-down (fn [event] - (when (kbd/enter? event) - (select-all event))) - :id "file-menu-select-all"} - [:span {:class (stl/css :item-name)} - (tr "workspace.header.menu.select-all")] - [:span {:class (stl/css :shortcut)} - - (for [sc (scd/split-sc (sc/get-tooltip :select-all))] - [:span {:class (stl/css :shortcut-key) - :key sc} - sc])]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click undo - :on-key-down (fn [event] - (when (kbd/enter? event) - (undo event))) - :id "file-menu-undo"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.undo")] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :undo))] - [:span {:class (stl/css :shortcut-key) - :key sc} - sc])]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click redo - :on-key-down (fn [event] - (when (kbd/enter? event) - (redo event))) - :id "file-menu-redo"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.redo")] - [:span {:class (stl/css :shortcut)} - - (for [sc (scd/split-sc (sc/get-tooltip :redo))] - [:span {:class (stl/css :shortcut-key) - :key sc} - sc])]]])) - -(mf/defc file-menu - {::mf/wrap-props false} - [{:keys [on-close file]}] - (let [file-id (:id file) - shared? (:is-shared file) - - objects (mf/deref refs/workspace-page-objects) - frames (->> (cfh/get-immediate-children objects uuid/zero) - (filterv cfh/frame-shape?)) - - on-remove-shared - (mf/use-fn - (mf/deps file-id) - (fn [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (modal/show! - {:type :delete-shared-libraries - :origin :unpublish - :ids #{file-id} - :on-accept #(st/emit! (dwl/set-file-shared file-id false)) - :count-libraries 1}))) - - on-remove-shared-key-down - (mf/use-fn - (mf/deps on-remove-shared) - (fn [event] - (when (kbd/enter? event) - (on-remove-shared event)))) - - on-add-shared - (mf/use-fn - (mf/deps file-id) - (fn [_event] - (let [on-accept #(st/emit! (dwl/set-file-shared file-id true))] - (st/emit! (dcm/show-shared-dialog file-id on-accept))))) - - on-add-shared-key-down - (mf/use-fn - (mf/deps on-add-shared) - (fn [event] - (when (kbd/enter? event) - (on-add-shared event)))) - - on-export-shapes - (mf/use-fn #(st/emit! (de/show-workspace-export-dialog))) - - on-export-shapes-key-down - (mf/use-fn - (mf/deps on-export-shapes) - (fn [event] - (when (kbd/enter? event) - (on-export-shapes event)))) - - on-export-file - (mf/use-fn - (mf/deps file) - (fn [event] - (let [target (dom/get-current-target event) - binary? (= (dom/get-data target "binary") "true") - evname (if binary? - "export-binary-files" - "export-standard-files")] - (st/emit! - (ptk/event ::ev/event {::ev/name evname - ::ev/origin "workspace" - :num-files 1}) - (dcm/export-files [file] binary?))))) - - on-export-file-key-down - (mf/use-fn - (mf/deps on-export-file) - (fn [event] - (when (kbd/enter? event) - (on-export-file event)))) - - on-export-frames - (mf/use-fn - (mf/deps frames) - (fn [_] - (st/emit! (de/show-workspace-export-frames-dialog (reverse frames))))) - - on-export-frames-key-down - (mf/use-fn - (mf/deps on-export-frames) - (fn [event] - (when (kbd/enter? event) - (on-export-frames event))))] - - [:& dropdown-menu {:show true - :list-class (stl/css-case :sub-menu true - :file true) - :on-close on-close} - - (if ^boolean shared? - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click on-remove-shared - :on-key-down on-remove-shared-key-down - :id "file-menu-remove-shared"} - [:span {:class (stl/css :item-name)} (tr "dashboard.unpublish-shared")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click on-add-shared - :on-key-down on-add-shared-key-down - :id "file-menu-add-shared"} - [:span {:class (stl/css :item-name)} (tr "dashboard.add-shared")]]) - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click on-export-shapes - :on-key-down on-export-shapes-key-down - :id "file-menu-export-shapes"} - [:span {:class (stl/css :item-name)} (tr "dashboard.export-shapes")] - [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :export-shapes))] - [:span {:class (stl/css :shortcut-key) :key sc} sc])]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click on-export-file - :on-key-down on-export-file-key-down - :data-binary true - :id "file-menu-binary-file"} - [:span {:class (stl/css :item-name)} - (tr "dashboard.download-binary-file")]] - - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click on-export-file - :on-key-down on-export-file-key-down - :data-binary false - :id "file-menu-standard-file"} - [:span {:class (stl/css :item-name)} - (tr "dashboard.download-standard-file")]] - - (when (seq frames) - [:> dropdown-menu-item* {:class (stl/css :submenu-item) - :on-click on-export-frames - :on-key-down on-export-frames-key-down - :id "file-menu-export-frames"} - [:span {:class (stl/css :item-name)} - (tr "dashboard.export-frames")]])])) - -(mf/defc menu - {::mf/wrap-props false} - [{:keys [layout file]}] - (let [show-menu* (mf/use-state false) - show-menu? (deref show-menu*) - sub-menu* (mf/use-state false) - sub-menu (deref sub-menu*) - - open-menu - (mf/use-fn - (fn [event] - (dom/stop-propagation event) - (reset! show-menu* true))) - - close-menu - (mf/use-fn - (fn [event] - (dom/stop-propagation event) - (reset! show-menu* false))) - - close-sub-menu - (mf/use-fn - (fn [event] - (dom/stop-propagation event) - (reset! sub-menu* nil))) - - on-menu-click - (mf/use-fn - (fn [event] - (dom/stop-propagation event) - (let [menu (-> (dom/get-current-target event) - (dom/get-data "test") - (keyword))] - (reset! sub-menu* menu)))) - - toggle-flag - (mf/use-fn - (fn [event] - (dom/stop-propagation event) - (let [flag (-> (dom/get-current-target event) - (dom/get-data "test") - (keyword))] - (st/emit! - (-> (dw/toggle-layout-flag flag) - (vary-meta assoc ::ev/origin "workspace-menu"))) - (reset! show-menu* false) - (reset! sub-menu* nil))))] - - - [:* - [:div {:on-click open-menu - :class (stl/css :menu-btn)} i/menu-refactor] - - [:& dropdown-menu {:show show-menu? - :on-close close-menu - :list-class (stl/css :menu)} - - [:> dropdown-menu-item* {:class (stl/css :menu-item) - :on-click on-menu-click - :on-key-down (fn [event] - (when (kbd/enter? event) - (on-menu-click event))) - :on-pointer-enter on-menu-click - :data-test "file" - :id "file-menu-file"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.file")] - [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] - - [:> dropdown-menu-item* {:class (stl/css :menu-item) - :on-click on-menu-click - :on-key-down (fn [event] - (when (kbd/enter? event) - (on-menu-click event))) - :on-pointer-enter on-menu-click - :data-test "edit" - :id "file-menu-edit"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.edit")] - [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] - - [:> dropdown-menu-item* {:class (stl/css :menu-item) - :on-click on-menu-click - :on-key-down (fn [event] - (when (kbd/enter? event) - (on-menu-click event))) - :on-pointer-enter on-menu-click - :data-test "view" - :id "file-menu-view"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.view")] - [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] - - [:> dropdown-menu-item* {:class (stl/css :menu-item) - :on-click on-menu-click - :on-key-down (fn [event] - (when (kbd/enter? event) - (on-menu-click event))) - :on-pointer-enter on-menu-click - :data-test "preferences" - :id "file-menu-preferences"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.preferences")] - [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] - [:div {:class (stl/css :separator)}] - [:> dropdown-menu-item* {:class (stl/css-case :menu-item true) - :on-click on-menu-click - :on-key-down (fn [event] - (when (kbd/enter? event) - (on-menu-click event))) - :on-pointer-enter on-menu-click - :data-test "help-info" - :id "file-menu-help-info"} - [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.help-info")] - [:span {:class (stl/css :open-arrow)} i/arrow-refactor]]] - - (case sub-menu - :file - [:& file-menu - {:file file - :on-close close-sub-menu}] - - :edit - [:& edit-menu - {:on-close close-sub-menu}] - - :view - [:& view-menu - {:layout layout - :toggle-flag toggle-flag - :on-close close-sub-menu}] - - :preferences - [:& preferences-menu - {:layout layout - :toggle-flag toggle-flag - :on-close close-sub-menu}] - - :help-info - [:& help-info-menu - {:layout layout - :on-close close-sub-menu}] - - nil)])) - ;; --- Header Component (mf/defc left-header {::mf/wrap-props false} [{:keys [file layout project page-id class]}] - (let [file-id (:id file) + (let [profile (mf/deref refs/profile) + file-id (:id file) file-name (:name file) project-id (:id project) team-id (:team-id project) @@ -809,9 +110,10 @@ (when ^boolean shared? [:span {:class (stl/css :shared-badge)} i/library-refactor]) [:div {:class (stl/css :menu-section)} - [:& menu {:layout layout - :file file - :read-only? read-only? - :team-id team-id - :page-id page-id}]]])) - + [:& main-menu/menu + {:layout layout + :file file + :profile profile + :read-only? read-only? + :team-id team-id + :page-id page-id}]]])) diff --git a/frontend/src/app/main/ui/workspace/left_header.scss b/frontend/src/app/main/ui/workspace/left_header.scss index 6b0e5016e..1b441f83b 100644 --- a/frontend/src/app/main/ui/workspace/left_header.scss +++ b/frontend/src/app/main/ui/workspace/left_header.scss @@ -77,97 +77,3 @@ width: $s-16; } } - -.menu-btn { - @extend .button-tertiary; - height: $s-32; - width: calc($s-24 + $s-4); - padding: 0; - border-radius: $br-8; - svg { - @extend .button-icon; - stroke: var(--icon-foreground); - } -} - -.menu { - @extend .menu-dropdown; - top: $s-48; - left: calc(var(--width, $s-256) - $s-16); - width: $s-192; - margin: 0; -} - -.menu-item { - @extend .menu-item-base; - cursor: pointer; - .open-arrow { - @include flexCenter; - svg { - @extend .button-icon; - stroke: var(--icon-foreground); - } - } - &:hover { - color: var(--menu-foreground-color-hover); - .open-arrow { - svg { - stroke: var(--menu-foreground-color-hover); - } - } - .shortcut-key { - color: var(--menu-shortcut-foreground-color-hover); - } - } -} - -.separator { - margin-top: $s-8; - height: $s-4; - border-top: $s-1 solid $db-secondary; -} - -.shortcut { - @extend .shortcut-base; -} -.shortcut-key { - @extend .shortcut-key-base; -} - -.sub-menu { - @extend .menu-dropdown; - left: calc(var(--width, $s-256) + $s-180); - width: $s-192; - min-width: calc($s-272 - $s-2); - width: 110%; - - .submenu-item { - @extend .menu-item-base; - &:hover { - color: var(--menu-foreground-color-hover); - .shortcut-key { - color: var(--menu-shortcut-foreground-color-hover); - } - } - } - - &.file { - top: $s-48; - } - - &.edit { - top: $s-76; - } - - &.view { - top: $s-116; - } - - &.preferences { - top: $s-148; - } - - &.help-info { - top: $s-196; - } -} diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs new file mode 100644 index 000000000..42d17f70b --- /dev/null +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -0,0 +1,747 @@ +;; 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.main.ui.workspace.main-menu + (:require-macros [app.main.style :as stl]) + (:require + [app.common.files.helpers :as cfh] + [app.common.uuid :as uuid] + [app.config :as cf] + [app.main.data.common :as dcm] + [app.main.data.events :as ev] + [app.main.data.exports :as de] + [app.main.data.modal :as modal] + [app.main.data.shortcuts :as scd] + [app.main.data.users :as du] + [app.main.data.workspace :as dw] + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.shortcuts :as sc] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]] + [app.main.ui.context :as ctx] + [app.main.ui.hooks.resize :as r] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [app.util.keyboard :as kbd] + [app.util.router :as rt] + [potok.v2.core :as ptk] + [rumext.v2 :as mf])) + +;; --- Header menu and submenus + +(mf/defc help-info-menu + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [{:keys [layout on-close]}] + (let [nav-to-helpc-center + (mf/use-fn #(dom/open-new-window "https://help.penpot.app")) + + nav-to-community + (mf/use-fn #(dom/open-new-window "https://community.penpot.app")) + + nav-to-youtube + (mf/use-fn #(dom/open-new-window "https://www.youtube.com/c/Penpot")) + + nav-to-templates + (mf/use-fn #(dom/open-new-window "https://penpot.app/libraries-templates")) + + nav-to-github + (mf/use-fn #(dom/open-new-window "https://github.com/penpot/penpot")) + + nav-to-terms + (mf/use-fn #(dom/open-new-window "https://penpot.app/terms")) + + nav-to-feedback + (mf/use-fn #(st/emit! (rt/nav-new-window* {:rname :settings-feedback}))) + + show-shortcuts + (mf/use-fn + (mf/deps layout) + (fn [] + (when (contains? layout :collapse-left-sidebar) + (st/emit! (dw/toggle-layout-flag :collapse-left-sidebar))) + + (st/emit! + (-> (dw/toggle-layout-flag :shortcuts) + (vary-meta assoc ::ev/origin "workspace-header"))))) + + show-release-notes + (mf/use-fn + (fn [event] + (let [version (:main cf/version)] + (st/emit! (ptk/event ::ev/event {::ev/name "show-release-notes" :version version})) + (if (and (kbd/alt? event) (kbd/mod? event)) + (st/emit! (modal/show {:type :onboarding})) + (st/emit! (modal/show {:type :release-notes :version version}))))))] + + [:& dropdown-menu {:show true + :on-close on-close + :list-class (stl/css-case :sub-menu true + :help-info true)} + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-helpc-center + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-helpc-center event))) + :id "file-menu-help-center"} + [:span {:class (stl/css :item-name)} (tr "labels.help-center")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-community + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-community event))) + :id "file-menu-community"} + [:span {:class (stl/css :item-name)} (tr "labels.community")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-youtube + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-youtube event))) + :id "file-menu-youtube"} + [:span {:class (stl/css :item-name)} (tr "labels.tutorials")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click show-release-notes + :on-key-down (fn [event] + (when (kbd/enter? event) + (show-release-notes event))) + :id "file-menu-release-notes"} + [:span {:class (stl/css :item-name)} (tr "labels.release-notes")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-templates + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-templates event))) + :id "file-menu-templates"} + [:span {:class (stl/css :item-name)} (tr "labels.libraries-and-templates")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-github + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-github event))) + :id "file-menu-github"} + [:span {:class (stl/css :item-name)} (tr "labels.github-repo")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-terms + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-terms event))) + :id "file-menu-terms"} + [:span {:class (stl/css :item-name)} (tr "auth.terms-of-service")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click show-shortcuts + :on-key-down (fn [event] + (when (kbd/enter? event) + (show-shortcuts event))) + :id "file-menu-shortcuts"} + [:span {:class (stl/css :item-name)} (tr "label.shortcuts")] + [:span {:class (stl/css :shortcut)} + + (for [sc (scd/split-sc (sc/get-tooltip :show-shortcuts))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + (when (contains? cf/flags :user-feedback) + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click nav-to-feedback + :on-key-down (fn [event] + (when (kbd/enter? event) + (nav-to-feedback event))) + :id "file-menu-feedback"} + [:span {:class (stl/css-case :feedback true + :item-name true)} (tr "labels.give-feedback")]])])) + +(mf/defc preferences-menu + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [{:keys [layout profile toggle-flag on-close toggle-theme]}] + (let [show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))] + + [:& dropdown-menu {:show true + :list-class (stl/css-case :sub-menu true + :preferences true) + :on-close on-close} + [:> dropdown-menu-item* {:on-click toggle-flag + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "scale-text" + :id "file-menu-scale-text"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :scale-text) + (tr "workspace.header.menu.disable-scale-content") + (tr "workspace.header.menu.enable-scale-content"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-scale-text))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:on-click toggle-flag + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "snap-guides" + :id "file-menu-snap-guides"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :snap-guides) + (tr "workspace.header.menu.disable-snap-guides") + (tr "workspace.header.menu.enable-snap-guides"))] + [:span {:class (stl/css :shortcut)} + + (for [sc (scd/split-sc (sc/get-tooltip :toggle-snap-guide))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:on-click toggle-flag + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "snap-grid" + :id "file-menu-snap-grid"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :snap-grid) + (tr "workspace.header.menu.disable-snap-grid") + (tr "workspace.header.menu.enable-snap-grid"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-snap-grid))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:on-click toggle-flag + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "dynamic-alignment" + :id "file-menu-dynamic-alignment"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :dynamic-alignment) + (tr "workspace.header.menu.disable-dynamic-alignment") + (tr "workspace.header.menu.enable-dynamic-alignment"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-alignment))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:on-click toggle-flag + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "snap-pixel-grid" + :id "file-menu-pixel-grid"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :snap-pixel-grid) + (tr "workspace.header.menu.disable-snap-pixel-grid") + (tr "workspace.header.menu.enable-snap-pixel-grid"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :snap-pixel-grid))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:on-click show-nudge-options + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (show-nudge-options event))) + :data-test "snap-pixel-grid" + :id "file-menu-nudge"} + [:span {:class (stl/css :item-name)} (tr "modals.nudge-title")]] + + + [:> dropdown-menu-item* {:on-click toggle-theme + :class (stl/css :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-theme event))) + :data-test "toggle-theme" + :id "file-menu-toggle-theme"} + [:span {:class (stl/css :item-name)} + (if (= (:theme profile) "default") + (tr "workspace.header.menu.toggle-light-theme") + (tr "workspace.header.menu.toggle-dark-theme"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-theme))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]]])) + +(mf/defc view-menu + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [{:keys [layout toggle-flag on-close]}] + (let [read-only? (mf/use-ctx ctx/workspace-read-only?) + + toggle-color-palette + (mf/use-fn + (fn [] + (r/set-resize-type! :bottom) + (st/emit! (dw/remove-layout-flag :textpalette) + (-> (dw/toggle-layout-flag :colorpalette) + (vary-meta assoc ::ev/origin "workspace-menu"))))) + + toggle-text-palette + (mf/use-fn + (fn [] + (r/set-resize-type! :bottom) + (st/emit! (dw/remove-layout-flag :colorpalette) + (-> (dw/toggle-layout-flag :textpalette) + (vary-meta assoc ::ev/origin "workspace-menu")))))] + + [:& dropdown-menu {:show true + :list-class (stl/css-case :sub-menu true + :view true) + :on-close on-close} + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-flag + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "rules" + :id "file-menu-rules"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :rules) + (tr "workspace.header.menu.hide-rules") + (tr "workspace.header.menu.show-rules"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-rules))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-flag + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "display-grid" + :id "file-menu-grid"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :display-grid) + (tr "workspace.header.menu.hide-grid") + (tr "workspace.header.menu.show-grid"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-grid))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + + (when-not ^boolean read-only? + [:* + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-color-palette + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-color-palette event))) + :id "file-menu-color-palette"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :colorpalette) + (tr "workspace.header.menu.hide-palette") + (tr "workspace.header.menu.show-palette"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-colorpalette))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-text-palette + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-text-palette event))) + :id "file-menu-text-palette"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :textpalette) + (tr "workspace.header.menu.hide-textpalette") + (tr "workspace.header.menu.show-textpalette"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :toggle-textpalette))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]]]) + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-flag + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "display-artboard-names" + :id "file-menu-artboards"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :display-artboard-names) + (tr "workspace.header.menu.hide-artboard-names") + (tr "workspace.header.menu.show-artboard-names"))]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-flag + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "show-pixel-grid" + :id "file-menu-pixel-grid"} + [:span {:class (stl/css :item-name)} + (if (contains? layout :show-pixel-grid) + (tr "workspace.header.menu.hide-pixel-grid") + (tr "workspace.header.menu.show-pixel-grid"))] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :show-pixel-grid))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click toggle-flag + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-flag event))) + :data-test "hide-ui" + :id "file-menu-hide-ui"} + [:span {:class (stl/css :item-name)} + (tr "workspace.shape.menu.hide-ui")] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :hide-ui))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]]])) + +(mf/defc edit-menu + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [{:keys [on-close]}] + (let [select-all (mf/use-fn #(st/emit! (dw/select-all))) + undo (mf/use-fn #(st/emit! dwc/undo)) + redo (mf/use-fn #(st/emit! dwc/redo))] + [:& dropdown-menu {:show true + :list-class (stl/css-case :sub-menu true + :edit true) + :on-close on-close} + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click select-all + :on-key-down (fn [event] + (when (kbd/enter? event) + (select-all event))) + :id "file-menu-select-all"} + [:span {:class (stl/css :item-name)} + (tr "workspace.header.menu.select-all")] + [:span {:class (stl/css :shortcut)} + + (for [sc (scd/split-sc (sc/get-tooltip :select-all))] + [:span {:class (stl/css :shortcut-key) + :key sc} + sc])]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click undo + :on-key-down (fn [event] + (when (kbd/enter? event) + (undo event))) + :id "file-menu-undo"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.undo")] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :undo))] + [:span {:class (stl/css :shortcut-key) + :key sc} + sc])]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click redo + :on-key-down (fn [event] + (when (kbd/enter? event) + (redo event))) + :id "file-menu-redo"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.redo")] + [:span {:class (stl/css :shortcut)} + + (for [sc (scd/split-sc (sc/get-tooltip :redo))] + [:span {:class (stl/css :shortcut-key) + :key sc} + sc])]]])) + +(mf/defc file-menu + {::mf/wrap-props false} + [{:keys [on-close file]}] + (let [file-id (:id file) + shared? (:is-shared file) + + objects (mf/deref refs/workspace-page-objects) + frames (->> (cfh/get-immediate-children objects uuid/zero) + (filterv cfh/frame-shape?)) + + on-remove-shared + (mf/use-fn + (mf/deps file-id) + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (modal/show! + {:type :delete-shared-libraries + :origin :unpublish + :ids #{file-id} + :on-accept #(st/emit! (dwl/set-file-shared file-id false)) + :count-libraries 1}))) + + on-remove-shared-key-down + (mf/use-fn + (mf/deps on-remove-shared) + (fn [event] + (when (kbd/enter? event) + (on-remove-shared event)))) + + on-add-shared + (mf/use-fn + (mf/deps file-id) + (fn [_event] + (let [on-accept #(st/emit! (dwl/set-file-shared file-id true))] + (st/emit! (dcm/show-shared-dialog file-id on-accept))))) + + on-add-shared-key-down + (mf/use-fn + (mf/deps on-add-shared) + (fn [event] + (when (kbd/enter? event) + (on-add-shared event)))) + + on-export-shapes + (mf/use-fn #(st/emit! (de/show-workspace-export-dialog))) + + on-export-shapes-key-down + (mf/use-fn + (mf/deps on-export-shapes) + (fn [event] + (when (kbd/enter? event) + (on-export-shapes event)))) + + on-export-file + (mf/use-fn + (mf/deps file) + (fn [event] + (let [target (dom/get-current-target event) + binary? (= (dom/get-data target "binary") "true") + evname (if binary? + "export-binary-files" + "export-standard-files")] + (st/emit! + (ptk/event ::ev/event {::ev/name evname + ::ev/origin "workspace" + :num-files 1}) + (dcm/export-files [file] binary?))))) + + on-export-file-key-down + (mf/use-fn + (mf/deps on-export-file) + (fn [event] + (when (kbd/enter? event) + (on-export-file event)))) + + on-export-frames + (mf/use-fn + (mf/deps frames) + (fn [_] + (st/emit! (de/show-workspace-export-frames-dialog (reverse frames))))) + + on-export-frames-key-down + (mf/use-fn + (mf/deps on-export-frames) + (fn [event] + (when (kbd/enter? event) + (on-export-frames event))))] + + [:& dropdown-menu {:show true + :list-class (stl/css-case :sub-menu true + :file true) + :on-close on-close} + + (if ^boolean shared? + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click on-remove-shared + :on-key-down on-remove-shared-key-down + :id "file-menu-remove-shared"} + [:span {:class (stl/css :item-name)} (tr "dashboard.unpublish-shared")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click on-add-shared + :on-key-down on-add-shared-key-down + :id "file-menu-add-shared"} + [:span {:class (stl/css :item-name)} (tr "dashboard.add-shared")]]) + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click on-export-shapes + :on-key-down on-export-shapes-key-down + :id "file-menu-export-shapes"} + [:span {:class (stl/css :item-name)} (tr "dashboard.export-shapes")] + [:span {:class (stl/css :shortcut)} + (for [sc (scd/split-sc (sc/get-tooltip :export-shapes))] + [:span {:class (stl/css :shortcut-key) :key sc} sc])]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click on-export-file + :on-key-down on-export-file-key-down + :data-binary true + :id "file-menu-binary-file"} + [:span {:class (stl/css :item-name)} + (tr "dashboard.download-binary-file")]] + + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click on-export-file + :on-key-down on-export-file-key-down + :data-binary false + :id "file-menu-standard-file"} + [:span {:class (stl/css :item-name)} + (tr "dashboard.download-standard-file")]] + + (when (seq frames) + [:> dropdown-menu-item* {:class (stl/css :submenu-item) + :on-click on-export-frames + :on-key-down on-export-frames-key-down + :id "file-menu-export-frames"} + [:span {:class (stl/css :item-name)} + (tr "dashboard.export-frames")]])])) + +(mf/defc menu + {::mf/wrap-props false} + [{:keys [layout file profile]}] + (let [show-menu* (mf/use-state false) + show-menu? (deref show-menu*) + sub-menu* (mf/use-state false) + sub-menu (deref sub-menu*) + + open-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! show-menu* true))) + + close-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! show-menu* false))) + + close-sub-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! sub-menu* nil))) + + on-menu-click + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (let [menu (-> (dom/get-current-target event) + (dom/get-data "test") + (keyword))] + (reset! sub-menu* menu)))) + + toggle-flag + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (let [flag (-> (dom/get-current-target event) + (dom/get-data "test") + (keyword))] + (st/emit! + (-> (dw/toggle-layout-flag flag) + (vary-meta assoc ::ev/origin "workspace-menu"))) + (reset! show-menu* false) + (reset! sub-menu* nil)))) + + + toggle-theme + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (st/emit! (du/toggle-theme))))] + + + [:* + [:div {:on-click open-menu + :class (stl/css :menu-btn)} i/menu-refactor] + + [:& dropdown-menu {:show show-menu? + :on-close close-menu + :list-class (stl/css :menu)} + + [:> dropdown-menu-item* {:class (stl/css :menu-item) + :on-click on-menu-click + :on-key-down (fn [event] + (when (kbd/enter? event) + (on-menu-click event))) + :on-pointer-enter on-menu-click + :data-test "file" + :id "file-menu-file"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.file")] + [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] + + [:> dropdown-menu-item* {:class (stl/css :menu-item) + :on-click on-menu-click + :on-key-down (fn [event] + (when (kbd/enter? event) + (on-menu-click event))) + :on-pointer-enter on-menu-click + :data-test "edit" + :id "file-menu-edit"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.edit")] + [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] + + [:> dropdown-menu-item* {:class (stl/css :menu-item) + :on-click on-menu-click + :on-key-down (fn [event] + (when (kbd/enter? event) + (on-menu-click event))) + :on-pointer-enter on-menu-click + :data-test "view" + :id "file-menu-view"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.view")] + [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] + + [:> dropdown-menu-item* {:class (stl/css :menu-item) + :on-click on-menu-click + :on-key-down (fn [event] + (when (kbd/enter? event) + (on-menu-click event))) + :on-pointer-enter on-menu-click + :data-test "preferences" + :id "file-menu-preferences"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.preferences")] + [:span {:class (stl/css :open-arrow)} i/arrow-refactor]] + [:div {:class (stl/css :separator)}] + [:> dropdown-menu-item* {:class (stl/css-case :menu-item true) + :on-click on-menu-click + :on-key-down (fn [event] + (when (kbd/enter? event) + (on-menu-click event))) + :on-pointer-enter on-menu-click + :data-test "help-info" + :id "file-menu-help-info"} + [:span {:class (stl/css :item-name)} (tr "workspace.header.menu.option.help-info")] + [:span {:class (stl/css :open-arrow)} i/arrow-refactor]]] + + (case sub-menu + :file + [:& file-menu + {:file file + :on-close close-sub-menu}] + + :edit + [:& edit-menu + {:on-close close-sub-menu}] + + :view + [:& view-menu + {:layout layout + :toggle-flag toggle-flag + :on-close close-sub-menu}] + + :preferences + [:& preferences-menu + {:layout layout + :profile profile + :toggle-flag toggle-flag + :toggle-theme toggle-theme + :on-close close-sub-menu}] + + :help-info + [:& help-info-menu + {:layout layout + :on-close close-sub-menu}] + + nil)])) diff --git a/frontend/src/app/main/ui/workspace/main_menu.scss b/frontend/src/app/main/ui/workspace/main_menu.scss new file mode 100644 index 000000000..55732dab2 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/main_menu.scss @@ -0,0 +1,101 @@ +// 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 + +@import "refactor/common-refactor.scss"; + +.menu-btn { + @extend .button-tertiary; + height: $s-32; + width: calc($s-24 + $s-4); + padding: 0; + border-radius: $br-8; + svg { + @extend .button-icon; + stroke: var(--icon-foreground); + } +} + +.menu { + @extend .menu-dropdown; + top: $s-48; + left: calc(var(--width, $s-256) - $s-16); + width: $s-192; + margin: 0; +} + +.menu-item { + @extend .menu-item-base; + cursor: pointer; + .open-arrow { + @include flexCenter; + svg { + @extend .button-icon; + stroke: var(--icon-foreground); + } + } + &:hover { + color: var(--menu-foreground-color-hover); + .open-arrow { + svg { + stroke: var(--menu-foreground-color-hover); + } + } + .shortcut-key { + color: var(--menu-shortcut-foreground-color-hover); + } + } +} + +.separator { + margin-top: $s-8; + height: $s-4; + border-top: $s-1 solid $db-secondary; +} + +.shortcut { + @extend .shortcut-base; +} +.shortcut-key { + @extend .shortcut-key-base; +} + +.sub-menu { + @extend .menu-dropdown; + left: calc(var(--width, $s-256) + $s-180); + width: $s-192; + min-width: calc($s-272 - $s-2); + width: 110%; + + .submenu-item { + @extend .menu-item-base; + &:hover { + color: var(--menu-foreground-color-hover); + .shortcut-key { + color: var(--menu-shortcut-foreground-color-hover); + } + } + } + + &.file { + top: $s-48; + } + + &.edit { + top: $s-76; + } + + &.view { + top: $s-116; + } + + &.preferences { + top: $s-148; + } + + &.help-info { + top: $s-196; + } +} diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 69cad92bf..46f2e63c1 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3442,6 +3442,12 @@ msgstr "Show fonts palette" msgid "workspace.header.menu.undo" msgstr "Undo" +msgid "workspace.header.menu.toggle-light-theme" +msgstr "Switch to light theme" + +msgid "workspace.header.menu.toggle-dark-theme" +msgstr "Switch to dark theme" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Reset" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index fbd44e23a..8af585f2b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3504,6 +3504,12 @@ msgstr "Mostrar paleta de textos" msgid "workspace.header.menu.undo" msgstr "Deshacer" +msgid "workspace.header.menu.toggle-light-theme" +msgstr "Cambiar a tema claro" + +msgid "workspace.header.menu.toggle-dark-theme" +msgstr "Cambiar a tema oscuro" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Restablecer"