commit
73effb57cd
@ -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) |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
} |
@ -0,0 +1,44 @@ |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils' |
||||
import { AppModal } from '@remix-ui/app' |
||||
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface' |
||||
import { dispatchModalInterface } from 'libs/remix-ui/app/src/lib/remix-app/context/context' |
||||
|
||||
interface IModalApi { |
||||
events: StatusEvents, |
||||
methods: { |
||||
modal: (args: AppModal) => void |
||||
alert: (args: AlertModal) => void |
||||
toast: (message: string) => void |
||||
} |
||||
} |
||||
|
||||
const profile:LibraryProfile<IModalApi> = { |
||||
name: 'modal', |
||||
displayName: 'Modal', |
||||
description: 'Modal', |
||||
methods: ['modal', 'alert', 'toast'] |
||||
} |
||||
|
||||
export class ModalPlugin extends Plugin implements MethodApi<IModalApi> { |
||||
dispatcher: dispatchModalInterface |
||||
constructor () { |
||||
super(profile) |
||||
} |
||||
|
||||
setDispatcher (dispatcher: dispatchModalInterface) { |
||||
this.dispatcher = dispatcher |
||||
} |
||||
|
||||
async modal (args: AppModal) { |
||||
this.dispatcher.modal(args) |
||||
} |
||||
|
||||
async alert (args: AlertModal) { |
||||
this.dispatcher.alert(args) |
||||
} |
||||
|
||||
async toast (message: string) { |
||||
this.dispatcher.toast(message) |
||||
} |
||||
} |
@ -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,74 +0,0 @@ |
||||
'use strict' |
||||
var modalDialogCustom = require('../app/ui/modal-dialog-custom') |
||||
var request = require('request') |
||||
|
||||
// Allowing window to be overriden for testing
|
||||
function GistHandler (_window) { |
||||
if (_window !== undefined) { |
||||
modalDialogCustom = _window |
||||
} |
||||
|
||||
this.handleLoad = function (params, cb) { |
||||
if (!cb) cb = () => {} |
||||
var loadingFromGist = false |
||||
var gistId |
||||
if (params.gist === '') { |
||||
loadingFromGist = true |
||||
modalDialogCustom.prompt('Load a Gist', 'Enter the ID of the Gist or URL you would like to load.', null, (target) => { |
||||
if (target !== '') { |
||||
gistId = getGistId(target) |
||||
if (gistId) { |
||||
cb(gistId) |
||||
} else { |
||||
modalDialogCustom.alert('Gist load error', 'Error while loading gist. Please provide a valid Gist ID or URL.') |
||||
} |
||||
} |
||||
}) |
||||
return loadingFromGist |
||||
} else { |
||||
gistId = params.gist |
||||
loadingFromGist = !!gistId |
||||
} |
||||
if (loadingFromGist) { |
||||
cb(gistId) |
||||
} |
||||
return loadingFromGist |
||||
} |
||||
|
||||
function getGistId (str) { |
||||
var idr = /[0-9A-Fa-f]{8,}/ |
||||
var match = idr.exec(str) |
||||
return match ? match[0] : null |
||||
} |
||||
|
||||
this.loadFromGist = (params, fileManager) => { |
||||
const self = this |
||||
return self.handleLoad(params, function (gistId) { |
||||
request.get({ |
||||
url: `https://api.github.com/gists/${gistId}`, |
||||
json: true |
||||
}, async (error, response, data = {}) => { |
||||
if (error || !data.files) { |
||||
modalDialogCustom.alert('Gist load error', error || data.message) |
||||
return |
||||
} |
||||
const obj = {} |
||||
Object.keys(data.files).forEach((element) => { |
||||
const path = element.replace(/\.\.\./g, '/') |
||||
|
||||
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] |
||||
}) |
||||
fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { |
||||
if (!errorLoadingFile) { |
||||
const provider = fileManager.getProvider('workspace') |
||||
provider.lastLoadedGistId = gistId |
||||
} else { |
||||
modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile) |
||||
} |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
module.exports = GistHandler |
@ -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,16 +0,0 @@ |
||||
'use strict' |
||||
|
||||
var test = require('tape') |
||||
|
||||
var Compiler = require('@remix-project/remix-solidity').Compiler |
||||
|
||||
test('compiler.compile smoke', function (t) { |
||||
t.plan(1) |
||||
|
||||
var noop = function () {} |
||||
var fakeImport = function (url, cb) { cb('Not implemented') } |
||||
var compiler = new Compiler(fakeImport) |
||||
compiler.compileJSON = noop |
||||
compiler.compile({ 'test': '' }, 'test') |
||||
t.ok(compiler) |
||||
}) |
@ -1,52 +0,0 @@ |
||||
'use strict' |
||||
var modalDialogCustom |
||||
if (typeof window !== 'undefined') { |
||||
modalDialogCustom = require('../app/ui/modal-dialog-custom') |
||||
} |
||||
// ^ this class can be load in a non browser context when running node unit testing.
|
||||
// should not load UI in that case
|
||||
|
||||
// Allowing window to be overriden for testing
|
||||
function GistHandler (_window) { |
||||
if (_window !== undefined) { |
||||
modalDialogCustom = _window |
||||
} |
||||
|
||||
this.handleLoad = function (params, cb) { |
||||
if (!cb) cb = () => {} |
||||
var loadingFromGist = false |
||||
var gistId |
||||
if (params['gist'] === '') { |
||||
loadingFromGist = true |
||||
modalDialogCustom.prompt( |
||||
'Load a Gist', |
||||
'Enter the URL or ID of the Gist you would like to load.', |
||||
null, |
||||
target => { |
||||
if (target !== '') { |
||||
gistId = getGistId(target) |
||||
if (gistId) { |
||||
cb(gistId) |
||||
} |
||||
} |
||||
} |
||||
) |
||||
return loadingFromGist |
||||
} else { |
||||
gistId = params['gist'] |
||||
loadingFromGist = !!gistId |
||||
} |
||||
if (loadingFromGist) { |
||||
cb(gistId) |
||||
} |
||||
return loadingFromGist |
||||
} |
||||
|
||||
function getGistId (str) { |
||||
var idr = /[0-9A-Fa-f]{8,}/ |
||||
var match = idr.exec(str) |
||||
return match ? match[0] : null |
||||
} |
||||
} |
||||
|
||||
module.exports = GistHandler |
@ -1,5 +0,0 @@ |
||||
'use strict' |
||||
|
||||
require('./compiler-test') |
||||
require('./gist-handler-test') |
||||
require('./query-params-test') |
@ -1,23 +0,0 @@ |
||||
'use strict' |
||||
|
||||
var test = require('tape') |
||||
|
||||
var QueryParams = require('../src/lib/query-params') |
||||
|
||||
test('queryParams.get', function (t) { |
||||
t.plan(2) |
||||
|
||||
var fakeWindow = {location: {hash: '#wat=sup&foo=bar', search: ''}} |
||||
var params = new QueryParams(fakeWindow).get() |
||||
t.equal(params.wat, 'sup') |
||||
t.equal(params.foo, 'bar') |
||||
}) |
||||
|
||||
test('queryParams.update', function (t) { |
||||
t.plan(1) |
||||
|
||||
var fakeWindow = {location: {hash: '#wat=sup', search: ''}} |
||||
var qp = new QueryParams(fakeWindow) |
||||
qp.update({foo: 'bar'}) |
||||
t.equal(fakeWindow.location.hash, '#wat=sup&foo=bar') |
||||
}) |
@ -0,0 +1,138 @@ |
||||
/* global fetch */ |
||||
'use strict' |
||||
import { Plugin } from '@remixproject/engine' |
||||
|
||||
interface StringByString { |
||||
[key: string]: string; |
||||
} |
||||
|
||||
const profile = { |
||||
name: 'gistHandler', |
||||
methods: ['load'], |
||||
events: [], |
||||
version: '0.0.1' |
||||
} |
||||
|
||||
export class GistHandler extends Plugin { |
||||
constructor () { |
||||
super(profile) |
||||
} |
||||
|
||||
async handleLoad (gistId: String | null, cb: Function) { |
||||
if (!cb) cb = () => {} |
||||
|
||||
var loadingFromGist = false |
||||
if (!gistId) { |
||||
loadingFromGist = true |
||||
let value |
||||
try { |
||||
value = await (() => { |
||||
return new Promise((resolve, reject) => { |
||||
const modalContent = { |
||||
id: 'gisthandler', |
||||
title: 'Load a Gist', |
||||
message: 'Enter the ID of the Gist or URL you would like to load.', |
||||
modalType: 'prompt', |
||||
okLabel: 'OK', |
||||
cancelLabel: 'Cancel', |
||||
okFn: (value) => { |
||||
setTimeout(() => resolve(value), 0) |
||||
}, |
||||
cancelFn: () => { |
||||
setTimeout(() => reject(new Error('Canceled')), 0) |
||||
}, |
||||
hideFn: () => { |
||||
setTimeout(() => reject(new Error('Hide')), 0) |
||||
} |
||||
} |
||||
this.call('modal', 'modal', modalContent) |
||||
}) |
||||
})() |
||||
} catch (e) { |
||||
// the modal has been canceled
|
||||
return |
||||
} |
||||
|
||||
if (value !== '') { |
||||
gistId = getGistId(value) |
||||
if (gistId) { |
||||
cb(gistId) |
||||
} else { |
||||
const modalContent = { |
||||
id: 'gisthandler', |
||||
title: 'Gist load error', |
||||
message: 'Error while loading gist. Please provide a valid Gist ID or URL.' |
||||
} |
||||
this.call('modal', 'alert', modalContent) |
||||
} |
||||
} else { |
||||
const modalContent = { |
||||
id: 'gisthandlerEmpty', |
||||
title: 'Gist load error', |
||||
message: 'Error while loading gist. Id cannot be empty.' |
||||
} |
||||
this.call('modal', 'alert', modalContent) |
||||
} |
||||
return loadingFromGist |
||||
} else { |
||||
loadingFromGist = !!gistId |
||||
} |
||||
if (loadingFromGist) { |
||||
cb(gistId) |
||||
} |
||||
return loadingFromGist |
||||
} |
||||
|
||||
load (gistId: String | null) { |
||||
const self = this |
||||
return self.handleLoad(gistId, async (gistId: String | null) => { |
||||
let data: any |
||||
try { |
||||
data = await (await fetch(`https://api.github.com/gists/${gistId}`)).json() as any |
||||
if (!data.files) { |
||||
const modalContent = { |
||||
id: 'gisthandler', |
||||
title: 'Gist load error', |
||||
message: data.message, |
||||
modalType: 'alert', |
||||
okLabel: 'OK' |
||||
} |
||||
await this.call('modal', 'modal', modalContent) |
||||
return |
||||
} |
||||
} catch (e: any) { |
||||
const modalContent = { |
||||
id: 'gisthandler', |
||||
title: 'Gist load error', |
||||
message: e.message |
||||
|
||||
} |
||||
await this.call('modal', 'alert', modalContent) |
||||
return |
||||
} |
||||
|
||||
const obj: StringByString = {} |
||||
Object.keys(data.files).forEach((element) => { |
||||
const path = element.replace(/\.\.\./g, '/') |
||||
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] |
||||
}) |
||||
this.call('fileManager', 'setBatchFiles', obj, 'workspace', true, async (errorSavingFiles: any) => { |
||||
if (errorSavingFiles) { |
||||
const modalContent = { |
||||
id: 'gisthandler', |
||||
title: 'Gist load error', |
||||
message: errorSavingFiles.message || errorSavingFiles |
||||
|
||||
} |
||||
this.call('modal', 'alert', modalContent) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
const getGistId = (str) => { |
||||
var idr = /[0-9A-Fa-f]{8,}/ |
||||
var match = idr.exec(str) |
||||
return match ? match[0] : null |
||||
} |
@ -1 +1,6 @@ |
||||
export { default as RemixApp } from './lib/remix-app/remix-app' |
||||
export { dispatchModalContext } from './lib/remix-app/context/context' |
||||
export { ModalProvider } from './lib/remix-app/context/provider' |
||||
export { AppModal } from './lib/remix-app/interface/index' |
||||
export { AlertModal } from './lib/remix-app/interface/index' |
||||
export * from './lib/remix-app/types/index' |
||||
|
@ -0,0 +1,30 @@ |
||||
import { AppModal } from '../interface' |
||||
|
||||
type ActionMap<M extends { [index: string]: any }> = { |
||||
[Key in keyof M]: M[Key] extends undefined |
||||
? { |
||||
type: Key; |
||||
} |
||||
: { |
||||
type: Key; |
||||
payload: M[Key]; |
||||
} |
||||
} |
||||
|
||||
export const enum modalActionTypes { |
||||
setModal = 'SET_MODAL', |
||||
setToast = 'SET_TOAST', |
||||
handleHideModal = 'HANDLE_HIDE_MODAL', |
||||
handleToaster = 'HANDLE_HIDE_TOAST' |
||||
} |
||||
|
||||
type ModalPayload = { |
||||
[modalActionTypes.setModal]: AppModal |
||||
[modalActionTypes.handleHideModal]: any |
||||
[modalActionTypes.setToast]: string |
||||
[modalActionTypes.handleToaster]: any |
||||
} |
||||
|
||||
export type ModalAction = ActionMap<ModalPayload>[keyof ActionMap< |
||||
ModalPayload |
||||
>] |
@ -0,0 +1,27 @@ |
||||
/* dragbar UI */ |
||||
|
||||
.dragbar { |
||||
display: block; |
||||
height: 100%; |
||||
position: absolute; |
||||
left: 0px; |
||||
top: 0px; |
||||
width: 0.3em; |
||||
z-index: 9999; |
||||
} |
||||
|
||||
.overlay { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
display: block; |
||||
z-index: 9998; |
||||
} |
||||
|
||||
.dragbar:hover, |
||||
.dragbar.ondrag { |
||||
background-color: var(--secondary); |
||||
cursor: col-resize; |
||||
} |
@ -0,0 +1,15 @@ |
||||
import React, { useContext, useEffect } from 'react' |
||||
import { AppContext } from '../../context/context' |
||||
import { useDialogDispatchers } from '../../context/provider' |
||||
|
||||
const DialogViewPlugin = () => { |
||||
const { modal, alert, toast } = useDialogDispatchers() |
||||
const app = useContext(AppContext) |
||||
|
||||
useEffect(() => { |
||||
app.modal.setDispatcher({ modal, alert, toast }) |
||||
}, []) |
||||
return <></> |
||||
} |
||||
|
||||
export default DialogViewPlugin |
@ -0,0 +1,16 @@ |
||||
import React from 'react' |
||||
import { useDialogDispatchers, useDialogs } from '../../context/provider' |
||||
import { Toaster } from '@remix-ui/toaster' |
||||
import ModalWrapper from './modal-wrapper' |
||||
|
||||
const AppDialogs = () => { |
||||
const { handleHideModal, handleToaster } = useDialogDispatchers() |
||||
const { focusModal, focusToaster } = useDialogs() |
||||
|
||||
return ( |
||||
<> |
||||
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper> |
||||
<Toaster message={focusToaster} handleHide={handleToaster} /> |
||||
</>) |
||||
} |
||||
export default AppDialogs |
@ -0,0 +1,45 @@ |
||||
import React, { useContext, useEffect, useState } from 'react' |
||||
import { AppContext } from '../../context/context' |
||||
import { useDialogDispatchers } from '../../context/provider' |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
const MatomoDialog = (props) => { |
||||
const { settings, showMatamo, appManager } = useContext(AppContext) |
||||
const { modal } = useDialogDispatchers() |
||||
const [visible, setVisible] = useState<boolean>(props.hide) |
||||
|
||||
const message = () => { |
||||
return (<><p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p> |
||||
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> |
||||
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p> |
||||
<p>We do not collect nor store any personally identifiable information (PII).</p> |
||||
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p> |
||||
<p>You can change your choice in the Settings panel anytime.</p></>) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (visible && showMatamo) { |
||||
modal({ id: 'matomoModal', title: 'Help us to improve Remix IDE', message: message(), okLabel: 'Accept', okFn: handleModalOkClick, cancelLabel: 'Decline', cancelFn: declineModal }) |
||||
} |
||||
}, [visible]) |
||||
|
||||
const declineModal = async () => { |
||||
settings.updateMatomoAnalyticsChoice(false) |
||||
_paq.push(['optUserOut']) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
|
||||
const handleModalOkClick = async () => { |
||||
_paq.push(['forgetUserOptOut']) |
||||
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
|
||||
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' |
||||
settings.updateMatomoAnalyticsChoice(true) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
|
||||
return (<></>) |
||||
} |
||||
|
||||
export default MatomoDialog |
@ -0,0 +1,56 @@ |
||||
import React, { useEffect, useRef, useState } from 'react' |
||||
import { ModalDialog } from '@remix-ui/modal-dialog' |
||||
import { ModalDialogProps } from 'libs/remix-ui/modal-dialog/src/lib/types' |
||||
import { ModalTypes } from '../../types' |
||||
|
||||
interface ModalWrapperProps extends ModalDialogProps { |
||||
modalType?: ModalTypes |
||||
defaultValue?: string |
||||
} |
||||
|
||||
const ModalWrapper = (props: ModalWrapperProps) => { |
||||
const [state, setState] = useState<ModalDialogProps>() |
||||
const ref = useRef() |
||||
|
||||
const onFinishPrompt = async () => { |
||||
if (ref.current === undefined) { |
||||
props.okFn() |
||||
} else { |
||||
// @ts-ignore: Object is possibly 'null'.
|
||||
props.okFn(ref.current.value) |
||||
} |
||||
} |
||||
|
||||
const createModalMessage = (defaultValue: string) => { |
||||
return ( |
||||
<> |
||||
{props.message} |
||||
<input type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /></> |
||||
) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (props.modalType) { |
||||
switch (props.modalType) { |
||||
case ModalTypes.prompt: |
||||
case ModalTypes.password: |
||||
setState({ |
||||
...props, |
||||
okFn: onFinishPrompt, |
||||
message: createModalMessage(props.defaultValue) |
||||
}) |
||||
break |
||||
default: |
||||
setState({ ...props }) |
||||
break |
||||
} |
||||
} else { |
||||
setState({ ...props }) |
||||
} |
||||
}, [props]) |
||||
|
||||
return ( |
||||
<ModalDialog id={props.id} {...state} handleHide={props.handleHide} /> |
||||
) |
||||
} |
||||
export default ModalWrapper |
@ -1,5 +1,24 @@ |
||||
import React from 'react' |
||||
import { AlertModal, AppModal } from '../interface' |
||||
import { ModalInitialState } from '../state/modals' |
||||
import { ModalTypes } from '../types' |
||||
|
||||
const AppContext = React.createContext(null) |
||||
export const AppContext = React.createContext<any>(null) |
||||
|
||||
export default AppContext |
||||
export interface dispatchModalInterface { |
||||
modal: (data: AppModal) => void |
||||
toast: (message: string) => void |
||||
alert: (data: AlertModal) => void |
||||
handleHideModal: () => void, |
||||
handleToaster: () => void |
||||
} |
||||
|
||||
export const dispatchModalContext = React.createContext<dispatchModalInterface>({ |
||||
modal: (data: AppModal) => { }, |
||||
toast: (message: string) => {}, |
||||
alert: (data: AlertModal) => {}, |
||||
handleHideModal: () => {}, |
||||
handleToaster: () => {} |
||||
}) |
||||
|
||||
export const modalContext = React.createContext(ModalInitialState) |
||||
|
@ -0,0 +1,64 @@ |
||||
import React, { useReducer } from 'react' |
||||
import { modalActionTypes } from '../actions/modals' |
||||
import { AlertModal, AppModal } from '../interface' |
||||
import { modalReducer } from '../reducer/modals' |
||||
import { ModalInitialState } from '../state/modals' |
||||
import { ModalTypes } from '../types' |
||||
import { AppContext, dispatchModalContext, modalContext } from './context' |
||||
|
||||
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { |
||||
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) |
||||
|
||||
const modal = (data: AppModal) => { |
||||
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data |
||||
dispatch({ |
||||
type: modalActionTypes.setModal, |
||||
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn } |
||||
}) |
||||
} |
||||
|
||||
const alert = (data: AlertModal) => { |
||||
modal({ id: data.id, title: data.title || 'Alert', message: data.message || data.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} }) |
||||
} |
||||
|
||||
const handleHideModal = () => { |
||||
dispatch({ |
||||
type: modalActionTypes.handleHideModal, |
||||
payload: null |
||||
}) |
||||
} |
||||
|
||||
const toast = (message: string) => { |
||||
dispatch({ |
||||
type: modalActionTypes.setToast, |
||||
payload: message |
||||
}) |
||||
} |
||||
|
||||
const handleToaster = () => { |
||||
dispatch({ |
||||
type: modalActionTypes.handleToaster, |
||||
payload: null |
||||
}) |
||||
} |
||||
|
||||
return (<dispatchModalContext.Provider value={{ modal, toast, alert, handleHideModal, handleToaster }}> |
||||
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}> |
||||
{children} |
||||
</modalContext.Provider> |
||||
</dispatchModalContext.Provider>) |
||||
} |
||||
|
||||
export const AppProvider = ({ children = [], value = {} } = {}) => { |
||||
return <AppContext.Provider value={value}> |
||||
<ModalProvider>{children}</ModalProvider> |
||||
</AppContext.Provider> |
||||
} |
||||
|
||||
export const useDialogs = () => { |
||||
return React.useContext(modalContext) |
||||
} |
||||
|
||||
export const useDialogDispatchers = () => { |
||||
return React.useContext(dispatchModalContext) |
||||
} |
@ -1,26 +0,0 @@ |
||||
/* dragbar UI */ |
||||
|
||||
.dragbar { |
||||
display : block; |
||||
height : 100%; |
||||
position : absolute; |
||||
left: 0px; |
||||
top: 0px; |
||||
width: 0.3em; |
||||
z-index: 9999; |
||||
} |
||||
|
||||
.overlay { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
display: block; |
||||
z-index: 9998; |
||||
} |
||||
|
||||
.dragbar:hover, .dragbar.ondrag{ |
||||
background-color: var(--secondary); |
||||
cursor:col-resize; |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { ModalTypes } from '../types' |
||||
|
||||
export interface AppModal { |
||||
id: string |
||||
hide?: boolean |
||||
title: string |
||||
// eslint-disable-next-line no-undef
|
||||
message: string | JSX.Element |
||||
okLabel: string |
||||
okFn: (value?:any) => void |
||||
cancelLabel: string |
||||
cancelFn: () => void, |
||||
modalType?: ModalTypes, |
||||
defaultValue?: string |
||||
hideFn?: () => void |
||||
} |
||||
|
||||
export interface AlertModal { |
||||
id: string |
||||
title?: string, |
||||
message: string | JSX.Element, |
||||
} |
||||
|
||||
export interface ModalState { |
||||
modals: AppModal[], |
||||
toasters: string[], |
||||
focusModal: AppModal, |
||||
focusToaster: string |
||||
} |
@ -1,51 +0,0 @@ |
||||
import React, { useContext, useEffect, useState } from 'react' |
||||
import { ModalDialog } from '@remix-ui/modal-dialog' |
||||
import AppContext from '../context/context' |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
const MatomoDialog = (props) => { |
||||
const { settings, showMatamo, appManager } = useContext(AppContext) |
||||
const [visible, setVisible] = useState<boolean>(props.hide) |
||||
useEffect(() => { |
||||
if (showMatamo) { |
||||
setVisible(true) |
||||
} else { |
||||
setVisible(false) |
||||
} |
||||
}, []) |
||||
const declineModal = async () => { |
||||
settings.updateMatomoAnalyticsChoice(false) |
||||
_paq.push(['optUserOut']) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
const hideModal = async () => { |
||||
setVisible(false) |
||||
} |
||||
const handleModalOkClick = async () => { |
||||
_paq.push(['forgetUserOptOut']) |
||||
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
|
||||
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' |
||||
settings.updateMatomoAnalyticsChoice(true) |
||||
appManager.call('walkthrough', 'start') |
||||
setVisible(false) |
||||
} |
||||
return (<ModalDialog |
||||
handleHide={hideModal} |
||||
id="matomoDialog" |
||||
hide={!visible} |
||||
title="Help us to improve Remix IDE" |
||||
okLabel="Accept" |
||||
okFn={ handleModalOkClick } |
||||
cancelLabel="Decline" |
||||
cancelFn={declineModal}> |
||||
<p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p> |
||||
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> |
||||
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p> |
||||
<p>We do not collect nor store any personally identifiable information (PII).</p> |
||||
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p> |
||||
<p>You can change your choice in the Settings panel anytime.</p> |
||||
</ModalDialog>) |
||||
} |
||||
|
||||
export default MatomoDialog |
@ -0,0 +1,50 @@ |
||||
import { modalActionTypes, ModalAction } from '../actions/modals' |
||||
import { ModalInitialState } from '../state/modals' |
||||
import { AppModal, ModalState } from '../interface' |
||||
|
||||
export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => { |
||||
switch (action.type) { |
||||
case modalActionTypes.setModal: { |
||||
let modalList:AppModal[] = state.modals |
||||
modalList.push(action.payload) |
||||
if (state.modals.length === 1 && state.focusModal.hide === true) { // if it's the first one show it
|
||||
const focusModal: AppModal = { |
||||
id: modalList[0].id, |
||||
hide: false, |
||||
title: modalList[0].title, |
||||
message: modalList[0].message, |
||||
okLabel: modalList[0].okLabel, |
||||
okFn: modalList[0].okFn, |
||||
cancelLabel: modalList[0].cancelLabel, |
||||
cancelFn: modalList[0].cancelFn, |
||||
modalType: modalList[0].modalType, |
||||
defaultValue: modalList[0].defaultValue, |
||||
hideFn: modalList[0].hideFn |
||||
} |
||||
|
||||
modalList = modalList.slice() |
||||
modalList.shift() |
||||
return { ...state, modals: modalList, focusModal: focusModal } |
||||
} |
||||
return { ...state, modals: modalList } |
||||
} |
||||
case modalActionTypes.handleHideModal: |
||||
if (state.focusModal.hideFn) { |
||||
state.focusModal.hideFn() |
||||
} |
||||
state.focusModal = { ...state.focusModal, hide: true, message: null } |
||||
return { ...state } |
||||
|
||||
case modalActionTypes.setToast: |
||||
state.toasters.push(action.payload) |
||||
if (state.toasters.length > 0) { |
||||
const focus = state.toasters[0] |
||||
state.toasters.shift() |
||||
return { ...state, focusToaster: focus } |
||||
} |
||||
return { ...state } |
||||
|
||||
case modalActionTypes.handleToaster: |
||||
return { ...state, focusToaster: '' } |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
import { ModalState } from '../interface' |
||||
|
||||
export const ModalInitialState: ModalState = { |
||||
modals: [], |
||||
toasters: [], |
||||
focusModal: { |
||||
id: '', |
||||
hide: true, |
||||
title: '', |
||||
message: '', |
||||
okLabel: '', |
||||
okFn: () => { }, |
||||
cancelLabel: '', |
||||
cancelFn: () => { } |
||||
}, |
||||
focusToaster: '' |
||||
} |
@ -0,0 +1,7 @@ |
||||
export const enum ModalTypes { |
||||
alert = 'alert', |
||||
confirm = 'confirm', |
||||
prompt = 'prompt', |
||||
password = 'password', |
||||
default = 'default', |
||||
} |
@ -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"] |
||||
} |
@ -1,4 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
Loading…
Reference in new issue