diff --git a/frontend/resources/plugins-runtime/index.js b/frontend/resources/plugins-runtime/index.js index 2155ad986a..4f538445b6 100644 --- a/frontend/resources/plugins-runtime/index.js +++ b/frontend/resources/plugins-runtime/index.js @@ -8232,25 +8232,25 @@ window.addEventListener("message", (t) => { console.error(e); } }); -const Dl = async function(t) { +const Dl = async function(t, e) { try { - const e = yn && yn(t.pluginId); - if (!e) + const r = yn && yn(t.pluginId); + if (!r) return; bo(); - const r = await Ll( - P.harden(e), + const n = await Ll( + P.harden(r), t, () => { - ht = ht.filter((n) => n !== r); + ht = ht.filter((o) => o !== n), e && e(); } ); - ht.push(r); - } catch (e) { - bo(), console.error(e); + ht.push(n); + } catch (r) { + bo(), console.error(r); } -}, zs = async function(t) { - Dl(t); +}, zs = async function(t, e) { + Dl(t, e); }, Ul = async function(t) { const e = await Al(t); zs(e); diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index a2c2db7cbb..ba27e6a0a8 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -6,14 +6,42 @@ (ns app.main.data.plugins (:require - [app.plugins.register :as pr] + [app.common.data.macros :as dm] + [app.main.data.modal :as modal] + [app.main.store :as st] + [app.plugins.register :as preg] [app.util.globals :as ug] + [app.util.http :as http] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) -(defn open-plugin! +(defn fetch-manifest + [plugin-url] + (->> (http/send! {:method :get + :uri plugin-url + :omit-default-headers true + :response-type :json}) + (rx/map :body) + (rx/map #(preg/parse-manifest plugin-url %)))) + +(defn save-current-plugin + [id] + (ptk/reify ::save-current-plugin + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :open-plugins] (fnil conj #{}) id)))) + +(defn remove-current-plugin + [id] + (ptk/reify ::remove-current-plugin + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :open-plugins] (fnil disj #{}) id)))) + +(defn- load-plugin! [{:keys [plugin-id name description host code icon permissions]}] (try + (st/emit! (save-current-plugin plugin-id)) (.ɵloadPlugin ^js ug/global #js {:pluginId plugin-id @@ -22,10 +50,44 @@ :host host :code code :icon icon - :permissions (apply array permissions)}) + :permissions (apply array permissions)} + (fn [] + (st/emit! (remove-current-plugin plugin-id)))) + (catch :default e + (st/emit! (remove-current-plugin plugin-id)) (.error js/console "Error" e)))) +(defn open-plugin! + [{:keys [url] :as manifest}] + (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]))] + (cond + (not= (:permissions new-manifest) (:permissions manifest)) + (modal/show! + :plugin-permissions-update + {:plugin new-manifest + :on-accept + #(do + (preg/install-plugin! new-manifest) + (load-plugin! new-manifest))}) + + (not= new-manifest manifest) + (do (preg/install-plugin! new-manifest) + (load-plugin! manifest)) + :else + (load-plugin! manifest)))) + (fn [] + ;; Error fetching the manifest we'll load the plugin with the + ;; old manifest + (load-plugin! manifest)))) + (load-plugin! manifest))) + (defn close-plugin! [{:keys [plugin-id]}] (try @@ -33,6 +95,15 @@ (catch :default e (.error js/console "Error" e)))) +(defn close-current-plugin + [] + (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))))))) + (defn delay-open-plugin [plugin] (ptk/reify ::delay-open-plugin @@ -46,5 +117,5 @@ ptk/WatchEvent (watch [_ state _] (when-let [pid (::open-plugin state)] - (open-plugin! (pr/get-plugin pid)) + (open-plugin! (preg/get-plugin pid)) (rx/of #(dissoc % ::open-plugin)))))) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 5741087697..fd045067a4 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -6,9 +6,11 @@ (ns app.main.data.workspace.shortcuts (:require + [app.common.data.macros :as dm] [app.main.data.events :as ev] [app.main.data.exports :as de] [app.main.data.modal :as modal] + [app.main.data.plugins :as dpl] [app.main.data.preview :as dp] [app.main.data.shortcuts :as ds] [app.main.data.users :as du] @@ -28,6 +30,7 @@ [app.main.store :as st] [app.main.ui.hooks.resize :as r] [app.util.dom :as dom] + [beicon.v2.core :as rx] [potok.v2.core :as ptk])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -44,6 +47,17 @@ (when-not (deref refs/workspace-read-only?) (run! st/emit! events))) +(def esc-pressed + (ptk/reify ::esc-pressed + ptk/WatchEvent + (watch [_ state _] + (rx/of + :interrupt + (let [selection (dm/get-in state [:workspace-local :selected])] + (if (empty? selection) + (dpl/close-current-plugin) + (dw/deselect-all true))))))) + ;; Shortcuts format https://github.com/ccampbell/mousetrap (def base-shortcuts @@ -111,7 +125,7 @@ :escape {:tooltip (ds/esc) :command "escape" :subsections [:edit] - :fn #(st/emit! :interrupt (dw/deselect-all true))} + :fn #(st/emit! esc-pressed)} ;; MODIFY LAYERS diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 277690ae9c..a158221043 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -33,7 +33,6 @@ [app.main.ui.workspace.plugins] [app.plugins.register :as preg] [app.util.dom :as dom] - [app.util.http :as http] [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.router :as rt] @@ -202,19 +201,14 @@ (mf/with-layout-effect [plugin-url team-id project-id] (when plugin-url - (->> (http/send! {:method :get - :uri plugin-url - :omit-default-headers true - :response-type :json}) - (rx/map :body) + (->> (dp/fetch-manifest plugin-url) (rx/subs! - (fn [body] - (if-let [plugin (preg/parse-manifest plugin-url body)] + (fn [plugin] + (if plugin (do (st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url})) (open-permissions-dialog plugin)) (st/emit! (notif/error "Cannot parser the plugin manifest")))) - (fn [_] (st/emit! (notif/error "The plugin URL is incorrect"))))))))) diff --git a/frontend/src/app/main/ui/workspace/plugins.cljs b/frontend/src/app/main/ui/workspace/plugins.cljs index 0b1ce37306..cecc22bf65 100644 --- a/frontend/src/app/main/ui/workspace/plugins.cljs +++ b/frontend/src/app/main/ui/workspace/plugins.cljs @@ -20,7 +20,6 @@ [app.plugins.register :as preg] [app.util.avatars :as avatars] [app.util.dom :as dom] - [app.util.http :as http] [app.util.i18n :as i18n :refer [tr]] [beicon.v2.core :as rx] [cuerdas.core :as str] @@ -96,15 +95,11 @@ (mf/deps plugins-state plugin-url) (fn [] (reset! fetching-manifest? true) - (->> (http/send! {:method :get - :uri plugin-url - :omit-default-headers true - :response-type :json}) - (rx/map :body) + (->> (dp/fetch-manifest plugin-url) (rx/subs! - (fn [body] + (fn [plugin] (reset! fetching-manifest? false) - (if-let [plugin (preg/parse-manifest plugin-url body)] + (if plugin (do (st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url})) (modal/show! @@ -118,7 +113,8 @@ (reset! plugin-url* "")) ;; Cannot get the manifest (reset! input-status* :error-manifest))) - (fn [_] + (fn [err] + (.error js/console err) (reset! fetching-manifest? false) (reset! input-status* :error-url)))))) @@ -169,10 +165,11 @@ [:div {:class (stl/css-case :info true :error error?)} (tr "workspace.plugins.error.manifest")]) - [:> i18n/tr-html* - {:class (stl/css :discover) - :on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"})) - :content (tr "workspace.plugins.discover" cf/plugins-list-uri)}] + (when-not (empty? plugins-state) + [:> i18n/tr-html* + {:class (stl/css :discover) + :on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"})) + :content (tr "workspace.plugins.discover" cf/plugins-list-uri)}]) [:hr] @@ -198,6 +195,62 @@ :on-open-plugin handle-open-plugin :on-remove-plugin handle-remove-plugin}])]])]]])) +(mf/defc plugins-permission-list + [{:keys [permissions]}] + [:div {:class (stl/css :permissions-list)} + (cond + (contains? permissions "content:write") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-1 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.content-write")]] + + (contains? permissions "content:read") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-1 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.content-read")]]) + + (cond + (contains? permissions "user:read") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-2 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.user-read")]]) + + (cond + (contains? permissions "library:write") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-3 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.library-write")]] + + (contains? permissions "library:read") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-3 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.library-read")]]) + + (cond + (contains? permissions "comment:write") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-1 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.comment-write")]] + + (contains? permissions "comment:read") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-1 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.comment-read")]]) + + (cond + (contains? permissions "allow:downloads") + [:div {:class (stl/css :permissions-list-entry)} + i/oauth-1 + [:p {:class (stl/css :permissions-list-text)} + (tr "workspace.plugins.permissions.allow-download")]])]) + (mf/defc plugins-permissions-dialog {::mf/register modal/components ::mf/register-as :plugin-permissions} @@ -232,59 +285,7 @@ [:div {:class (stl/css :modal-title)} (tr "workspace.plugins.permissions.title" (str/upper (:name plugin)))] [:div {:class (stl/css :modal-content)} - [:div {:class (stl/css :permissions-list)} - (cond - (contains? permissions "content:write") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-1 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.content-write")]] - - (contains? permissions "content:read") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-1 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.content-read")]]) - - (cond - (contains? permissions "user:read") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-2 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.user-read")]]) - - (cond - (contains? permissions "library:write") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-3 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.library-write")]] - - (contains? permissions "library:read") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-3 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.library-read")]]) - - (cond - (contains? permissions "comment:write") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-1 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.comment-write")]] - - (contains? permissions "comment:read") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-1 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.comment-read")]]) - - (cond - (contains? permissions "allow:downloads") - [:div {:class (stl/css :permissions-list-entry)} - i/oauth-1 - [:p {:class (stl/css :permissions-list-text)} - (tr "workspace.plugins.permissions.allow-download")]])] + [:& plugins-permission-list {:permissions permissions}] [:div {:class (stl/css :permissions-disclaimer)} (tr "workspace.plugins.permissions.disclaimer")]] @@ -304,6 +305,60 @@ :on-click handle-accept-dialog}]]]]])) +(mf/defc plugins-permissions-updated-dialog + {::mf/register modal/components + ::mf/register-as :plugin-permissions-update} + [{:keys [plugin on-accept on-close]}] + + (let [{:keys [host permissions]} plugin + permissions (set permissions) + + handle-accept-dialog + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (ptk/event ::ev/event {::ev/name "allow-plugin-permissions" + :host host + :permissions (->> permissions (str/join ", "))}) + (modal/hide)) + (when on-accept (on-accept)))) + + handle-close-dialog + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (ptk/event ::ev/event {::ev/name "reject-plugin-permissions" + :host host + :permissions (->> permissions (str/join ", "))}) + (modal/hide)) + (when on-close (on-close))))] + + [:div {:class (stl/css :modal-overlay)} + [:div {:class (stl/css :modal-dialog :plugin-permissions)} + [:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon] + [:div {:class (stl/css :modal-title)} + (tr "workspace.plugins.permissions-update.title" (str/upper (:name plugin)))] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-paragraph)} + (tr "workspace.plugins.permissions-update.warning")] + [:& plugins-permission-list {:permissions permissions}]] + + [:div {:class (stl/css :modal-footer)} + [:div {:class (stl/css :action-buttons)} + [:input + {:class (stl/css :cancel-button :button-expand) + :type "button" + :value (tr "ds.confirm-cancel") + :on-click handle-close-dialog}] + + [:input + {:class (stl/css :primary-button :button-expand) + :type "button" + :value (tr "ds.confirm-allow") + :on-click handle-accept-dialog}]]]]])) + + (mf/defc plugins-try-out-dialog {::mf/register modal/components ::mf/register-as :plugin-try-out} diff --git a/frontend/src/app/main/ui/workspace/plugins.scss b/frontend/src/app/main/ui/workspace/plugins.scss index bc63bbe1ff..ba736acf88 100644 --- a/frontend/src/app/main/ui/workspace/plugins.scss +++ b/frontend/src/app/main/ui/workspace/plugins.scss @@ -76,6 +76,11 @@ color: var(--color-foreground-secondary); } +.modal-paragraph { + font-size: $fs-14; + color: var(--color-foreground-primary); +} + .primary-button { @extend .button-primary; @include headlineSmallTypography; diff --git a/frontend/src/app/plugins/register.cljs b/frontend/src/app/plugins/register.cljs index 19e98a805c..f8e3e03bee 100644 --- a/frontend/src/app/plugins/register.cljs +++ b/frontend/src/app/plugins/register.cljs @@ -64,6 +64,7 @@ manifest (d/without-nils {:plugin-id plugin-id + :url plugin-url :name name :description desc :host origin diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a87e94f7cd..b0b931a058 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -5610,6 +5610,12 @@ msgstr "'%s' PLUGIN WANTS ACCESS TO:" msgid "workspace.plugins.permissions.user-read" msgstr "Read the profile information of the current user." +msgid "workspace.plugins.permissions-update.title" +msgstr "UPDATE THIS PLUGIN" + +msgid "workspace.plugins.permissions-update.warning" +msgstr "The plugin has been modified since you last opened it. It now also wants to access:" + msgid "workspace.plugins.try-out.title" msgstr "'%s' PLUGIN IS INSTALLED FOR YOUR USER!" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 54cbc1a11a..7400fd0b9c 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -5594,6 +5594,12 @@ msgstr "LA EXTENSIÓN '%s' SOLICITA PERMISO PARA ACCEDER:" msgid "workspace.plugins.permissions.user-read" msgstr "Leer la información del usuario actual." +msgid "workspace.plugins.permissions-update.title" +msgstr "EXTENSIÓN ACTUALIZADA" + +msgid "workspace.plugins.permissions-update.warning" +msgstr "La extensión ha cambiado desde la última vez que la abriste. Ahora quiere acceder a:" + msgid "workspace.plugins.try-out.title" msgstr "¡LA EXTENSIÓN '%s' HA SIDO INSTALADA PARA TU USUARIO!"