Unverified Commit 5b546a73 authored by bunsenstraat's avatar bunsenstraat Committed by GitHub

Merge pull request #1344 from ethereum/react-plugin-manager

React Plugin Manager
parents 39137f32 b951fbf9
...@@ -51,4 +51,3 @@ testem.log ...@@ -51,4 +51,3 @@ testem.log
# System Files # System Files
.DS_Store .DS_Store
Thumbs.db
\ No newline at end of file
...@@ -34,7 +34,7 @@ function removeFile (browser: NightwatchBrowser, path: string, workspace: string ...@@ -34,7 +34,7 @@ function removeFile (browser: NightwatchBrowser, path: string, workspace: string
contextMenuClick(document.querySelector('[data-path="' + path + '"]')) contextMenuClick(document.querySelector('[data-path="' + path + '"]'))
}, [path], function () { }, [path], function () {
browser browser
.waitForElementVisible('#menuitemdelete') .waitForElementVisible('#menuitemdelete', 60000)
.click('#menuitemdelete') .click('#menuitemdelete')
.pause(2000) .pause(2000)
.perform(() => { .perform(() => {
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
declare global {
interface Window { testmode: boolean; }
}
const testData = { const testData = {
pluginName: 'remixIde', pluginName: 'remixIde',
pluginDisplayName: 'Remix IDE', pluginDisplayName: 'Remix IDE',
...@@ -17,6 +21,7 @@ module.exports = { ...@@ -17,6 +21,7 @@ module.exports = {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000) .pause(3000)
.click('*[plugin="pluginManager"]') .click('*[plugin="pluginManager"]')
.pause(3000)
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]') .waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'PLUGIN MANAGER') .assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'PLUGIN MANAGER')
}, },
...@@ -36,7 +41,8 @@ module.exports = { ...@@ -36,7 +41,8 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonZoKrates"]') .waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonZoKrates"]')
.clearValue('*[data-id="pluginManagerComponentSearchInput"]') .clearValue('*[data-id="pluginManagerComponentSearchInput"]')
.click('*[data-id="pluginManagerComponentSearchInput"]') .click('*[data-id="pluginManagerComponentSearchInput"]')
.keys(browser.Keys.ENTER) .keys(browser.Keys.SPACE)
.keys(browser.Keys.BACK_SPACE)
}, },
'Should activate plugins': function (browser: NightwatchBrowser) { 'Should activate plugins': function (browser: NightwatchBrowser) {
...@@ -46,7 +52,7 @@ module.exports = { ...@@ -46,7 +52,7 @@ module.exports = {
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 60000) .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 60000)
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonvyper"]') .scrollAndClick('*[data-id="pluginManagerComponentActivateButtonvyper"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonvyper"]', 60000) .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonvyper"]', 70000)
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonZoKrates"]') .scrollAndClick('*[data-id="pluginManagerComponentActivateButtonZoKrates"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]', 60000) .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]', 60000)
}, },
...@@ -103,37 +109,42 @@ module.exports = { ...@@ -103,37 +109,42 @@ module.exports = {
'Should connect a local plugin': function (browser: NightwatchBrowser) { 'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]') browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.execute(function () {
window.testmode = true
})
.click('*[data-id="pluginManagerComponentPluginSearchButton"]') .click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="modalDialogModalBody"]') .click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]') .waitForElementVisible('*[data-id="localPluginName"]')
.setValue('*[data-id="localPluginName"]', testData.pluginName) .clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName) .clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.setValue('*[data-id="localPluginUrl"]', testData.pluginUrl) .clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]') .click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]') .click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="modalDialogModalFooter"]') .click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.modalFooterOKClick() .click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonremixIde"]', 100000) // .modalFooterOKClick()
// .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonremixIde"]', 60000)
}, },
'Should display error message for creating already existing plugin': function (browser: NightwatchBrowser) { 'Should display error message for creating already existing plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]') browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginSearchButton"]') .click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="modalDialogModalBody"]') .click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]') .waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName) .clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName) .clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl) .clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]') .click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]') .click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="modalDialogModalFooter"]') .waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]', 60000)
.modalFooterOKClick() .click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]')
// .modalFooterOKClick()
// .pause(2000)
.waitForElementVisible('*[data-shared="tooltipPopup"]', 60000)
.pause(5000) .pause(5000)
.waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)') .assert.containsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
.pause(2000)
.assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Cannot create Plugin : This name has already been used')
}, },
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) { 'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
...@@ -143,8 +154,9 @@ module.exports = { ...@@ -143,8 +154,9 @@ module.exports = {
.waitForElementVisible('*[data-id="remixIdeSidePanel"]') .waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000) .pause(3000)
.perform((done) => { .perform((done) => {
// const filtered = plugins.filter(plugin => plugin !== 'testremixIde') // remove this when localplugin bug is resolved
plugins.forEach(plugin => { plugins.forEach(plugin => {
if (plugin !== testData.pluginName) { if (plugin !== testData.pluginName && plugin !== 'testremixIde') {
browser.waitForElementVisible(`[plugin="${plugin}"`) browser.waitForElementVisible(`[plugin="${plugin}"`)
} }
}) })
......
...@@ -104,7 +104,7 @@ module.exports = { ...@@ -104,7 +104,7 @@ module.exports = {
'Close Remixd': function (browser) { 'Close Remixd': function (browser) {
browser browser
.clickLaunchIcon('pluginManager') .clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button') .scrollAndClick('#pluginManager *[data-id="pluginManagerComponentDeactivateButtonremixd"]')
.end() .end()
} }
} }
...@@ -121,10 +121,11 @@ function runTests (browser: NightwatchBrowser) { ...@@ -121,10 +121,11 @@ function runTests (browser: NightwatchBrowser) {
.waitForElementVisible('#icon-panel', 2000) .waitForElementVisible('#icon-panel', 2000)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.clickLaunchIcon('pluginManager') .clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button') .scrollAndClick('#pluginManager *[data-id="pluginManagerComponentActivateButtonremixd"]')
.waitForElementVisible('#modal-footer-ok', 2000) .waitForElementVisible('#modal-footer-ok', 2000)
.pause(2000) .pause(2000)
.click('#modal-footer-ok') .click('#modal-footer-ok')
// .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.waitForElementVisible('[data-path="folder1"]') .waitForElementVisible('[data-path="folder1"]')
.click('[data-path="folder1"]') .click('[data-path="folder1"]')
......
...@@ -27,7 +27,6 @@ module.exports = { ...@@ -27,7 +27,6 @@ module.exports = {
.click('*[id="menuitemdeactivate"]') .click('*[id="menuitemdeactivate"]')
.click('*[data-id="verticalIconsKindsettings"]') .click('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindpluginManager"]') .click('*[data-id="verticalIconsKindpluginManager"]')
.scrollInto('*[data-id="pluginManagerComponentActivateButtondebugger"]') .waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugPlugin"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
} }
} }
/* global localStorage */
const yo = require('yo-yo')
const modalDialog = require('../ui/modaldialog')
const defaultProfile = {
methods: [],
location: 'sidePanel',
type: 'iframe'
}
module.exports = 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) {
this.profile = JSON.parse(localStorage.getItem('plugins/local')) || defaultProfile
return new Promise((resolve, reject) => {
const onValidation = () => {
try {
const profile = this.create()
resolve(profile)
} catch (err) {
reject(err)
}
}
modalDialog('Local Plugin', this.form(),
{ fn: () => onValidation() },
{ fn: () => resolve() }
)
})
}
/**
* Create the object to add to the plugin-list
*/
create () {
const profile = {
icon: 'assets/img/localPlugin.webp',
methods: [],
location: 'sidePanel',
type: 'iframe',
...this.profile,
hash: `local-${this.profile.name}`
}
if (!profile.location) throw new Error('Plugin should have a location')
if (!profile.name) throw new Error('Plugin should have a name')
if (!profile.url) throw new Error('Plugin should have an URL')
localStorage.setItem('plugins/local', JSON.stringify(profile))
return profile
}
updateName ({ target }) {
this.profile.name = target.value
}
updateUrl ({ target }) {
this.profile.url = target.value
}
updateDisplayName ({ target }) {
this.profile.displayName = target.value
}
updateProfile (key, e) {
this.profile[key] = e.target.value
}
updateMethods ({ target }) {
if (target.value) {
try {
this.profile.methods = target.value.split(',')
} catch (e) {}
}
}
/** The form to create a local plugin */
form () {
const name = this.profile.name || ''
const url = this.profile.url || ''
const displayName = this.profile.displayName || ''
const methods = (this.profile.methods && this.profile.methods.join(',')) || ''
const radioSelection = (key, label, message) => {
return this.profile[key] === label
? yo`<div class="radio">
<input class="form-check-input" type="radio" name="${key}" onclick="${e => this.updateProfile(key, e)}" value="${label}" id="${label}" data-id="localPluginRadioButton${label}" checked="checked" />
<label class="form-check-label" for="${label}">${message}</label>
</div>`
: yo`<div class="radio">
<input class="form-check-input" type="radio" name="${key}" onclick="${e => this.updateProfile(key, e)}" value="${label}" id="${label}" data-id="localPluginRadioButton${label}" />
<label class="form-check-label" for="${label}">${message}</label>
</div>`
}
return yo`
<form id="local-plugin-form">
<div class="form-group">
<label for="plugin-name">Plugin Name <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateName(e)}" value="${name}" id="plugin-name" data-id="localPluginName" placeholder="Should be camelCase">
</div>
<div class="form-group">
<label for="plugin-displayname">Display Name</label>
<input class="form-control" onchange="${e => this.updateDisplayName(e)}" value="${displayName}" id="plugin-displayname" data-id="localPluginDisplayName" placeholder="Name in the header">
</div>
<div class="form-group">
<label for="plugin-methods">Api (comma separated list of methods name)</label>
<input class="form-control" onchange="${e => this.updateMethods(e)}" value="${methods}" id="plugin-methods" data-id="localPluginMethods" placeholder="Name in the header">
</div>
<div class="form-group">
<label for="plugin-url">Url <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateUrl(e)}" value="${url}" id="plugin-url" data-id="localPluginUrl" placeholder="ex: https://localhost:8000">
</div>
<h6>Type of connection <small>(required)</small></h6>
<div class="form-check form-group">
${radioSelection('type', 'iframe', 'Iframe')}
${radioSelection('type', 'ws', 'Websocket')}
</div>
<h6>Location in remix <small>(required)</small></h6>
<div class="form-check form-group">
${radioSelection('location', 'sidePanel', 'Side Panel')}
${radioSelection('location', 'mainPanel', 'Main Panel')}
${radioSelection('location', 'none', 'None')}
</div>
</form>`
}
}
...@@ -2,8 +2,8 @@ const yo = require('yo-yo') ...@@ -2,8 +2,8 @@ const yo = require('yo-yo')
const csjs = require('csjs-inject') const csjs = require('csjs-inject')
const modalDialog = require('../ui/modaldialog') const modalDialog = require('../ui/modaldialog')
const css = csjs` const css = csjs`
.permissions { .remixui_permissions {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
display: flex; display: flex;
...@@ -44,9 +44,12 @@ const css = csjs` ...@@ -44,9 +44,12 @@ const css = csjs`
` `
export class PluginManagerSettings { export class PluginManagerSettings {
openDialog () { constructor () {
const fromLocal = window.localStorage.getItem('plugins/permissions') const fromLocal = window.localStorage.getItem('plugins/permissions')
this.permissions = JSON.parse(fromLocal || '{}') this.permissions = JSON.parse(fromLocal || '{}')
}
openDialog () {
this.currentSetting = this.settings() this.currentSetting = this.settings()
modalDialog('Plugin Manager Permissions', this.currentSetting, modalDialog('Plugin Manager Permissions', this.currentSetting,
{ fn: () => this.onValidation() } { fn: () => this.onValidation() }
...@@ -60,6 +63,8 @@ export class PluginManagerSettings { ...@@ -60,6 +63,8 @@ export class PluginManagerSettings {
/** Clear one permission from a plugin */ /** Clear one permission from a plugin */
clearPersmission (from, to, method) { clearPersmission (from, to, method) {
// eslint-disable-next-line no-debugger
debugger
if (this.permissions[to] && this.permissions[to][method]) { if (this.permissions[to] && this.permissions[to][method]) {
delete this.permissions[to][method][from] delete this.permissions[to][method][from]
if (Object.keys(this.permissions[to][method]).length === 0) { if (Object.keys(this.permissions[to][method]).length === 0) {
...@@ -74,6 +79,8 @@ export class PluginManagerSettings { ...@@ -74,6 +79,8 @@ export class PluginManagerSettings {
/** Clear all persmissions from a plugin */ /** Clear all persmissions from a plugin */
clearAllPersmission (to) { clearAllPersmission (to) {
// eslint-disable-next-line no-debugger
debugger
if (!this.permissions[to]) return if (!this.permissions[to]) return
delete this.permissions[to] delete this.permissions[to]
yo.update(this.currentSetting, this.settings()) yo.update(this.currentSetting, this.settings())
......
...@@ -3,6 +3,10 @@ import { ModalDialogProps } from './types' // eslint-disable-line ...@@ -3,6 +3,10 @@ import { ModalDialogProps } from './types' // eslint-disable-line
import './remix-ui-modal-dialog.css' import './remix-ui-modal-dialog.css'
declare global {
interface Window { testmode: boolean; }
}
export const ModalDialog = (props: ModalDialogProps) => { export const ModalDialog = (props: ModalDialogProps) => {
const [state, setState] = useState({ const [state, setState] = useState({
toggleBtn: true toggleBtn: true
...@@ -21,7 +25,7 @@ export const ModalDialog = (props: ModalDialogProps) => { ...@@ -21,7 +25,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
if (!e.currentTarget.contains(e.relatedTarget)) { if (!e.currentTarget.contains(e.relatedTarget)) {
e.stopPropagation() e.stopPropagation()
if (document.activeElement !== this) { if (document.activeElement !== this) {
handleHide() !window.testmode && handleHide()
} }
} }
} }
......
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}
# remix-ui-plugin-manager
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-plugin-manager` to execute the unit tests via [Jest](https://jestjs.io).
export * from './lib/remix-ui-plugin-manager'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react'
import '../remix-ui-plugin-manager.css'
interface PluginCardProps {
profile: any
buttonText: string
deactivatePlugin: (pluginName: string) => void
}
function ActivePluginCard ({
profile,
buttonText,
deactivatePlugin
}: PluginCardProps) {
return (
<div className="list-group list-group-flush plugins-list-group" data-id="pluginManagerComponentActiveTile">
<article className="list-group-item py-1 mb-1 plugins-list-group-item" title={profile.displayName || profile.name}>
<div className="remixui_row justify-content-between align-items-center mb-2">
<h6 className="remixui_displayName plugin-name">
<div>
{ profile.displayName || profile.name }
{ profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/>
</a>
}
{ profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)
? <small title="Version Alpha" className="remixui_versionWarning plugin-version">alpha</small>
: profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)
? <small title="Version Beta" className="remixui_versionWarning plugin-version">beta</small>
: null
}
</div>
{<button
onClick={() => {
deactivatePlugin(profile.name)
} }
className="btn btn-secondary btn-sm"
data-id={`pluginManagerComponentDeactivateButton${profile.name}`}
>
{buttonText}
</button>}
</h6>
</div>
<div className="remixui_description d-flex text-body plugin-text mb-2">
{profile.icon ? <img src={profile.icon} className="mr-1 mt-1 remixui_pluginIcon" alt="profile icon" /> : null}
<span className="remixui_descriptiontext">{profile.description}</span>
</div>
</article>
</div>
)
}
export default ActivePluginCard
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Profile } from '@remixproject/plugin-utils'
import React from 'react'
import { PluginManagerComponent } from '../../types'
import ActivePluginCard from './ActivePluginCard'
import ModuleHeading from './moduleHeading'
interface ActivePluginCardContainerProps {
pluginComponent: PluginManagerComponent
setActiveProfiles: React.Dispatch<React.SetStateAction<Profile<any>[]>>
activeProfiles: Profile[]
}
function ActivePluginCardContainer ({ pluginComponent }: ActivePluginCardContainerProps) {
const deactivatePlugin = (pluginName: string) => {
pluginComponent.deactivateP(pluginName)
}
return (
<React.Fragment>
{(pluginComponent.activePlugins && pluginComponent.activePlugins.length) ? <ModuleHeading headingLabel="Active Modules" count={pluginComponent.activePlugins.length} /> : null}
{pluginComponent.activePlugins && pluginComponent.activePlugins.map((profile, idx) => {
return (
<ActivePluginCard
buttonText="Deactivate"
profile={profile}
deactivatePlugin={deactivatePlugin}
key={idx}
/>
)
})
}
</React.Fragment>
)
}
export default ActivePluginCardContainer
import { Profile } from '@remixproject/plugin-utils'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react'
import '../remix-ui-plugin-manager.css'
interface PluginCardProps {
profile: Profile & {
icon?: string
}
buttonText: string
activatePlugin: (plugin: string) => void
}
function InactivePluginCard ({
profile,
buttonText,
activatePlugin
}: PluginCardProps) {
return (
<div className="list-group list-group-flush plugins-list-group" data-id="pluginManagerComponentActiveTile">
<article className="list-group-item py-1 mb-1 plugins-list-group-item" title={profile.displayName || profile.name}>
<div className="remixui_row justify-content-between align-items-center mb-2">
<h6 className="remixui_displayName plugin-name">
<div>
{ profile.displayName || profile.name }
{ profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/>
</a>
}
{ profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)
? <small title="Version Alpha" className="remixui_versionWarning plugin-version">alpha</small>
: profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)
? <small title="Version Beta" className="remixui_versionWarning plugin-version">beta</small>
: null
}
</div>
{
<button
onClick={() => {
activatePlugin(profile.name)
}}
className="btn btn-success btn-sm"
data-id={`pluginManagerComponentActivateButton${profile.name}`}
>
{buttonText}
</button>
}
</h6>
</div>
<div className="remixui_description d-flex text-body plugin-text mb-2">
{ profile.icon ? <img src={profile.icon} className="mr-1 mt-1 remixui_pluginIcon" alt="profile icon"/> : null }
<span className="remixui_descriptiontext">{profile.description}</span>
</div>
</article>
</div>
)
}
export default InactivePluginCard
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Profile } from '@remixproject/plugin-utils'
import React from 'react'
import { PluginManagerComponent, PluginManagerProfile } from '../../types'
import InactivePluginCard from './InactivePluginCard'
import ModuleHeading from './moduleHeading'
interface InactivePluginCardContainerProps {
pluginComponent: PluginManagerComponent
setInactiveProfiles: React.Dispatch<React.SetStateAction<Profile<any>[]>>
inactiveProfiles: Profile<any>[]
}
interface LocalPluginInterface {
profile: Partial<PluginManagerProfile>
activateService: {}
requestQueue: []
options: { queueTimeout: number }
id: number
pendingRequest: {}
listener: []
iframe: {}
}
function InactivePluginCardContainer ({ pluginComponent }: InactivePluginCardContainerProps) {
const activatePlugin = (pluginName: string) => {
pluginComponent.activateP(pluginName)
}
return (
<React.Fragment>
{(pluginComponent.inactivePlugins && pluginComponent.inactivePlugins.length) ? <ModuleHeading headingLabel="Inactive Modules" count={pluginComponent.inactivePlugins.length} /> : null}
{pluginComponent.inactivePlugins && pluginComponent.inactivePlugins.map((profile, idx) => {
return (
<InactivePluginCard
buttonText="Activate"
profile={profile}
key={idx}
activatePlugin={activatePlugin}
/>
)
})
}
</React.Fragment>
)
}
export default InactivePluginCardContainer
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useReducer, useState } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog'
import { Toaster } from '@remix-ui/toaster'
import { IframePlugin, WebsocketPlugin } from '@remixproject/engine-web'
import { localPluginReducerActionType, localPluginToastReducer } from '../reducers/pluginManagerReducer'
import { FormStateProps, PluginManagerComponent } from '../../types'
interface LocalPluginFormProps {
closeModal: () => void
visible: boolean
pluginManager: PluginManagerComponent
}
const initialState: FormStateProps = {
name: '',
displayName: '',
url: '',
type: 'iframe',
hash: '',
methods: [],
location: 'sidePanel'
}
const defaultProfile = {
methods: [],
location: 'sidePanel',
type: 'iframe',
name: '',
displayName: '',
url: '',
hash: ''
}
function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFormProps) {
const [errorMsg, dispatchToastMsg] = useReducer(localPluginToastReducer, '')
const [name, setName] = useState<string>('')
const [displayName, setDisplayName] = useState<string>('')
const [url, setUrl] = useState<string>('')
const [type, setType] = useState<'iframe' | 'ws'>('iframe')
const [location, setLocation] = useState<'sidePanel' | 'mainPanel' | 'none'>('sidePanel')
const [methods, setMethods] = useState<string>('')
useEffect(() => {
const storagePlugin:FormStateProps = localStorage.getItem('plugins/local') ? JSON.parse(localStorage.getItem('plugins/local')) : defaultProfile
setName(storagePlugin.name)
setUrl(storagePlugin.url)
setLocation(storagePlugin.location as 'sidePanel' | 'mainPanel' | 'none')
setMethods(storagePlugin.methods)
setType(storagePlugin.type)
setDisplayName(storagePlugin.displayName)
}, [])
const handleModalOkClick = async () => {
try {
if (!name) throw new Error('Plugin should have a name')
if (pluginManager.appManager.getIds().includes(name)) {
throw new Error('This name has already been used')
}
if (!location) throw new Error('Plugin should have a location')
if (!url) throw new Error('Plugin should have an URL')
const newMethods = typeof methods === 'string' ? methods.split(',').filter(val => val) : []
const targetPlugin = {
name: name,
displayName: displayName,
description: '',
documentation: '',
events: [],
hash: '',
kind: '',
methods: newMethods,
url: url,
type: type,
location: location,
icon: 'assets/img/localPlugin.webp'
}
const localPlugin = type === 'iframe' ? new IframePlugin(initialState) : new WebsocketPlugin(initialState)
localPlugin.profile.hash = `local-${name}`
targetPlugin.description = localPlugin.profile.description !== undefined ? localPlugin.profile.description : ''
targetPlugin.events = localPlugin.profile.events !== undefined ? localPlugin.profile.events : []
targetPlugin.kind = localPlugin.profile.kind !== undefined ? localPlugin.profile.kind : ''
localPlugin.profile = { ...localPlugin.profile, ...targetPlugin }
pluginManager.activateAndRegisterLocalPlugin(localPlugin)
} catch (error) {
const action: localPluginReducerActionType = { type: 'show', payload: `${error.message}` }
dispatchToastMsg(action)
console.log(error)
}
}
return (
<><ModalDialog
handleHide={closeModal}
id="pluginManagerLocalPluginModalDialog"
hide={visible}
title="Local Plugin"
okLabel="OK"
okFn={ handleModalOkClick }
cancelLabel="Cancel"
cancelFn={closeModal}
>
<form id="local-plugin-form">
<div className="form-group">
<label htmlFor="plugin-name">Plugin Name <small>(required)</small></label>
<input
className="form-control"
onChange={e => setName(e.target.value)}
value={ name}
id="plugin-name"
data-id="localPluginName"
placeholder="Should be camelCase" />
</div>
<div className="form-group">
<label htmlFor="plugin-displayname">Display Name</label>
<input
className="form-control"
onChange={e => setDisplayName(e.target.value)}
value={ displayName }
id="plugin-displayname"
data-id="localPluginDisplayName"
placeholder="Name in the header" />
</div>
<div className="form-group">
<label htmlFor="plugin-methods">Api (comma separated list of methods name)</label>
<input
className="form-control"
onChange={e => setMethods(e.target.value)}
value={ methods }
id="plugin-methods"
data-id="localPluginMethods"
placeholder="Name in the header" />
</div>
<div className="form-group">
<label htmlFor="plugin-url">Url <small>(required)</small></label>
<input
className="form-control"
onChange={e => setUrl(e.target.value)}
value={ url }
id="plugin-url"
data-id="localPluginUrl"
placeholder="ex: https://localhost:8000" />
</div>
<h6>Type of connection <small>(required)</small></h6>
<div className="form-check form-group">
<div className="radio">
<input
className="form-check-input"
type="radio"
name="type"
value="iframe"
id="iframe"
data-id='localPluginRadioButtoniframe'
checked={type === 'iframe'}
onChange={(e) => setType(e.target.value as 'iframe' | 'ws')} />
<label className="form-check-label" htmlFor="iframe">Iframe</label>
</div>
<div className="radio">
<input
className="form-check-input"
type="radio"
name="type"
value="ws"
id="ws"
data-id='localPluginRadioButtonws'
checked={type === 'ws'}
onChange={(e) => setType(e.target.value as 'iframe' | 'ws')} />
<label className="form-check-label" htmlFor="ws">Websocket</label>
</div>
</div>
<h6>Location in remix <small>(required)</small></h6>
<div className="form-check form-group">
<div className="radio">
<input
className="form-check-input"
type="radio"
name="location"
value="sidePanel"
id="sidePanel"
data-id='localPluginRadioButtonsidePanel'
checked={location === 'sidePanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
<label className="form-check-label" htmlFor="sidePanel">Side Panel</label>
</div>
<div className="radio">
<input
className="form-check-input"
type="radio"
name="location"
value="mainPanel"
id="mainPanel"
data-id='localPluginRadioButtonmainPanel'
checked={location === 'mainPanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
<label className="form-check-label" htmlFor="mainPanel">Main Panel</label>
</div>
<div className="radio">
<input
className="form-check-input"
type="radio"
name="location"
value="none"
id="none"
data-id='localPluginRadioButtonnone'
checked={location === 'none'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
<label className="form-check-label" htmlFor="none">None</label>
</div>
</div>
</form>
</ModalDialog>
{errorMsg ? <Toaster message={errorMsg} /> : null}
</>
)
}
export default LocalPluginForm
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
interface ModuleHeadingProps {
headingLabel: string
count: number
}
function ModuleHeading ({ headingLabel, count }: ModuleHeadingProps) {
return (
<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="badge badge-primary" style={{ cursor: 'default' }} data-id="pluginManagerComponentInactiveTilesCount">
{count}
</span>
</nav>
)
}
export default ModuleHeading
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { Fragment, useState } from 'react'
/* eslint-disable-line */
import { ModalDialog } from '@remix-ui/modal-dialog'
import useLocalStorage from '../custom-hooks/useLocalStorage'
import { PluginPermissions } from '../../types'
interface PermissionSettingsProps {
pluginSettings: any
}
function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
const [modalVisibility, setModalVisibility] = useState<boolean>(true)
const [permissions, setPermissions] = useLocalStorage<PluginPermissions>('plugins/permissions', {} as PluginPermissions)
const [permissionCache, setpermissionCache] = useState<PluginPermissions>()
const closeModal = () => setModalVisibility(true)
const openModal = () => {
const currentValue = JSON.parse(window.localStorage.getItem('plugins/permissions') || '{}')
setpermissionCache(currentValue)
setPermissions(currentValue)
setModalVisibility(!modalVisibility)
}
const cancel = () => {
setPermissions(permissionCache)
}
const getState = (targetPlugin:string, funcName:string, pluginName :string) => {
return permissions[targetPlugin][funcName][pluginName].allow
}
const handleCheckboxClick = (targetPlugin:string, funcName:string, pluginName :string) => {
setPermissions((permissions) => {
permissions[targetPlugin][funcName][pluginName].allow = !permissions[targetPlugin][funcName][pluginName].allow
return permissions
})
}
function clearFunctionPermission (targetPlugin:string, funcName:string, pluginName :string) {
setPermissions((permissions) => {
delete permissions[targetPlugin][funcName][pluginName]
if (Object.keys(permissions[targetPlugin][funcName]).length === 0) delete permissions[targetPlugin][funcName]
if (Object.keys(permissions[targetPlugin]).length === 0) delete permissions[targetPlugin]
return permissions
})
}
function clearTargetPermission (targetPlugin: string) {
setPermissions((permissions) => {
delete permissions[targetPlugin]
return permissions
})
}
function RenderPluginHeader ({ headingName }) {
return (
<div className="pb-2 remixui_permissionKey">
<h3>{headingName} permissions:</h3>
<i
onClick={() => {
clearTargetPermission(headingName)
}}
className="far fa-trash-alt"
data-id={`pluginManagerSettingsClearAllPermission-${headingName}`}>
</i>
</div>
)
}
function RenderPermissions ({ targetPlugin }) {
return <>{Object.keys(permissions[targetPlugin]).map(funcName => {
return Object.keys(permissions[targetPlugin][funcName]).map((pluginName, index) => (
<div className="form-group remixui_permissionKey" key={pluginName}>
{ permissions && Object.keys(permissions).length > 0
? (
<><div className="remixui_checkbox">
<span className="mr-2">
<input
type="checkbox"
onChange={() => handleCheckboxClick(targetPlugin, funcName, pluginName)}
checked={getState(targetPlugin, funcName, pluginName)}
id={`permission-checkbox-${targetPlugin}-${funcName}-${pluginName}`}
aria-describedby={`module ${pluginName} asks permission for ${funcName}`} />
<label
className="ml-4"
htmlFor={`permission-checkbox-${targetPlugin}-${funcName}-${targetPlugin}`}
data-id={`permission-label-${targetPlugin}-${funcName}-${targetPlugin}`}
>
Allow <u>{pluginName}</u> to call <u>{funcName}</u>
</label>
</span>
</div><i
onClick={() => {
clearFunctionPermission(targetPlugin, funcName, pluginName)
} }
className="fa fa-trash-alt"
data-id={`pluginManagerSettingsRemovePermission-${targetPlugin}-${funcName}-${targetPlugin}`} /></>
) : null
}
</div>
))
})}</>
}
return (
<Fragment>
<ModalDialog
handleHide={closeModal}
cancelFn={cancel}
hide={modalVisibility}
title="Plugin Manager Permissions"
okLabel="OK"
cancelLabel="Cancel"
>
{permissions && Object.keys(permissions).length > 0
? (<h4 className="text-center">Current Permission Settings</h4>)
: (<h4 className="text-center">No Permission requested yet.</h4>)
}
<form className="remixui_permissionForm" data-id="pluginManagerSettingsPermissionForm">
<div className="p-2">
{
Object.keys(permissions).map(targetPlugin => (
<div key={`container-${targetPlugin}`}>
<RenderPluginHeader key={`header-${targetPlugin}`} headingName={targetPlugin} />
<RenderPermissions key={`permissions-${targetPlugin}`} targetPlugin={targetPlugin}/>
</div>
))
}
</div>
</form>
</ModalDialog>
<footer className="bg-light remixui_permissions remix-bg-opacity">
<button
onClick={openModal}
className="btn btn-primary settings-button"
data-id="pluginManagerPermissionsButton">
Permissions
</button>
</footer>
</Fragment>
)
}
export default PermisssionsSettings
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { Fragment, ReactNode, useEffect, useState } from 'react'
import { PluginManagerComponent, PluginManagerSettings } from '../../types'
import PermisssionsSettings from './permissionsSettings'
import { Profile } from '@remixproject/plugin-utils'
import LocalPluginForm from './LocalPluginForm'
interface RootViewProps {
pluginComponent: PluginManagerComponent
pluginManagerSettings: PluginManagerSettings
children: ReactNode
}
export interface pluginDeactivated {
flag: boolean
profile: Profile
}
export interface pluginActivated {
flag: boolean
profile: Profile
}
function RootView ({ pluginComponent, pluginManagerSettings, children }: RootViewProps) {
const [visible, setVisible] = useState<boolean>(true)
const [filterPlugins, setFilterPlugin] = useState<string>('')
const openModal = () => {
setVisible(false)
}
const closeModal = () => setVisible(true)
useEffect(() => {
pluginComponent.getAndFilterPlugins(filterPlugins)
}, [filterPlugins])
return (
<Fragment>
<div id="pluginManager" data-id="pluginManagerComponentPluginManager">
<header className="form-group remixui_pluginSearch plugins-header py-3 px-4 border-bottom" data-id="pluginManagerComponentPluginManagerHeader">
<input
type="text"
onChange={(event) => {
setFilterPlugin(event.target.value.toLowerCase())
}}
value={filterPlugins}
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">
Connect to a Local Plugin
</button>
</header>
{children}
<PermisssionsSettings pluginSettings={pluginManagerSettings}/>
</div>
<LocalPluginForm
closeModal={closeModal}
visible={visible}
pluginManager={pluginComponent}
/>
</Fragment>
)
}
export default RootView
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
type SetValue<T> = Dispatch<SetStateAction<T>>
function useLocalStorage<T> (key: string, initialValue: T): [T, SetValue<T>] {
// Get from local storage then
// parse stored json or return initialValue
const readValue = (): T => {
// Prevent build error "window is undefined" but keep keep working
if (typeof window === 'undefined') {
return initialValue
}
try {
const item = window.localStorage.getItem(key)
return item ? (JSON.parse(item) as T) : initialValue
} catch (error) {
console.warn(`Error reading localStorage key “${key}”:`, error)
return initialValue
}
}
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState<T>(readValue)
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue: SetValue<T> = value => {
// Prevent build error "window is undefined" but keeps working
if (typeof window === 'undefined') {
console.warn(
`Tried setting localStorage key “${key}” even though environment is not a client`
)
}
try {
// Allow value to be a function so we have the same API as useState
const newValue = value instanceof Function ? value(storedValue) : value
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(newValue))
// Save state
setStoredValue(newValue)
// We dispatch a custom event so every useLocalStorage hook are notified
window.dispatchEvent(new Event('local-storage'))
} catch (error) {
console.warn(`Error setting localStorage key “${key}”:`, error)
}
}
useEffect(() => {
setStoredValue(readValue())
}, [])
useEffect(() => {
const handleStorageChange = () => {
setStoredValue(readValue())
}
// this only works for other documents, not the current one
window.addEventListener('storage', handleStorageChange)
// this is a custom event, triggered in writeValueToLocalStorage
window.addEventListener('local-storage', handleStorageChange)
return () => {
window.removeEventListener('storage', handleStorageChange)
window.removeEventListener('local-storage', handleStorageChange)
}
}, [])
return [storedValue, setValue]
}
export default useLocalStorage
export type localPluginReducerActionType = {
type: 'show' | 'close',
payload?: any
}
export function localPluginToastReducer (currentState: string, toastAction: localPluginReducerActionType) {
switch (toastAction.type) {
case 'show':
return `Cannot create Plugin : ${toastAction.payload!}`
default:
return currentState
}
}
.remixui_pluginSearch {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--light);
padding: 10px;
position: sticky;
top: 0;
z-index: 2;
margin-bottom: 0px;
}
.remixui_pluginSearchInput {
height: 38px;
}
.remixui_pluginSearchButton {
font-size: 13px;
}
.remixui_displayName {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.remixui_pluginIcon {
height: 0.7rem;
width: 0.7rem;
filter: invert(0.5);
}
.remixui_description {
font-size: 13px;
line-height: 18px;
}
.remixui_descriptiontext {
display: block;
}
.remixui_descriptiontext:first-letter {
text-transform: uppercase;
}
.remixui_row {
display: flex;
flex-direction: row;
}
.remixui_isStuck {
background-color: var(--primary);
/* color: */
}
.remixui_versionWarning {
padding: 4px;
margin: 0 8px;
font-weight: 700;
font-size: 9px;
line-height: 12px;
text-transform: uppercase;
cursor: default;
border: 1px solid;
border-radius: 2px;
}
.remixui_permissions {
position: sticky;
bottom: 0;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 5px 20px;
}
.remixui_permissions button {
padding: 2px 5px;
cursor: pointer;
}
.remixui_permissionForm h4 {
font-size: 1.3rem;
text-align: center;
}
.remixui_permissionForm h6 {
font-size: 1.1rem;
}
.remixui_permissionForm hr {
width: 80%;
}
.remixui_permissionKey {
display: flex;
justify-content: space-between;
align-items: center;
}
.remixui_permissionKey i {
cursor: pointer;
}
.remixui_checkbox {
display: flex;
align-items: center;
}
.remixui_checkbox label {
margin: 0;
font-size: 1rem;
}
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Profile } from '@remixproject/plugin-utils'
import React, { useState } from 'react'
import { RemixUiPluginManagerProps } from '../types'
import ActivePluginCardContainer from './components/ActivePluginCardContainer'
import InactivePluginCardContainer from './components/InactivePluginCardContainer'
import RootView from './components/rootView'
import './remix-ui-plugin-manager.css'
export const RemixUiPluginManager = ({ pluginComponent, pluginManagerSettings }: RemixUiPluginManagerProps) => {
const [activeProfiles, setActiveProfiles] = useState<Profile[]>(pluginComponent.activePlugins)
const [inactiveProfiles, setinactiveProfiles] = useState<Profile[]>(pluginComponent.inactivePlugins)
return (
<RootView pluginComponent={pluginComponent} pluginManagerSettings={pluginManagerSettings}>
<section data-id="pluginManagerComponentPluginManagerSection">
<ActivePluginCardContainer
pluginComponent={pluginComponent}
setActiveProfiles={setActiveProfiles}
activeProfiles={activeProfiles}
/>
<InactivePluginCardContainer
pluginComponent={pluginComponent}
setInactiveProfiles={setinactiveProfiles}
inactiveProfiles={inactiveProfiles}
/>
</section>
</RootView>
)
}
import { PermissionHandler } from './app/ui/persmission-handler'
import { PluginManager } from '@remixproject/engine/lib/manager'
import { EventEmitter } from 'events'
import { Engine } from '@remixproject/engine/lib/engine'
import { PluginBase, Profile } from '@remixproject/plugin-utils'
import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web'
/* eslint-disable camelcase */
interface SetPluginOptionType {
queueTimeout: number
}
export class RemixEngine extends Engine {
event: EventEmitter;
setPluginOption ({ name, kind }) : SetPluginOptionType
onRegistration (plugin) : void
}
export function isNative(name: any): any;
/**
* Checks if plugin caller 'from' is allowed to activate plugin 'to'
* The caller can have 'canActivate' as a optional property in the plugin profile.
* This is an array containing the 'name' property of the plugin it wants to call.
* canActivate = ['plugin1-to-call','plugin2-to-call',....]
* or the plugin is allowed by default because it is native
*
* @param {any, any}
* @returns {boolean}
*/
export function canActivate(from: any, to: any): boolean;
export class RemixAppManager extends PluginManager {
constructor();
event: EventEmitter;
pluginsDirectory: string;
pluginLoader: PluginLoader;
permissionHandler: PermissionHandler;
getAll(): import('@remixproject/plugin-utils').Profile<any>[];
getIds(): string[];
isDependent(name: any): any;
isRequired(name: any): any;
registeredPlugins(): Promise<any>;
turnPluginOn(name: string | string[]);
turnPluginOff(name: string);
}
export class PluginManagerSettings {
openDialog(): void;
permissions: any;
currentSetting: any;
onValidation(): void;
/** Clear one permission from a plugin */
clearPersmission(from: string, to: string, method: string): void;
/** Clear all persmissions from a plugin */
clearAllPersmission(to: string): void;
settings(): any;
render(): any;
}
export type PluginPermissions = {
fileManager : {
writeFile: {
pluginName: {
allow: boolean
}
}
}
}
export class PluginManagerComponent extends ViewPlugin extends Plugin implements PluginBase {
constructor(appManager: RemixAppManager, engine: Engine)
appManager: RemixAppManager
pluginSettings: PluginManagerSettings
app: PluginApi<any>
engine: Engine
htmlElement: HTMLDivElement
views: { root: null, items: {} }
localPlugin: LocalPlugin
pluginNames: string[]
inactivePlugins: Profile[]
activePlugins: Profile[]
filter: string
isActive(name: string): boolean
activateP(name: string): void
deactivateP(name: string): void
onActivation(): void
renderComponent(): void
openLocalPlugin(): Promise<void>
render(): HTMLDivElement
getAndFilterPlugins: (filter?: string, profiles?: Profile[]) => void
triggerEngineEventListener: () => void
activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | WebsocketPlugin) => Promise<void>
activeProfiles: string[]
_paq: any
}
// 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 {
children: React.ReactNode
pluginComponent: PluginManagerComponent
}
export interface RemixUiPluginManagerProps {
pluginComponent: PluginManagerComponent
pluginManagerSettings: PluginManagerSettings
}
/** @class Reference loaders.
* A loader is a get,set based object which load a workspace from a defined sources.
* (localStorage, queryParams)
**/
declare class PluginLoader {
get currentLoader(): any;
donotAutoReload: string[];
loaders: {};
current: string;
set(plugin: any, actives: any): void;
get(): any;
}
export type PluginManagerSettings = {
openDialog: () => void
onValidation: () => void
clearPermission: (from: any, to: any, method: any) => void
settings: () => HTMLElement
render: () => HTMLElement
}
export interface DefaultLocalPlugin extends Profile {
name: string
displayName: string
url: string
type: string
hash: string
methods: any
location: string
}
export interface FormStateProps {
name: string
displayName: string
url: string
type: 'iframe' | 'ws'
hash: string
methods: any
location: string
}
export type PluginManagerProfile = Profile & {
name: string,
displayName: string,
methods: Array<any>,
events?: Array<any>,
icon: 'assets/img/pluginManager.webp',
description: string,
kind?: string,
location: 'sidePanel' | 'mainPanel' | 'none',
documentation: 'https://remix-ide.readthedocs.io/en/latest/plugin_manager.html',
version: any
type: 'iframe' | 'ws'
hash: string
}
export type LocalPlugin = {
create: () => Profile
updateName: (target: string) => void
updateDisplayName: (displayName: string) => void
updateProfile: (key: string, e: Event) => void
updateMethods: (target: any) => void
form: () => HTMLElement
}
export { }
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}
...@@ -106,6 +106,9 @@ ...@@ -106,6 +106,9 @@
"remix-ui-checkbox": { "remix-ui-checkbox": {
"tags": [] "tags": []
}, },
"remix-ui-plugin-manager": {
"tags": []
},
"remix-core-plugin": { "remix-core-plugin": {
"tags": [] "tags": []
}, },
...@@ -118,5 +121,5 @@ ...@@ -118,5 +121,5 @@
"remix-ui-renderer": { "remix-ui-renderer": {
"tags": [] "tags": []
} }
} }
} }
This diff is collapsed.
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler", "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,remix-ui-plugin-manager",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs", "publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",
...@@ -208,6 +208,7 @@ ...@@ -208,6 +208,7 @@
"@types/chai": "^4.2.11", "@types/chai": "^4.2.11",
"@types/fs-extra": "^9.0.1", "@types/fs-extra": "^9.0.1",
"@types/jest": "25.1.4", "@types/jest": "25.1.4",
"@types/lodash": "^4.14.172",
"@types/mocha": "^7.0.2", "@types/mocha": "^7.0.2",
"@types/nightwatch": "^1.1.6", "@types/nightwatch": "^1.1.6",
"@types/node": "~8.9.4", "@types/node": "~8.9.4",
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
"@remix-project/core-plugin": ["libs/remix-core-plugin/src/index.ts"], "@remix-project/core-plugin": ["libs/remix-core-plugin/src/index.ts"],
"@remix-ui/solidity-compiler": ["libs/remix-ui/solidity-compiler/src/index.ts"], "@remix-ui/solidity-compiler": ["libs/remix-ui/solidity-compiler/src/index.ts"],
"@remix-ui/publish-to-storage": ["libs/remix-ui/publish-to-storage/src/index.ts"], "@remix-ui/publish-to-storage": ["libs/remix-ui/publish-to-storage/src/index.ts"],
"@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"],
"@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"] "@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"]
} }
}, },
......
...@@ -777,6 +777,15 @@ ...@@ -777,6 +777,15 @@
} }
} }
}, },
"remix-ui-plugin-manager": {
"root": "libs/remix-ui/plugin-manager",
"sourceRoot": "libs/remix-ui/plugin-manager/src",
"tsConfig": ["libs/remix-ui/plugin-manager/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/plugin-manager/**/*"
]
},
"remix-core-plugin": { "remix-core-plugin": {
"root": "libs/remix-core-plugin", "root": "libs/remix-core-plugin",
"sourceRoot": "libs/remix-core-plugin/src", "sourceRoot": "libs/remix-core-plugin/src",
...@@ -858,6 +867,7 @@ ...@@ -858,6 +867,7 @@
"tsConfig": ["libs/remix-ui/renderer/tsconfig.lib.json"], "tsConfig": ["libs/remix-ui/renderer/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/renderer/**/*"] "exclude": ["**/node_modules/**", "!libs/remix-ui/renderer/**/*"]
} }
} }
} }
} }
...@@ -928,4 +938,4 @@ ...@@ -928,4 +938,4 @@
} }
}, },
"defaultProject": "remix-ide" "defaultProject": "remix-ide"
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment