From d9eff00a719a78f5d8c17a2d317b31392da7d289 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 7 Nov 2024 13:42:32 +0100 Subject: [PATCH] :sparkles: Integrate viewer role with plugin menus and popup --- frontend/src/app/main/data/plugins.cljs | 70 +++++++++++++++++-- .../main/data/workspace/notifications.cljs | 4 +- frontend/src/app/main/refs.cljs | 5 ++ frontend/src/app/main/ui/workspace.cljs | 4 +- .../src/app/main/ui/workspace/main_menu.cljs | 57 ++++++++++----- .../src/app/main/ui/workspace/main_menu.scss | 18 +++++ .../src/app/main/ui/workspace/plugins.cljs | 33 +++++++-- .../src/app/main/ui/workspace/plugins.scss | 5 +- frontend/translations/en.po | 4 ++ frontend/translations/es.po | 4 ++ 10 files changed, 168 insertions(+), 36 deletions(-) diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index ba27e6a0a..0dd933651 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -8,13 +8,23 @@ (:require [app.common.data.macros :as dm] [app.main.data.modal :as modal] + [app.main.data.notifications :as ntf] [app.main.store :as st] [app.plugins.register :as preg] [app.util.globals :as ug] [app.util.http :as http] + [app.util.i18n :as i18n :refer [tr]] + [app.util.time :as dt] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) +(defn save-plugin-permissions-peek + [id permissions] + (ptk/reify ::save-plugin-permissions-peek + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:plugins-permissions-peek :data id] permissions)))) + (defn fetch-manifest [plugin-url] (->> (http/send! {:method :get @@ -59,15 +69,21 @@ (.error js/console "Error" e)))) (defn open-plugin! - [{:keys [url] :as manifest}] + [{:keys [url] :as manifest} user-can-edit?] (if url ;; If the saved manifest has a URL we fetch the manifest to check ;; for updates (->> (fetch-manifest url) (rx/subs! (fn [new-manifest] - (let [new-manifest (merge new-manifest (select-keys manifest [:plugin-id]))] + (let [new-manifest (merge new-manifest (select-keys manifest [:plugin-id])) + permissions (:permissions new-manifest) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write"))] + (st/emit! (save-plugin-permissions-peek (:plugin-id new-manifest) permissions)) (cond + (and is-edition-plugin? (not user-can-edit?)) + (st/emit! (ntf/warn (tr "workspace.plugins.error.need-editor"))) (not= (:permissions new-manifest) (:permissions manifest)) (modal/show! :plugin-permissions-update @@ -96,13 +112,21 @@ (.error js/console "Error" e)))) (defn close-current-plugin - [] + [& {:keys [close-only-edition-plugins?]}] (ptk/reify ::close-current-plugin ptk/EffectEvent (effect [_ state _] (let [ids (dm/get-in state [:workspace-local :open-plugins])] (doseq [id ids] - (close-plugin! (preg/get-plugin id))))))) + (let [plugin (preg/get-plugin id) + permissions (or (dm/get-in state [:plugins-permissions-peek :data id]) + (:permissions plugin)) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write"))] + + (when (or (not close-only-edition-plugins?) + is-edition-plugin?) + (close-plugin! plugin)))))))) (defn delay-open-plugin [plugin] @@ -116,6 +140,38 @@ (ptk/reify ::check-open-plugin ptk/WatchEvent (watch [_ state _] - (when-let [pid (::open-plugin state)] - (open-plugin! (preg/get-plugin pid)) - (rx/of #(dissoc % ::open-plugin)))))) + (let [user-can-edit? (dm/get-in state [:permissions :can-edit])] + (when-let [pid (::open-plugin state)] + (open-plugin! (preg/get-plugin pid) user-can-edit?) + (rx/of #(dissoc % ::open-plugin))))))) + +(defn- update-plugin-permissions-peek + [{:keys [plugin-id url]}] + (when url + ;; If the saved manifest has a URL we fetch the manifest to check + ;; for updates + (->> (fetch-manifest url) + (rx/subs! + (fn [new-manifest] + (let [permissions (:permissions new-manifest)] + (when permissions + (st/emit! (save-plugin-permissions-peek plugin-id permissions))))))))) + +(defn update-plugins-permissions-peek + [] + (ptk/reify ::update-plugins-permissions-peek + ptk/UpdateEvent + (update [_ state] + (let [now (dt/now) + expiration (dt/minus now (dt/duration {:days 1})) + updated-at (dm/get-in state [:plugins-permissions-peek :updated-at] 0) + expired? (> expiration updated-at)] + + (if expired? + (let [plugins (preg/plugins-list)] + (doseq [plugin plugins] + (update-plugin-permissions-peek plugin)) + (-> state + (assoc-in [:plugins-permissions-peek :updated-at] now))) + + state))))) diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index fc49d273f..d4b6b7ffe 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -14,6 +14,7 @@ [app.main.data.changes :as dch] [app.main.data.common :as dc] [app.main.data.modal :as modal] + [app.main.data.plugins :as dpl] [app.main.data.websocket :as dws] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.edition :as dwe] @@ -117,7 +118,8 @@ (rx/delay 100)) (if (= :viewer role) (rx/of (modal/hide) - (dwly/set-options-mode :inspect)) + (dwly/set-options-mode :inspect) + (dpl/close-current-plugin {:close-only-edition-plugins? true})) (rx/of (dwly/set-options-mode :design))))))) (defn- process-message diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 6f73be8dc..063bb5b79 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -512,6 +512,11 @@ (def workspace-selected-token-set-tokens (l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state)) +(def plugins-permissions-peek + (l/derived (fn [state] + (dm/get-in state [:plugins-permissions-peek :data])) + st/state)) + ;; ---- Viewer refs (defn lookup-viewer-objects-by-id diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 4f6360733..760269254 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -11,6 +11,7 @@ [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] [app.main.data.persistence :as dps] + [app.main.data.plugins :as dpl] [app.main.data.workspace :as dw] [app.main.data.workspace.colors :as dc] [app.main.features :as features] @@ -185,7 +186,8 @@ background-color (:background-color wglobal)] (mf/with-effect [] - (st/emit! (dps/initialize-persistence))) + (st/emit! (dps/initialize-persistence) + (dpl/update-plugins-permissions-peek))) ;; Setting the layout preset by its name (mf/with-effect [layout-name] diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 7fd4cddb5..d870e3ac5 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -632,7 +632,9 @@ ::mf/wrap [mf/memo]} [{:keys [open-plugins on-close]}] (when (features/active-feature? @st/state "plugins/runtime") - (let [plugins (preg/plugins-list)] + (let [plugins (preg/plugins-list) + user-can-edit? (:can-edit (deref refs/permissions)) + permissions-peek (deref refs/plugins-permissions-peek)] [:& dropdown-menu {:show true :list-class (stl/css-case :sub-menu true :plugins true) :on-close on-close} @@ -653,24 +655,41 @@ (when (d/not-empty? plugins) [:div {:class (stl/css :separator)}]) - (for [[idx {:keys [name host] :as manifest}] (d/enumerate plugins)] - [:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx) - :on-click #(do - (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" - ::ev/origin "workspace:menu" - :name name - :host host})) - (dp/open-plugin! manifest)) - :class (stl/css :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - #(do - (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" - ::ev/origin "workspace:menu" - :name name - :host host})) - (dp/open-plugin! manifest))))} - [:span {:class (stl/css :item-name)} name]])]))) + (for [[idx {:keys [plugin-id name host permissions] :as manifest}] (d/enumerate plugins)] + (let [permissions (or (get permissions-peek plugin-id) permissions) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write")) + can-open? (or user-can-edit? + (not is-edition-plugin?)) + on-click + (mf/use-fn + (mf/deps can-open? name host manifest user-can-edit?) + (fn [event] + (if can-open? + (do + (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" + ::ev/origin "workspace:menu" + :name name + :host host})) + (dp/open-plugin! manifest user-can-edit?)) + (dom/stop-propagation event)))) + on-key-down + (mf/use-fn + (mf/deps can-open? name host manifest user-can-edit?) + (fn [event] + (when can-open? + (when (kbd/enter? event) + (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" + ::ev/origin "workspace:menu" + :name name + :host host})) + (dp/open-plugin! manifest user-can-edit?)))))] + [:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx) + :on-click on-click + :title (when-not can-open? (tr "workspace.plugins.error.need-editor")) + :class (stl/css-case :submenu-item true :menu-disabled (not can-open?)) + :on-key-down on-key-down} + [:span {:class (stl/css :item-name)} name]]))]))) (mf/defc menu {::mf/props :obj} diff --git a/frontend/src/app/main/ui/workspace/main_menu.scss b/frontend/src/app/main/ui/workspace/main_menu.scss index e74ddcba6..56ae1cd19 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.scss +++ b/frontend/src/app/main/ui/workspace/main_menu.scss @@ -17,20 +17,25 @@ .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); } @@ -46,6 +51,7 @@ .shortcut { @extend .shortcut-base; } + .shortcut-key { @extend .shortcut-key-base; } @@ -59,14 +65,26 @@ .submenu-item { @extend .menu-item-base; + &:hover { color: var(--menu-foreground-color-hover); + .shortcut-key { color: var(--menu-shortcut-foreground-color-hover); } } } + .menu-disabled { + color: var(--color-foreground-secondary); + + &:hover { + cursor: default; + color: var(--color-foreground-secondary); + background-color: var(--menu-background-color); + } + } + &.file { top: $s-48; } diff --git a/frontend/src/app/main/ui/workspace/plugins.cljs b/frontend/src/app/main/ui/workspace/plugins.cljs index b6aee18d6..c0ad1c580 100644 --- a/frontend/src/app/main/ui/workspace/plugins.cljs +++ b/frontend/src/app/main/ui/workspace/plugins.cljs @@ -13,9 +13,11 @@ [app.main.data.events :as ev] [app.main.data.modal :as modal] [app.main.data.plugins :as dp] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.search-bar :refer [search-bar]] [app.main.ui.components.title-bar :refer [title-bar]] + [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.icons :as i] [app.plugins.register :as preg] @@ -40,14 +42,22 @@ icon)) (mf/defc plugin-entry - [{:keys [index manifest on-open-plugin on-remove-plugin]}] + [{:keys [index manifest user-can-edit on-open-plugin on-remove-plugin]}] + + (let [{:keys [plugin-id host icon name description permissions]} manifest + plugins-permissions-peek (deref refs/plugins-permissions-peek) + permissions (or (get plugins-permissions-peek plugin-id) + permissions) + is-edition-plugin? (or (contains? permissions "content:write") + (contains? permissions "library:write")) + can-open? (or user-can-edit + (not is-edition-plugin?)) - (let [{:keys [host icon name description]} manifest handle-open-click (mf/use-callback - (mf/deps index manifest on-open-plugin) + (mf/deps index manifest on-open-plugin can-open?) (fn [] - (when on-open-plugin + (when (and can-open? on-open-plugin) (on-open-plugin manifest)))) handle-delete-click @@ -64,8 +74,14 @@ [:div {:class (stl/css :plugin-description)} [:div {:class (stl/css :plugin-title)} name] [:div {:class (stl/css :plugin-summary)} (d/nilv description "")]] - [:button {:class (stl/css :open-button) - :on-click handle-open-click} (tr "workspace.plugins.button-open")] + + + [:> button* {:class (stl/css :open-button) + :variant "secondary" + :on-click handle-open-click + :title (when-not can-open? (tr "workspace.plugins.error.need-editor")) + :disabled (not can-open?)} (tr "workspace.plugins.button-open")] + [:> icon-button* {:variant "ghost" :aria-label (tr "workspace.plugins.remove-plugin") :on-click handle-delete-click @@ -91,6 +107,8 @@ error-manifest? (= :error-manifest input-status) error? (or error-url? error-manifest?) + user-can-edit? (:can-edit (deref refs/permissions)) + handle-close-dialog (mf/use-callback (fn [] @@ -137,7 +155,7 @@ ::ev/origin "workspace:plugins" :name (:name manifest) :host (:host manifest)})) - (dp/open-plugin! manifest) + (dp/open-plugin! manifest user-can-edit?) (modal/hide!))) handle-remove-plugin @@ -204,6 +222,7 @@ [:& plugin-entry {:key (dm/str "plugin-" idx) :index idx :manifest manifest + :user-can-edit user-can-edit? :on-open-plugin handle-open-plugin :on-remove-plugin handle-remove-plugin}])]])]]])) diff --git a/frontend/src/app/main/ui/workspace/plugins.scss b/frontend/src/app/main/ui/workspace/plugins.scss index 96ad29243..82d0bb6cd 100644 --- a/frontend/src/app/main/ui/workspace/plugins.scss +++ b/frontend/src/app/main/ui/workspace/plugins.scss @@ -102,6 +102,7 @@ @include flexCenter; width: $s-20; padding: 0 0 0 $s-8; + svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -114,7 +115,9 @@ } .open-button { - @extend .button-secondary; + display: flex; + justify-content: center; + align-items: center; width: $s-68; min-width: $s-68; height: $s-32; diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 96dc22c16..e44b01166 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -5859,6 +5859,10 @@ msgstr "Plugins" msgid "workspace.plugins.remove-plugin" msgstr "Remove plugin" +#: src/app/main/data/plugins.cljs:78 +msgid "workspace.plugins.error.need-editor" +msgstr "You need to be an editor to use this plugin" + #: /src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1005 msgid "workspace.shape.menu.add-layout" msgstr "Add layout" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 82864350b..d50e66603 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -5837,6 +5837,10 @@ msgstr "Extensiones" msgid "workspace.plugins.remove-plugin" msgstr "Eliminar extensión" +#: src/app/main/data/plugins.cljs:78 +msgid "workspace.plugins.error.need-editor" +msgstr "Debes ser un editor para usar este plugin" + #: /src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1005 msgid "workspace.shape.menu.add-layout" msgstr "Añadir layout"