0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-18 18:51:29 -05:00

Merge pull request #5261 from penpot/palba-viewer-and-plugins

 Integrate viewer role with plugin menus and popup
This commit is contained in:
Andrey Antukh 2024-11-08 10:49:18 +01:00 committed by GitHub
commit 960f095c1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 168 additions and 36 deletions

View file

@ -8,13 +8,23 @@
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.register :as preg] [app.plugins.register :as preg]
[app.util.globals :as ug] [app.util.globals :as ug]
[app.util.http :as http] [app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.time :as dt]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [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 (defn fetch-manifest
[plugin-url] [plugin-url]
(->> (http/send! {:method :get (->> (http/send! {:method :get
@ -59,15 +69,21 @@
(.error js/console "Error" e)))) (.error js/console "Error" e))))
(defn open-plugin! (defn open-plugin!
[{:keys [url] :as manifest}] [{:keys [url] :as manifest} user-can-edit?]
(if url (if url
;; If the saved manifest has a URL we fetch the manifest to check ;; If the saved manifest has a URL we fetch the manifest to check
;; for updates ;; for updates
(->> (fetch-manifest url) (->> (fetch-manifest url)
(rx/subs! (rx/subs!
(fn [new-manifest] (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 (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)) (not= (:permissions new-manifest) (:permissions manifest))
(modal/show! (modal/show!
:plugin-permissions-update :plugin-permissions-update
@ -96,13 +112,21 @@
(.error js/console "Error" e)))) (.error js/console "Error" e))))
(defn close-current-plugin (defn close-current-plugin
[] [& {:keys [close-only-edition-plugins?]}]
(ptk/reify ::close-current-plugin (ptk/reify ::close-current-plugin
ptk/EffectEvent ptk/EffectEvent
(effect [_ state _] (effect [_ state _]
(let [ids (dm/get-in state [:workspace-local :open-plugins])] (let [ids (dm/get-in state [:workspace-local :open-plugins])]
(doseq [id ids] (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 (defn delay-open-plugin
[plugin] [plugin]
@ -116,6 +140,38 @@
(ptk/reify ::check-open-plugin (ptk/reify ::check-open-plugin
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [user-can-edit? (dm/get-in state [:permissions :can-edit])]
(when-let [pid (::open-plugin state)] (when-let [pid (::open-plugin state)]
(open-plugin! (preg/get-plugin pid)) (open-plugin! (preg/get-plugin pid) user-can-edit?)
(rx/of #(dissoc % ::open-plugin)))))) (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)))))

View file

@ -14,6 +14,7 @@
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
[app.main.data.common :as dc] [app.main.data.common :as dc]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.plugins :as dpl]
[app.main.data.websocket :as dws] [app.main.data.websocket :as dws]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.data.workspace.edition :as dwe] [app.main.data.workspace.edition :as dwe]
@ -117,7 +118,8 @@
(rx/delay 100)) (rx/delay 100))
(if (= :viewer role) (if (= :viewer role)
(rx/of (modal/hide) (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))))))) (rx/of (dwly/set-options-mode :design)))))))
(defn- process-message (defn- process-message

View file

@ -518,6 +518,11 @@
(def workspace-selected-token-set-tokens (def workspace-selected-token-set-tokens
(l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state)) (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 ;; ---- Viewer refs
(defn lookup-viewer-objects-by-id (defn lookup-viewer-objects-by-id

View file

@ -11,6 +11,7 @@
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.persistence :as dps] [app.main.data.persistence :as dps]
[app.main.data.plugins :as dpl]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
[app.main.features :as features] [app.main.features :as features]
@ -185,7 +186,8 @@
background-color (:background-color wglobal)] background-color (:background-color wglobal)]
(mf/with-effect [] (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 ;; Setting the layout preset by its name
(mf/with-effect [layout-name] (mf/with-effect [layout-name]

View file

@ -632,7 +632,9 @@
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[{:keys [open-plugins on-close]}] [{:keys [open-plugins on-close]}]
(when (features/active-feature? @st/state "plugins/runtime") (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 [:& dropdown-menu {:show true
:list-class (stl/css-case :sub-menu true :plugins true) :list-class (stl/css-case :sub-menu true :plugins true)
:on-close on-close} :on-close on-close}
@ -653,24 +655,41 @@
(when (d/not-empty? plugins) (when (d/not-empty? plugins)
[:div {:class (stl/css :separator)}]) [:div {:class (stl/css :separator)}])
(for [[idx {:keys [name host] :as manifest}] (d/enumerate plugins)] (for [[idx {:keys [plugin-id name host permissions] :as manifest}] (d/enumerate plugins)]
[:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx) (let [permissions (or (get permissions-peek plugin-id) permissions)
:on-click #(do 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" (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin"
::ev/origin "workspace:menu" ::ev/origin "workspace:menu"
:name name :name name
:host host})) :host host}))
(dp/open-plugin! manifest)) (dp/open-plugin! manifest user-can-edit?))
:class (stl/css :submenu-item) (dom/stop-propagation event))))
:on-key-down (fn [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) (when (kbd/enter? event)
#(do
(st/emit! (ptk/event ::ev/event {::ev/name "start-plugin" (st/emit! (ptk/event ::ev/event {::ev/name "start-plugin"
::ev/origin "workspace:menu" ::ev/origin "workspace:menu"
:name name :name name
:host host})) :host host}))
(dp/open-plugin! manifest))))} (dp/open-plugin! manifest user-can-edit?)))))]
[:span {:class (stl/css :item-name)} name]])]))) [:> 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/defc menu
{::mf/props :obj} {::mf/props :obj}

View file

@ -17,20 +17,25 @@
.menu-item { .menu-item {
@extend .menu-item-base; @extend .menu-item-base;
cursor: pointer; cursor: pointer;
.open-arrow { .open-arrow {
@include flexCenter; @include flexCenter;
svg { svg {
@extend .button-icon; @extend .button-icon;
stroke: var(--icon-foreground); stroke: var(--icon-foreground);
} }
} }
&:hover { &:hover {
color: var(--menu-foreground-color-hover); color: var(--menu-foreground-color-hover);
.open-arrow { .open-arrow {
svg { svg {
stroke: var(--menu-foreground-color-hover); stroke: var(--menu-foreground-color-hover);
} }
} }
.shortcut-key { .shortcut-key {
color: var(--menu-shortcut-foreground-color-hover); color: var(--menu-shortcut-foreground-color-hover);
} }
@ -46,6 +51,7 @@
.shortcut { .shortcut {
@extend .shortcut-base; @extend .shortcut-base;
} }
.shortcut-key { .shortcut-key {
@extend .shortcut-key-base; @extend .shortcut-key-base;
} }
@ -59,14 +65,26 @@
.submenu-item { .submenu-item {
@extend .menu-item-base; @extend .menu-item-base;
&:hover { &:hover {
color: var(--menu-foreground-color-hover); color: var(--menu-foreground-color-hover);
.shortcut-key { .shortcut-key {
color: var(--menu-shortcut-foreground-color-hover); 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 { &.file {
top: $s-48; top: $s-48;
} }

View file

@ -13,9 +13,11 @@
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.plugins :as dp] [app.main.data.plugins :as dp]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.search-bar :refer [search-bar]] [app.main.ui.components.search-bar :refer [search-bar]]
[app.main.ui.components.title-bar :refer [title-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.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.plugins.register :as preg] [app.plugins.register :as preg]
@ -40,14 +42,22 @@
icon)) icon))
(mf/defc plugin-entry (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 handle-open-click
(mf/use-callback (mf/use-callback
(mf/deps index manifest on-open-plugin) (mf/deps index manifest on-open-plugin can-open?)
(fn [] (fn []
(when on-open-plugin (when (and can-open? on-open-plugin)
(on-open-plugin manifest)))) (on-open-plugin manifest))))
handle-delete-click handle-delete-click
@ -64,8 +74,14 @@
[:div {:class (stl/css :plugin-description)} [:div {:class (stl/css :plugin-description)}
[:div {:class (stl/css :plugin-title)} name] [:div {:class (stl/css :plugin-title)} name]
[:div {:class (stl/css :plugin-summary)} (d/nilv description "")]] [: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" [:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.plugins.remove-plugin") :aria-label (tr "workspace.plugins.remove-plugin")
:on-click handle-delete-click :on-click handle-delete-click
@ -91,6 +107,8 @@
error-manifest? (= :error-manifest input-status) error-manifest? (= :error-manifest input-status)
error? (or error-url? error-manifest?) error? (or error-url? error-manifest?)
user-can-edit? (:can-edit (deref refs/permissions))
handle-close-dialog handle-close-dialog
(mf/use-callback (mf/use-callback
(fn [] (fn []
@ -137,7 +155,7 @@
::ev/origin "workspace:plugins" ::ev/origin "workspace:plugins"
:name (:name manifest) :name (:name manifest)
:host (:host manifest)})) :host (:host manifest)}))
(dp/open-plugin! manifest) (dp/open-plugin! manifest user-can-edit?)
(modal/hide!))) (modal/hide!)))
handle-remove-plugin handle-remove-plugin
@ -204,6 +222,7 @@
[:& plugin-entry {:key (dm/str "plugin-" idx) [:& plugin-entry {:key (dm/str "plugin-" idx)
:index idx :index idx
:manifest manifest :manifest manifest
:user-can-edit user-can-edit?
:on-open-plugin handle-open-plugin :on-open-plugin handle-open-plugin
:on-remove-plugin handle-remove-plugin}])]])]]])) :on-remove-plugin handle-remove-plugin}])]])]]]))

View file

@ -102,6 +102,7 @@
@include flexCenter; @include flexCenter;
width: $s-20; width: $s-20;
padding: 0 0 0 $s-8; padding: 0 0 0 $s-8;
svg { svg {
@extend .button-icon-small; @extend .button-icon-small;
stroke: var(--icon-foreground); stroke: var(--icon-foreground);
@ -114,7 +115,9 @@
} }
.open-button { .open-button {
@extend .button-secondary; display: flex;
justify-content: center;
align-items: center;
width: $s-68; width: $s-68;
min-width: $s-68; min-width: $s-68;
height: $s-32; height: $s-32;

View file

@ -5859,6 +5859,10 @@ msgstr "Plugins"
msgid "workspace.plugins.remove-plugin" msgid "workspace.plugins.remove-plugin"
msgstr "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 #: /src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1005
msgid "workspace.shape.menu.add-layout" msgid "workspace.shape.menu.add-layout"
msgstr "Add layout" msgstr "Add layout"

View file

@ -5837,6 +5837,10 @@ msgstr "Extensiones"
msgid "workspace.plugins.remove-plugin" msgid "workspace.plugins.remove-plugin"
msgstr "Eliminar extensión" 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 #: /src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1005
msgid "workspace.shape.menu.add-layout" msgid "workspace.shape.menu.add-layout"
msgstr "Añadir layout" msgstr "Añadir layout"