parent
d0b2dac397
commit
80bfb7fc11
@ -0,0 +1,48 @@ |
||||
import { Profile } from '@remixproject/plugin-utils' |
||||
import React, { Fragment, useEffect, useState } from 'react' |
||||
import { PluginManagerComponent } from '../../types' |
||||
import ActivePluginCard from './ActivePluginCard' |
||||
import ModuleHeading from './moduleHeading' |
||||
|
||||
interface ActivePluginCardContainerProps { |
||||
pluginComponent: PluginManagerComponent |
||||
} |
||||
function ActivePluginCardContainer ({ pluginComponent }: ActivePluginCardContainerProps) { |
||||
const [activeProfiles, setActiveProfiles] = useState<Profile[]>() |
||||
const [inactiveProfiles, setinactiveProfiles] = useState<Profile[]>([]) |
||||
const deactivatePlugin = (pluginName: string) => { |
||||
pluginComponent.deactivateP(pluginName) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
const savedActiveProfiles = JSON.parse(localStorage.getItem('newActivePlugins')) |
||||
if (savedActiveProfiles === null) { |
||||
localStorage.setItem('newActivePlugins', '[]') |
||||
} |
||||
if (pluginComponent.activePlugins && pluginComponent.activePlugins.length > 0) { |
||||
setActiveProfiles(pluginComponent.activePlugins) |
||||
} else if (savedActiveProfiles && savedActiveProfiles.length > 0 && pluginComponent.activePlugins.length === 0) { |
||||
setActiveProfiles(savedActiveProfiles) |
||||
} |
||||
}, [pluginComponent, pluginComponent.activePlugins]) |
||||
return ( |
||||
<Fragment> |
||||
{(activeProfiles && activeProfiles.length) ? <ModuleHeading headingLabel="Active Modules" count={activeProfiles.length} /> : null} |
||||
{activeProfiles && activeProfiles.map(profile => ( |
||||
<ActivePluginCard |
||||
buttonText="Deactivate" |
||||
profile={profile} |
||||
deactivatePlugin={deactivatePlugin} |
||||
key={profile.name} |
||||
setInactivePlugins={setinactiveProfiles} |
||||
inactivePlugins={inactiveProfiles} |
||||
activePlugins={activeProfiles} |
||||
setActivePlugins={setActiveProfiles} |
||||
/> |
||||
)) |
||||
} |
||||
</Fragment> |
||||
) |
||||
} |
||||
|
||||
export default ActivePluginCardContainer |
@ -0,0 +1,50 @@ |
||||
import { Profile } from '@remixproject/plugin-utils' |
||||
import React, { Fragment, useEffect, useState } from 'react' |
||||
import { PluginManagerComponent } from '../../types' |
||||
import InactivePluginCard from './InactivePluginCard' |
||||
import ModuleHeading from './moduleHeading' |
||||
|
||||
interface InactivePluginCardContainerProps { |
||||
pluginComponent: PluginManagerComponent |
||||
} |
||||
function InactivePluginCardContainer ({ pluginComponent }: InactivePluginCardContainerProps) { |
||||
const [activeProfiles, setActiveProfiles] = useState<Profile[]>() |
||||
const [inactiveProfiles, setinactiveProfiles] = useState<Profile[]>([]) |
||||
|
||||
const activatePlugin = (profile: Profile) => { |
||||
pluginComponent.activateP(profile.name) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
const savedInactiveProfiles = JSON.parse(localStorage.getItem('updatedInactives')) |
||||
if (savedInactiveProfiles === null) { |
||||
localStorage.setItem('updatedInactives', '[]') |
||||
} |
||||
if (pluginComponent.inactivePlugins && pluginComponent.inactivePlugins.length > 0) { |
||||
setinactiveProfiles(pluginComponent.inactivePlugins) |
||||
} else if (savedInactiveProfiles && pluginComponent.inactivePlugins.length > savedInactiveProfiles.length) { |
||||
setinactiveProfiles(savedInactiveProfiles) |
||||
} |
||||
}, [pluginComponent, pluginComponent.inactivePlugins]) |
||||
return ( |
||||
<Fragment> |
||||
{(inactiveProfiles && inactiveProfiles.length) ? <ModuleHeading headingLabel="Inactive Modules" count={inactiveProfiles.length} /> : null} |
||||
{inactiveProfiles && inactiveProfiles.map(profile => ( |
||||
<InactivePluginCard |
||||
buttonText="Activate" |
||||
profile={profile} |
||||
key={profile.name} |
||||
activatePlugin={activatePlugin} |
||||
setInactivePlugins={setinactiveProfiles} |
||||
inactivePlugins={inactiveProfiles} |
||||
activePlugins={activeProfiles} |
||||
setActivePlugins={setActiveProfiles} |
||||
pluginComponent={pluginComponent} |
||||
/> |
||||
)) |
||||
} |
||||
</Fragment> |
||||
) |
||||
} |
||||
|
||||
export default InactivePluginCardContainer |
@ -0,0 +1,157 @@ |
||||
import { ModalDialog } from '@remix-ui/modal-dialog' |
||||
import { IframePlugin, WebsocketPlugin } from '@remixproject/engine-web' |
||||
import React from 'react' |
||||
import { FormStateProps, PluginManagerComponent } from '../../types' |
||||
|
||||
interface LocalPluginFormProps { |
||||
changeHandler: (propertyName: string, value: any) => void |
||||
plugin: FormStateProps |
||||
closeModal: () => void |
||||
visible: boolean |
||||
pluginManager: PluginManagerComponent |
||||
} |
||||
|
||||
function LocalPluginForm ({ changeHandler, plugin, closeModal, visible, pluginManager }: LocalPluginFormProps) { |
||||
return ( |
||||
<ModalDialog |
||||
handleHide={closeModal} |
||||
id="pluginManagerLocalPluginModalDialog" |
||||
hide={visible} |
||||
title="Local Plugin" |
||||
okLabel="OK" |
||||
okFn={() => { |
||||
const profile = JSON.parse(localStorage.getItem('plugins/local')) || plugin |
||||
if (!profile) return |
||||
if (pluginManager.appManager.getIds().includes(profile.pname)) { |
||||
throw new Error('This name has already been used') |
||||
} |
||||
if (!profile.location) throw new Error('Plugin should have a location') |
||||
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) |
||||
localPlugin.profile.hash = `local-${profile.pname}` |
||||
localStorage.setItem('plugins/local', JSON.stringify(localPlugin)) |
||||
pluginManager.engine.register(localPlugin) |
||||
pluginManager.appManager.activatePlugin(localPlugin.name) |
||||
} } |
||||
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 => changeHandler('pname', e.target.value)} |
||||
value={plugin.pname} |
||||
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 => changeHandler('displayName', e.target.value)} |
||||
value={plugin.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 => changeHandler('methods', e.target.value)} |
||||
value={plugin.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 => changeHandler('url', e.target.value)} |
||||
value={plugin.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={plugin.type === 'iframe'} |
||||
onChange={(e) => changeHandler('type', e.target.value)} /> |
||||
<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={plugin.type === 'ws'} |
||||
onChange={(e) => changeHandler('type', e.target.value)} /> |
||||
<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={plugin.location === 'sidePanel'} |
||||
onChange={(e) => changeHandler('location', e.target.value)} /> |
||||
<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={plugin.location === 'mainPanel'} |
||||
onChange={(e) => changeHandler('location', e.target.value)} /> |
||||
<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={plugin.location === 'none'} |
||||
onChange={(e) => changeHandler('location', e.target.value)} /> |
||||
<label className="form-check-label" htmlFor="none">None</label> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
</ModalDialog> |
||||
) |
||||
} |
||||
|
||||
export default LocalPluginForm |
@ -0,0 +1,44 @@ |
||||
import { Profile } from '@remixproject/plugin-utils' |
||||
import React, { createContext, useEffect, useState } from 'react' |
||||
import { PluginManagerContextProviderProps } from '../../types' |
||||
|
||||
interface PluginManagerContextInterface { |
||||
trackActiveProfiles: Profile[] |
||||
trackInactiveProfiles: Profile[] |
||||
setTrackActiveProfiles: React.Dispatch<Profile[]> |
||||
setTrackInactiveProfiles: React.Dispatch<Profile[]> |
||||
} |
||||
|
||||
export const PluginManagerContext = createContext<PluginManagerContextInterface>(null) |
||||
|
||||
function PluginManagerContextProvider ({ children, pluginComponent }: PluginManagerContextProviderProps) { |
||||
const [trackActiveProfiles, setTrackActiveProfiles] = useState([]) |
||||
const [trackInactiveProfiles, setTrackInactiveProfiles] = useState([]) |
||||
|
||||
useEffect(() => { |
||||
const checkedActives = JSON.parse(localStorage.getItem('newActivePlugins')) |
||||
if (checkedActives && checkedActives.length > 0) { |
||||
setTrackActiveProfiles([...trackActiveProfiles, ...checkedActives]) |
||||
} else { |
||||
localStorage.setItem('newActivePlugins', JSON.stringify(trackActiveProfiles)) |
||||
} |
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [trackActiveProfiles]) |
||||
useEffect(() => { |
||||
const checkedInactives = JSON.parse(localStorage.getItem('updatedInactives')) |
||||
if (checkedInactives && checkedInactives.length > 0 && trackInactiveProfiles.length === 0) { |
||||
setTrackInactiveProfiles([...pluginComponent.inactivePlugins, ...checkedInactives]) |
||||
} else { |
||||
localStorage.setItem('updatedInactives', JSON.stringify(trackInactiveProfiles)) |
||||
} |
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pluginComponent.inactivePlugins]) |
||||
return ( |
||||
<PluginManagerContext.Provider value={{ trackActiveProfiles, trackInactiveProfiles, setTrackActiveProfiles, setTrackInactiveProfiles }}> |
||||
{children} |
||||
</PluginManagerContext.Provider> |
||||
) |
||||
} |
||||
|
||||
export default PluginManagerContextProvider |
@ -1,10 +1,77 @@ |
||||
import React from 'react' |
||||
import { RemixUiPluginManagerProps } from '../types' |
||||
import { PluginManagerComponent, 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 = (props: RemixUiPluginManagerProps) => { |
||||
export function getSolidity (pluginComponent: PluginManagerComponent) { |
||||
const fetchSolidity = async () => { |
||||
const solidity = await pluginComponent.appManager.getProfile('solidity') |
||||
const solidityLogic = await pluginComponent.appManager.getProfile('solidity-logic') |
||||
return [solidity, solidityLogic] |
||||
} |
||||
const materializeFetch = fetchSolidity() |
||||
return materializeFetch |
||||
} |
||||
|
||||
export function getWorkspacePluginNames () { |
||||
const workspace: string[] = JSON.parse(localStorage.getItem('workspace')) |
||||
return workspace |
||||
} |
||||
|
||||
export const RemixUiPluginManager = ({ pluginComponent }: RemixUiPluginManagerProps) => { |
||||
// const [, setWorkspacePlugins] = useState<string[]>([])
|
||||
|
||||
// useEffect(() => {
|
||||
// const newActives = localStorage.getItem('newActivePlugins')
|
||||
// const updatedInactives = localStorage.getItem('updatedInactives')
|
||||
// if (newActives === null && updatedInactives === null) {
|
||||
// if (getWorkspacePluginNames().includes('solidity') && getWorkspacePluginNames().includes('solidity-logic')) {
|
||||
// if (pluginComponent.activeProfiles.includes('solidity') && pluginComponent.activeProfiles.includes('solidity-logic')) {
|
||||
// localStorage.setItem('newActivePlugins', JSON.stringify(getSolidity(pluginComponent)))
|
||||
// const filteredInactives = pluginComponent.inactivePlugins.filter(inactive => inactive.name !== 'solidity' &&
|
||||
// inactive.name !== 'solidity-logic')
|
||||
// }
|
||||
// }
|
||||
// localStorage.setItem('newActivePlugins', '[]')
|
||||
// localStorage.setItem('updatedInactives', '[]')
|
||||
// }
|
||||
// console.log('current Active Profiles from pluginComponent', pluginComponent.activeProfiles)
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// }, [pluginComponent.activePlugins, pluginComponent.activeProfiles, pluginComponent.inactivePlugins])
|
||||
// useEffect(() => {
|
||||
// const workspaceLogic = async () => {
|
||||
// const workspace = JSON.parse(localStorage.getItem('workspace'))
|
||||
// const fromLocalStorage = JSON.parse(localStorage.getItem('newActivePlugins')) as Profile[]
|
||||
// if (workspace && workspace.length > 0) {
|
||||
// setWorkspacePlugins(workspace)
|
||||
// if (workspace.includes('solidity') && workspace.includes('solidity-logic')) {
|
||||
// const solidity = await pluginComponent.appManager.getProfile('solidity')
|
||||
// const logic = await pluginComponent.appManager.getProfile('solidity-logic')
|
||||
// const updates = [...fromLocalStorage, solidity, logic]
|
||||
// localStorage.setItem('newActivePlugins', JSON.stringify(updates))
|
||||
// // setActiveProfiles(updates)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// workspaceLogic()
|
||||
// return () => {
|
||||
// console.log('finished second effect!')
|
||||
// }
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// }, [])
|
||||
|
||||
return ( |
||||
<RootView pluginComponent={props.pluginComponent}/> |
||||
<RootView pluginComponent={pluginComponent}> |
||||
<section data-id="pluginManagerComponentPluginManagerSection"> |
||||
<ActivePluginCardContainer |
||||
pluginComponent={pluginComponent} |
||||
/> |
||||
<InactivePluginCardContainer |
||||
pluginComponent={pluginComponent} |
||||
/> |
||||
</section> |
||||
</RootView> |
||||
) |
||||
} |
||||
|
@ -1,66 +0,0 @@ |
||||
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<any | null>(() => { |
||||
const readFromStore = localStorage.getItem(key) |
||||
if (readFromStore) { |
||||
return JSON.parse(readFromStore) |
||||
} else { |
||||
localStorage.setItem('plugins/permissions', '{}') |
||||
} |
||||
}) |
||||
|
||||
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, JSON.stringify(currentValue)) |
||||
}, [key, currentValue]) |
||||
|
||||
// use as const to tell TypeScript this is a tuple
|
||||
return [currentValue, setCurrentValue] as const |
||||
} |
Loading…
Reference in new issue