create separate plugincards for active and inactive state. create custom hook for localstorage and method to handle pluginManager state

pull/1344/head
joseph izang 4 years ago
parent e3c887f88c
commit 3c951a9cbf
  1. 71
      apps/remix-ide/src/app/components/plugin-manager-component.js
  2. 29
      libs/remix-ui/plugin-manager/src/lib/components/ActivateButton.tsx
  3. 76
      libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx
  4. 67
      libs/remix-ui/plugin-manager/src/lib/components/InactivePluginCard.tsx
  5. 28
      libs/remix-ui/plugin-manager/src/lib/components/deactivateButton.tsx
  6. 20
      libs/remix-ui/plugin-manager/src/lib/components/permissions/permissionsSettings.tsx
  7. 37
      libs/remix-ui/plugin-manager/src/lib/components/pluginCard.tsx
  8. 20
      libs/remix-ui/plugin-manager/src/lib/components/rootView.tsx
  9. 14
      libs/remix-ui/plugin-manager/src/lib/contexts/pluginmanagercontext.tsx
  10. 61
      libs/remix-ui/plugin-manager/src/lib/useLocalStorage.ts
  11. 75
      libs/remix-ui/plugin-manager/src/pluginManagerStateMachine.ts
  12. 5
      libs/remix-ui/plugin-manager/src/types.d.ts

@ -1,3 +1,4 @@
/* eslint-disable no-debugger */
/* eslint-disable no-unused-vars */
import {
IframePlugin,
@ -16,66 +17,6 @@ const LocalPlugin = require('./local-plugin') // eslint-disable-line
const addToolTip = require('../ui/tooltip')
const _paq = window._paq = window._paq || []
const css = csjs`
.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;
}
.pluginSearchInput {
height: 38px;
}
.pluginSearchButton {
font-size: 13px;
}
.displayName {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.pluginIcon {
height: 0.7rem;
width: 0.7rem;
filter: invert(0.5);
}
.description {
font-size: 13px;
line-height: 18px;
}
.descriptiontext {
display: block;
}
.descriptiontext:first-letter {
text-transform: uppercase;
}
.row {
display: flex;
flex-direction: row;
}
.isStuck {
background-color: var(--primary);
color:
}
.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;
}
`
const profile = {
name: 'pluginManager',
displayName: 'Plugin manager',
@ -107,6 +48,12 @@ class PluginManagerComponent extends ViewPlugin {
this.pluginNames = this.appManager.actives
this.activePlugins = []
this.inactivePlugins = []
this.activeProfiles = this.appManager.actives
this._paq = _paq
}
triggerEngineEventListener () {
this.engine.event.on('onRegistration', () => this.getAndFilterPlugins())
}
/**
@ -128,6 +75,7 @@ class PluginManagerComponent extends ViewPlugin {
this.appManager.activatePlugin(name)
this.appManager.event.on('activate', () => {
this.getAndFilterPlugins()
this.triggerEngineEventListener()
})
_paq.push(['trackEvent', 'manager', 'activate', name])
}
@ -139,6 +87,7 @@ class PluginManagerComponent extends ViewPlugin {
* @param {string} name name of Plugin
*/
deactivateP (name) {
debugger
this.call('manager', 'deactivatePlugin', name)
_paq.push(['trackEvent', 'manager', 'deactivate', name])
this.getAndFilterPlugins()
@ -214,7 +163,7 @@ class PluginManagerComponent extends ViewPlugin {
})
this.activePlugins = activatedPlugins
this.inactivePlugins = deactivatedPlugins
console.log('The Length of appManager.actives is :', this.activeProfiles)
this.renderComponent()
}
}

@ -1,29 +0,0 @@
import React, { useState } from 'react'
import { PluginManagerComponent } from '../../types'
interface ActivateButtonProps {
buttonText: string
pluginName: string
pluginComponent: PluginManagerComponent
}
function ActivateButton ({
buttonText,
pluginName,
pluginComponent
}: ActivateButtonProps) {
const [dataId] = useState(`pluginManagerComponent${buttonText}Button${pluginName}`)
return (
<button
onClick={() => {
pluginComponent.activateP(pluginName)
}}
className={buttonText === 'Activate' ? 'btn btn-success btn-sm' : 'btn btn-secondary btn-sm'}
data-id={dataId}
>
{buttonText}
</button>
)
}
export default ActivateButton

@ -0,0 +1,76 @@
import { Profile } from '@remixproject/plugin-utils'
import React, { useState } from 'react'
import { RemoveActivatedPlugin } from '../../pluginManagerStateMachine'
// import { RemoveActivatedPlugin } from '../../pluginManagerStateMachine'
import { PluginManagerComponent } from '../../types'
import '../remix-ui-plugin-manager.css'
interface PluginCardProps {
profile: Profile & {
icon?: string
}
pluginComponent: PluginManagerComponent
buttonText: string
reRender: () => void
}
// eslint-disable-next-line no-empty-pattern
function ActivePluginCard ({
profile,
pluginComponent,
buttonText,
reRender
}: PluginCardProps) {
const [displayName] = useState<string>((profile.displayName) ? profile.displayName : profile.name)
const [docLink] = useState<JSX.Element>((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>
) : null)
const [versionWarning] = useState<JSX.Element>((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)
// const [stateManager] = useState<PluginManagerStateMachine>(new PluginManagerStateMachine(pluginComponent))
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={displayName}>
<div className="remixui_row justify-content-between align-items-center mb-2">
<h6 className="remixui_displayName plugin-name">
<div>
{displayName}
{docLink}
{versionWarning}
</div>
{
<button
onClick={() => {
// pluginComponent.deactivateP(profile.name)
console.log('calling pluginComponent.call directly...')
pluginComponent.call('manager', 'deactivatePlugin', profile.name)
console.log('called pluginComponent.call successfully')
pluginComponent._paq.push(['trackEvent', 'manager', 'deactivate', profile.name])
console.log('matomo tracking captured for deactivation successfully')
RemoveActivatedPlugin(profile.name)
reRender()
}}
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

@ -0,0 +1,67 @@
import { Profile } from '@remixproject/plugin-utils'
import React, { useState } from 'react'
import { PersistActivatedPlugin } from '../../pluginManagerStateMachine'
import { PluginManagerComponent } from '../../types'
import '../remix-ui-plugin-manager.css'
interface PluginCardProps {
profile: Profile & {
icon?: string
}
pluginComponent: PluginManagerComponent
buttonText: string
}
// eslint-disable-next-line no-empty-pattern
function InactivePluginCard ({
profile,
pluginComponent,
buttonText
}: PluginCardProps) {
const [displayName] = useState<string>((profile.displayName) ? profile.displayName : profile.name)
const [docLink] = useState<JSX.Element>((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>
) : null)
const [versionWarning] = useState<JSX.Element>((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)
// const [stateManager] = useState<PluginManagerStateMachine>(new PluginManagerStateMachine(pluginComponent))
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={displayName}>
<div className="remixui_row justify-content-between align-items-center mb-2">
<h6 className="remixui_displayName plugin-name">
<div>
{displayName}
{docLink}
{versionWarning}
</div>
{
<button
onClick={() => {
pluginComponent.activateP(profile.name)
PersistActivatedPlugin(pluginComponent, profile)
}}
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

@ -1,28 +0,0 @@
import React, { useState } from 'react'
import { PluginManagerComponent } from '../../types'
interface DeactivateButtonProps {
buttonText: string
pluginName: string
pluginComponent: PluginManagerComponent
}
function DeactivateButton ({
buttonText,
pluginName,
pluginComponent
}: DeactivateButtonProps) {
const [dataId] = useState(`pluginManagerComponent${buttonText}Button${pluginName}`)
return (
<button
onClick={() => {
pluginComponent.deactivateP(pluginName)
}}
className={buttonText === 'Deactivate' ? 'btn btn-secondary btn-sm' : ''}
data-id={dataId}
>
{buttonText}
</button>
)
}
export default DeactivateButton

@ -1,5 +1,4 @@
import React, { Fragment, useState } from 'react'
import { RemixUiCheckbox } from '@remix-ui/checkbox'
import { PluginManagerSettings } from '../../../types'
import { ModalDialog } from '@remix-ui/modal-dialog'
@ -12,21 +11,6 @@ interface PermissionSettingsProps {
}
interface ShowPermissionsByMethodProps {
methodName: string
fromPlugins: any
toPlugin: string
togglePermission: (fromName: string, methodName: string, toPlugin: string) => void
pluginSettings: PluginManagerSettings
}
function ShowPermissionsByMethod (fromPlugins) {
const checkBoxes = Object.keys(fromPlugins).map(fromName => {
return fromPlugins[fromName]
})
return checkBoxes
}
function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
/**
* Declare component local state
@ -37,10 +21,6 @@ function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
const methodName = ''
const closeModal = () => setModalVisibility(true)
const togglePermission = (fromPlugin: string, toPlugin: string, methodName: string) => {
pluginSettings.permissions[toPlugin][methodName][fromPlugin].allow = !pluginSettings.permissions[toPlugin][methodName][fromPlugin].allow
}
return (
<Fragment>
<ModalDialog

@ -1,9 +1,8 @@
import { Profile } from '@remixproject/plugin-utils'
import React, { useState } from 'react'
import { PersistActivatedPlugin, RemoveActivatedPlugin } from '../../pluginManagerStateMachine'
import { PluginManagerComponent } from '../../types'
import '../remix-ui-plugin-manager.css'
import ActivateButton from './ActivateButton'
import DeactivateButton from './deactivateButton'
interface PluginCardProps {
profile: Profile & {
icon?: string
@ -30,6 +29,8 @@ function PluginCard ({
) : (profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)) ? (
<small title="Version Beta" className="remixui_versionWarning plugin-version">beta</small>
) : null)
// const [stateManager] = useState<PluginManagerStateMachine>(new PluginManagerStateMachine(pluginComponent))
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={displayName}>
@ -40,17 +41,27 @@ function PluginCard ({
{docLink}
{versionWarning}
</div>
{ pluginComponent.isActive(profile.name) ? (
<DeactivateButton
buttonText={buttonText}
pluginName={profile.name}
pluginComponent={pluginComponent}
/>)
: <ActivateButton
buttonText={buttonText}
pluginName={profile.name}
pluginComponent={pluginComponent}
/>
{ pluginComponent.isActive(profile.name)
? <button
onClick={() => {
pluginComponent.deactivateP(profile.name)
RemoveActivatedPlugin(profile.name)
}}
className="btn btn-secondary btn-sm"
data-id={`pluginManagerComponentDeactivateButton${profile.name}`}
>
{buttonText}
</button>
: <button
onClick={() => {
pluginComponent.activateP(profile.name)
PersistActivatedPlugin(pluginComponent, profile)
}}
className="btn btn-success btn-sm"
data-id={`pluginManagerComponentActivateButton${profile.name}`}
>
{buttonText}
</button>
}
</h6>
</div>

@ -1,12 +1,13 @@
/* eslint-disable no-debugger */
import React, { Fragment, useEffect, useState } from 'react'
import ModuleHeading from './moduleHeading'
import PluginCard from './pluginCard'
import { ModalDialog } from '@remix-ui/modal-dialog'
import { FormStateProps, PluginManagerComponent } from '../../types'
import { IframePlugin, WebsocketPlugin } from '@remixproject/engine-web'
import PermisssionsSettings from './permissions/permissionsSettings'
import { Profile } from '@remixproject/plugin-utils'
import ActivePluginCard from './ActivePluginCard'
import InactivePluginCard from './InactivePluginCard'
const initialState: FormStateProps = {
pname: 'test',
@ -31,6 +32,7 @@ function RootView ({ pluginComponent }: RootViewProps) {
const [filterPlugins, setFilterPlugin] = useState('')
const [activeP, setActiveP] = useState<Profile[]>([])
const [inactiveP, setInactiveP] = useState<Profile[]>([])
// const [storagePlugins, setStoragePlugins] = useLocalStorage('newActivePlugins')
function pluginChangeHandler<P extends keyof FormStateProps> (formProps: P, value: FormStateProps[P]) {
setPlugin({ ...plugin, [formProps]: value })
@ -45,6 +47,11 @@ function RootView ({ pluginComponent }: RootViewProps) {
const closeModal = () => setVisible(true)
// <-- End Modal Visibility States -->
const reRender = () => {
pluginComponent.getAndFilterPlugins()
console.log('Called rerender after deactivating a plugin')
}
useEffect(() => {
pluginComponent.getAndFilterPlugins(filterPlugins)
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -57,7 +64,8 @@ function RootView ({ pluginComponent }: RootViewProps) {
if (pluginComponent.inactivePlugins && pluginComponent.inactivePlugins.length) {
setInactiveP(pluginComponent.inactivePlugins)
}
}, [pluginComponent.activePlugins, pluginComponent.inactivePlugins, activeP, inactiveP])
console.log('contents of appManager', pluginComponent.appManager)
}, [pluginComponent.activePlugins, pluginComponent.inactivePlugins, activeP, inactiveP, pluginComponent.activeProfiles, pluginComponent])
return (
<Fragment>
@ -69,8 +77,6 @@ function RootView ({ pluginComponent }: RootViewProps) {
okLabel="OK"
okFn={() => {
const profile = JSON.parse(localStorage.getItem('plugins/local')) || plugin
console.log('profile from local storage looks like this', profile)
if (!profile) return
if (pluginComponent.appManager.getIds().includes(profile.pname)) {
throw new Error('This name has already been used')
@ -79,7 +85,6 @@ function RootView ({ pluginComponent }: RootViewProps) {
if (!profile.pname) throw new Error('Plugin should have a name')
if (!profile.url) throw new Error('Plugin should have an URL')
const localPlugin = profile.type === 'iframe' ? new IframePlugin(profile) : new WebsocketPlugin(profile)
debugger
localPlugin.profile.hash = `local-${profile.pname}`
localStorage.setItem('plugins/local', JSON.stringify(localPlugin))
pluginComponent.engine.register(localPlugin)
@ -222,10 +227,11 @@ function RootView ({ pluginComponent }: RootViewProps) {
{activeP && <Fragment>
<ModuleHeading headingLabel="Active Modules" count={activeP.length} />
{activeP.map((profile) => (
<PluginCard
<ActivePluginCard
buttonText="Deactivate"
key={profile.name}
profile={profile}
reRender={reRender}
pluginComponent={pluginComponent}
/>
))}
@ -234,7 +240,7 @@ function RootView ({ pluginComponent }: RootViewProps) {
{inactiveP && <Fragment>
<ModuleHeading headingLabel="Inactive Modules" count={inactiveP.length} />
{inactiveP.map((profile) => (
<PluginCard
<InactivePluginCard
buttonText="Activate"
key={profile.name}
profile={profile}

@ -1,14 +0,0 @@
import React, { createContext } from 'react'
import { PluginManagerContextProviderProps } from '../../types'
export const PluginManagerContext = createContext<Partial<PluginManagerContextProviderProps>>({})
function PluginManagerContextProvider ({ children, props }) {
return (
<PluginManagerContext.Provider value={props}>
{children}
</PluginManagerContext.Provider>
)
}
export default PluginManagerContextProvider

@ -0,0 +1,61 @@
import { useRef, useEffect, useState } from 'react'
interface EventHandler<T extends Event = Event> {
(e: T): void;
}
interface WindowEventHook {
<K extends keyof WindowEventMap>(
eventName: K,
handler: EventHandler<WindowEventMap[K]>
): void;
}
export const useWindowEvent: WindowEventHook = (eventName, handler) => {
// optimization: using useRef here helps us guarantee that this function is
// is only mutated during effect lifecycles, adding some assurance that the
// function invoked by the event listener is the same function passed to the
// hook.
const handlerRef = useRef<typeof handler>()
useEffect(() => {
handlerRef.current = handler
}, [handler])
useEffect(() => {
const eventListener: typeof handler = event => handlerRef.current(event)
window.addEventListener(eventName, eventListener)
return () => {
window.removeEventListener(eventName, eventListener)
}
}, [eventName, handler])
}
export const useLocalStorage = (key: string) => {
// initialize the value from localStorage
const [currentValue, setCurrentValue] = useState<string | null>(() =>
localStorage.getItem(key)
)
const handler = (e: StorageEvent) => {
if (
e.storageArea === localStorage &&
e.key === key &&
e.newValue !== currentValue
) {
setCurrentValue(e.newValue)
}
}
// set up the event listener
useWindowEvent('storage', handler)
// update localStorage when the currentValue changes via setCurrentValue
useEffect(() => {
localStorage.setItem(key, currentValue)
}, [key, currentValue])
// use as const to tell TypeScript this is a tuple
return [currentValue, setCurrentValue] as const
}

@ -0,0 +1,75 @@
import { Profile } from '@remixproject/plugin-utils'
import { PluginManagerComponent } from './types'
const defaultActivatedPlugins = [
'manager',
'contentImport',
'theme',
'editor',
'fileManager',
'compilerMetadata',
'compilerArtefacts',
'network',
'web3Provider',
'offsetToLineColumnConverter',
'mainPanel',
'menuicons',
'tabs',
'sidePanel',
'home',
'hiddenPanel',
'contextualListener',
'terminal',
'fetchAndCompile',
'pluginManager',
'filePanel',
'settings',
'udapp'
]
/**
* Compares default enabled plugins of remix ide
* and tracks newly activated plugins and manages
* their state by persisting in local storage
* @param newPlugin Profile of a Plugin
* @param pluginComponent PluginManagerComponent Instance
*/
export async function PersistActivatedPlugin (pluginComponent: PluginManagerComponent, newPlugin: Profile) {
const persisted = localStorage.getItem('newActivePlugins')
const activatedPlugins: Profile[] = JSON.parse(persisted)
const newlyActivatedPlugins: Array<Profile> = []
if (newPlugin) {
if (defaultActivatedPlugins.includes(newPlugin.name) === false) {
// if this is true then we are sure that the profile name is in localStorage/workspace
if (activatedPlugins.length > 0 && !activatedPlugins.includes(newPlugin)) {
await FetchAndPersistPlugin(pluginComponent, newPlugin, activatedPlugins)
localStorage.setItem('newActivePlugins', JSON.stringify(activatedPlugins))
} else {
await FetchAndPersistPlugin(pluginComponent, newPlugin, newlyActivatedPlugins)
localStorage.setItem('newActivePlugins', JSON.stringify(newlyActivatedPlugins))
}
}
}
}
async function FetchAndPersistPlugin (pluginComponent: PluginManagerComponent, newPlugin: Profile<any>, newlyActivatedPlugins: Profile<any>[]) {
try {
const targetProfile = await pluginComponent.appManager.getProfile(newPlugin.name)
if (targetProfile !== null || targetProfile !== undefined) newlyActivatedPlugins.push(targetProfile)
} catch {
throw new Error('Could not fetch and persist target profile!')
}
}
/**
* Remove a deactivated plugin from persistence (localStorage)
* @param pluginName Name of plugin profile to be removed
*/
export function RemoveActivatedPlugin (pluginName: string) {
// eslint-disable-next-line no-debugger
debugger
const getWorkspacePlugins = JSON.parse(localStorage.getItem('newActivePlugins'))
const removeExisting = getWorkspacePlugins.filter(target => target.name === pluginName)
localStorage.setItem('newActivePlugins', JSON.stringify(removeExisting))
}

@ -77,6 +77,7 @@ export class PluginManagerComponent extends ViewPlugin extends Plugin implements
constructor(appManager: RemixAppManager, engine: Engine)
appManager: RemixAppManager
pluginSettings: PluginManagerSettings
app: PluginApi<any>
engine: Engine
htmlElement: HTMLDivElement
views: { root: null, items: {} }
@ -92,8 +93,10 @@ export class PluginManagerComponent extends ViewPlugin extends Plugin implements
renderComponent(): void
openLocalPlugin(): Promise<void>
render(): HTMLDivElement
filterPlugins({ target }: { target: any }) : void
getAndFilterPlugins: (filter?: string) => void
triggerEngineEventListener: () => void
activeProfiles: string[]
_paq: any
}
// eslint-disable-next-line no-use-before-define

Loading…
Cancel
Save