Merge pull request #1857 from ethereum/panels

Panels
pull/5370/head
David Disu 3 years ago committed by GitHub
commit f5b6bde34f
  1. 14
      apps/remix-ide-e2e/nightwatch.ts
  2. 1
      apps/remix-ide-e2e/src/helpers/init.ts
  3. 170
      apps/remix-ide/src/app.js
  4. 31
      apps/remix-ide/src/app/components/hidden-panel.js
  5. 37
      apps/remix-ide/src/app/components/hidden-panel.tsx
  6. 38
      apps/remix-ide/src/app/components/main-panel.js
  7. 57
      apps/remix-ide/src/app/components/main-panel.tsx
  8. 111
      apps/remix-ide/src/app/components/panel.js
  9. 63
      apps/remix-ide/src/app/components/panel.ts
  10. 156
      apps/remix-ide/src/app/components/side-panel.js
  11. 95
      apps/remix-ide/src/app/components/side-panel.tsx
  12. 2
      apps/remix-ide/src/app/components/vertical-icons.js
  13. 94
      apps/remix-ide/src/app/panels/layout.ts
  14. 204
      apps/remix-ide/src/app/panels/main-view.js
  15. 18
      apps/remix-ide/src/app/panels/tab-proxy.js
  16. 2
      apps/remix-ide/src/app/panels/terminal.js
  17. 4
      apps/remix-ide/src/app/udapp/run-tab.js
  18. 34
      apps/remix-ide/src/framingService.js
  19. 88
      apps/remix-ide/src/lib/panels-resize.js
  20. 8
      apps/remix-ide/src/remixAppManager.js
  21. 1
      apps/remix-ide/tsconfig.json
  22. 49
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css
  23. 21
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  24. 16
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  25. 4
      libs/remix-ui/panel/.babelrc
  26. 18
      libs/remix-ui/panel/.eslintrc.json
  27. 7
      libs/remix-ui/panel/README.md
  28. 2
      libs/remix-ui/panel/src/index.ts
  29. 27
      libs/remix-ui/panel/src/lib/dragbar/dragbar.css
  30. 51
      libs/remix-ui/panel/src/lib/dragbar/dragbar.tsx
  31. 8
      libs/remix-ui/panel/src/lib/main/main-panel.css
  32. 60
      libs/remix-ui/panel/src/lib/main/main-panel.tsx
  33. 27
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  34. 37
      libs/remix-ui/panel/src/lib/plugins/panel-plugin.tsx
  35. 110
      libs/remix-ui/panel/src/lib/plugins/panel.css
  36. 29
      libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx
  37. 9
      libs/remix-ui/panel/src/lib/types/index.ts
  38. 20
      libs/remix-ui/panel/tsconfig.json
  39. 13
      libs/remix-ui/panel/tsconfig.lib.json
  40. 3
      libs/remix-ui/plugin-manager/src/types.d.ts
  41. 2
      libs/remix-ui/solidity-unit-testing/.babelrc
  42. 4
      libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
  43. 77
      libs/remix-ui/terminal/src/lib/custom-hooks/useDragTerminal.tsx
  44. 34
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  45. 3
      nx.json
  46. 4
      package.json
  47. 5
      tsconfig.base.json
  48. 15
      workspace.json

@ -68,7 +68,13 @@ module.exports = {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', browserName: 'firefox',
javascriptEnabled: true, javascriptEnabled: true,
acceptSslCerts: true acceptSslCerts: true,
'moz:firefoxOptions': {
args: [
'-width=2560',
'-height=1440'
]
}
} }
}, },
@ -78,7 +84,11 @@ module.exports = {
javascriptEnabled: true, javascriptEnabled: true,
acceptSslCerts: true, acceptSslCerts: true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: ['-headless'] args: [
'-headless',
'-width=2560',
'-height=1440'
]
} }
} }
} }

@ -9,6 +9,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.switchBrowserTab(0) .switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]') .waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]') .click('[id="remixTourSkipbtn"]')
.maximizeWindow()
.fullscreenWindow(() => { .fullscreenWindow(() => {
if (preloadPlugins) { if (preloadPlugins) {
initModules(browser, () => { initModules(browser, () => {

@ -2,7 +2,6 @@
import { RunTab, makeUdapp } from './app/udapp' import { RunTab, makeUdapp } from './app/udapp'
import { RemixEngine } from './remixEngine' import { RemixEngine } from './remixEngine'
import { RemixAppManager } from './remixAppManager' import { RemixAppManager } from './remixAppManager'
import { MainView } from './app/panels/main-view'
import { ThemeModule } from './app/tabs/theme-module' import { ThemeModule } from './app/tabs/theme-module'
import { NetworkModule } from './app/tabs/network-module' import { NetworkModule } from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider' import { Web3ProviderModule } from './app/tabs/web3-provider'
@ -11,7 +10,6 @@ import { HiddenPanel } from './app/components/hidden-panel'
import { VerticalIcons } from './app/components/vertical-icons' import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page' import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import { MainPanel } from './app/components/main-panel'
import { FramingService } from './framingService'
import { WalkthroughService } from './walkthroughService' import { WalkthroughService } from './walkthroughService'
@ -20,6 +18,7 @@ import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, Fetch
import migrateFileSystem from './migrateFileSystem' import migrateFileSystem from './migrateFileSystem'
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config' import { ConfigPlugin } from './app/plugins/config'
import { Layout } from './app/panels/layout'
import { ModalPlugin } from './app/plugins/modal' import { ModalPlugin } from './app/plugins/modal'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -50,6 +49,7 @@ const TestTab = require('./app/tabs/test-tab')
const FilePanel = require('./app/panels/file-panel') const FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor') const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal') const Terminal = require('./app/panels/terminal')
const { TabProxy } = require('./app/panels/tab-proxy.js')
class AppComponent { class AppComponent {
constructor () { constructor () {
@ -67,13 +67,27 @@ class AppComponent {
// load file system // load file system
self._components.filesProviders = {} self._components.filesProviders = {}
self._components.filesProviders.browser = new FileProvider('browser') self._components.filesProviders.browser = new FileProvider('browser')
Registry.getInstance().put({ api: self._components.filesProviders.browser, name: 'fileproviders/browser' }) Registry.getInstance().put({
self._components.filesProviders.localhost = new RemixDProvider(self.appManager) api: self._components.filesProviders.browser,
Registry.getInstance().put({ api: self._components.filesProviders.localhost, name: 'fileproviders/localhost' }) name: 'fileproviders/browser'
})
self._components.filesProviders.localhost = new RemixDProvider(
self.appManager
)
Registry.getInstance().put({
api: self._components.filesProviders.localhost,
name: 'fileproviders/localhost'
})
self._components.filesProviders.workspace = new WorkspaceFileProvider() self._components.filesProviders.workspace = new WorkspaceFileProvider()
Registry.getInstance().put({ api: self._components.filesProviders.workspace, name: 'fileproviders/workspace' }) Registry.getInstance().put({
api: self._components.filesProviders.workspace,
name: 'fileproviders/workspace'
})
Registry.getInstance().put({ api: self._components.filesProviders, name: 'fileproviders' }) Registry.getInstance().put({
api: self._components.filesProviders,
name: 'fileproviders'
})
migrateFileSystem(self._components.filesProviders.browser) migrateFileSystem(self._components.filesProviders.browser)
} }
@ -83,6 +97,7 @@ class AppComponent {
// APP_MANAGER // APP_MANAGER
const appManager = self.appManager const appManager = self.appManager
const pluginLoader = self.appManager.pluginLoader const pluginLoader = self.appManager.pluginLoader
self.panels = {}
self.workspace = pluginLoader.get() self.workspace = pluginLoader.get()
self.engine = new RemixEngine() self.engine = new RemixEngine()
self.engine.register(appManager) self.engine.register(appManager)
@ -92,8 +107,15 @@ class AppComponent {
'remix-beta.ethereum.org': 25, 'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23 'remix.ethereum.org': 23
} }
self.showMatamo = (matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics')) self.showMatamo =
self.walkthroughService = new WalkthroughService(appManager, self.showMatamo) matomoDomains[window.location.hostname] &&
!Registry.getInstance()
.get('config')
.api.exists('settings/matomo-analytics')
self.walkthroughService = new WalkthroughService(
appManager,
self.showMatamo
)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080'] const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support // workaround for Electron support
@ -114,7 +136,9 @@ class AppComponent {
// ----------------- editor service ---------------------------- // ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({ api: editor, name: 'editor' }) Registry.getInstance().put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile()) editor.event.register('requiringToSaveCurrentfile', () =>
fileManager.saveCurrentFile()
)
// ----------------- fileManager service ---------------------------- // ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager) const fileManager = new FileManager(editor, appManager)
@ -131,7 +155,10 @@ class AppComponent {
const compilerMetadataGenerator = new CompilerMetadata() const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ---------------------------- // ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name) const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
Registry.getInstance().put({ api: compilersArtefacts, name: 'compilersartefacts' }) Registry.getInstance().put({
api: compilersArtefacts,
name: 'compilersartefacts'
})
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it // service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile() const fetchAndCompile = new FetchAndCompile()
@ -142,19 +169,22 @@ class AppComponent {
const hardhatProvider = new HardhatProvider(blockchain) const hardhatProvider = new HardhatProvider(blockchain)
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' }) Registry.getInstance().put({
api: offsetToLineColumnConverter,
name: 'offsettolinecolumnconverter'
})
// -------------------Terminal---------------------------------------- // -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl)) makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl))
const terminal = new Terminal( const terminal = new Terminal(
{ appManager, blockchain }, { appManager, blockchain },
{ {
getPosition: (event) => { getPosition: event => {
const limitUp = 36 const limitUp = 36
const limitDown = 20 const limitDown = 20
const height = window.innerHeight const height = window.innerHeight
let newpos = (event.pageY < limitUp) ? limitUp : event.pageY let newpos = event.pageY < limitUp ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown newpos = newpos < height - limitDown ? newpos : height - limitDown
return height - newpos return height - newpos
} }
} }
@ -164,8 +194,10 @@ class AppComponent {
self.modal = new ModalPlugin() self.modal = new ModalPlugin()
const configPlugin = new ConfigPlugin() const configPlugin = new ConfigPlugin()
self.layout = new Layout()
self.engine.register([ self.engine.register([
self.layout,
self.modal, self.modal,
self.gistHandler, self.gistHandler,
configPlugin, configPlugin,
@ -189,22 +221,27 @@ class AppComponent {
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel() const appPanel = new MainPanel()
self.mainview = new MainView(contextualListener, editor, appPanel, fileManager, appManager, terminal)
Registry.getInstance().put({ api: self.mainview, name: 'mainview' }) Registry.getInstance().put({ api: self.mainview, name: 'mainview' })
const tabProxy = new TabProxy(fileManager, editor)
self.engine.register([ self.engine.register([appPanel, tabProxy])
appPanel,
self.mainview.tabProxy
])
// those views depend on app_manager // those views depend on app_manager
self.menuicons = new VerticalIcons(appManager) self.menuicons = new VerticalIcons(appManager)
self.sidePanel = new SidePanel(appManager, self.menuicons) self.sidePanel = new SidePanel(appManager, self.menuicons)
self.hiddenPanel = new HiddenPanel() self.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, self.engine) const pluginManagerComponent = new PluginManagerComponent(
appManager,
self.engine
)
const filePanel = new FilePanel(appManager) const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(appManager, self.menuicons, fileManager, filePanel, contentImport) const landingPage = new LandingPage(
appManager,
self.menuicons,
fileManager,
filePanel,
contentImport
)
self.settings = new SettingsTab( self.settings = new SettingsTab(
Registry.getInstance().get('config').api, Registry.getInstance().get('config').api,
editor, editor,
@ -222,7 +259,10 @@ class AppComponent {
]) ])
// CONTENT VIEWS & DEFAULT PLUGINS // CONTENT VIEWS & DEFAULT PLUGINS
const compileTab = new CompileTab(Registry.getInstance().get('config').api, Registry.getInstance().get('filemanager').api) const compileTab = new CompileTab(
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api
)
const run = new RunTab( const run = new RunTab(
blockchain, blockchain,
Registry.getInstance().get('config').api, Registry.getInstance().get('config').api,
@ -231,7 +271,6 @@ class AppComponent {
filePanel, filePanel,
Registry.getInstance().get('compilersartefacts').api, Registry.getInstance().get('compilersartefacts').api,
networkModule, networkModule,
self.mainview,
Registry.getInstance().get('fileproviders/browser').api Registry.getInstance().get('fileproviders/browser').api
) )
const analysis = new AnalysisTab() const analysis = new AnalysisTab()
@ -256,6 +295,13 @@ class AppComponent {
filePanel.hardhatHandle, filePanel.hardhatHandle,
filePanel.slitherHandle filePanel.slitherHandle
]) ])
self.layout.panels = {
tabs: { plugin: tabProxy, active: true },
editor: { plugin: editor, active: true },
main: { plugin: appPanel, active: false },
terminal: { plugin: terminal, active: true, minimized: false }
}
} }
async activate () { async activate () {
@ -270,9 +316,9 @@ class AppComponent {
try { try {
self.engine.register(await self.appManager.registeredPlugins()) self.engine.register(await self.appManager.registeredPlugins())
} catch (e) { } catch (e) {
console.log('couldn\'t register iframe plugins', e.message) console.log("couldn't register iframe plugins", e.message)
} }
await self.appManager.activatePlugin(['layout'])
await self.appManager.activatePlugin(['modal']) await self.appManager.activatePlugin(['modal'])
await self.appManager.activatePlugin(['editor']) await self.appManager.activatePlugin(['editor'])
await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
@ -284,48 +330,56 @@ class AppComponent {
await self.appManager.activatePlugin(['settings']) await self.appManager.activatePlugin(['settings'])
await self.appManager.activatePlugin(['walkthrough']) await self.appManager.activatePlugin(['walkthrough'])
self.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => { self.appManager.on(
await self.appManager.registerContextMenuItems() 'filePanel',
}) 'workspaceInitializationCompleted',
async () => {
await self.appManager.registerContextMenuItems()
}
)
await self.appManager.activatePlugin(['filePanel']) await self.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation // Set workspace after initial activation
self.appManager.on('editor', 'editorMounted', () => { self.appManager.on('editor', 'editorMounted', () => {
if (Array.isArray(self.workspace)) { if (Array.isArray(self.workspace)) {
self.appManager.activatePlugin(self.workspace).then(async () => { self.appManager
try { .activatePlugin(self.workspace)
if (params.deactivate) { .then(async () => {
await self.appManager.deactivatePlugin(params.deactivate.split(',')) try {
if (params.deactivate) {
await self.appManager.deactivatePlugin(
params.deactivate.split(',')
)
}
} catch (e) {
console.log(e)
}
if (params.code) {
// if code is given in url we focus on solidity plugin
self.menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (
self.appManager.pluginLoader.current === 'queryParams' &&
self.workspace.length > 0
) { self.menuicons.select(self.workspace[self.workspace.length - 1]) }
} }
} catch (e) {
console.log(e) if (params.call) {
} const callDetails = params.call.split('//')
if (params.code) { if (callDetails.length > 1) {
// if code is given in url we focus on solidity plugin toolTip(`initiating ${callDetails[0]} ...`)
self.menuicons.select('solidity') // @todo(remove the timeout when activatePlugin is on 0.3.0)
} else { self.appManager.call(...callDetails).catch(console.error)
// If plugins are loaded from the URL params, we focus on the last one. }
if (self.appManager.pluginLoader.current === 'queryParams' && self.workspace.length > 0) self.menuicons.select(self.workspace[self.workspace.length - 1])
}
if (params.call) {
const callDetails = params.call.split('//')
if (callDetails.length > 1) {
toolTip(`initiating ${callDetails[0]} ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
self.appManager.call(...callDetails).catch(console.error)
} }
} })
}).catch(console.error) .catch(console.error)
} }
}) })
// activate solidity plugin // activate solidity plugin
self.appManager.activatePlugin(['solidity', 'udapp']) self.appManager.activatePlugin(['solidity', 'udapp'])
// Load and start the service who manager layout and frame // Load and start the service who manager layout and frame
const framingService = new FramingService(self.sidePanel, self.menuicons, self.mainview, null)
if (params.embed) framingService.embed()
framingService.start(params)
} }
} }

@ -1,31 +0,0 @@
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
const csjs = require('csjs-inject')
const yo = require('yo-yo')
const css = csjs`
.pluginsContainer {
display: none;
}
`
const profile = {
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class HiddenPanel extends AbstractPanel {
constructor () {
super(profile)
}
render () {
return yo`
<div class=${css.pluginsContainer}>
${this.view}
</div>`
}
}

@ -0,0 +1,37 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import ReactDOM from 'react-dom' // eslint-disable-line
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
import { RemixPluginPanel } from '@remix-ui/panel'
const profile = {
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class HiddenPanel extends AbstractPanel {
el: HTMLElement
constructor () {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer')
}
addView (profile: any, view: any): void {
super.removeView(profile)
super.addView(profile, view)
this.renderComponent()
}
render () {
return this.el
}
renderComponent () {
ReactDOM.render(<RemixPluginPanel header={<></>} plugins={this.plugins}/>, this.el)
}
}

@ -1,38 +0,0 @@
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const css = csjs`
.pluginsContainer {
height: 100%;
display: flex;
overflow-y: hidden;
}
`
const profile = {
name: 'mainPanel',
displayName: 'Main Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class MainPanel extends AbstractPanel {
constructor () {
super(profile)
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
}
render () {
return yo`
<div class=${css.pluginsContainer} data-id="mainPanelPluginsContainer" id='mainPanelPluginsContainer-id'>
${this.view}
</div>`
}
}

@ -0,0 +1,57 @@
import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel'
import ReactDOM from 'react-dom' // eslint-disable-line
import { RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
const profile = {
name: 'mainPanel',
displayName: 'Main Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView', 'showContent']
}
export class MainPanel extends AbstractPanel {
element: HTMLDivElement
constructor (config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config
}
onActivation () {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.renderComponent()
}
removeView (profile) {
super.removeView(profile)
this.renderComponent()
}
async showContent (name) {
super.showContent(name)
this.renderComponent()
}
render () {
return this.element
}
renderComponent () {
ReactDOM.render(<RemixPluginPanel header={<></>} plugins={this.plugins}/>, this.element)
}
}

@ -1,111 +0,0 @@
import { EventEmitter } from 'events'
import { HostPlugin } from '@remixproject/engine-web'
const csjs = require('csjs-inject')
const yo = require('yo-yo')
const css = csjs`
.plugins {
height: 100%;
}
.plugItIn {
display : none;
height : 100%;
}
.plugItIn > div {
overflow-y : auto;
overflow-x : hidden;
height : 100%;
width : 100%;
}
.plugItIn.active {
display : block;
}
.pluginsContainer {
height : 100%;
overflow-y : hidden;
}
`
/** Abstract class used for hosting the view of a plugin */
export class AbstractPanel extends HostPlugin {
constructor (profile) {
super(profile)
this.events = new EventEmitter()
this.contents = {}
this.active = undefined
// View where the plugin HTMLElement leaves
this.view = yo`<div id="plugins" class="${css.plugins}"></div>`
}
/**
* Add the plugin to the panel
* @param {String} name the name of the plugin
* @param {HTMLElement} content the HTMLContent of the plugin
*/
add (view, name) {
if (this.contents[name]) throw new Error(`Plugin ${name} already rendered`)
view.style.height = '100%'
view.style.width = '100%'
view.style.border = '0'
const isIframe = view.tagName === 'IFRAME'
view.style.display = isIframe ? 'none' : 'block'
const loading = isIframe ? yo`
<div class="d-flex justify-content-center align-items-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
` : ''
this.contents[name] = yo`<div class="${css.plugItIn}" >${view}${loading}</div>`
if (view.tagName === 'IFRAME') {
view.addEventListener('load', () => {
if (this.contents[name].contains(loading)) {
this.contents[name].removeChild(loading)
}
view.style.display = 'block'
})
}
this.contents[name].style.display = 'none'
this.view.appendChild(this.contents[name])
}
addView (profile, view) {
this.add(view, profile.name)
}
removeView (profile) {
this.remove(profile.name)
}
/**
* Remove a plugin from the panel
* @param {String} name The name of the plugin to remove
*/
remove (name) {
const el = this.contents[name]
delete this.contents[name]
if (el) el.parentElement.removeChild(el)
if (name === this.active) this.active = undefined
}
/**
* Display the content of this specific plugin
* @param {String} name The name of the plugin to display the content
*/
showContent (name) {
if (!this.contents[name]) throw new Error(`Plugin ${name} is not yet activated`)
// hiding the current view and display the `moduleName`
if (this.active) {
this.contents[this.active].style.display = 'none'
}
this.contents[name].style.display = 'flex'
this.active = name
}
focus (name) {
this.showContent(name)
}
}

@ -0,0 +1,63 @@
import React from 'react' // eslint-disable-line
import { EventEmitter } from 'events'
import { HostPlugin } from '@remixproject/engine-web' // eslint-disable-line
import { PluginRecord } from 'libs/remix-ui/panel/src/lib/types'
const EventManager = require('../../lib/events')
export class AbstractPanel extends HostPlugin {
events: EventEmitter
event: any
public plugins: Record<string, PluginRecord> = {}
constructor (profile) {
super(profile)
this.events = new EventEmitter()
this.event = new EventManager()
}
currentFocus (): string {
return Object.values(this.plugins).find(plugin => {
return plugin.active
}).profile.name
}
addView (profile, view) {
if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`)
this.plugins[profile.name] = {
profile: profile,
view: view,
active: false,
class: 'plugItIn active'
}
}
removeView (profile) {
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.remove(profile.name)
}
/**
* Remove a plugin from the panel
* @param {String} name The name of the plugin to remove
*/
remove (name) {
delete this.plugins[name]
}
/**
* Display the content of this specific plugin
* @param {String} name The name of the plugin to display the content
*/
showContent (name) {
if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`)
Object.values(this.plugins).forEach(plugin => {
plugin.active = false
})
this.plugins[name].active = true
}
focus (name) {
this.showContent(name)
}
}

@ -1,156 +0,0 @@
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
const csjs = require('csjs-inject')
const yo = require('yo-yo')
const css = csjs`
.panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
flex: auto;
}
.swapitTitle {
margin: 0;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.swapitTitle i{
padding-left: 6px;
font-size: 14px;
}
.swapitHeader {
display: flex;
align-items: center;
padding: 16px 24px 15px;
justify-content: space-between;
}
.icons i {
height: 80%;
cursor: pointer;
}
.pluginsContainer {
height: 100%;
overflow-y: auto;
}
.titleInfo {
padding-left: 10px;
}
.versionBadge {
background-color: var(--light);
padding: 0 7px;
font-weight: bolder;
margin-left: 5px;
text-transform: lowercase;
cursor: default;
}
`
const sidePanel = {
name: 'sidePanel',
displayName: 'Side Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
// TODO merge with vertical-icons.js
export class SidePanel extends AbstractPanel {
constructor (appManager, verticalIcons) {
super(sidePanel)
this.appManager = appManager
this.header = yo`<header></header>`
this.renderHeader()
this.verticalIcons = verticalIcons
// Toggle content
verticalIcons.events.on('toggleContent', (name) => {
if (!this.contents[name]) return
if (this.active === name) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggle', name)
this.events.emit('toggle', name)
return
}
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
// Force opening
verticalIcons.events.on('showContent', (name) => {
if (!this.contents[name]) return
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
}
removeView (profile) {
super.removeView(profile)
this.emit('pluginDisabled', profile.name)
this.verticalIcons.unlinkContent(profile)
}
addView (profile, view) {
super.addView(profile, view)
this.verticalIcons.linkContent(profile)
}
/**
* Display content and update the header
* @param {String} name The name of the plugin to display
*/
async showContent (name) {
super.showContent(name)
this.renderHeader()
this.emit('focusChanged', name)
}
/** The header of the side panel */
async renderHeader () {
let name = ' - '
let docLink = ''
let versionWarning
if (this.active) {
const profile = await this.appManager.getProfile(this.active)
name = profile.displayName ? profile.displayName : profile.name
docLink = profile.documentation ? yo`<a href="${profile.documentation}" class="${css.titleInfo}" title="link to documentation" target="_blank"><i aria-hidden="true" class="fas fa-book"></i></a>` : ''
if (profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)) {
versionWarning = yo`<small title="Version Alpha" class="badge-light ${css.versionBadge}">alpha</small>`
}
// Beta
if (profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)) {
versionWarning = yo`<small title="Version Beta" class="badge-light ${css.versionBadge}">beta</small>`
}
}
const header = yo`
<header class="${css.swapitHeader}">
<h6 class="${css.swapitTitle}" data-id="sidePanelSwapitTitle">${name}</h6>
${docLink}
${versionWarning}
</header>
`
yo.update(this.header, header)
}
render () {
return yo`
<section class="${css.panel} plugin-manager">
${this.header}
<div class="${css.pluginsContainer}">
${this.view}
</div>
</section>`
}
}

@ -0,0 +1,95 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import ReactDOM from 'react-dom'
import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { RemixAppManager } from '../../remixAppManager'
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import RemixUIPanelHeader from 'libs/remix-ui/panel/src/lib/plugins/panel-header'
// const csjs = require('csjs-inject')
const sidePanel = {
name: 'sidePanel',
displayName: 'Side Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
// TODO merge with vertical-icons.js
export class SidePanel extends AbstractPanel {
appManager: RemixAppManager
sideelement: any
verticalIcons: VerticalIcons;
constructor (appManager: RemixAppManager, verticalIcons: VerticalIcons) {
super(sidePanel)
this.appManager = appManager
this.sideelement = document.createElement('section')
this.sideelement.setAttribute('class', 'panel plugin-manager')
this.verticalIcons = verticalIcons
// Toggle content
verticalIcons.events.on('toggleContent', (name) => {
if (!this.plugins[name]) return
if (this.plugins[name].active) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggle', name)
this.events.emit('toggle', name)
return
}
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
// Force opening
verticalIcons.events.on('showContent', (name) => {
if (!this.plugins[name]) return
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
}
onActivation () {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
}
removeView (profile) {
super.removeView(profile)
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.verticalIcons.linkContent(profile)
this.renderComponent()
}
/**
* Display content and update the header
* @param {String} name The name of the plugin to display
*/
async showContent (name) {
super.showContent(name)
this.emit('focusChanged', name)
this.renderComponent()
}
render () {
return this.sideelement
}
renderComponent () {
ReactDOM.render(<RemixPluginPanel header={<RemixUIPanelHeader plugins={this.plugins}></RemixUIPanelHeader>} plugins={this.plugins}/>, this.sideelement)
}
}

@ -15,7 +15,7 @@ const profile = {
displayName: 'Vertical Icons', displayName: 'Vertical Icons',
description: '', description: '',
version: packageJson.version, version: packageJson.version,
methods: ['select'] methods: ['select', 'unlinkContent']
} }
// TODO merge with side-panel.js. VerticalIcons should not be a plugin // TODO merge with side-panel.js. VerticalIcons should not be a plugin

@ -0,0 +1,94 @@
import { Plugin } from '@remixproject/engine'
import { Profile } from '@remixproject/plugin-utils'
import { EventEmitter } from 'events'
import QueryParams from '../../lib/query-params'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize']
}
interface panelState {
active: boolean
plugin: Plugin
minimized: boolean
}
interface panels {
tabs: panelState
editor: panelState
main: panelState
terminal: panelState
}
export class Layout extends Plugin {
event: any
panels: panels
constructor () {
super(profile)
this.event = new EventEmitter()
}
async onActivation (): Promise<void> {
this.on('fileManager', 'currentFileChanged', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'openFile', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'switchApp', (name: string) => {
this.call('mainPanel', 'showContent', name)
this.panels.editor.active = false
this.panels.main.active = true
this.event.emit('change', null)
})
this.on('tabs', 'closeApp', (name: string) => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'tabCountChanged', async count => {
if (!count) await this.call('manager', 'activatePlugin', 'home')
})
this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) {
case 'filePanel':
this.call('menuicons', 'select', 'filePanel')
break
}
})
document.addEventListener('keypress', e => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') {
// Ctrl+Shift+F
this.call('menuicons', 'select', 'filePanel')
} else if (e.code === 'KeyA') {
// Ctrl+Shift+A
this.call('menuicons', 'select', 'pluginManager')
} else if (e.code === 'KeyS') {
// Ctrl+Shift+S
this.call('menuicons', 'select', 'settings')
}
e.preventDefault()
}
})
const queryParams = new QueryParams()
const params = queryParams.get()
if (params.minimizeterminal || params.embed) {
this.panels.terminal.minimized = true
this.event.emit('change', null)
}
if (params.minimizesidepanel || params.embed) {
this.event.emit('minimizesidepanel')
}
}
minimize (name: string, minimized:boolean): void {
this.panels[name].minimized = minimized
this.event.emit('change', null)
}
}

@ -1,204 +0,0 @@
import Registry from '../state/registry'
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
var { TabProxy } = require('./tab-proxy.js')
var csjs = require('csjs-inject')
var css = csjs`
.mainview {
display : flex;
flex-direction : column;
height : 100%;
width : 100%;
}
`
// @todo(#650) Extract this into two classes: MainPanel (TabsProxy + Iframe/Editor) & BottomPanel (Terminal)
export class MainView {
constructor (contextualListener, editor, mainPanel, fileManager, appManager, terminal) {
var self = this
self.event = new EventManager()
self._view = {}
self._components = {}
self._components.registry = Registry.getInstance()
self.contextualListener = contextualListener
self.editor = editor
self.fileManager = fileManager
self.mainPanel = mainPanel
self.txListener = Registry.getInstance().get('txlistener').api
self._components.terminal = terminal
this.appManager = appManager
this.init()
}
showApp (name) {
this.fileManager.unselectCurrentFile()
this.mainPanel.showContent(name)
this._view.editor.style.display = 'none'
this._view.mainPanel.style.display = 'block'
}
getAppPanel () {
return this.mainPanel
}
init () {
var self = this
self._deps = {
config: self._components.registry.get('config').api,
fileManager: self._components.registry.get('filemanager').api
}
self.tabProxy = new TabProxy(self.fileManager, self.editor)
/*
We listen here on event from the tab component to display / hide the editor and mainpanel
depending on the content that should be displayed
*/
self.fileManager.events.on('currentFileChanged', (file) => {
// we check upstream for "fileChanged"
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('openFile', (file) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('closeFile', (file) => {
})
self.tabProxy.event.on('switchApp', self.showApp.bind(self))
self.tabProxy.event.on('closeApp', (name) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('tabCountChanged', (count) => {
if (!count) this.editor.displayEmptyReadOnlySession()
})
self.data = {
_layout: {
top: {
offset: self._terminalTopOffset(),
show: true
}
}
}
self._components.terminal.event.register('resize', delta => self._adjustLayout('top', delta))
if (self.txListener) {
self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => {
self.txListener.setListenOnNetwork(listenOnNetWork)
})
}
}
_terminalTopOffset () {
return this._deps.config.get('terminal-top-offset') || 150
}
_adjustLayout (direction, delta) {
var limitUp = 0
var limitDown = 32
var containerHeight = window.innerHeight - limitUp // - menu bar containerHeight
var self = this
var layout = self.data._layout[direction]
if (layout) {
if (delta === undefined) {
layout.show = !layout.show
if (layout.show) delta = layout.offset
else delta = 0
} else {
layout.show = true
self._deps.config.set(`terminal-${direction}-offset`, delta)
layout.offset = delta
}
}
var tmp = delta - limitDown
delta = tmp > 0 ? tmp : 0
if (direction === 'top') {
var mainPanelHeight = containerHeight - delta
mainPanelHeight = mainPanelHeight < 0 ? 0 : mainPanelHeight
self._view.editor.style.height = `${mainPanelHeight}px`
self._view.mainPanel.style.height = `${mainPanelHeight}px`
self._view.terminal.style.height = `${delta}px` // - menu bar height
self.editor.resize((document.querySelector('#editorWrap') || {}).checked)
self._components.terminal.scroll2bottom()
}
}
minimizeTerminal () {
this._adjustLayout('top')
}
showTerminal (offset) {
this._adjustLayout('top', offset || this._terminalTopOffset())
}
getTerminal () {
return this._components.terminal
}
getEditor () {
var self = this
return self.editor
}
refresh () {
var self = this
self._view.tabs.onmouseenter()
}
log (data = {}) {
var self = this
var command = self._components.terminal.commands[data.type]
if (typeof command === 'function') command(data.value)
}
logMessage (msg) {
var self = this
self.log({ type: 'log', value: msg })
}
logHtmlMessage (msg) {
var self = this
self.log({ type: 'html', value: msg })
}
render () {
var self = this
if (self._view.mainview) return self._view.mainview
self._view.editor = self.editor.render()
self._view.editor.style.display = 'none'
self._view.mainPanel = self.mainPanel.render()
self._view.terminal = self._components.terminal.render()
self._view.mainview = yo`
<div class=${css.mainview}>
${self.tabProxy.renderTabsbar()}
${self._view.editor}
${self._view.mainPanel}
<div class="${css.contextview} contextview"></div>
${self._view.terminal}
</div>
`
// INIT
self._adjustLayout('top', self.data._layout.top.offset)
document.addEventListener('keydown', (e) => {
if (e.altKey && e.keyCode === 84) self.tabProxy.switchNextTab() // alt + t
})
return self._view.mainview
}
registerCommand (name, command, opts) {
var self = this
return self._components.terminal.registerCommand(name, command, opts)
}
updateTerminalFilter (filter) {
this._components.terminal.updateJournal(filter)
}
}

@ -22,6 +22,7 @@ export class TabProxy extends Plugin {
this._view = {} this._view = {}
this._handlers = {} this._handlers = {}
this.loadedTabs = [] this.loadedTabs = []
this.el = document.createElement('div')
} }
onActivation () { onActivation () {
@ -72,10 +73,12 @@ export class TabProxy extends Plugin {
this.addTab(workspacePath, '', () => { this.addTab(workspacePath, '', () => {
this.fileManager.open(file) this.fileManager.open(file)
this.event.emit('openFile', file) this.event.emit('openFile', file)
this.emit('openFile', file)
}, },
() => { () => {
this.fileManager.closeFile(file) this.fileManager.closeFile(file)
this.event.emit('closeFile', file) this.event.emit('closeFile', file)
this.emit('closeFile', file)
}) })
this.tabsApi.activateTab(workspacePath) this.tabsApi.activateTab(workspacePath)
} else { } else {
@ -88,10 +91,12 @@ export class TabProxy extends Plugin {
this.addTab(path, '', () => { this.addTab(path, '', () => {
this.fileManager.open(file) this.fileManager.open(file)
this.event.emit('openFile', file) this.event.emit('openFile', file)
this.emit('openFile', file)
}, },
() => { () => {
this.fileManager.closeFile(file) this.fileManager.closeFile(file)
this.event.emit('closeFile', file) this.event.emit('closeFile', file)
this.emit('closeFile', file)
}) })
this.tabsApi.activateTab(path) this.tabsApi.activateTab(path)
} }
@ -132,9 +137,9 @@ export class TabProxy extends Plugin {
this.addTab( this.addTab(
name, name,
displayName, displayName,
() => this.event.emit('switchApp', name), () => this.emit('switchApp', name),
() => { () => {
this.event.emit('closeApp', name) this.emit('closeApp', name)
this.call('manager', 'deactivatePlugin', name) this.call('manager', 'deactivatePlugin', name)
}, },
icon icon
@ -149,7 +154,7 @@ export class TabProxy extends Plugin {
} }
focus (name) { focus (name) {
this.event.emit('switchApp', name) this.emit('switchApp', name)
this.tabsApi.activateTab(name) this.tabsApi.activateTab(name)
} }
@ -199,6 +204,7 @@ export class TabProxy extends Plugin {
() => { () => {
this.fileManager.closeFile(newName) this.fileManager.closeFile(newName)
this.event.emit('closeFile', newName) this.event.emit('closeFile', newName)
this.emit('closeFile', newName)
}) })
this.removeTab(oldName) this.removeTab(oldName)
} }
@ -285,7 +291,7 @@ export class TabProxy extends Plugin {
if (this.loadedTabs[index]) { if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].switchTo() if (this._handlers[name]) this._handlers[name].switchTo()
this.event.emit('tabCountChanged', this.loadedTabs.length) this.emit('tabCountChanged', this.loadedTabs.length)
} }
} }
@ -293,7 +299,7 @@ export class TabProxy extends Plugin {
if (this.loadedTabs[index]) { if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].close() if (this._handlers[name]) this._handlers[name].close()
this.event.emit('tabCountChanged', this.loadedTabs.length) this.emit('tabCountChanged', this.loadedTabs.length)
} }
} }
@ -308,8 +314,6 @@ export class TabProxy extends Plugin {
} }
renderTabsbar () { renderTabsbar () {
this.el = document.createElement('div')
this.renderComponent()
return this.el return this.el
} }
} }

@ -20,7 +20,7 @@ function register (api) { KONSOLES.push(api) }
const profile = { const profile = {
displayName: 'Terminal', displayName: 'Terminal',
name: 'terminal', name: 'terminal',
methods: ['log'], methods: ['log', 'logHtml'],
events: [], events: [],
description: ' - ', description: ' - ',
version: packageJson.version version: packageJson.version

@ -34,14 +34,14 @@ const profile = {
} }
export class RunTab extends ViewPlugin { export class RunTab extends ViewPlugin {
constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, mainView, fileProvider) { constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, fileProvider) {
super(profile) super(profile)
this.event = new EventManager() this.event = new EventManager()
this.config = config this.config = config
this.blockchain = blockchain this.blockchain = blockchain
this.fileManager = fileManager this.fileManager = fileManager
this.editor = editor this.editor = editor
this.logCallback = (msg) => { mainView.getTerminal().logHtml(yo`<pre>${msg}</pre>`) } this.logCallback = (msg) => { this.call('terminal', 'logHtml', yo`<pre>${msg}</pre>`) }
this.filePanel = filePanel this.filePanel = filePanel
this.compilersArtefacts = compilersArtefacts this.compilersArtefacts = compilersArtefacts
this.networkModule = networkModule this.networkModule = networkModule

@ -1,34 +0,0 @@
export class FramingService {
constructor (sidePanel, verticalIcons, mainView, resizeFeature) {
this.sidePanel = sidePanel
this.verticalIcons = verticalIcons
this.mainPanel = mainView.getAppPanel()
this.mainView = mainView
this.resizeFeature = resizeFeature
}
start (params) {
this.verticalIcons.select('filePanel')
document.addEventListener('keypress', (e) => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') { // Ctrl+Shift+F
this.verticalIcons.select('filePanel')
} else if (e.code === 'KeyA') { // Ctrl+Shift+A
this.verticalIcons.select('pluginManager')
} else if (e.code === 'KeyS') { // Ctrl+Shift+S
this.verticalIcons.select('settings')
}
e.preventDefault()
}
})
if (params.minimizeterminal) this.mainView.minimizeTerminal()
if (params.minimizesidepanel) this.resizeFeature.hidePanel()
}
embed () {
this.mainView.minimizeTerminal()
this.resizeFeature.hidePanel()
}
}

@ -1,88 +0,0 @@
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const css = csjs`
.dragbar {
width : 2px;
height : 100%;
cursor : col-resize;
z-index : 999;
}
.ghostbar {
width : 3px;
background-color : var(--primary);
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
`
export default class PanelsResize {
constructor (panel) {
this.panel = panel
const string = panel.style.minWidth
this.minWidth = string.length > 2 ? parseInt(string.substring(0, string.length - 2)) : 0
}
render () {
this.ghostbar = yo`<div class=${css.ghostbar}></div>`
const mousedown = (event) => {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(this.ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
const cancelGhostbar = (event) => {
if (event.keyCode === 27) {
document.body.removeChild(this.ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
const moveGhostbar = (event) => {
this.ghostbar.style.left = event.x + 'px'
}
const removeGhostbar = (event) => {
document.body.removeChild(this.ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
this.setPosition(event)
}
return yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>`
}
calculatePanelWidth (event) {
return event.x - this.panel.offsetLeft
}
setPosition (event) {
const panelWidth = this.calculatePanelWidth(event)
// close the panel if the width is less than a minWidth
if (panelWidth > this.minWidth - 10 || this.panel.style.display === 'none') {
this.panel.style.width = panelWidth + 'px'
this.showPanel()
} else this.hidePanel()
}
hidePanel () {
this.panel.style.display = 'none'
}
showPanel () {
this.panel.style.display = 'flex'
}
}

@ -1,20 +1,20 @@
/* global localStorage, fetch */ /* global localStorage, fetch */
import { PluginManager } from '@remixproject/engine' import { PluginManager } from '@remixproject/engine'
import { IframePlugin } from '@remixproject/engine-web'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import QueryParams from './lib/query-params' import QueryParams from './lib/query-params'
import { PermissionHandler } from './app/ui/persmission-handler' import { PermissionHandler } from './app/ui/persmission-handler'
import { IframePlugin } from '@remixproject/engine-web'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler'] 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'modal']
const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd) const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) { export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting'] const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'modal']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }
@ -78,6 +78,7 @@ export class RemixAppManager extends PluginManager {
onPluginActivated (plugin) { onPluginActivated (plugin) {
this.pluginLoader.set(plugin, this.actives) this.pluginLoader.set(plugin, this.actives)
this.event.emit('activate', plugin) this.event.emit('activate', plugin)
this.emit('activate', plugin)
if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name]) if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name])
} }
@ -131,6 +132,7 @@ export class RemixAppManager extends PluginManager {
} }
return plugins.map(plugin => { return plugins.map(plugin => {
return new IframePlugin(plugin) return new IframePlugin(plugin)
// return new IframeReactPlugin(plugin)
}) })
} }

@ -4,6 +4,7 @@
"jsx": "react", "jsx": "react",
"allowJs": true, "allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"types": ["node", "jest"], "types": ["node", "jest"],
"module": "es6", "module": "es6",

@ -1,26 +1,27 @@
/* dragbar UI */ /* dragbar UI */
.dragbar { .dragbar {
display : block; display: block;
height : 100%; height: 100%;
position : absolute; position: absolute;
left: 0px; left: 0px;
top: 0px; top: 0px;
width: 0.3em; width: 0.3em;
z-index: 9999; z-index: 9999;
} }
.overlay { .overlay {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: block; display: block;
z-index: 9998; z-index: 9998;
} }
.dragbar:hover, .dragbar.ondrag{ .dragbar:hover,
background-color: var(--secondary); .dragbar.ondrag {
cursor:col-resize; background-color: var(--secondary);
} cursor: col-resize;
}

@ -15,18 +15,23 @@ const DragBar = (props: IRemixDragBarUi) => {
const [offset, setOffSet] = useState<number>(0) const [offset, setOffSet] = useState<number>(0)
const nodeRef = React.useRef(null) // fix for strictmode const nodeRef = React.useRef(null) // fix for strictmode
useEffect(() => {
// arbitrary time out to wait the the UI to be completely done
setTimeout(() => {
setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(offset + props.refObject.current.offsetWidth)
}, 1000)
}, [])
useEffect(() => { useEffect(() => {
setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth)) setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth))
}, [props.hidden, offset]) }, [props.hidden, offset])
const handleResize = () => {
setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth)
}
useEffect(() => {
window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [])
function stopDrag (e: MouseEvent, data: any) { function stopDrag (e: MouseEvent, data: any) {
setDragState(false) setDragState(false)
if (data.x < props.minWidth) { if (data.x < props.minWidth) {

@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import './style/remix-app.css' import './style/remix-app.css'
import { RemixUIMainPanel } from '@remix-ui/panel'
import RemixSplashScreen from './components/splashscreen' import RemixSplashScreen from './components/splashscreen'
import MatomoDialog from './components/modals/matomo' import MatomoDialog from './components/modals/matomo'
import OriginWarning from './components/modals/origin-warning' import OriginWarning from './components/modals/origin-warning'
@ -62,6 +63,13 @@ const RemixApp = (props: IRemixAppUi) => {
props.app.sidePanel.events.on('showing', () => { props.app.sidePanel.events.on('showing', () => {
setHideSidePanel(false) setHideSidePanel(false)
}) })
props.app.layout.event.on('minimizesidepanel', () => {
// the 'showing' event always fires from sidepanel, so delay this a bit
setTimeout(() => {
setHideSidePanel(true)
}, 1000)
})
} }
const components = { const components = {
@ -75,7 +83,8 @@ const RemixApp = (props: IRemixAppUi) => {
settings: props.app.settings, settings: props.app.settings,
showMatamo: props.app.showMatamo, showMatamo: props.app.showMatamo,
appManager: props.app.appManager, appManager: props.app.appManager,
modal: props.app.modal modal: props.app.modal,
layout: props.app.layout
} }
return ( return (
@ -88,8 +97,9 @@ const RemixApp = (props: IRemixAppUi) => {
{components.iconPanel} {components.iconPanel}
{components.sidePanel} {components.sidePanel}
<DragBar minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar> <DragBar minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
{components.mainPanel} <div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel'>
<RemixUIMainPanel></RemixUIMainPanel>
</div>
</div> </div>
{components.hiddenPanel} {components.hiddenPanel}
<AppDialogs></AppDialogs> <AppDialogs></AppDialogs>

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

@ -0,0 +1,7 @@
# remix-ui-side-panel
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-side-panel` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1,2 @@
export { default as RemixPluginPanel } from './lib/plugins/remix-ui-panel'
export { default as RemixUIMainPanel } from './lib/main/main-panel'

@ -0,0 +1,27 @@
/* dragbar UI */
.dragbar_terminal {
display: block;
width: 100%;
position: absolute;
left: 0px;
top: 0px;
height: 0.3em;
z-index: 9999;
}
.overlay {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
display: block;
z-index: 900;
}
.dragbar_terminal:hover,
.dragbar_terminal.ondrag {
background-color: var(--secondary);
cursor: row-resize;
}

@ -0,0 +1,51 @@
// eslint-disable-next-line no-use-before-define
import React, { useEffect, useState } from 'react'
import Draggable from 'react-draggable'
import './dragbar.css'
interface IRemixDragBarUi {
refObject: React.MutableRefObject<any>;
setHideStatus: (hide: boolean) => void;
hidden: boolean
minHeight?: number
}
const DragBar = (props: IRemixDragBarUi) => {
const [dragState, setDragState] = useState<boolean>(false)
const [dragBarPosY, setDragBarPosY] = useState<number>(0)
const nodeRef = React.useRef(null) // fix for strictmode
function stopDrag (e: MouseEvent, data: any) {
const h = window.innerHeight - data.y
props.refObject.current.setAttribute('style', `height: ${h}px;`)
setDragBarPosY(window.innerHeight - props.refObject.current.offsetHeight)
setDragState(false)
}
const handleResize = () => {
setDragBarPosY(window.innerHeight - props.refObject.current.offsetHeight)
}
useEffect(() => {
handleResize()
}, [props.hidden])
useEffect(() => {
window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [])
function startDrag () {
setDragState(true)
}
return <>
<div className={`overlay ${dragState ? '' : 'd-none'}`} ></div>
<Draggable nodeRef={nodeRef} position={{ x: 0, y: dragBarPosY }} onStart={startDrag} onStop={stopDrag} axis="y">
<div ref={nodeRef} className={`dragbar_terminal ${dragState ? 'ondrag' : ''}`}></div>
</Draggable>
</>
}
export default DragBar

@ -0,0 +1,8 @@
.mainview {
display : flex;
flex-direction : column;
height : 100%;
width : 100%;
position: relative;
}

@ -0,0 +1,60 @@
/* eslint-disable no-unused-expressions */
import { AppContext } from 'libs/remix-ui/app/src/lib/remix-app/context/context'
import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react' // eslint-disable-line
import DragBar from '../dragbar/dragbar'
import RemixUIPanelPlugin from '../plugins/panel-plugin'
import { PluginRecord } from '../types'
import './main-panel.css'
const RemixUIMainPanel = () => {
const appContext = useContext(AppContext)
const [plugins, setPlugins] = useState<PluginRecord[]>([])
const editorRef = useRef<HTMLDivElement>(null)
const mainPanelRef = useRef<HTMLDivElement>(null)
const tabsRef = useRef<HTMLDivElement>(null)
const terminalRef = useRef<HTMLDivElement>(null)
const refs = [tabsRef, editorRef, mainPanelRef, terminalRef]
const renderPanels = () => {
if (appContext) {
const pluginPanels: PluginRecord[] = []
Object.values(appContext.layout.panels).map((panel: any) => {
pluginPanels.push({
profile: panel.plugin.profile,
active: panel.active,
view: panel.plugin.profile.name === 'tabs' ? panel.plugin.renderTabsbar() : panel.plugin.render(),
class: panel.plugin.profile.name + '-wrap ' + (panel.minimized ? 'minimized' : ''),
minimized: panel.minimized
})
})
setPlugins(pluginPanels)
}
}
useEffect(() => {
renderPanels()
appContext.layout.event.on('change', () => {
renderPanels()
})
}, [])
return (
<div className="mainview">
{Object.values(plugins).map((pluginRecord, i) => {
return (
<React.Fragment key={`mainView${i}`}>
{(pluginRecord.profile.name === 'terminal') ? <DragBar key='dragbar-terminal' hidden={pluginRecord.minimized || false} setHideStatus={() => {}} refObject={terminalRef}></DragBar> : null}
<RemixUIPanelPlugin
ref={refs[i]}
key={pluginRecord.profile.name}
pluginRecord={pluginRecord}
/>
</React.Fragment>
)
})}
</div>
)
}
export default RemixUIMainPanel

@ -0,0 +1,27 @@
/* eslint-disable jsx-a11y/anchor-has-content */
import React, { useEffect, useRef, useState } from 'react' // eslint-disable-line
import { PluginRecord } from '../types'
import './panel.css'
export interface RemixPanelProps {
plugins: Record<string, PluginRecord>;
}
const RemixUIPanelHeader = (props: RemixPanelProps) => {
const [plugin, setPlugin] = useState<PluginRecord>()
useEffect(() => {
if (props.plugins) {
const p = Object.values(props.plugins).find((pluginRecord) => {
return pluginRecord.active === true
})
setPlugin(p)
}
}, [props])
return (
<header className='swapitHeader'><h6 data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
{plugin?.profile.documentation ? (<a href={plugin.profile.documentation} className="titleInfo" title="link to documentation" target="_blank" rel="noreferrer"><i aria-hidden="true" className="fas fa-book"></i></a>) : ''}
</header>)
}
export default RemixUIPanelHeader

@ -0,0 +1,37 @@
/* eslint-disable no-undef */
import React, { forwardRef, useEffect, useRef, useState } from 'react' // eslint-disable-line
import { PluginRecord } from '../types'
import './panel.css'
interface panelPLuginProps {
pluginRecord: PluginRecord
}
const RemixUIPanelPlugin = (props: panelPLuginProps, panelRef: any) => {
const localRef = useRef<HTMLDivElement>(null)
const [view, setView] = useState<JSX.Element | HTMLDivElement>()
useEffect(() => {
const ref:any = panelRef || localRef
if (ref.current) {
if (props.pluginRecord.view) {
if (React.isValidElement(props.pluginRecord.view)) {
setView(props.pluginRecord.view)
} else {
ref.current.appendChild(props.pluginRecord.view)
}
}
}
}, [])
return (
<div
className={
props.pluginRecord.active ? `${props.pluginRecord.class}` : 'd-none'
}
ref={panelRef || localRef}
>
{view}
</div>
)
}
export default forwardRef(RemixUIPanelPlugin)

@ -0,0 +1,110 @@
.panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
flex: auto;
}
.swapitTitle {
margin: 0;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.swapitTitle i {
padding-left: 6px;
font-size: 14px;
}
.swapitHeader {
display: flex;
align-items: center;
padding: 16px 24px 15px;
justify-content: space-between;
text-transform: uppercase;
}
.icons i {
height: 80%;
cursor: pointer;
}
.pluginsContainer {
height: 100%;
overflow-y: auto;
}
.titleInfo {
padding-left: 10px;
}
.versionBadge {
background-color: var(--light);
padding: 0 7px;
font-weight: bolder;
margin-left: 5px;
text-transform: lowercase;
cursor: default;
}
iframe {
height: 100%;
width: 100%;
border: 0;
}
.plugins {
height: 100%;
}
.plugItIn {
display: none;
height: 100%;
}
.plugItIn>div {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
width: 100%;
}
.plugItIn.active {
display: block;
}
.pluginsContainer {
height: 100%;
overflow-y: hidden;
}
#editorView {
height: 100%;
width: 100%;
border: 0;
display: block;
}
#mainPanel {
height: 100%;
width: 100%;
border: 0;
display: block;
}
.mainPanel-wrap, .editor-wrap {
flex: 1;
min-height: 100px;
}
.terminal-wrap {
min-height: 35px;
height: 20%;
}
.terminal-wrap.minimized {
height: 2rem !important;
}

@ -0,0 +1,29 @@
/* eslint-disable no-undef */
import React, { useEffect, useState } from 'react' // eslint-disable-line
import './panel.css'
import RemixUIPanelPlugin from './panel-plugin'
import { PluginRecord } from '../types'
/* eslint-disable-next-line */
export interface RemixPanelProps {
plugins: Record<string, PluginRecord>
header: JSX.Element
}
export function RemixPluginPanel (props: RemixPanelProps) {
return (
<>
{props.header}
<div className="pluginsContainer">
<div className='plugins' id='plugins'>
{Object.values(props.plugins).map((pluginRecord) => {
return <RemixUIPanelPlugin key={pluginRecord.profile.name} pluginRecord={pluginRecord} />
})}
</div>
</div>
</>
)
}
export default RemixPluginPanel

@ -0,0 +1,9 @@
import { Profile } from '@remixproject/plugin-utils'
export type PluginRecord = {
profile: Profile
view: any
active: boolean
class?: string
minimized?: boolean
}

@ -0,0 +1,20 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"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"]
}

@ -4,6 +4,7 @@ import { EventEmitter } from 'events'
import { Engine } from '@remixproject/engine/lib/engine' import { Engine } from '@remixproject/engine/lib/engine'
import { PluginBase, Profile } from '@remixproject/plugin-utils' import { PluginBase, Profile } from '@remixproject/plugin-utils'
import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web' import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web'
import { IframeReactPlugin } from '@remix-ui/app'
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface SetPluginOptionType { interface SetPluginOptionType {
@ -88,7 +89,7 @@ export class PluginManagerComponent extends ViewPlugin extends Plugin implements
render(): HTMLDivElement render(): HTMLDivElement
getAndFilterPlugins: (filter?: string, profiles?: Profile[]) => void getAndFilterPlugins: (filter?: string, profiles?: Profile[]) => void
triggerEngineEventListener: () => void triggerEngineEventListener: () => void
activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | WebsocketPlugin) => Promise<void> activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | IframeReactPlugin | WebsocketPlugin) => Promise<void>
activeProfiles: string[] activeProfiles: string[]
_paq: any _paq: any
} }

@ -1,4 +1,4 @@
{ {
"presets": ["@nrwl/react/babel"], "presets": ["@nrwl/react/babel"],
"plugins": [] "plugins": []
} }

@ -106,8 +106,8 @@ export const registerErrorScriptRunnerAction = (on, commandName, commandFn, disp
}) })
} }
export const listenOnNetworkAction = async (event, isListening) => { export const listenOnNetworkAction = async (plugins, isListening) => {
event.trigger('listenOnNetWork', [isListening]) plugins.txListener.setListenOnNetwork(isListening)
} }
export const initListeningOnNetwork = (plugins, dispatch: React.Dispatch<any>) => { export const initListeningOnNetwork = (plugins, dispatch: React.Dispatch<any>) => {

@ -1,77 +0,0 @@
import React, { useEffect, useState } from 'react'
export const useDragTerminal = (minHeight: number, defaultPosition: number) => {
const [isOpen, setIsOpen] = useState(defaultPosition > minHeight)
const [lastYPosition, setLastYPosition] = useState(0)
const [terminalPosition, setTerminalPosition] = useState(defaultPosition)
// Used to save position of the terminal when it is closed
const [lastTerminalPosition, setLastTerminalPosition] = useState(defaultPosition)
const [isDragging, setIsDragging] = useState(false)
const handleDraggingStart = (event: React.MouseEvent) => {
setLastYPosition(event.clientY)
setIsDragging(true)
}
const handleDragging = (event: MouseEvent) => {
event.preventDefault()
if (isDragging) {
const mouseYPosition = event.clientY
const difference = lastYPosition - mouseYPosition
const newTerminalPosition = terminalPosition + difference
setTerminalPosition(newTerminalPosition)
setLastYPosition(mouseYPosition)
}
}
const handleDraggingEnd = () => {
if (!isDragging) return
setIsDragging(false)
// Check terminal position to determine if it should be open or closed
setIsOpen(terminalPosition > minHeight)
}
const handleToggleTerminal = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault()
event.stopPropagation()
if (isOpen) {
setLastTerminalPosition(terminalPosition)
setLastYPosition(0)
setTerminalPosition(minHeight)
} else {
setTerminalPosition(lastTerminalPosition <= minHeight ? 323 : lastTerminalPosition)
}
setIsOpen(!isOpen)
}
// Add event listeners for dragging
useEffect(() => {
document.addEventListener('mousemove', handleDragging)
document.addEventListener('mouseup', handleDraggingEnd)
return () => {
document.removeEventListener('mousemove', handleDragging)
document.removeEventListener('mouseup', handleDraggingEnd)
}
}, [handleDragging, handleDraggingEnd])
// Reset terminal position
useEffect(() => {
if (!terminalPosition) {
setTerminalPosition(defaultPosition)
}
}, [terminalPosition, setTerminalPosition])
return {
isOpen,
terminalPosition,
isDragging,
handleDraggingStart,
handleToggleTerminal
}
}

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
import { registerCommandAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, listenOnNetworkAction, initListeningOnNetwork } from './actions/terminalAction' import { registerCommandAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, listenOnNetworkAction, initListeningOnNetwork } from './actions/terminalAction'
import { initialState, registerCommandReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer' import { initialState, registerCommandReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer'
@ -17,7 +18,6 @@ import RenderKnownTransactions from './components/RenderKnownTransactions' // es
import parse from 'html-react-parser' import parse from 'html-react-parser'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes' import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import { wrapScript } from './utils/wrapScript' import { wrapScript } from './utils/wrapScript'
import { useDragTerminal } from './custom-hooks/useDragTerminal'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
@ -28,7 +28,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const { call, _deps, on, config, event, version } = props.plugin const { call, _deps, on, config, event, version } = props.plugin
const [_cmdIndex, setCmdIndex] = useState(-1) const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('') const [_cmdTemp, setCmdTemp] = useState('')
const [isOpen, setIsOpen] = useState<boolean>(true)
const [newstate, dispatch] = useReducer(registerCommandReducer, initialState) const [newstate, dispatch] = useReducer(registerCommandReducer, initialState)
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState)
const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState)
@ -79,24 +79,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const terminalMenuOffsetHeight = (terminalMenu.current && terminalMenu.current.offsetHeight) || 35 const terminalMenuOffsetHeight = (terminalMenu.current && terminalMenu.current.offsetHeight) || 35
const terminalDefaultPosition = config.get('terminal-top-offset') const terminalDefaultPosition = config.get('terminal-top-offset')
const {
isOpen,
isDragging,
terminalPosition,
handleDraggingStart,
handleToggleTerminal
} = useDragTerminal(terminalMenuOffsetHeight, terminalDefaultPosition)
// Check open state
useEffect(() => {
const resizeValue = isOpen ? [config.get('terminal-top-offset')] : []
event.trigger('resize', resizeValue)
}, [isOpen])
useEffect(() => {
event.trigger('resize', [terminalPosition])
}, [terminalPosition])
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
} }
@ -106,6 +88,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
logHtml: (html) => { logHtml: (html) => {
scriptRunnerDispatch({ type: 'html', payload: { message: [html.innerText] } }) scriptRunnerDispatch({ type: 'html', payload: { message: [html.innerText] } })
}, },
log: (message) => { log: (message) => {
scriptRunnerDispatch({ type: 'log', payload: { message: [message] } }) scriptRunnerDispatch({ type: 'log', payload: { message: [message] } })
} }
@ -332,8 +315,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const listenOnNetwork = (e: any) => { const listenOnNetwork = (e: any) => {
const isListening = e.target.checked const isListening = e.target.checked
// setIsListeningOnNetwork(isListening) listenOnNetworkAction(props.plugin, isListening)
listenOnNetworkAction(event, isListening)
} }
const onChange = (event: any) => { const onChange = (event: any) => {
@ -426,10 +408,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false })) setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false }))
} }
const handleToggleTerminal = () => {
setIsOpen(!isOpen)
props.plugin.call('layout', 'minimize', props.plugin.profile.name, isOpen)
}
return ( return (
<div style={{ height: '323px', flexGrow: 1 }} className='panel' ref={panelRef}> <div style={{ flexGrow: 1 }} className='panel' ref={panelRef}>
<div className="bar"> <div className="bar">
<div className={`dragbarHorizontal ${isDragging ? 'dragbarDragging' : ''}`} onMouseDown={handleDraggingStart} ref={leftRef}></div>
<div className="menu border-top border-dark bg-light" ref={terminalMenu} data-id="terminalToggleMenu"> <div className="menu border-top border-dark bg-light" ref={terminalMenu} data-id="terminalToggleMenu">
<i className={`mx-2 toggleTerminal fas ${isOpen ? 'fa-angle-double-down' : 'fa-angle-double-up'}`} data-id="terminalToggleIcon" onClick={handleToggleTerminal}></i> <i className={`mx-2 toggleTerminal fas ${isOpen ? 'fa-angle-double-down' : 'fa-angle-double-up'}`} data-id="terminalToggleIcon" onClick={handleToggleTerminal}></i>
<div className="mx-2 console" id="clearConsole" data-id="terminalClearConsole" onClick={handleClearConsole} > <div className="mx-2 console" id="clearConsole" data-id="terminalClearConsole" onClick={handleClearConsole} >

@ -145,6 +145,9 @@
"remix-ui-tabs": { "remix-ui-tabs": {
"tags": [] "tags": []
}, },
"remix-ui-panel": {
"tags": []
},
"remix-ui-theme-module": { "remix-ui-theme-module": {
"tags": [] "tags": []
}, },

@ -45,7 +45,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-helper,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,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs", "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-helper,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,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel",
"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",
@ -162,7 +162,7 @@
"chokidar": "^2.1.8", "chokidar": "^2.1.8",
"color-support": "^1.1.3", "color-support": "^1.1.3",
"commander": "^2.20.3", "commander": "^2.20.3",
"core-js": "^3.19.3", "core-js": "^3.6.5",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"document-register-element": "1.13.1", "document-register-element": "1.13.1",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",

@ -69,8 +69,11 @@
"@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"], "@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"],
"@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"], "@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"],
"@remix-ui/app": ["libs/remix-ui/app/src/index.ts"], "@remix-ui/app": ["libs/remix-ui/app/src/index.ts"],
"@remix-ui/vertical-icons-panel": ["libs/remix-ui/vertical-icons-panel/src/index.ts"], "@remix-ui/vertical-icons-panel": [
"libs/remix-ui/vertical-icons-panel/src/index.ts"
],
"@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"], "@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"],
"@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"],
"@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"], "@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"],
"@remix-ui/solidity-unit-testing": [ "@remix-ui/solidity-unit-testing": [
"libs/remix-ui/solidity-unit-testing/src/index.ts" "libs/remix-ui/solidity-unit-testing/src/index.ts"

@ -1107,6 +1107,21 @@
} }
} }
}, },
"remix-ui-panel": {
"root": "libs/remix-ui/panel",
"sourceRoot": "libs/remix-ui/panel/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/panel/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/panel/**/*"]
}
}
}
},
"solidity-unit-testing": { "solidity-unit-testing": {
"root": "libs/remix-ui/solidity-unit-testing", "root": "libs/remix-ui/solidity-unit-testing",
"sourceRoot": "libs/remix-ui/solidity-unit-testing/src", "sourceRoot": "libs/remix-ui/solidity-unit-testing/src",

Loading…
Cancel
Save