pass one large prop pluginComponent to maintain context

pull/1344/head
joseph izang 3 years ago
parent e04206b826
commit 6d2083b8e3
  1. 3
      .gitignore
  2. 79
      apps/remix-ide/src/app/components/plugin-manager-component.js
  3. 8
      libs/remix-ui/plugin-manager/src/lib/components/button.tsx
  4. 9
      libs/remix-ui/plugin-manager/src/lib/components/moduleHeading.tsx
  5. 4
      libs/remix-ui/plugin-manager/src/lib/components/pluginCard.tsx
  6. 133
      libs/remix-ui/plugin-manager/src/lib/components/rootView.tsx
  7. 2
      libs/remix-ui/plugin-manager/src/lib/remix-ui-plugin-manager.tsx
  8. 130
      libs/remix-ui/plugin-manager/src/types.d.ts

3
.gitignore vendored

@ -51,4 +51,5 @@ testem.log
# System Files # System Files
.DS_Store .DS_Store
Thumbs.db Thumbs.db
.vscode/settings.json

@ -97,7 +97,6 @@ class PluginManagerComponent extends ViewPlugin {
this.engine = engine this.engine = engine
this.htmlElement = document.createElement('div') this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'pluginManager') this.htmlElement.setAttribute('id', 'pluginManager')
this.props = {}
this.views = { this.views = {
root: null, root: null,
items: {} items: {}
@ -106,12 +105,10 @@ class PluginManagerComponent extends ViewPlugin {
this.filter = '' this.filter = ''
this.activePlugins = [] this.activePlugins = []
this.inactivePlugins = [] this.inactivePlugins = []
this.activePluginNames = this.appManager.actives this.pluginNames = this.appManager.actives
// this.appManager.event.on('activate', () => { this.reRender() }) // this.appManager.event.on('activate', () => { this.renderComponent() })
// this.appManager.event.on('deactivate', () => { this.reRender() }) // this.appManager.event.on('deactivate', () => { this.renderComponent() })
// this.engine.event.on('onRegistration', () => { this.reRender() }) // this.engine.event.on('onRegistration', () => { this.renderComponent() })
// const { actives, inactives } = this.getAllPlugins()
console.log('views property contents', this.views)
} }
isActive (name) { isActive (name) {
@ -119,15 +116,19 @@ class PluginManagerComponent extends ViewPlugin {
} }
activateP (name) { activateP (name) {
// console.log(this.appManager)
this.appManager.turnPluginOn(name) this.appManager.turnPluginOn(name)
console.log('activateP method reached. And activation of method was successful')
_paq.push(['trackEvent', 'manager', 'activate', name]) _paq.push(['trackEvent', 'manager', 'activate', name])
this.renderComponent()
console.log('activation was logged in _paq and renderComponent has been called.')
} }
deactivateP (name) { deactivateP (name) {
// console.log(this.appManager)
this.call('manager', 'deactivatePlugin', name) this.call('manager', 'deactivatePlugin', name)
console.log('deactivateP has been called successfully')
_paq.push(['trackEvent', 'manager', 'deactivate', name]) _paq.push(['trackEvent', 'manager', 'deactivate', name])
this.renderComponent()
console.log('deactivation was logged and renderComponent has has been called.')
} }
onActivation () { onActivation () {
@ -138,15 +139,17 @@ class PluginManagerComponent extends ViewPlugin {
ReactDOM.render( ReactDOM.render(
<RemixUiPluginManager <RemixUiPluginManager
profile={profile} profile={profile}
pluginComponent={this}
appManager={this.appManager} appManager={this.appManager}
engine={this.engine} engine={this.engine}
localPlugin={this.localPlugin} localPlugin={this.localPlugin}
activePluginNames={this.activePluginsNames} activePluginNames={this.pluginNames}
actives={this.activePlugins} actives={this.activePlugins}
inactives={this.inactivePlugins} inactives={this.inactivePlugins}
activatePlugin={this.activateP} // activatePlugin={this.activateP}
deActivatePlugin={this.deactivateP} // deActivatePlugin={this.deactivateP}
_paq={_paq} _paq={_paq}
filter={this.filter}
/>, />,
document.getElementById('pluginManager')) document.getElementById('pluginManager'))
} }
@ -176,29 +179,29 @@ class PluginManagerComponent extends ViewPlugin {
render () { render () {
// Filtering helpers // Filtering helpers
const isFiltered = (profile) => (profile.displayName ? profile.displayName : profile.name).toLowerCase().includes(this.filter) // const isFiltered = (profile) => (profile.displayName ? profile.displayName : profile.name).toLowerCase().includes(this.filter)
const isNotRequired = (profile) => !this.appManager.isRequired(profile.name) // const isNotRequired = (profile) => !this.appManager.isRequired(profile.name)
const isNotDependent = (profile) => !this.appManager.isDependent(profile.name) // const isNotDependent = (profile) => !this.appManager.isDependent(profile.name)
const isNotHome = (profile) => profile.name !== 'home' // const isNotHome = (profile) => profile.name !== 'home'
const sortByName = (profileA, profileB) => { // const sortByName = (profileA, profileB) => {
const nameA = ((profileA.displayName) ? profileA.displayName : profileA.name).toUpperCase() // const nameA = ((profileA.displayName) ? profileA.displayName : profileA.name).toUpperCase()
const nameB = ((profileB.displayName) ? profileB.displayName : profileB.name).toUpperCase() // const nameB = ((profileB.displayName) ? profileB.displayName : profileB.name).toUpperCase()
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0 // return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0
} // }
const { actives, inactives } = this.appManager.getAll() // const { actives, inactives } = this.appManager.getAll()
.filter(isFiltered) // .filter(isFiltered)
.filter(isNotRequired) // .filter(isNotRequired)
.filter(isNotDependent) // .filter(isNotDependent)
.filter(isNotHome) // .filter(isNotHome)
.sort(sortByName) // .sort(sortByName)
.reduce(({ actives, inactives }, profile) => { // .reduce(({ actives, inactives }, profile) => {
return this.isActive(profile.name) // return this.isActive(profile.name)
? { actives: [...actives, profile], inactives } // ? { actives: [...actives, profile], inactives }
: { inactives: [...inactives, profile], actives } // : { inactives: [...inactives, profile], actives }
}, { actives: [], inactives: [] }) // }, { actives: [], inactives: [] })
this.activePlugins = actives // this.activePlugins = actives
this.inactivePlugins = inactives // this.inactivePlugins = inactives
return this.htmlElement return this.htmlElement
} }
@ -208,10 +211,10 @@ class PluginManagerComponent extends ViewPlugin {
// } // }
// } // }
// filterPlugins ({ target }) { filterPlugins ({ target }) {
// this.filter = target.value.toLowerCase() this.filter = target.value.toLowerCase()
// this.reRender() this.renderComponent()
// } }
} }
module.exports = PluginManagerComponent module.exports = PluginManagerComponent

@ -7,19 +7,17 @@ interface ButtonProps {
} }
function Button ({ buttonText, pluginName }: ButtonProps) { function Button ({ buttonText, pluginName }: ButtonProps) {
const { appManager, _paq } = useContext(PluginManagerContext) const { pluginComponent } = useContext(PluginManagerContext)
const dataId = `pluginManagerComponentDeactivateButton${pluginName}` const dataId = `pluginManagerComponentDeactivateButton${pluginName}`
const [needToDeactivate] = useState('btn btn-secondary btn-sm') const [needToDeactivate] = useState('btn btn-secondary btn-sm')
const [needToActivate] = useState('btn btn-success btn-sm') const [needToActivate] = useState('btn btn-success btn-sm')
return ( return (
<button <button
onClick={buttonText === 'Activate' ? () => { onClick={buttonText === 'Activate' ? () => {
appManager.turnPluginOn(pluginName) pluginComponent.activateP(pluginName)
_paq.push(['trackEvent', 'manager', 'activate', pluginName])
buttonText = 'Deactivate' buttonText = 'Deactivate'
} : () => { } : () => {
appManager.turnPluginOff(pluginName) pluginComponent.deactivateP(pluginName)
_paq.push(['trackEvent', 'manager', 'deactivate', pluginName])
buttonText = 'Activate' buttonText = 'Activate'
}} }}
className={buttonText === 'Activate' ? needToActivate : needToDeactivate} className={buttonText === 'Activate' ? needToActivate : needToDeactivate}

@ -1,12 +1,13 @@
import React, { useContext } from 'react' import React from 'react'
import { PluginManagerContext } from '../contexts/pluginmanagercontext' import { PluginManagerProfile } from '../../types'
interface ModuleHeadingProps { interface ModuleHeadingProps {
headingLabel: string headingLabel: string
actives: Partial<PluginManagerProfile>[]
inactives: Partial<PluginManagerProfile>[]
} }
function ModuleHeading ({ headingLabel }: ModuleHeadingProps) { function ModuleHeading ({ headingLabel, actives, inactives }: ModuleHeadingProps) {
const { actives, inactives } = useContext(PluginManagerContext)
return ( return (
<nav className="plugins-list-header justify-content-between navbar navbar-expand-lg bg-light navbar-light align-items-center"> <nav className="plugins-list-header justify-content-between navbar navbar-expand-lg bg-light navbar-light align-items-center">
<span className="navbar-brand plugins-list-title h6 mb-0 mr-2">{headingLabel}</span> <span className="navbar-brand plugins-list-title h6 mb-0 mr-2">{headingLabel}</span>

@ -9,7 +9,7 @@ interface PluginCardProps {
// eslint-disable-next-line no-empty-pattern // eslint-disable-next-line no-empty-pattern
function PluginCard ({ profile }: PluginCardProps) { function PluginCard ({ profile }: PluginCardProps) {
const { activePluginNames } = useContext(PluginManagerContext) const { pluginComponent } = useContext(PluginManagerContext)
const [displayName] = useState<string>((profile.displayName) ? profile.displayName : profile.name) const [displayName] = useState<string>((profile.displayName) ? profile.displayName : profile.name)
const [docLink] = useState<JSX.Element>((profile.documentation) ? ( const [docLink] = useState<JSX.Element>((profile.documentation) ? (
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer"> <a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
@ -31,7 +31,7 @@ function PluginCard ({ profile }: PluginCardProps) {
{docLink} {docLink}
{versionWarning} {versionWarning}
</div> </div>
{ activePluginNames.includes(profile.name) ? ( { pluginComponent.isActive(profile.name) ? (
<Button <Button
buttonText="Deactivate" buttonText="Deactivate"
pluginName={profile.name} pluginName={profile.name}

@ -4,10 +4,7 @@ import { PluginManagerContext } from '../contexts/pluginmanagercontext'
import ModuleHeading from './moduleHeading' import ModuleHeading from './moduleHeading'
import PluginCard from './pluginCard' import PluginCard from './pluginCard'
import { ModalDialog } from '@remix-ui/modal-dialog' import { ModalDialog } from '@remix-ui/modal-dialog'
import { FormStateProps, PluginManagerProfile, RemixAppManager } from '../../types' import { FormStateProps, PluginManagerProfile } from '../../types'
import { IframePlugin, WebsocketPlugin } from '@remixproject/engine-web'
import { Profile } from '@remixproject/plugin-utils'
import * as lo from 'lodash'
const initialState: FormStateProps = { const initialState: FormStateProps = {
name: 'test', name: 'test',
@ -19,49 +16,60 @@ const initialState: FormStateProps = {
location: 'sidePanel' location: 'sidePanel'
} }
interface ShowInactivesProps {
inactives: Partial<PluginManagerProfile>[]
headinglabel: string
}
function ShowInactives ({ inactives, headinglabel }: ShowInactivesProps) {
return (
<Fragment>
<ModuleHeading headingLabel={headinglabel} />
{inactives.map((profile) => (
<PluginCard key={profile.name} profile={profile} />
))}
</Fragment>
)
}
function ShowActives ({ inactives, headinglabel }: ShowInactivesProps) {
console.log('actived plugins are :', inactives)
return (
<Fragment>
<ModuleHeading headingLabel={headinglabel} />
{inactives.map((profile) => (
<PluginCard key={profile.name} profile={profile} />
))}
</Fragment>
)
}
function RootView () { function RootView () {
const { appManager, actives, engine, inactives, localPlugin } = useContext(PluginManagerContext) const { appManager, pluginComponent, activePluginNames } = useContext(PluginManagerContext)
const [visible, setVisible] = useState<boolean>(true) const [visible, setVisible] = useState<boolean>(true)
const [plugin, setPlugin] = useState(initialState) const [plugin, setPlugin] = useState(initialState)
const [filterPlugins, setFilterPlugin] = useState('')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, setGetAndFilter] = useState<any>()
const [activeP, setActiveP] = useState<any[]>()
const [inactiveP, setInactiveP] = useState<any[]>()
function pluginChangeHandler<P extends keyof FormStateProps> (formProps: P, value: FormStateProps[P]) { function pluginChangeHandler<P extends keyof FormStateProps> (formProps: P, value: FormStateProps[P]) {
setPlugin({ ...plugin, [formProps]: value }) setPlugin({ ...plugin, [formProps]: value })
appManager.activatePlugin('')
} }
const openModal = () => { const openModal = () => {
setVisible(false) setVisible(false)
} }
const closeModal = () => setVisible(true) const closeModal = () => setVisible(true)
const isFiltered = (profile) => (profile.displayName ? profile.displayName : profile.name).toLowerCase().includes(filterPlugins)
const isNotRequired = (profile) => !appManager.isRequired(profile.name)
const isNotDependent = (profile) => !appManager.isDependent(profile.name)
const isNotHome = (profile) => profile.name !== 'home'
const sortByName = (profileA, profileB) => {
const nameA = ((profileA.displayName) ? profileA.displayName : profileA.name).toUpperCase()
const nameB = ((profileB.displayName) ? profileB.displayName : profileB.name).toUpperCase()
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0
}
const getAndFilterPlugins = () => {
const { actives, inactives } = appManager.getAll()
.filter(isFiltered)
.filter(isNotRequired)
.filter(isNotDependent)
.filter(isNotHome)
.sort(sortByName)
.reduce(({ actives, inactives }, profile) => {
return activePluginNames.includes(profile.name)
? { actives: [...actives, profile], inactives }
: { inactives: [...inactives, profile], actives }
}, { actives: [], inactives: [] })
setActiveP(actives)
setInactiveP(inactives)
}
useEffect(() => { useEffect(() => {
// engine.event.on('onRegistration', () => ) setGetAndFilter(getAndFilterPlugins())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterPlugins])
useEffect(() => {
}) })
console.log('This is the result of getting and filtering all plugins from app manager :', inactiveP)
return ( return (
<Fragment> <Fragment>
<form id="local-plugin-form"> <form id="local-plugin-form">
@ -71,19 +79,20 @@ function RootView () {
title="Local Plugin" title="Local Plugin"
okLabel="OK" okLabel="OK"
okFn={async () => { okFn={async () => {
const plugins = appManager.getActiveProfiles() // const plugins = appManager.getActiveProfiles()
console.log('There are the active plugins from appManager :', plugins) // console.log('There are the active plugins from appManager :', plugins)
const profile: any = await localPlugin.open(appManager.getAll()) // const profile: any = await localPlugin.open(appManager.getAll())
if (appManager.getIds().includes(profile.name)) { // if (appManager.getIds().includes(profile.name)) {
throw new Error('This name has already been used') // throw new Error('This name has already been used')
} // }
const lPlugin = profile.type === 'iframe' ? new IframePlugin(profile) : new WebsocketPlugin(profile) // const lPlugin = profile.type === 'iframe' ? new IframePlugin(profile) : new WebsocketPlugin(profile)
console.log('handle submit local plugin', lPlugin) // console.log('handle submit local plugin', lPlugin)
console.log('Local PLugin type from legacy as props', localPlugin) // console.log('Local PLugin type from legacy as props', localPlugin)
engine.register(lPlugin) // engine.register(lPlugin)
console.log('engine has registered lPlugin') // console.log('engine has registered lPlugin')
await appManager.activatePlugin(lPlugin.name) // await appManager.activatePlugin(lPlugin.name)
console.log('appManager has activated lPlugin') // console.log('appManager has activated lPlugin')
await pluginComponent.openLocalPlugin()
} } } }
cancelLabel="Cancel" cancelLabel="Cancel"
cancelFn={closeModal} cancelFn={closeModal}
@ -176,17 +185,39 @@ function RootView () {
</ModalDialog> </ModalDialog>
</form><div id="pluginManager" data-id="pluginManagerComponentPluginManager"> </form><div id="pluginManager" data-id="pluginManagerComponentPluginManager">
<header className="form-group remixui_pluginSearch plugins-header py-3 px-4 border-bottom" data-id="pluginManagerComponentPluginManagerHeader"> <header className="form-group remixui_pluginSearch plugins-header py-3 px-4 border-bottom" data-id="pluginManagerComponentPluginManagerHeader">
<input type="text" className="form-control" placeholder="Search" data-id="pluginManagerComponentSearchInput" /> <input
type="text"
onChange={(event) => {
setFilterPlugin(event.target.value.toLowerCase())
}}
className="form-control"
placeholder="Search"
data-id="pluginManagerComponentSearchInput"
/>
<button onClick={openModal} className="remixui_pluginSearchButton btn bg-transparent text-dark border-0 mt-2 text-underline" data-id="pluginManagerComponentPluginSearchButton"> <button onClick={openModal} className="remixui_pluginSearchButton btn bg-transparent text-dark border-0 mt-2 text-underline" data-id="pluginManagerComponentPluginSearchButton">
Connect to a Local Plugin Connect to a Local Plugin
</button> </button>
</header> </header>
<section data-id="pluginManagerComponentPluginManagerSection"> <section data-id="pluginManagerComponentPluginManagerSection">
{actives !== undefined {activeP !== undefined
? (<ShowActives headinglabel="Active Modules" inactives={actives} />) ? (
: (<ShowActives headinglabel="Active Modules" inactives={actives}/>) <Fragment>
<ModuleHeading headingLabel="Active Modules" actives={activeP} inactives={inactiveP} />
{activeP.map((profile) => (
<PluginCard key={profile.name} profile={profile} />
))}
</Fragment>
)
: null
} }
{inactives !== undefined ? (<ShowInactives inactives={inactives} headinglabel="Inactive Modules" />) : <ShowInactives inactives={inactives} headinglabel="Inactive Modules" />} {inactiveP !== undefined ? (
<Fragment>
<ModuleHeading headingLabel="Inactive Modules" inactives={inactiveP} actives={activeP} />
{inactiveP.map((profile) => (
<PluginCard key={profile.name} profile={profile} />
))}
</Fragment>
) : null}
</section> </section>
</div> </div>
</Fragment> </Fragment>

@ -5,7 +5,7 @@ import PluginManagerContextProvider from './contexts/pluginmanagercontext'
import './remix-ui-plugin-manager.css' import './remix-ui-plugin-manager.css'
export const RemixUiPluginManager = (props: RemixUiPluginManagerProps) => { export const RemixUiPluginManager = (props: RemixUiPluginManagerProps) => {
console.log('current state of appmanager', props.appManager) console.log('current state of appmanager', props.pluginComponent)
console.log('The state of props ', props) console.log('The state of props ', props)
return ( return (

@ -2,48 +2,9 @@ import { PermissionHandler } from './app/ui/persmission-handler'
import { PluginManager } from '@remixproject/engine/lib/manager' import { PluginManager } from '@remixproject/engine/lib/manager'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { Engine } from '@remixproject/engine/lib/engine' import { Engine } from '@remixproject/engine/lib/engine'
import { Profile } from '@remixproject/plugin-utils' import { PluginBase, Profile } from '@remixproject/plugin-utils'
import { ViewPlugin } from '@remixproject/engine-web'
/* eslint-disable camelcase */ /* eslint-disable camelcase */
// eslint-disable-next-line no-use-before-define
export = LocalPlugin;
declare class LocalPlugin {
/**
* Open a modal to create a local plugin
* @param {Profile[]} plugins The list of the plugins in the store
* @returns {Promise<{api: any, profile: any}>} A promise with the new plugin profile
*/
open(plugins: any[]): Promise<{
api: any;
profile: any;
}>;
profile: any;
/**
* Create the object to add to the plugin-list
*/
create(): any;
updateName({ target }: {
target: any;
}): void;
updateUrl({ target }: {
target: any;
}): void;
updateDisplayName({ target }: {
target: any;
}): void;
updateProfile(key: any, e: any): void;
updateMethods({ target }: {
target: any;
}): void;
/** The form to create a local plugin */
form(): any;
}
declare module 'yo-yo'{ declare module 'yo-yo'{
interface yo_yo { interface yo_yo {
(strings:string[], ...values:any[]):HTMLElement; (strings:string[], ...values:any[]):HTMLElement;
@ -99,17 +60,86 @@ export class RemixAppManager extends PluginManager {
turnPluginOff(name: string); turnPluginOff(name: string);
} }
export interface PluginManagerContextProviderProps { export class PluginManagerSettings {
openDialog(): void;
permissions: any;
currentSetting: any;
onValidation(): void;
/** Clear one permission from a plugin */
clearPersmission(from: any, to: any, method: any): void;
/** Clear all persmissions from a plugin */
clearAllPersmission(to: any): void;
settings(): any;
render(): any;
}
export class PluginManagerComponent extends ViewPlugin extends Plugin implements PluginBase {
constructor(appManager: RemixAppManager, engine: Engine)
appManager: RemixAppManager appManager: RemixAppManager
engine: RemixEngine engine: Engine
htmlElement: HTMLDivElement
views: { root: null, items: {} }
localPlugin: LocalPlugin localPlugin: LocalPlugin
_paq: any pluginNames: string[]
inactivePlugins: Profile[]
activePlugins: Profile[]
filter: string filter: string
isActive(name: string): boolean
activateP(name: string): void
deactivateP(name: string): void
onActivation(): void
renderComponent(): void
openLocalPlugin(): Promise<void>
render(): HTMLDivElement
filterPlugins({ target }: { target: any }) : void
}
// eslint-disable-next-line no-use-before-define
export = LocalPlugin;
declare class LocalPlugin {
/**
* Open a modal to create a local plugin
* @param {Profile[]} plugins The list of the plugins in the store
* @returns {Promise<{api: any, profile: any}>} A promise with the new plugin profile
*/
open(plugins: any[]): Promise<{
api: any;
profile: any;
}>;
profile: any;
/**
* Create the object to add to the plugin-list
*/
create(): any;
updateName({ target }: {
target: any;
}): void;
updateUrl({ target }: {
target: any;
}): void;
updateDisplayName({ target }: {
target: any;
}): void;
updateProfile(key: any, e: any): void;
updateMethods({ target }: {
target: any;
}): void;
/** The form to create a local plugin */
form(): any;
}
export interface PluginManagerContextProviderProps {
appManager: RemixAppManager
pluginComponent: PluginManagerComponent
pluginSettings: PluginManagerSettings
activePluginNames: string[] activePluginNames: string[]
actives: Partial<PluginManagerProfile>[] actives: Partial<PluginManagerProfile>[]
inactives: Partial<PluginManagerProfile>[] inactives: Partial<PluginManagerProfile>[]
activatePlugin: (name: string) => void
deActivatePlugin: (name: string) => void
isActive?: (name: string) => boolean isActive?: (name: string) => boolean
filterPlugins: () => void filterPlugins: () => void
profile: Partial<PluginManagerProfile> profile: Partial<PluginManagerProfile>
@ -119,15 +149,11 @@ export interface PluginManagerContextProviderProps {
export interface RemixUiPluginManagerProps { export interface RemixUiPluginManagerProps {
appManager: RemixAppManager appManager: RemixAppManager
engine: RemixEngine pluginComponent: PluginManagerComponent
localPlugin: LocalPlugin pluginSettings: PluginManagerSettings // Window & typeof globalThis | []
_paq: any // Window & typeof globalThis | []
filter: string
activePluginNames: string[] activePluginNames: string[]
actives: Partial<PluginManagerProfile>[] actives: Partial<PluginManagerProfile>[]
inactives: Partial<PluginManagerProfile>[] inactives: Partial<PluginManagerProfile>[]
activatePlugin: (name: string) => void
deActivatePlugin: (name: string) => void
isActive?: (name: string) => boolean isActive?: (name: string) => boolean
filterPlugins: () => void filterPlugins: () => void
profile: Partial<PluginManagerProfile> profile: Partial<PluginManagerProfile>

Loading…
Cancel
Save