New plugin install workflow

This commit is contained in:
alonso.torres 2024-09-27 09:04:46 +02:00
parent a510d01136
commit d1277afee6
11 changed files with 252 additions and 41 deletions

View file

@ -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

View file

@ -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))))))

View file

@ -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)))))

View file

@ -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}])])]]))

View file

@ -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})))))))))

View file

@ -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

View file

@ -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}]]]]]))

View file

@ -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;

View file

@ -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."