diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 7a0e3297a..1bedc29dd 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -941,7 +941,7 @@ (update-in [:dashboard-projects project-id :count] inc))))) (defn create-file - [{:keys [project-id] :as params}] + [{:keys [project-id name] :as params}] (dm/assert! (uuid? project-id)) (ptk/reify ::create-file ev/Event @@ -955,7 +955,7 @@ files (get state :dashboard-files) unames (cfh/get-used-names files) - name (cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")) + name (or name (cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1"))) features (-> (features/get-team-enabled-features state) (set/difference cfeat/frontend-only-features)) params (-> params diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs new file mode 100644 index 000000000..b970bfe2d --- /dev/null +++ b/frontend/src/app/main/data/plugins.cljs @@ -0,0 +1,42 @@ +;; 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.data.plugins + (:require + [app.plugins.register :as pr] + [potok.v2.core :as ptk])) + +(defn open-plugin! + [{:keys [plugin-id name description host code icon permissions]}] + (try + (.ɵloadPlugin + js/window + #js {:pluginId plugin-id + :name name + :description description + :host host + :code code + :icon icon + :permissions (apply array permissions)}) + (catch :default e + (.error js/console "Error" e)))) + +(defn delay-open-plugin + [plugin] + (ptk/reify ::delay-open-plugin + ptk/UpdateEvent + (update [_ state] + (assoc state ::open-plugin (:plugin-id plugin))))) + +(defn check-open-plugin + [] + (ptk/reify ::check-open-plugin + ptk/WatchEvent + (watch [_ state _] + (when-let [pid (::open-plugin state)] + (open-plugin! (pr/get-plugin pid)) + (fn [state] + (dissoc state ::open-plugin)))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 50aea4a1e..3bda2e901 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -42,6 +42,7 @@ [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] [app.main.data.persistence :as dps] + [app.main.data.plugins :as dp] [app.main.data.users :as du] [app.main.data.workspace.bool :as dwb] [app.main.data.workspace.collapse :as dwco] @@ -131,6 +132,7 @@ (when (and (not (boolean (-> state :profile :props :v2-info-shown))) (features/active-feature? state "components/v2")) (modal/show :v2-info {})) + (dp/check-open-plugin) (fdf/fix-deleted-fonts) (fbs/fix-broken-shapes))))) diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 0ec58ba16..277690ae9 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -8,10 +8,15 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.spec :as us] [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.dashboard.shortcuts :as sc] + [app.main.data.events :as ev] + [app.main.data.modal :as modal] + [app.main.data.notifications :as notif] + [app.main.data.plugins :as dp] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] @@ -25,11 +30,17 @@ [app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page team-webhooks-page]] [app.main.ui.dashboard.templates :refer [templates-section]] [app.main.ui.hooks :as hooks] + [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] + [beicon.v2.core :as rx] [goog.events :as events] [okulary.core :as l] + [potok.v2.core :as ptk] [rumext.v2 :as mf])) (defn ^boolean uuid-str? @@ -143,6 +154,70 @@ (def dashboard-initialized (l/derived :current-team-id st/state)) +(defn use-plugin-register + [plugin-url team-id project-id] + + (let [navegate-file! + (fn [plugin {:keys [project-id id data]}] + (st/emit! + (dp/delay-open-plugin plugin) + (rt/nav :workspace + {:project-id project-id :file-id id} + {:page-id (dm/get-in data [:pages 0])}))) + + create-file! + (fn [plugin] + (st/emit! + (modal/hide) + (let [data + (with-meta + {:project-id project-id + :name (dm/str "Try plugin: " (:name plugin))} + {:on-success (partial navegate-file! plugin)})] + (-> (dd/create-file data) + (with-meta {::ev/origin "plugin-try-out"}))))) + + open-try-out-dialog + (fn [plugin] + (modal/show + :plugin-try-out + {:plugin plugin + :on-accept #(create-file! plugin) + :on-close #(modal/hide!)})) + + open-permissions-dialog + (fn [plugin] + (modal/show! + :plugin-permissions + {:plugin plugin + :on-accept + #(do (preg/install-plugin! plugin) + (st/emit! (modal/hide) + (rt/nav :dashboard-projects {:team-id team-id}) + (open-try-out-dialog plugin))) + :on-close + #(st/emit! (modal/hide) + (rt/nav :dashboard-projects {:team-id team-id}))}))] + + (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) + (rx/subs! + (fn [body] + (if-let [plugin (preg/parse-manifest plugin-url body)] + (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"))))))))) + (mf/defc dashboard {::mf/props :obj} [{:keys [route profile]}] @@ -150,8 +225,12 @@ params (parse-params route) project-id (:project-id params) + team-id (:team-id params) search-term (:search-term params) + + plugin-url (-> route :query-params :plugin) + invite-email (-> route :query-params :invite-email) teams (mf/deref refs/teams) @@ -160,6 +239,8 @@ projects (mf/deref refs/dashboard-projects) project (get projects project-id) + default-project (->> projects vals (d/seek :is-default)) + initialized? (mf/deref dashboard-initialized)] (hooks/use-shortcuts ::dashboard sc/shortcuts) @@ -178,6 +259,8 @@ (fn [] (events/unlistenByKey key)))) + (use-plugin-register plugin-url team-id (:id default-project)) + [:& (mf/provider ctx/current-team-id) {:value team-id} [:& (mf/provider ctx/current-project-id) {:value project-id} ;; NOTE: dashboard events and other related functions assumes @@ -206,4 +289,3 @@ :search-term search-term :team team :invite-email invite-email}])])]])) - diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index 7b26b8561..782ac5526 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -16,6 +16,7 @@ [app.util.router :as rt] [beicon.v2.core :as rx] [cljs.spec.alpha :as s] + [cuerdas.core :as str] [potok.v2.core :as ptk])) (s/def ::page-id ::us/uuid) @@ -94,10 +95,17 @@ (defn on-navigate [router path] (let [location (.-location js/document) + [base-path qs] (str/split path "?") + + qstring + (->> (str/split qs "&") + (map #(str/split % "=")) + (into {})) + location-path (dm/str (.-origin location) (.-pathname location)) valid-location? (= location-path (dm/str cf/public-uri)) match (match-path router path) - empty-path? (or (= path "") (= path "/"))] + empty-path? (or (= base-path "") (= base-path "/"))] (cond (not valid-location?) (st/emit! (rt/assign-exception {:type :not-found})) @@ -116,7 +124,7 @@ (st/emit! (rt/nav :auth-login)) empty-path? - (st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})) + (st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)} qstring)) :else (st/emit! (rt/assign-exception {:type :not-found}))))))))) diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 8c4e93156..fb2ba7148 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -16,6 +16,7 @@ [app.main.data.events :as ev] [app.main.data.exports :as de] [app.main.data.modal :as modal] + [app.main.data.plugins :as dp] [app.main.data.shortcuts :as scd] [app.main.data.users :as du] [app.main.data.workspace :as dw] @@ -29,7 +30,6 @@ [app.main.ui.context :as ctx] [app.main.ui.hooks.resize :as r] [app.main.ui.icons :as i] - [app.main.ui.workspace.plugins :as uwp] [app.plugins.register :as preg] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] @@ -637,7 +637,7 @@ ::ev/origin "workspace:menu" :name name :host host})) - (uwp/open-plugin! manifest)) + (dp/open-plugin! manifest)) :class (stl/css :submenu-item) :on-key-down (fn [event] (when (kbd/enter? event) @@ -646,7 +646,7 @@ ::ev/origin "workspace:menu" :name name :host host})) - (uwp/open-plugin! manifest))))} + (dp/open-plugin! manifest))))} [:span {:class (stl/css :item-name)} name]])]))) (mf/defc menu diff --git a/frontend/src/app/main/ui/workspace/plugins.cljs b/frontend/src/app/main/ui/workspace/plugins.cljs index bc6346b59..693eba0b4 100644 --- a/frontend/src/app/main/ui/workspace/plugins.cljs +++ b/frontend/src/app/main/ui/workspace/plugins.cljs @@ -12,6 +12,7 @@ [app.config :as cf] [app.main.data.events :as ev] [app.main.data.modal :as modal] + [app.main.data.plugins :as dp] [app.main.store :as st] [app.main.ui.components.search-bar :refer [search-bar]] [app.main.ui.components.title-bar :refer [title-bar]] @@ -59,22 +60,6 @@ [:button {:class (stl/css :trash-button) :on-click handle-delete-click} i/delete]])) - -(defn open-plugin! - [{:keys [plugin-id name description host code icon permissions]}] - (try - (.ɵloadPlugin - js/window - #js {:pluginId plugin-id - :name name - :description description - :host host - :code code - :icon icon - :permissions (apply array permissions)}) - (catch :default e - (.error js/console "Error" e)))) - (mf/defc plugin-management-dialog {::mf/register modal/components ::mf/register-as :plugin-management} @@ -144,7 +129,7 @@ ::ev/origin "workspace:plugins" :name (:name manifest) :host (:host manifest)})) - (open-plugin! manifest) + (dp/open-plugin! manifest) (modal/hide!))) handle-remove-plugin @@ -215,7 +200,7 @@ (mf/defc plugins-permissions-dialog {::mf/register modal/components ::mf/register-as :plugin-permissions} - [{:keys [plugin on-accept]}] + [{:keys [plugin on-accept on-close]}] (let [{:keys [host permissions]} plugin permissions (set permissions) @@ -224,25 +209,26 @@ (mf/use-callback (fn [event] (dom/prevent-default event) - (st/emit! (modal/hide)) - (ptk/event ::ev/event {::ev/name "allow-plugin-permissions" - :host host - :permissions (->> permissions (str/join ", "))}) - (on-accept))) + (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) - (ptk/event ::ev/event {::ev/name "reject-plugin-permissions" - :host host - :permissions (->> permissions (str/join ", "))}) - (st/emit! (modal/hide))))] + (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.title")] + [: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)} @@ -293,7 +279,7 @@ (tr "workspace.plugins.permissions.comment-read")]]) (cond - (contains? permissions "allow:download") + (contains? permissions "allow:downloads") [:div {:class (stl/css :permissions-list-entry)} i/oauth-1 [:p {:class (stl/css :permissions-list-text)} @@ -315,3 +301,55 @@ :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} + [{:keys [plugin on-accept on-close]}] + + (let [{:keys [icon host name]} plugin + + handle-accept-dialog + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (ptk/event ::ev/event {::ev/name "try-out-accept"}) + (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 "try-out-cancel"}) + (modal/hide)) + (when on-close (on-close))))] + + [:div {:class (stl/css :modal-overlay)} + [:div {:class (stl/css :modal-dialog :plugin-try-out)} + [:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon] + [:div {:class (stl/css :modal-title)} + [:div {:class (stl/css :plugin-icon)} + [:img {:src (if (some? icon) + (dm/str host icon) + (avatars/generate {:name name}))}]] + (tr "workspace.plugins.try-out.title" (str/upper (:name plugin)))] + + [:div {:class (stl/css :modal-content)} + [:div {:class (stl/css :modal-message)} + (tr "workspace.plugins.try-out.message")]] + + [:div {:class (stl/css :modal-footer)} + [:div {:class (stl/css :action-buttons)} + [:input + {:class (stl/css :cancel-button :button-expand) + :type "button" + :value (tr "workspace.plugins.try-out.cancel") + :on-click handle-close-dialog}] + + [:input + {:class (stl/css :primary-button :button-expand) + :type "button" + :value (tr "workspace.plugins.try-out.try") + :on-click handle-accept-dialog}]]]]])) diff --git a/frontend/src/app/main/ui/workspace/plugins.scss b/frontend/src/app/main/ui/workspace/plugins.scss index 2276601a4..bc63bbe1f 100644 --- a/frontend/src/app/main/ui/workspace/plugins.scss +++ b/frontend/src/app/main/ui/workspace/plugins.scss @@ -26,6 +26,11 @@ max-width: $s-472; } + &.plugin-try-out { + width: $s-452; + max-width: $s-452; + } + hr { border-color: var(--color-background-tertiary); } @@ -48,6 +53,8 @@ @include headlineMediumTypography; margin-block-end: $s-32; color: var(--modal-title-foreground-color); + display: flex; + gap: $s-12; } .modal-content { @@ -64,6 +71,11 @@ } } +.modal-message { + font-size: $fs-14; + color: var(--color-foreground-secondary); +} + .primary-button { @extend .button-primary; @include headlineSmallTypography; diff --git a/frontend/src/app/plugins/register.cljs b/frontend/src/app/plugins/register.cljs index c0dd27050..19e98a805 100644 --- a/frontend/src/app/plugins/register.cljs +++ b/frontend/src/app/plugins/register.cljs @@ -5,7 +5,6 @@ ;; Copyright (c) KALEIDOS INC (ns app.plugins.register - "RPC for plugins runtime." (:require [app.common.data :as d] [app.common.data.macros :as dm] @@ -26,6 +25,10 @@ (->> (:ids @registry) (mapv #(dm/get-in @registry [:data %])))) +(defn get-plugin + [id] + (dm/get-in @registry [:data id])) + (defn parse-manifest "Read the manifest.json defined by the plugins definition and transforms it into an object that will be stored in the register." diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 15211ffb5..c5e76a8c0 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -5601,12 +5601,24 @@ msgstr "Start file downloads." #: src/app/main/ui/workspace/plugins.cljs:236 msgid "workspace.plugins.permissions.title" -msgstr "THIS PLUGIN WANTS ACCESS TO:" +msgstr "'%s' PLUGIN WANTS ACCESS TO:" #: src/app/main/ui/workspace/plugins.cljs:258 msgid "workspace.plugins.permissions.user-read" msgstr "Read the profile information of the current user." +msgid "workspace.plugins.try-out.title" +msgstr "'%s' PLUGIN IS INSTALLED FOR YOUR USER!" + +msgid "workspace.plugins.try-out.message" +msgstr "Want to take a look? It will open in a new draft for your current team. (If not, you can always find it in the installed plugins of any file.)" + +msgid "workspace.plugins.try-out.cancel" +msgstr "NOT NOW" + +msgid "workspace.plugins.try-out.try" +msgstr "TRY PLUGIN" + #: src/app/main/ui/workspace/plugins.cljs:192 msgid "workspace.plugins.plugin-list-link" msgstr "Plugins List" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6ba3d8668..b8aebb881 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -5567,7 +5567,7 @@ msgstr "Leer y modificar el contenido de sus archivos." #: src/app/main/ui/workspace/plugins.cljs:274 msgid "workspace.plugins.permissions.disclaimer" -msgstr "Tenga en cuenta que las extensiones están desarrolladas por terceros, asegursé que confía antes de conceder el permiso. Su privacidad y seguridad es importante para nosotros. Si tiene cualquier duda, contacte soporte." +msgstr "Ten en cuenta que las extensiones están desarrolladas por terceros, aseguraté que confías antes de conceder el permiso. Tu privacidad y seguridad es importante para nosotros. Si tienes cualquier duda, contacta con soporte." #: src/app/main/ui/workspace/plugins.cljs:271 msgid "workspace.plugins.permissions.library-read" @@ -5588,12 +5588,24 @@ msgstr "Comenzar descargas de ficheros." #: src/app/main/ui/workspace/plugins.cljs:236 msgid "workspace.plugins.permissions.title" -msgstr "LA EXTENSIÓN SOLICITA PERMISO PARA ACCEDER:" +msgstr "LA EXTENSIÓN '%s' SOLICITA PERMISO PARA ACCEDER:" #: src/app/main/ui/workspace/plugins.cljs:258 msgid "workspace.plugins.permissions.user-read" msgstr "Leer la información del usuario actual." +msgid "workspace.plugins.try-out.title" +msgstr "¡LA EXTENSIÓN '%s' HA SIDO INSTALADA PARA TU USUARIO!" + +msgid "workspace.plugins.try-out.message" +msgstr "¿Quieres echar un vistazo?. Crearemos un nuevo borrador en tu equipo actual. (Si no, puedes encontrar los plugins instalados en cualquier fichero.)" + +msgid "workspace.plugins.try-out.cancel" +msgstr "AHORA NO" + +msgid "workspace.plugins.try-out.try" +msgstr "PROBAR PLUGIN" + #: src/app/main/ui/workspace/plugins.cljs:192 msgid "workspace.plugins.plugin-list-link" msgstr "Lista de extensiones"