commit
63f7e02936
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,155 @@ |
||||
/* global localStorage */ |
||||
const yo = require('yo-yo') |
||||
const modalDialog = require('../ui/modaldialog') |
||||
|
||||
const unexposedEvents = ['statusChanged'] |
||||
|
||||
module.exports = class LocalPlugin { |
||||
|
||||
/** |
||||
* Open a modal to create a local plugin |
||||
* @param {PluginApi[]} plugins The list of the plugins in the store |
||||
* @returns {Promise<{api: any, profile: any}>} A promise with the new plugin profile |
||||
*/ |
||||
open (plugins) { |
||||
this.profile = JSON.parse(localStorage.getItem('plugins/local')) || { notifications: {} } |
||||
return new Promise((resolve, reject) => { |
||||
const onValidation = () => { |
||||
try { |
||||
const profile = this.create() |
||||
resolve(profile) |
||||
} catch (err) { |
||||
reject(err) |
||||
} |
||||
} |
||||
modalDialog('Local Plugin', this.form(plugins), |
||||
{ fn: () => onValidation() }, |
||||
{ fn: () => resolve() } |
||||
) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Create the object to add to the plugin-list |
||||
*/ |
||||
create () { |
||||
const profile = { |
||||
...this.profile, |
||||
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMjYyIDEwNzVxLTM3IDEyMS0xMzggMTk1dC0yMjggNzQtMjI4LTc0LTEzOC0xOTVxLTgtMjUgNC00OC41dDM4LTMxLjVxMjUtOCA0OC41IDR0MzEuNSAzOHEyNSA4MCA5Mi41IDEyOS41dDE1MS41IDQ5LjUgMTUxLjUtNDkuNSA5Mi41LTEyOS41cTgtMjYgMzItMzh0NDktNCAzNyAzMS41IDQgNDguNXptLTQ5NC00MzVxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptNTEyIDBxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptMjU2IDI1NnEwLTEzMC01MS0yNDguNXQtMTM2LjUtMjA0LTIwNC0xMzYuNS0yNDguNS01MS0yNDguNSA1MS0yMDQgMTM2LjUtMTM2LjUgMjA0LTUxIDI0OC41IDUxIDI0OC41IDEzNi41IDIwNCAyMDQgMTM2LjUgMjQ4LjUgNTEgMjQ4LjUtNTEgMjA0LTEzNi41IDEzNi41LTIwNCA1MS0yNDguNXptMTI4IDBxMCAyMDktMTAzIDM4NS41dC0yNzkuNSAyNzkuNS0zODUuNSAxMDMtMzg1LjUtMTAzLTI3OS41LTI3OS41LTEwMy0zODUuNSAxMDMtMzg1LjUgMjc5LjUtMjc5LjUgMzg1LjUtMTAzIDM4NS41IDEwMyAyNzkuNSAyNzkuNSAxMDMgMzg1LjV6Ii8+PC9zdmc+', |
||||
methods: [], |
||||
hash: `local-${this.profile.name}`, |
||||
location: 'swapPanel' |
||||
} |
||||
profile.events = profile.events.filter((item) => { return item !== '' }) |
||||
if (!profile.name) throw new Error('Plugin should have a name') |
||||
if (!profile.url) throw new Error('Plugin should have an URL') |
||||
localStorage.setItem('plugins/local', JSON.stringify(profile)) |
||||
return profile |
||||
} |
||||
|
||||
/** |
||||
* Add or remove a notification to/from the profile |
||||
* @param {Event} e The event when checkbox changes |
||||
* @param {string} pluginName The name of the plugin |
||||
* @param {string} eventName The name of the event to listen on |
||||
*/ |
||||
toggleNotification (e, pluginName, eventName) { |
||||
const {checked} = e.target |
||||
if (checked) { |
||||
if (!this.profile.notifications[pluginName]) this.profile.notifications[pluginName] = [] |
||||
this.profile.notifications[pluginName].push(eventName) |
||||
} else { |
||||
this.profile.notifications[pluginName].splice(this.profile.notifications[pluginName].indexOf(eventName), 1) |
||||
if (this.profile.notifications[pluginName].length === 0) delete this.profile.notifications[pluginName] |
||||
} |
||||
} |
||||
|
||||
updateName ({target}) { |
||||
this.profile.name = target.value |
||||
} |
||||
|
||||
updateUrl ({target}) { |
||||
this.profile.url = target.value |
||||
} |
||||
|
||||
updateDisplayName ({target}) { |
||||
this.profile.displayName = target.value |
||||
} |
||||
|
||||
updateEvents ({target}, index) { |
||||
if (this.profile.events[index] !== undefined) { |
||||
this.profile.events[index] = target.value |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* The checkbox for a couple module / event |
||||
* @param {string} plugin The name of the plugin |
||||
* @param {string} event The name of the event exposed by the plugin |
||||
*/ |
||||
notificationCheckbox (plugin, event) { |
||||
const notifications = this.profile.notifications || {} |
||||
const checkbox = notifications[plugin] && notifications[plugin].includes(event) |
||||
? yo`<input type="checkbox" checked onchange="${e => this.toggleNotification(e, plugin, event)}">` |
||||
: yo`<input type="checkbox" onchange="${e => this.toggleNotification(e, plugin, event)}">` |
||||
return yo`<div>
|
||||
${checkbox} |
||||
<label>${plugin} - ${event}</label> |
||||
</div>` |
||||
} |
||||
|
||||
/** |
||||
* The form to create a local plugin |
||||
* @param {ProfileApi[]} plugins Liste of profile of the plugins |
||||
*/ |
||||
form (plugins = []) { |
||||
const name = this.profile.name || '' |
||||
const url = this.profile.url || '' |
||||
const displayName = this.profile.displayName || '' |
||||
const profiles = plugins |
||||
.filter(({profile}) => profile.events && profile.events.length > 0) |
||||
.map(({profile}) => profile) |
||||
|
||||
const eventsForm = (events) => { |
||||
return yo`<div>${events.map((event, i) => { |
||||
return yo`<input class="form-control" onchange="${e => this.updateEvents(e, i)}" value="${event}" />` |
||||
})}</div>` |
||||
} |
||||
const eventsEl = eventsForm(this.profile.events || []) |
||||
const pushEvent = () => { |
||||
if (!this.profile.events) this.profile.events = [] |
||||
this.profile.events.push('') |
||||
yo.update(eventsEl, eventsForm(this.profile.events)) |
||||
} |
||||
const addEvent = yo`<button type="button" class="btn btn-sm btn-light" onclick="${() => pushEvent()}">Add an event</button>` |
||||
|
||||
return yo` |
||||
<form id="local-plugin-form"> |
||||
<div class="form-group"> |
||||
<label for="plugin-name">Plugin Name <small>(required)</small></label> |
||||
<input class="form-control" onchange="${e => this.updateName(e)}" value="${name}" id="plugin-name" placeholder="Should be camelCase"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label for="plugin-displayname">Display Name</label> |
||||
<input class="form-control" onchange="${e => this.updateDisplayName(e)}" value="${displayName}" id="plugin-displayname" placeholder="Name in the header"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label for="plugin-url">Url <small>(required)</small></label> |
||||
<input class="form-control" onchange="${e => this.updateUrl(e)}" value="${url}" id="plugin-url" placeholder="ex: https://localhost:8000"> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>Events</label> |
||||
${eventsEl}${addEvent} |
||||
</div> |
||||
<div class="form-group"> |
||||
<label>Notifications</label> |
||||
${profiles.map(({name, events}) => { |
||||
return events |
||||
.filter(event => !unexposedEvents.includes(event)) |
||||
.map(event => this.notificationCheckbox(name, event)) |
||||
})} |
||||
</div> |
||||
</form>` |
||||
} |
||||
} |
||||
|
@ -0,0 +1,191 @@ |
||||
const yo = require('yo-yo') |
||||
const csjs = require('csjs-inject') |
||||
const EventEmitter = require('events') |
||||
const LocalPlugin = require('./local-plugin') |
||||
import { Plugin, BaseApi } from 'remix-plugin' |
||||
|
||||
const css = csjs` |
||||
.pluginSearch { |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
background-color: var(--light); |
||||
padding: 10px; |
||||
position: sticky; |
||||
top: 0; |
||||
z-index: 2; |
||||
margin-bottom: 0px; |
||||
} |
||||
.localPluginBtn { |
||||
margin-top: 15px; |
||||
} |
||||
.displayName { |
||||
text-transform: capitalize; |
||||
} |
||||
.description { |
||||
text-transform: capitalize; |
||||
} |
||||
.row { |
||||
display: flex; |
||||
flex-direction: row; |
||||
} |
||||
.isStuck { |
||||
background-color: var(--primary); |
||||
color:
|
||||
} |
||||
` |
||||
|
||||
const profile = { |
||||
name: 'pluginManager', |
||||
displayName: 'Plugin manager', |
||||
methods: [], |
||||
events: [], |
||||
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNzU1IDQ1M3EzNyAzOCAzNyA5MC41dC0zNyA5MC41bC00MDEgNDAwIDE1MCAxNTAtMTYwIDE2MHEtMTYzIDE2My0zODkuNSAxODYuNXQtNDExLjUtMTAwLjVsLTM2MiAzNjJoLTE4MXYtMTgxbDM2Mi0zNjJxLTEyNC0xODUtMTAwLjUtNDExLjV0MTg2LjUtMzg5LjVsMTYwLTE2MCAxNTAgMTUwIDQwMC00MDFxMzgtMzcgOTEtMzd0OTAgMzcgMzcgOTAuNS0zNyA5MC41bC00MDAgNDAxIDIzNCAyMzQgNDAxLTQwMHEzOC0zNyA5MS0zN3Q5MCAzN3oiLz48L3N2Zz4=', |
||||
description: 'Start/stop services, modules and plugins', |
||||
kind: 'settings', |
||||
location: 'swapPanel' |
||||
} |
||||
|
||||
class PluginManagerComponent extends BaseApi { |
||||
|
||||
constructor () { |
||||
super(profile) |
||||
this.event = new EventEmitter() |
||||
this.views = { |
||||
root: null, |
||||
items: {} |
||||
} |
||||
this.localPlugin = new LocalPlugin() |
||||
this.filter = '' |
||||
} |
||||
|
||||
setApp (appManager) { |
||||
this.appManager = appManager |
||||
} |
||||
|
||||
setStore (store) { |
||||
this.store = store |
||||
this.store.event.on('activate', (name) => { this.reRender() }) |
||||
this.store.event.on('deactivate', (name) => { this.reRender() }) |
||||
this.store.event.on('add', (api) => { this.reRender() }) |
||||
this.store.event.on('remove', (api) => { this.reRender() }) |
||||
} |
||||
|
||||
renderItem (name) { |
||||
const api = this.store.getOne(name) |
||||
if (!api) return |
||||
const isActive = this.store.actives.includes(name) |
||||
const displayName = (api.profile.displayName) ? api.profile.displayName : name |
||||
|
||||
const activationButton = isActive |
||||
? yo` |
||||
<button onclick="${_ => this.appManager.deactivateOne(name)}" class="btn btn-secondary btn-sm"> |
||||
Deactivate |
||||
</button>` |
||||
: yo` |
||||
<button onclick="${_ => this.appManager.activateOne(name)}" class="btn btn-success btn-sm"> |
||||
Activate |
||||
</button>` |
||||
|
||||
return yo` |
||||
<article class="list-group-item py-1" title="${name}" > |
||||
<div class="${css.row} justify-content-between align-items-center"> |
||||
<h6 class="${css.displayName}">${displayName}</h6> |
||||
${activationButton} |
||||
</div> |
||||
<p class="${css.description}">${api.profile.description}</p> |
||||
</article> |
||||
` |
||||
} |
||||
|
||||
/*************** |
||||
* SUB-COMPONENT |
||||
*/ |
||||
/** |
||||
* Add a local plugin to the list of plugins |
||||
*/ |
||||
async openLocalPlugin () { |
||||
try { |
||||
const profile = await this.localPlugin.open(this.store.getAll()) |
||||
if (!profile) return |
||||
this.appManager.registerOne(new Plugin(profile)) |
||||
this.appManager.activateOne(profile.name) |
||||
} catch (err) { |
||||
// TODO : Use an alert to handle this error instead of a console.log
|
||||
console.log(`Cannot create Plugin : ${err.message}`) |
||||
} |
||||
} |
||||
|
||||
render () { |
||||
// Filtering helpers
|
||||
const isFiltered = (api) => api.name.toLowerCase().includes(this.filter) |
||||
const isNotRequired = ({profile}) => !profile.required |
||||
const sortByName = (a, b) => { |
||||
const nameA = a.name.toUpperCase() |
||||
const nameB = b.name.toUpperCase() |
||||
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0 |
||||
} |
||||
|
||||
// Filter all active and inactive modules that are not required
|
||||
const { actives, inactives } = this.store.getAll() |
||||
.filter(isFiltered) |
||||
.filter(isNotRequired) |
||||
.sort(sortByName) |
||||
.reduce(({actives, inactives}, api) => { |
||||
return this.store.actives.includes(api.name) |
||||
? { actives: [...actives, api.name], inactives } |
||||
: { inactives: [...inactives, api.name], actives } |
||||
}, { actives: [], inactives: [] }) |
||||
|
||||
const activeTile = actives.length !== 0 |
||||
? yo` |
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light justify-content-between align-items-center"> |
||||
<span class="navbar-brand">Active Modules</span> |
||||
<span class="badge badge-pill badge-primary">${actives.length}</span> |
||||
</nav>` |
||||
: '' |
||||
const inactiveTile = inactives.length !== 0 |
||||
? yo` |
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light justify-content-between align-items-center"> |
||||
<span class="navbar-brand">Inactive Modules</span> |
||||
<span class="badge badge-pill badge-primary">${inactives.length}</span> |
||||
</nav>` |
||||
: '' |
||||
|
||||
const rootView = yo` |
||||
<div id='pluginManager'> |
||||
<div class="form-group ${css.pluginSearch}"> |
||||
<input onkeyup="${e => this.filterPlugins(e)}" class="form-control" placeholder="Search"> |
||||
<button onclick="${_ => this.openLocalPlugin()}" class="btn btn-sm text-info ${css.localPluginBtn}"> |
||||
Connect to a Local Plugin |
||||
</button> |
||||
</div> |
||||
<section> |
||||
${activeTile} |
||||
<div class="list-group list-group-flush"> |
||||
${actives.map(name => this.renderItem(name))} |
||||
</div> |
||||
${inactiveTile} |
||||
<div class="list-group list-group-flush"> |
||||
${inactives.map(name => this.renderItem(name))} |
||||
</div> |
||||
</section> |
||||
</div> |
||||
` |
||||
if (!this.views.root) this.views.root = rootView |
||||
return rootView |
||||
} |
||||
|
||||
reRender () { |
||||
if (this.views.root) { |
||||
yo.update(this.views.root, this.render()) |
||||
} |
||||
} |
||||
|
||||
filterPlugins ({ target }) { |
||||
this.filter = target.value.toLowerCase() |
||||
this.reRender() |
||||
} |
||||
} |
||||
|
||||
module.exports = PluginManagerComponent |
@ -0,0 +1,36 @@ |
||||
var registry = require('../../global/registry') |
||||
|
||||
const CompilerAbstract = require('../compiler/compiler-abstract') |
||||
|
||||
const EventManager = require('remix-lib').EventManager |
||||
|
||||
class PluginManagerProxy { |
||||
|
||||
constructor () { |
||||
this.event = new EventManager() |
||||
this._listeners = {} |
||||
this._listeners['vyper'] = (file, source, languageVersion, data) => { |
||||
registry.get('compilersartefacts').api['__last'] = new CompilerAbstract(languageVersion, data, source) |
||||
this.event.trigger('sendCompilationResult', [file, source, languageVersion, data]) |
||||
} |
||||
this._listeners['solidity'] = (file, source, languageVersion, data) => { |
||||
registry.get('compilersartefacts').api['__last'] = new CompilerAbstract(languageVersion, data, source) |
||||
this.event.trigger('sendCompilationResult', [file, source, languageVersion, data]) |
||||
} |
||||
} |
||||
|
||||
register (name, instance) { |
||||
if (this._listeners[name]) { |
||||
instance.events.on('compilationFinished', this._listeners[name]) |
||||
} |
||||
} |
||||
|
||||
unregister (name, instance) { |
||||
if (this._listeners[name]) { |
||||
instance.events.removeListener('compilationFinished', this._listeners[name]) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = PluginManagerProxy |
@ -0,0 +1,43 @@ |
||||
import EventEmmitter from 'events' |
||||
|
||||
class SwapPanelApi { |
||||
constructor (swapPanelComponent, verticalIconsComponent) { |
||||
this.event = new EventEmmitter() |
||||
this.component = swapPanelComponent |
||||
this.currentContent |
||||
verticalIconsComponent.events.on('toggleContent', (moduleName) => { |
||||
if (!swapPanelComponent.contents[moduleName]) return |
||||
if (this.currentContent === moduleName) { |
||||
this.event.emit('toggle', moduleName) |
||||
return |
||||
} |
||||
this.showContent(moduleName) |
||||
this.event.emit('showing', moduleName) |
||||
}) |
||||
|
||||
verticalIconsComponent.events.on('showContent', (moduleName) => { |
||||
if (!swapPanelComponent.contents[moduleName]) return |
||||
this.showContent(moduleName) |
||||
this.event.emit('showing', moduleName) |
||||
}) |
||||
} |
||||
|
||||
showContent (moduleName) { |
||||
this.component.showContent(moduleName) |
||||
this.currentContent = moduleName |
||||
} |
||||
|
||||
/* |
||||
content: DOM element |
||||
by appManager |
||||
*/ |
||||
add (profile, content) { |
||||
return this.component.add(profile.name, content) |
||||
} |
||||
|
||||
remove (profile) { |
||||
return this.component.remove(profile.name) |
||||
} |
||||
} |
||||
|
||||
module.exports = SwapPanelApi |
@ -0,0 +1,102 @@ |
||||
var yo = require('yo-yo') |
||||
var csjs = require('csjs-inject') |
||||
|
||||
class SwapPanelComponent { |
||||
constructor (name, appStore, appManager, opt) { |
||||
this.name = name |
||||
this.opt = opt |
||||
this.store = appStore |
||||
// list of contents
|
||||
this.contents = {} |
||||
// name of the current displayed content
|
||||
this.currentNode |
||||
|
||||
this.store.event.on('activate', (name) => { |
||||
const api = this.store.getOne(name) |
||||
const profile = api.profile |
||||
if (((profile.location === this.name) || (!profile.location && opt.default)) && |
||||
profile.icon && api.render && typeof api.render === 'function') { |
||||
this.add(name, api.render()) |
||||
} |
||||
}) |
||||
|
||||
this.store.event.on('deactivate', (name) => { |
||||
if (this.contents[name]) this.remove(name) |
||||
}) |
||||
this.store.event.on('add', (api) => { }) |
||||
this.store.event.on('remove', (api) => { }) |
||||
} |
||||
|
||||
showContent (moduleName) { |
||||
// hiding the current view and display the `moduleName`
|
||||
if (this.contents[moduleName]) { |
||||
if (this.currentNode) { |
||||
this.contents[this.currentNode].style.display = 'none' |
||||
} |
||||
this.contents[moduleName].style.display = 'block' |
||||
this.currentNode = moduleName |
||||
var api = this.store.getOne(moduleName) |
||||
this.header.querySelector('h6').innerHTML = api.profile ? api.profile.displayName : ' - ' |
||||
return |
||||
} |
||||
} |
||||
|
||||
add (moduleName, content) { |
||||
content.style.height = '100%' |
||||
content.style.width = '100%' |
||||
content.style.border = '0' |
||||
this.contents[moduleName] = yo`<div class=${css.plugItIn} >${content}</div>` |
||||
this.view.appendChild(this.contents[moduleName]) |
||||
} |
||||
|
||||
remove (moduleName) { |
||||
let el = this.contents[moduleName] |
||||
if (el) el.parentElement.removeChild(el) |
||||
} |
||||
|
||||
render () { |
||||
this.view = yo` |
||||
<div id='plugins' class=${css.plugins}> |
||||
</div> |
||||
` |
||||
this.header = yo`<header class="${css.swapitHeader}"><h6 class="${css.swapitTitle}"></h6></header>` |
||||
if (!this.opt.displayHeader) this.header.style.display = 'none' |
||||
|
||||
return yo`<div class=${css.pluginsContainer}>
|
||||
${this.header} |
||||
${this.view} |
||||
</div>` |
||||
} |
||||
} |
||||
|
||||
module.exports = SwapPanelComponent |
||||
|
||||
const css = csjs` |
||||
.plugins { |
||||
height : 95%; |
||||
} |
||||
.plugItIn { |
||||
display : none; |
||||
height : 100%; |
||||
} |
||||
.plugItIn > div { |
||||
overflow-y : auto; |
||||
height : 100%; |
||||
width : 100%; |
||||
} |
||||
.plugItIn.active { |
||||
display : block; |
||||
} |
||||
.pluginsContainer { |
||||
height: 100%; |
||||
overflow-y: hidden; |
||||
} |
||||
.swapitTitle { |
||||
text-transform: uppercase; |
||||
} |
||||
.swapitHeader { |
||||
height: 35px; |
||||
padding-top: 10px; |
||||
padding-left: 27px; |
||||
} |
||||
` |
@ -0,0 +1,20 @@ |
||||
// API
|
||||
class VerticalIconsApi { |
||||
|
||||
constructor (verticalIconsComponent) { |
||||
this.component = verticalIconsComponent |
||||
} |
||||
|
||||
addIcon (mod) { |
||||
this.component.addIcon(mod) |
||||
} |
||||
|
||||
removeIcon (mod) { |
||||
this.component.removeIcon(mod) |
||||
} |
||||
|
||||
select (moduleName) { |
||||
this.component.select(moduleName) |
||||
} |
||||
} |
||||
module.exports = VerticalIconsApi |
@ -0,0 +1,428 @@ |
||||
var yo = require('yo-yo') |
||||
var csjs = require('csjs-inject') |
||||
var helper = require('../../lib/helper') |
||||
let globalRegistry = require('../../global/registry') |
||||
|
||||
const EventEmitter = require('events') |
||||
|
||||
// Component
|
||||
class VerticalIconComponent { |
||||
|
||||
constructor (name, appStore, homeProfile) { |
||||
this.store = appStore |
||||
this.homeProfile = homeProfile |
||||
this.events = new EventEmitter() |
||||
this.icons = {} |
||||
this.iconKind = {} |
||||
this.iconStatus = {} |
||||
this.name = name |
||||
|
||||
this.store.event.on('activate', (name) => { |
||||
const api = this.store.getOne(name) |
||||
if (!api.profile.icon) return |
||||
if (api.profile.location === this.name) { |
||||
this.addIcon(api.profile) |
||||
this.listenOnStatus(api) |
||||
} |
||||
}) |
||||
this.store.event.on('deactivate', (name) => { |
||||
const api = this.store.getOne(name) |
||||
if (api && this.icons[name]) { |
||||
this.removeIcon(api.profile) |
||||
this.stopListenOnStatus(api) |
||||
} |
||||
}) |
||||
this.store.event.on('add', (api) => { }) |
||||
this.store.event.on('remove', (api) => { }) |
||||
|
||||
let themeModule = globalRegistry.get('themeModule').api |
||||
themeModule.events.on('themeChanged', (theme) => { |
||||
this.onThemeChanged(theme.quality) |
||||
}) |
||||
} |
||||
|
||||
stopListenOnStatus (api) { |
||||
if (!api.events) return |
||||
let fn = this.iconStatus[api.profile.name] |
||||
if (fn) { |
||||
api.events.removeListener('statusChanged', fn) |
||||
delete this.iconStatus[api.profile.name] |
||||
} |
||||
} |
||||
|
||||
listenOnStatus (api) { |
||||
if (!api.events) return |
||||
|
||||
// the list of supported keys. 'none' will remove the status
|
||||
const keys = ['edited', 'succeed', 'none', 'loading', 'failed'] |
||||
const types = ['error', 'warning', 'success', 'info', ''] |
||||
const fn = (status) => { |
||||
if (!types.includes(status.type) && status.type) throw new Error(`type should be ${keys.join()}`) |
||||
if (!status.key) throw new Error(`status key should be defined`) |
||||
|
||||
if (typeof status.key === 'string' && (!keys.includes(status.key))) { |
||||
throw new Error('key should contain either number or ' + keys.join()) |
||||
} |
||||
this.setIconStatus(api.profile.name, status) |
||||
} |
||||
this.iconStatus[api.profile.name] = fn |
||||
api.events.on('statusChanged', this.iconStatus[api.profile.name]) |
||||
} |
||||
|
||||
/** |
||||
* Add an icon to the map |
||||
* @param {ModuleProfile} profile The profile of the module |
||||
*/ |
||||
addIcon ({kind, name, icon, displayName, tooltip}) { |
||||
let title = (displayName || name)// + (tooltip ? tooltip : "")
|
||||
this.icons[name] = yo` |
||||
<div |
||||
class="${css.icon}" |
||||
onclick="${(e) => { this._iconClick(name) }}" |
||||
plugin="${name}" title="${title}" > |
||||
<img class="image" src="${icon}" alt="${name}" /> |
||||
</div>` |
||||
this.iconKind[kind || 'other'].appendChild(this.icons[name]) |
||||
} |
||||
|
||||
/** |
||||
* resolve a classes list for @arg key |
||||
* @param {Object} key |
||||
* @param {Object} type |
||||
*/ |
||||
resolveClasses (key, type) { |
||||
let classes = css.status |
||||
switch (key) { |
||||
case 'succeed': |
||||
classes += ' fas fa-check-circle text-' + type + ' ' + css.statusCheck |
||||
break |
||||
case 'edited': |
||||
classes += ' fas fa-sync text-' + type + ' ' + css.statusCheck |
||||
break |
||||
case 'loading': |
||||
classes += ' fas fa-spinner text-' + type + ' ' + css.statusCheck |
||||
break |
||||
case 'failed': |
||||
classes += ' fas fa-exclamation-triangle text-' + type + ' ' + css.statusCheck |
||||
break |
||||
default: { |
||||
classes += ' badge badge-pill badge-' + type |
||||
} |
||||
} |
||||
return classes |
||||
} |
||||
|
||||
/** |
||||
* Set a new status for the @arg name |
||||
* @param {String} name |
||||
* @param {Object} status |
||||
*/ |
||||
setIconStatus (name, status) { |
||||
const el = this.icons[name] |
||||
if (!el) return |
||||
let statusEl = el.querySelector('span') |
||||
if (statusEl) { |
||||
el.removeChild(statusEl) |
||||
} |
||||
if (status.key === 'none') return // remove status
|
||||
|
||||
let text = '' |
||||
let key = '' |
||||
if (typeof status.key === 'number') { |
||||
key = status.key.toString() |
||||
text = key |
||||
} else key = helper.checkSpecialChars(status.key) ? '' : status.key |
||||
|
||||
let type = '' |
||||
if (status.type === 'error') { |
||||
type = 'danger' // to use with bootstrap
|
||||
} else type = helper.checkSpecialChars(status.type) ? '' : status.type |
||||
let title = helper.checkSpecialChars(status.title) ? '' : status.title |
||||
|
||||
el.appendChild(yo`<span
|
||||
title="${title}" |
||||
class="${this.resolveClasses(key, type)}" |
||||
aria-hidden="true" |
||||
> |
||||
${text} |
||||
</span>`) |
||||
|
||||
el.classList.add(`${css.icon}`) |
||||
} |
||||
|
||||
/** |
||||
* Remove an icon from the map |
||||
* @param {ModuleProfile} profile The profile of the module |
||||
*/ |
||||
removeIcon ({kind, name}) { |
||||
if (this.icons[name]) this.iconKind[kind || 'other'].removeChild(this.icons[name]) |
||||
} |
||||
|
||||
/** |
||||
* Remove active for the current activated icons |
||||
*/ |
||||
removeActive () { |
||||
// reset filters
|
||||
const images = this.view.querySelectorAll(`.image`) |
||||
images.forEach(function (im) { |
||||
im.style.setProperty('filter', 'invert(0.5)') |
||||
}) |
||||
|
||||
// remove active
|
||||
const currentActive = this.view.querySelector(`.${css.active}`) |
||||
if (currentActive) { |
||||
currentActive.classList.remove(css.active) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Add active for the new activated icon |
||||
* @param {string} name Name of profile of the module to activate |
||||
*/ |
||||
addActive (name) { |
||||
const themeType = globalRegistry.get('themeModule').api.currentTheme().quality |
||||
const invert = themeType === 'dark' ? 1 : 0 |
||||
const nextActive = this.view.querySelector(`[plugin="${name}"]`) |
||||
if (nextActive) { |
||||
let image = nextActive.querySelector('.image') |
||||
nextActive.classList.add(css.active) |
||||
image.style.setProperty('filter', `invert(${invert})`) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Set an icon as active |
||||
* @param {string} name Name of profile of the module to activate |
||||
*/ |
||||
select (name) { |
||||
this.removeActive() |
||||
this.addActive(name) |
||||
this.events.emit('showContent', name) |
||||
} |
||||
|
||||
onThemeChanged (themeType) { |
||||
const invert = themeType === 'dark' ? 1 : 0 |
||||
const active = this.view.querySelector(`.${css.active}`) |
||||
if (active) { |
||||
let image = active.querySelector('.image') |
||||
image.style.setProperty('filter', `invert(${invert})`) |
||||
} |
||||
} |
||||
|
||||
_iconClick (name) { |
||||
this.removeActive() |
||||
this.addActive(name) |
||||
this.events.emit('toggleContent', name) |
||||
} |
||||
|
||||
render () { |
||||
let home = yo` |
||||
<div |
||||
class="${css.homeIcon}" |
||||
onclick="${(e) => { |
||||
globalRegistry.get('appmanager').api.ensureActivated('home') |
||||
}}" |
||||
plugin="${this.homeProfile.name}" title="${this.homeProfile.displayName}" |
||||
> |
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" |
||||
width="42px" height="42px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> |
||||
<g> |
||||
<path fill="#414042" d="M70.582,428.904c0.811,0,1.622,0.285,2.437,0.853c0.811,0.571,1.218,1.34,1.218,2.314 |
||||
c0,2.277-1.059,3.496-3.168,3.656c-5.038,0.814-9.381,2.356-13.037,4.63c-3.655,2.276-6.663,5.117-9.016,8.528 |
||||
c-2.357,3.411-4.104,7.272-5.239,11.575c-1.139,4.307-1.706,8.814-1.706,13.524v32.653c0,2.273-1.139,3.411-3.412,3.411 |
||||
c-2.277,0-3.412-1.138-3.412-3.411v-74.323c0-2.273,1.135-3.411,3.412-3.411c2.273,0,3.412,1.138,3.412,3.411v15.108 |
||||
c1.462-2.437,3.206-4.752,5.239-6.945c2.029-2.193,4.264-4.143,6.701-5.848c2.437-1.706,5.076-3.085,7.919-4.143 |
||||
C64.771,429.433,67.658,428.904,70.582,428.904z"/> |
||||
<path fill="#414042" d="M137.773,427.198c5.685,0,10.966,1.181,15.839,3.534c4.874,2.356,9.055,5.482,12.55,9.381 |
||||
c3.492,3.899,6.214,8.407,8.164,13.524c1.949,5.117,2.924,10.44,2.924,15.961c0,0.976-0.366,1.79-1.097,2.438 |
||||
c-0.731,0.65-1.583,0.975-2.559,0.975h-67.987c0.487,4.226,1.584,8.285,3.29,12.184c1.706,3.899,3.937,7.312,6.701,10.234 |
||||
c2.761,2.925,6.008,5.281,9.748,7.067c3.735,1.789,7.877,2.681,12.428,2.681c12.021,0,21.36-4.79,28.023-14.377 |
||||
c0.647-1.136,1.622-1.706,2.924-1.706c2.273,0,3.412,1.139,3.412,3.412c0,0.163-0.164,0.73-0.487,1.705 |
||||
c-3.412,6.013-8.205,10.479-14.377,13.402c-6.176,2.924-12.671,4.387-19.495,4.387c-5.689,0-10.928-1.181-15.718-3.533 |
||||
c-4.793-2.354-8.936-5.483-12.428-9.382c-3.495-3.899-6.214-8.407-8.163-13.524c-1.95-5.118-2.924-10.437-2.924-15.962 |
||||
c0-5.521,0.975-10.844,2.924-15.961c1.949-5.117,4.668-9.625,8.163-13.524c3.492-3.898,7.634-7.024,12.428-9.381 |
||||
C126.846,428.379,132.084,427.198,137.773,427.198z M169.94,466.188c-0.328-4.223-1.341-8.285-3.046-12.184 |
||||
c-1.706-3.899-3.982-7.312-6.823-10.235c-2.844-2.924-6.175-5.277-9.991-7.067c-3.819-1.785-7.92-2.68-12.306-2.68 |
||||
c-4.55,0-8.692,0.895-12.428,2.68c-3.739,1.79-6.987,4.144-9.748,7.067c-2.764,2.924-4.995,6.336-6.701,10.235 |
||||
c-1.706,3.898-2.802,7.961-3.29,12.184H169.94z"/> |
||||
<path fill="#414042" d="M304.69,427.441c5.034,0,9.504,1.018,13.402,3.047c3.899,2.033,7.189,4.672,9.87,7.92 |
||||
c2.68,3.251,4.709,7.066,6.092,11.452c1.379,4.387,2.07,8.856,2.07,13.402v43.62c0,0.975-0.365,1.789-1.097,2.438 |
||||
c-0.73,0.646-1.503,0.975-2.313,0.975c-2.276,0-3.412-1.14-3.412-3.412v-43.62c0-3.571-0.529-7.104-1.584-10.6 |
||||
c-1.059-3.491-2.602-6.618-4.63-9.382c-2.033-2.761-4.592-4.953-7.677-6.58c-3.088-1.621-6.662-2.436-10.722-2.436 |
||||
c-5.2,0-9.587,1.218-13.159,3.654c-3.574,2.438-6.457,5.566-8.65,9.382c-2.193,3.819-3.818,8.042-4.874,12.672 |
||||
c-1.059,4.63-1.584,9.058-1.584,13.28v33.629c0,0.975-0.365,1.789-1.096,2.438c-0.731,0.646-1.505,0.975-2.315,0.975 |
||||
c-2.276,0-3.411-1.14-3.411-3.412v-43.62c0-3.571-0.53-7.104-1.585-10.6c-1.058-3.491-2.601-6.618-4.629-9.382 |
||||
c-2.034-2.761-4.592-4.953-7.677-6.58c-3.087-1.621-6.663-2.436-10.722-2.436c-5.037,0-9.344,0.895-12.915,2.68 |
||||
c-3.575,1.79-6.542,4.266-8.895,7.433c-2.357,3.167-4.063,6.944-5.117,11.331c-1.059,4.386-1.584,9.1-1.584,14.134v3.899v0.243 |
||||
v32.897c0,2.272-1.138,3.412-3.412,3.412c-2.276,0-3.411-1.14-3.411-3.412v-74.567c0-2.273,1.135-3.411,3.411-3.411 |
||||
c2.273,0,3.412,1.138,3.412,3.411v12.428c2.924-5.197,6.861-9.382,11.819-12.55c4.954-3.167,10.517-4.752,16.692-4.752 |
||||
c6.983,0,12.995,1.991,18.032,5.97c5.033,3.983,8.688,9.223,10.966,15.719c2.76-6.336,6.739-11.533,11.94-15.596 |
||||
C291.125,429.475,297.38,427.441,304.69,427.441z"/> |
||||
<path fill="#414042" d="M378.753,429.392c0.811,0,1.584,0.365,2.314,1.097c0.731,0.73,1.097,1.504,1.097,2.314v74.08 |
||||
c0,0.814-0.365,1.584-1.097,2.315c-0.73,0.73-1.504,1.097-2.314,1.097c-0.975,0-1.79-0.366-2.438-1.097 |
||||
c-0.65-0.731-0.975-1.501-0.975-2.315v-74.08c0-0.811,0.324-1.584,0.975-2.314C376.963,429.757,377.778,429.392,378.753,429.392z" |
||||
/> |
||||
<path fill="#414042" d="M473.34,428.66c2.273,0,3.412,1.139,3.412,3.411l-0.487,1.95l-24.368,35.334l24.368,35.577 |
||||
c0.323,0.976,0.487,1.626,0.487,1.95c0,2.272-1.139,3.412-3.412,3.412c-1.302,0-2.193-0.488-2.68-1.463l-22.906-33.384 |
||||
l-22.663,33.384c-0.814,0.975-1.79,1.463-2.924,1.463c-2.277,0-3.411-1.14-3.411-3.412c0-0.324,0.159-0.975,0.486-1.95 |
||||
l24.369-35.577l-24.369-35.334l-0.486-1.95c0-2.272,1.134-3.411,3.411-3.411c1.134,0,2.109,0.487,2.924,1.462l22.663,33.141 |
||||
l22.906-33.141C471.146,429.147,472.038,428.66,473.34,428.66z"/> |
||||
</g> |
||||
<g> |
||||
<g> |
||||
<g opacity="0.45"> |
||||
<g> |
||||
<polygon fill="#010101" points="150.734,196.212 255.969,344.508 255.969,258.387"/> |
||||
</g> |
||||
</g> |
||||
<g opacity="0.8"> |
||||
<g> |
||||
<polygon fill="#010101" points="255.969,258.387 255.969,344.508 361.267,196.212"/> |
||||
</g> |
||||
</g> |
||||
<g opacity="0.6"> |
||||
<g> |
||||
<polygon fill="#010101" points="255.969,126.781 150.733,174.611 255.969,236.818 361.204,174.611"/> |
||||
</g> |
||||
</g> |
||||
<g opacity="0.45"> |
||||
<g> |
||||
<polygon fill="#010101" points="150.734,174.612 255.969,236.818 255.969,126.782 255.969,0.001"/> |
||||
</g> |
||||
</g> |
||||
<g opacity="0.8"> |
||||
<g> |
||||
<polygon fill="#010101" points="255.969,0 255.969,126.781 255.969,236.818 361.204,174.611"/> |
||||
</g> |
||||
</g> |
||||
</g> |
||||
</g> |
||||
</svg> |
||||
|
||||
|
||||
</div>` |
||||
|
||||
this.iconKind['fileexplorer'] = yo` |
||||
<div id='fileExplorerIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['compile'] = yo` |
||||
<div id='compileIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['run'] = yo` |
||||
<div id='runIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['testing'] = yo` |
||||
<div id='testingIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['analysis'] = yo` |
||||
<div id='analysisIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['debugging'] = yo` |
||||
<div id='debuggingIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['other'] = yo` |
||||
<div id='otherIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.iconKind['settings'] = yo` |
||||
<div id='settingsIcons'> |
||||
</div> |
||||
` |
||||
|
||||
this.view = yo` |
||||
<div class=${css.icons}> |
||||
${home} |
||||
${this.iconKind['fileexplorer']} |
||||
${this.iconKind['compile']} |
||||
${this.iconKind['run']} |
||||
${this.iconKind['testing']} |
||||
${this.iconKind['analysis']} |
||||
${this.iconKind['debugging']} |
||||
${this.iconKind['other']} |
||||
${this.iconKind['settings']} |
||||
</div> |
||||
` |
||||
return this.view |
||||
} |
||||
} |
||||
|
||||
module.exports = VerticalIconComponent |
||||
|
||||
const css = csjs` |
||||
.homeIcon { |
||||
display: block; |
||||
width: 42px; |
||||
height: 42px; |
||||
margin-bottom: 20px; |
||||
margin-left: -5px; |
||||
cursor: pointer; |
||||
} |
||||
.homeIcon svg path { |
||||
fill: var(--primary); |
||||
} |
||||
.homeIcon svg polygon { |
||||
fill: var(--primary); |
||||
} |
||||
.icons { |
||||
margin-left: 10px; |
||||
margin-top: 15px; |
||||
} |
||||
.icon { |
||||
cursor: pointer; |
||||
margin-bottom: 12px; |
||||
width: 36px; |
||||
height: 36px; |
||||
padding: 3px; |
||||
position: relative; |
||||
border-radius: 8px; |
||||
} |
||||
.icon img { |
||||
width: 28px; |
||||
height: 28px; |
||||
padding: 4px; |
||||
filter: invert(0.5); |
||||
} |
||||
.image { |
||||
} |
||||
.icon svg { |
||||
width: 28px; |
||||
height: 28px; |
||||
padding: 4px; |
||||
} |
||||
.icon[title='Settings'] { |
||||
position: absolute; |
||||
bottom: 0; |
||||
} |
||||
.status { |
||||
position: absolute; |
||||
bottom: 0; |
||||
right: 0; |
||||
} |
||||
.statusCheck { |
||||
font-size: 1.2em; |
||||
} |
||||
.statusWithBG |
||||
border-radius: 8px; |
||||
background-color: var(--danger); |
||||
color: var(--light); |
||||
font-size: 12px; |
||||
height: 15px; |
||||
text-align: center; |
||||
font-weight: bold; |
||||
padding-left: 5px; |
||||
padding-right: 5px; |
||||
} |
||||
` |
@ -0,0 +1,39 @@ |
||||
'use strict' |
||||
const SourceHighlighter = require('./sourceHighlighter') |
||||
|
||||
import { EditorApi } from 'remix-plugin' |
||||
|
||||
const profile = { |
||||
displayName: 'source highlighters', |
||||
name: 'editor', |
||||
description: 'service - highlight source code' |
||||
} |
||||
|
||||
// EditorApi:
|
||||
// - methods: ['highlight', 'discardHighlight'],
|
||||
|
||||
class SourceHighlighters extends EditorApi { |
||||
|
||||
constructor () { |
||||
super(profile) |
||||
this.highlighters = {} |
||||
} |
||||
|
||||
highlight (position, filePath, hexColor) { |
||||
const { from } = this.currentRequest |
||||
try { |
||||
if (!this.highlighters[from]) this.highlighters[from] = new SourceHighlighter() |
||||
this.highlighters[from].currentSourceLocation(null) |
||||
this.highlighters[from].currentSourceLocationFromfileName(position, filePath, hexColor) |
||||
} catch (e) { |
||||
throw e |
||||
} |
||||
} |
||||
|
||||
discardHighlight () { |
||||
const { from } = this.currentRequest |
||||
if (this.highlighters[from]) this.highlighters[from].currentSourceLocation(null) |
||||
} |
||||
} |
||||
|
||||
module.exports = SourceHighlighters |
@ -0,0 +1,106 @@ |
||||
let globalRegistry = require('../../global/registry') |
||||
import { BaseApi } from 'remix-plugin' |
||||
|
||||
var yo = require('yo-yo') |
||||
var modalDialog = require('../ui/modaldialog') |
||||
var modalDialogCustom = require('../ui/modal-dialog-custom') |
||||
|
||||
var csjs = require('csjs-inject') |
||||
|
||||
var css = csjs` |
||||
.dialog { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
.dialogParagraph { |
||||
margin-bottom: 2em; |
||||
word-break: break-word; |
||||
} |
||||
` |
||||
|
||||
const profile = { |
||||
name: 'remixd', |
||||
methods: [], |
||||
events: [], |
||||
description: 'using Remixd daemon, allow to access file system', |
||||
kind: 'other' |
||||
} |
||||
|
||||
export class RemixdHandle extends BaseApi { |
||||
constructor (fileSystemExplorer, locahostProvider) { |
||||
super(profile) |
||||
this.fileSystemExplorer = fileSystemExplorer |
||||
this.locahostProvider = locahostProvider |
||||
} |
||||
|
||||
deactivate () { |
||||
this.locahostProvider.close((error) => { |
||||
if (error) console.log(error) |
||||
}) |
||||
} |
||||
|
||||
activate () { |
||||
this.connectToLocalhost() |
||||
} |
||||
|
||||
canceled () { |
||||
let appManager = globalRegistry.get('appmanager').api |
||||
appManager.ensureDeactivated('remixd') |
||||
} |
||||
|
||||
/** |
||||
* connect to localhost if no connection and render the explorer |
||||
* disconnect from localhost if connected and remove the explorer |
||||
* |
||||
* @param {String} txHash - hash of the transaction |
||||
*/ |
||||
connectToLocalhost () { |
||||
if (this.locahostProvider.isConnected()) { |
||||
this.locahostProvider.close((error) => { |
||||
if (error) console.log(error) |
||||
}) |
||||
} else { |
||||
modalDialog( |
||||
'Connect to localhost', |
||||
remixdDialog(), |
||||
{ label: 'Connect', |
||||
fn: () => { |
||||
this.locahostProvider.init((error) => { |
||||
if (error) { |
||||
console.log(error) |
||||
modalDialogCustom.alert( |
||||
'Cannot connect to the remixd daemon.' + |
||||
'Please make sure you have the remixd running in the background.' |
||||
) |
||||
this.canceled() |
||||
} else { |
||||
this.fileSystemExplorer.ensureRoot() |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
{ label: 'Cancel', |
||||
fn: () => { |
||||
this.canceled() |
||||
} |
||||
} |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
function remixdDialog () { |
||||
return yo` |
||||
<div class=${css.dialog}> |
||||
<div class=${css.dialogParagraph}>Interact with your file system from Remix. Click connect and find shared folder in the Remix file explorer (under localhost). |
||||
Before you get started, check out <a target="_blank" href="https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html">Tutorial_remixd_filesystem</a> |
||||
to find out how to run Remixd. |
||||
</div> |
||||
<div class=${css.dialogParagraph}>Connection will start a session between <em>${window.location.href}</em> and your local file system <i>ws://127.0.0.1:65520</i> |
||||
so please make sure your system is secured enough (port 65520 neither opened nor forwarded). |
||||
<i class="fas fa-link"></i> will show you current connection status. |
||||
</div> |
||||
<div class=${css.dialogParagraph}>This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.</div> |
||||
</div> |
||||
` |
||||
} |
@ -1,164 +0,0 @@ |
||||
const yo = require('yo-yo') |
||||
const csjs = require('csjs-inject') |
||||
const EventManager = require('../../lib/events') |
||||
|
||||
var globalRegistry = require('../../global/registry') |
||||
|
||||
const styleguide = require('../ui/styles-guide/theme-chooser') |
||||
const TabbedMenu = require('../tabs/tabbed-menu') |
||||
const PluginTab = require('../tabs/plugin-tab') |
||||
const DraggableContent = require('../ui/draggableContent') |
||||
|
||||
const styles = styleguide.chooser() |
||||
|
||||
const css = csjs` |
||||
.righthandpanel { |
||||
display : flex; |
||||
flex-direction : column; |
||||
top : 0; |
||||
right : 0; |
||||
bottom : 0; |
||||
box-sizing : border-box; |
||||
overflow : hidden; |
||||
height : 100%; |
||||
} |
||||
.header { |
||||
height : 100%; |
||||
} |
||||
.dragbar { |
||||
position : absolute; |
||||
width : ${styles.rightPanel.dragbarWidth}; |
||||
top : 3em; |
||||
bottom : 0; |
||||
cursor : col-resize; |
||||
background-color : ${styles.rightPanel.dragbarBackgroundColor}; |
||||
z-index : 999; |
||||
border-left : 2px solid ${styles.rightPanel.bar_Dragging}; |
||||
} |
||||
.ghostbar { |
||||
width : 3px; |
||||
background-color : ${styles.rightPanel.bar_Ghost}; |
||||
opacity : 0.5; |
||||
position : absolute; |
||||
cursor : col-resize; |
||||
z-index : 9999; |
||||
top : 0; |
||||
bottom : 0; |
||||
} |
||||
` |
||||
|
||||
class RighthandPanel { |
||||
constructor ({pluginManager, tabs}, localRegistry) { |
||||
const self = this |
||||
self._components = {} |
||||
self._components.registry = localRegistry || globalRegistry |
||||
self._components.registry.put({api: this, name: 'righthandpanel'}) |
||||
self.event = new EventManager() |
||||
self._view = { |
||||
element: null, |
||||
tabbedMenu: null, |
||||
tabbedMenuViewport: null, |
||||
dragbar: null |
||||
} |
||||
|
||||
var tabbedMenu = new TabbedMenu(self._components.registry) |
||||
|
||||
self._components = { |
||||
tabbedMenu: tabbedMenu, |
||||
tabs |
||||
} |
||||
|
||||
self._components.tabs.settings.event.register('plugin-loadRequest', json => { |
||||
self.loadPlugin(json) |
||||
}) |
||||
|
||||
self.loadPlugin = function (json) { |
||||
var modal = new DraggableContent(() => { |
||||
pluginManager.unregister(json) |
||||
}) |
||||
var tab = new PluginTab(json) |
||||
var content = tab.render() |
||||
document.querySelector('body').appendChild(modal.render(json.title, json.url, content)) |
||||
pluginManager.register(json, modal, content) |
||||
} |
||||
|
||||
self._view.dragbar = yo`<div id="dragbar" class=${css.dragbar}></div>` |
||||
self._view.element = yo` |
||||
<div id="righthand-panel" class=${css.righthandpanel}> |
||||
${self._view.dragbar} |
||||
<div id="header" class=${css.header}> |
||||
${self._components.tabbedMenu.render()} |
||||
${self._components.tabbedMenu.renderViewport()} |
||||
</div> |
||||
</div>` |
||||
|
||||
const { compile, run, settings, analysis, debug, support, test } = tabs |
||||
self._components.tabbedMenu.addTab('Compile', 'compileView', compile.render()) |
||||
self._components.tabbedMenu.addTab('Run', 'runView', run.render()) |
||||
self._components.tabbedMenu.addTab('Analysis', 'staticanalysisView', analysis.render()) |
||||
self._components.tabbedMenu.addTab('Testing', 'testView', test.render()) |
||||
self._components.tabbedMenu.addTab('Debugger', 'debugView', debug.render()) |
||||
self._components.tabbedMenu.addTab('Settings', 'settingsView', settings.render()) |
||||
self._components.tabbedMenu.addTab('Support', 'supportView', support.render()) |
||||
self._components.tabbedMenu.selectTabByTitle('Compile') |
||||
} |
||||
|
||||
render () { |
||||
const self = this |
||||
if (self._view.element) return self._view.element |
||||
return self._view.element |
||||
} |
||||
|
||||
debugger () { |
||||
return this._components.tabs.debug.debugger() |
||||
} |
||||
|
||||
focusOn (x) { |
||||
if (this._components.tabbedMenu) this._components.tabbedMenu.selectTabByClassName(x) |
||||
} |
||||
|
||||
init () { |
||||
// @TODO: init is for resizable drag bar only and should be refactored in the future
|
||||
const self = this |
||||
const limit = 60 |
||||
self._view.dragbar.addEventListener('mousedown', mousedown) |
||||
const ghostbar = yo`<div class=${css.ghostbar}></div>` |
||||
function mousedown (event) { |
||||
event.preventDefault() |
||||
if (event.which === 1) { |
||||
moveGhostbar(event) |
||||
document.body.appendChild(ghostbar) |
||||
document.addEventListener('mousemove', moveGhostbar) |
||||
document.addEventListener('mouseup', removeGhostbar) |
||||
document.addEventListener('keydown', cancelGhostbar) |
||||
} |
||||
} |
||||
function cancelGhostbar (event) { |
||||
if (event.keyCode === 27) { |
||||
document.body.removeChild(ghostbar) |
||||
document.removeEventListener('mousemove', moveGhostbar) |
||||
document.removeEventListener('mouseup', removeGhostbar) |
||||
document.removeEventListener('keydown', cancelGhostbar) |
||||
} |
||||
} |
||||
function getPosition (event) { |
||||
const lhp = window['filepanel'].offsetWidth |
||||
const max = document.body.offsetWidth - limit |
||||
var newpos = (event.pageX > max) ? max : event.pageX |
||||
newpos = (newpos > (lhp + limit)) ? newpos : lhp + limit |
||||
return newpos |
||||
} |
||||
function moveGhostbar (event) { // @NOTE VERTICAL ghostbar
|
||||
ghostbar.style.left = getPosition(event) + 'px' |
||||
} |
||||
function removeGhostbar (event) { |
||||
document.body.removeChild(ghostbar) |
||||
document.removeEventListener('mousemove', moveGhostbar) |
||||
document.removeEventListener('mouseup', removeGhostbar) |
||||
document.removeEventListener('keydown', cancelGhostbar) |
||||
self.event.trigger('resize', [document.body.offsetWidth - getPosition(event)]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
module.exports = RighthandPanel |
@ -1,179 +0,0 @@ |
||||
var yo = require('yo-yo') |
||||
var csjs = require('csjs-inject') |
||||
var styleGuide = require('../../ui/styles-guide/theme-chooser') |
||||
var styles = styleGuide.chooser() |
||||
|
||||
var cssTabs = yo` |
||||
<style> |
||||
#files .file { |
||||
padding: 0 0.6em; |
||||
box-sizing: border-box; |
||||
background-color: ${styles.editor.backgroundColor_Tabs_Highlights}; |
||||
cursor: pointer; |
||||
margin-right: 10px; |
||||
margin-top: 5px; |
||||
position: relative; |
||||
display: table-cell; |
||||
text-align: center; |
||||
vertical-align: middle; |
||||
color: ${styles.editor.text_Teriary}; |
||||
} |
||||
#files .file.active { |
||||
color: ${styles.editor.text_Primary}; |
||||
font-weight: bold; |
||||
border-bottom: 0 none; |
||||
padding-right: 1.5em; |
||||
} |
||||
#files .file .remove { |
||||
font-size: 12px; |
||||
display: flex; |
||||
color: ${styles.editor.text_Primary}; |
||||
position: absolute; |
||||
top: -7px; |
||||
right: 5px; |
||||
display: none; |
||||
} |
||||
#files .file input { |
||||
background-color: ${styles.colors.transparent}; |
||||
border: 0 none; |
||||
border-bottom: 1px dotted ${styles.editor.text_Primary}; |
||||
line-height: 1em; |
||||
margin: 0.5em 0; |
||||
} |
||||
#files .file.active .remove { |
||||
display: inline-block; |
||||
color: ${styles.editor.text_Primary}; |
||||
} |
||||
</style> |
||||
` |
||||
|
||||
var css = csjs` |
||||
.editorpanel { |
||||
display : flex; |
||||
flex-direction : column; |
||||
height : 100%; |
||||
} |
||||
.tabsbar { |
||||
background-color : ${styles.editor.backgroundColor_Panel}; |
||||
display : flex; |
||||
overflow : hidden; |
||||
height : 30px; |
||||
} |
||||
.tabs { |
||||
position : relative; |
||||
display : flex; |
||||
margin : 0; |
||||
left : 10px; |
||||
margin-right : 10px; |
||||
width : 100%; |
||||
overflow : hidden; |
||||
} |
||||
.files { |
||||
display : flex; |
||||
position : relative; |
||||
list-style : none; |
||||
margin : 0; |
||||
font-size : 15px; |
||||
height : 2.5em; |
||||
box-sizing : border-box; |
||||
line-height : 2em; |
||||
top : 0; |
||||
border-bottom : 0 none; |
||||
} |
||||
.changeeditorfontsize { |
||||
margin : 0; |
||||
font-size : 9px; |
||||
margin-top : 0.5em; |
||||
} |
||||
.changeeditorfontsize i { |
||||
cursor : pointer; |
||||
display : block; |
||||
color : ${styles.editor.icon_Color_Editor}; |
||||
} |
||||
.changeeditorfontsize i { |
||||
cursor : pointer; |
||||
} |
||||
.changeeditorfontsize i:hover { |
||||
color : ${styles.editor.icon_HoverColor_Editor}; |
||||
} |
||||
.buttons { |
||||
display : flex; |
||||
flex-direction : row; |
||||
justify-content : space-around; |
||||
align-items : center; |
||||
min-width : 45px; |
||||
} |
||||
.toggleLHP { |
||||
display : flex; |
||||
padding : 10px; |
||||
width : 100%; |
||||
font-weight : bold; |
||||
color : ${styles.leftPanel.icon_Color_TogglePanel}; |
||||
} |
||||
.toggleLHP i { |
||||
cursor : pointer; |
||||
font-size : 14px; |
||||
font-weight : bold; |
||||
} |
||||
.toggleLHP i:hover { |
||||
color : ${styles.leftPanel.icon_HoverColor_TogglePanel}; |
||||
} |
||||
.scroller { |
||||
position : absolute; |
||||
z-index : 999; |
||||
text-align : center; |
||||
cursor : pointer; |
||||
vertical-align : middle; |
||||
background-color : ${styles.colors.general_BackgroundColor}; |
||||
height : 100%; |
||||
font-size : 1.3em; |
||||
color : orange; |
||||
} |
||||
.scrollerright { |
||||
right : 0; |
||||
margin-right : 15px; |
||||
} |
||||
.scrollerleft { |
||||
left : 0; |
||||
} |
||||
.toggleRHP { |
||||
margin : 0.5em; |
||||
font-weight : bold; |
||||
color : ${styles.rightPanel.icon_Color_TogglePanel}; |
||||
right : 0; |
||||
} |
||||
.toggleRHP i { |
||||
cursor : pointer; |
||||
font-size : 14px; |
||||
font-weight : bold; |
||||
} |
||||
.toggleRHP i:hover { |
||||
color : ${styles.rightPanel.icon_HoverColor_TogglePanel}; |
||||
} |
||||
.show { |
||||
opacity : 1; |
||||
transition : .3s opacity ease-in; |
||||
} |
||||
.hide { |
||||
opacity : 0; |
||||
pointer-events : none; |
||||
transition : .3s opacity ease-in; |
||||
} |
||||
.content { |
||||
position : relative; |
||||
display : flex; |
||||
flex-direction : column; |
||||
height : 100%; |
||||
width : 100%; |
||||
} |
||||
.contextviewcontainer{ |
||||
width : 100%; |
||||
height : 20px; |
||||
background-color : ${styles.editor.backgroundColor_Tabs_Highlights}; |
||||
} |
||||
` |
||||
|
||||
module.exports = { |
||||
cssTabs: cssTabs, |
||||
css: css |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,177 @@ |
||||
var yo = require('yo-yo') |
||||
var $ = require('jquery') |
||||
const EventEmitter = require('events') |
||||
|
||||
require('remix-tabs') |
||||
|
||||
export class TabProxy { |
||||
constructor (fileManager, editor, appStore, appManager) { |
||||
this.event = new EventEmitter() |
||||
this.fileManager = fileManager |
||||
this.appManager = appManager |
||||
this.editor = editor |
||||
this.data = {} |
||||
this._view = {} |
||||
this._handlers = {} |
||||
|
||||
fileManager.events.on('fileRemoved', (name) => { |
||||
this.removeTab(name) |
||||
}) |
||||
|
||||
fileManager.events.on('fileClosed', (name) => { |
||||
this.removeTab(name) |
||||
}) |
||||
|
||||
fileManager.events.on('currentFileChanged', (file) => { |
||||
if (this._handlers[file]) { |
||||
this._view.filetabs.activateTab(file) |
||||
return |
||||
} |
||||
this.addTab(file, '', () => { |
||||
this.fileManager.switchFile(file) |
||||
this.event.emit('switchFile', file) |
||||
}, |
||||
() => { |
||||
this.fileManager.closeFile(file) |
||||
this.event.emit('closeFile', file) |
||||
}) |
||||
}) |
||||
|
||||
fileManager.events.on('fileRenamed', (oldName, newName) => { |
||||
this.removeTab(oldName) |
||||
this.addTab(newName, '', () => { |
||||
this.fileManager.switchFile(newName) |
||||
this.event.emit('switchFile', newName) |
||||
}, |
||||
() => { |
||||
this.fileManager.closeFile(newName) |
||||
this.event.emit('closeFile', newName) |
||||
}) |
||||
}) |
||||
|
||||
appStore.event.on('activate', (name) => { |
||||
const { profile } = appStore.getOne(name) |
||||
if (profile.location === 'mainPanel') { |
||||
this.addTab( |
||||
name, |
||||
profile.displayName, |
||||
() => this.event.emit('switchApp', name), |
||||
() => { |
||||
this.event.emit('closeApp', name) |
||||
this.appManager.deactivateOne(name) |
||||
} |
||||
) |
||||
this.switchTab(name) |
||||
} |
||||
}) |
||||
|
||||
appStore.event.on('deactivate', (name) => { |
||||
this.removeTab(name) |
||||
}) |
||||
} |
||||
|
||||
switchTab (tabName) { |
||||
if (this._handlers[tabName]) { |
||||
this._handlers[tabName].switchTo() |
||||
this._view.filetabs.activateTab(tabName) |
||||
} |
||||
} |
||||
|
||||
switchNextTab () { |
||||
const active = this._view.filetabs.active |
||||
if (active && this._handlers[active]) { |
||||
const handlers = Object.keys(this._handlers) |
||||
let i = handlers.indexOf(active) |
||||
if (i >= 0) { |
||||
i = handlers[i + 1] ? i + 1 : 0 |
||||
this.switchTab(handlers[i]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
switchPreviousTab () { |
||||
const active = this._view.filetabs.active |
||||
if (active && this._handlers[active]) { |
||||
const handlers = Object.keys(this._handlers) |
||||
let i = handlers.indexOf(active) |
||||
if (i >= 0) { |
||||
i = handlers[i - 1] ? i - 1 : handlers.length - 1 |
||||
this.switchTab(handlers[i]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
showTab (name) { |
||||
this._view.filetabs.activateTab(name) |
||||
} |
||||
|
||||
addTab (name, title, switchTo, close, kind) { |
||||
if (this._handlers[name]) return |
||||
|
||||
var slash = name.split('/') |
||||
if (!title) { |
||||
title = name.indexOf('/') !== -1 ? slash[slash.length - 1] : name |
||||
} |
||||
this._view.filetabs.addTab({ |
||||
id: name, |
||||
title, |
||||
icon: '', |
||||
tooltip: name |
||||
}) |
||||
this._handlers[name] = { switchTo, close } |
||||
} |
||||
|
||||
removeTab (name) { |
||||
this._view.filetabs.removeTab(name) |
||||
delete this._handlers[name] |
||||
} |
||||
|
||||
addHandler (type, fn) { |
||||
this.handlers[type] = fn |
||||
} |
||||
|
||||
renderTabsbar () { |
||||
this._view.filetabs = yo`<remix-tabs></remix-tabs>` |
||||
this._view.filetabs.addEventListener('tabClosed', (event) => { |
||||
if (this._handlers[event.detail]) this._handlers[event.detail].close() |
||||
}) |
||||
this._view.filetabs.addEventListener('tabActivated', (event) => { |
||||
if (this._handlers[event.detail]) this._handlers[event.detail].switchTo() |
||||
}) |
||||
|
||||
this._view.filetabs.canAdd = false |
||||
|
||||
this._view.tabs = yo` |
||||
<div style="width: 100%; height: 100%;"> |
||||
${this._view.filetabs} |
||||
</div> |
||||
` |
||||
let tabsbar = yo` |
||||
<div class="d-flex align-items-center" style="max-height: 35px; height: 100%"> |
||||
${this._view.tabs} |
||||
</div> |
||||
` |
||||
|
||||
// tabs
|
||||
var $filesEl = $(this._view.filetabs) |
||||
|
||||
// Switch tab
|
||||
var self = this |
||||
$filesEl.on('click', '.file:not(.active)', function (ev) { |
||||
ev.preventDefault() |
||||
var name = $(this).find('.name').text() |
||||
self._handlers[name].switchTo() |
||||
return false |
||||
}) |
||||
|
||||
// Remove current tab
|
||||
$filesEl.on('click', '.file .remove', function (ev) { |
||||
ev.preventDefault() |
||||
var name = $(this).parent().find('.name').text() |
||||
self._handlers[name].close() |
||||
return false |
||||
}) |
||||
|
||||
return tabsbar |
||||
} |
||||
} |
@ -1,94 +0,0 @@ |
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ |
||||
'use strict'; |
||||
|
||||
var _createClass = function () { |
||||
function defineProperties(target, props) { |
||||
for (var i = 0; i < props.length; i++) { |
||||
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor); |
||||
} |
||||
}return function (Constructor, protoProps, staticProps) { |
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor; |
||||
}; |
||||
}(); |
||||
|
||||
function _classCallCheck(instance, Constructor) { |
||||
if (!(instance instanceof Constructor)) { |
||||
throw new TypeError("Cannot call a class as a function"); |
||||
} |
||||
} |
||||
|
||||
var RemixExtension = function () { |
||||
function RemixExtension() { |
||||
var _this = this; |
||||
|
||||
_classCallCheck(this, RemixExtension); |
||||
|
||||
this._notifications = {}; |
||||
this._pendingRequests = {}; |
||||
this._id = 0; |
||||
window.addEventListener('message', function (event) { |
||||
return _this._newMessage(event); |
||||
}, false); |
||||
} |
||||
|
||||
_createClass(RemixExtension, [{ |
||||
key: 'listen', |
||||
value: function listen(key, type, callback) { |
||||
if (!this._notifications[key]) this._notifications[key] = {}; |
||||
this._notifications[key][type] = callback; |
||||
} |
||||
}, { |
||||
key: 'call', |
||||
value: function call(key, type, params, callback) { |
||||
this._id++; |
||||
this._pendingRequests[this._id] = callback; |
||||
window.parent.postMessage(JSON.stringify({ |
||||
action: 'request', |
||||
key: key, |
||||
type: type, |
||||
value: params, |
||||
id: this._id |
||||
}), '*'); |
||||
} |
||||
}, { |
||||
key: '_newMessage', |
||||
value: function _newMessage(event) { |
||||
if (!event.data) return; |
||||
if (typeof event.data !== 'string') return; |
||||
|
||||
var msg; |
||||
try { |
||||
msg = JSON.parse(event.data); |
||||
} catch (e) { |
||||
return console.log('unable to parse data'); |
||||
} |
||||
var _msg = msg, |
||||
action = _msg.action, |
||||
key = _msg.key, |
||||
type = _msg.type, |
||||
value = _msg.value; |
||||
|
||||
if (action === 'notification') { |
||||
if (this._notifications[key] && this._notifications[key][type]) { |
||||
this._notifications[key][type](value); |
||||
} |
||||
} else if (action === 'response') { |
||||
var _msg2 = msg, |
||||
id = _msg2.id, |
||||
error = _msg2.error; |
||||
|
||||
if (this._pendingRequests[id]) { |
||||
this._pendingRequests[id](error, value); |
||||
delete this._pendingRequests[id]; |
||||
} |
||||
} |
||||
} |
||||
}]); |
||||
|
||||
return RemixExtension; |
||||
}(); |
||||
|
||||
if (window) window.RemixExtension = RemixExtension; |
||||
if (module && module.exports) module.exports = RemixExtension; |
||||
|
||||
},{}]},{},[1]); |
@ -1,55 +0,0 @@ |
||||
'use strict' |
||||
|
||||
class RemixExtension { |
||||
constructor () { |
||||
this._notifications = {} |
||||
this._pendingRequests = {} |
||||
this._id = 0 |
||||
window.addEventListener('message', (event) => this._newMessage(event), false) |
||||
} |
||||
|
||||
listen (key, type, callback) { |
||||
if (!this._notifications[key]) this._notifications[key] = {} |
||||
this._notifications[key][type] = callback |
||||
} |
||||
|
||||
call (key, type, params, callback) { |
||||
this._id++ |
||||
this._pendingRequests[this._id] = callback |
||||
window.parent.postMessage(JSON.stringify({ |
||||
action: 'request', |
||||
key, |
||||
type, |
||||
value: params, |
||||
id: this._id |
||||
}), '*') |
||||
} |
||||
|
||||
_newMessage (event) { |
||||
if (!event.data) return |
||||
if (typeof event.data !== 'string') return |
||||
|
||||
var msg |
||||
try { |
||||
msg = JSON.parse(event.data) |
||||
} catch (e) { |
||||
return console.log('unable to parse data') |
||||
} |
||||
const {action, key, type, value} = msg |
||||
if (action === 'notification') { |
||||
if (this._notifications[key] && this._notifications[key][type]) { |
||||
this._notifications[key][type](value) |
||||
} |
||||
} else if (action === 'response') { |
||||
const {id, error} = msg |
||||
if (this._pendingRequests[id]) { |
||||
this._pendingRequests[id](error, value) |
||||
delete this._pendingRequests[id] |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (window) window.RemixExtension = RemixExtension |
||||
if (module && module.exports) module.exports = RemixExtension |
||||
|
@ -1,71 +0,0 @@ |
||||
{ |
||||
"name": "remix-extension", |
||||
"version": "0.0.1", |
||||
"description": "Ethereum IDE and tools for the web", |
||||
"contributors": [ |
||||
{ |
||||
"name": "Yann Levreau", |
||||
"email": "yann@ethdev.com" |
||||
} |
||||
], |
||||
"main": "./index.js", |
||||
"dependencies": { |
||||
"babel-eslint": "^7.1.1", |
||||
"babel-plugin-transform-object-assign": "^6.22.0", |
||||
"babel-preset-es2015": "^6.24.0", |
||||
"babelify": "^7.3.0", |
||||
"standard": "^7.0.1", |
||||
"tape": "^4.6.0" |
||||
}, |
||||
"scripts": { |
||||
"browserify": "browserify index.js -o bundle.js" |
||||
}, |
||||
"standard": { |
||||
"ignore": [ |
||||
"node_modules/*" |
||||
], |
||||
"parser": "babel-eslint" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/ethereum/remix-ide.git" |
||||
}, |
||||
"author": "cpp-ethereum team", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/ethereum/remix-ide/issues" |
||||
}, |
||||
"homepage": "https://github.com/ethereum/remix-ide#readme", |
||||
"browserify": { |
||||
"transform": [ |
||||
[ |
||||
"babelify", |
||||
{ |
||||
"plugins": [ |
||||
[ |
||||
"fast-async", |
||||
{ |
||||
"runtimePatten": null, |
||||
"compiler": { |
||||
"promises": true, |
||||
"es7": true, |
||||
"noRuntime": true, |
||||
"wrapAwait": true |
||||
} |
||||
} |
||||
], |
||||
"transform-object-assign" |
||||
] |
||||
} |
||||
], |
||||
[ |
||||
"babelify", |
||||
{ |
||||
"presets": [ |
||||
"es2015" |
||||
] |
||||
} |
||||
] |
||||
] |
||||
} |
||||
} |
@ -1,53 +0,0 @@ |
||||
plugin api |
||||
|
||||
# current APIs: |
||||
|
||||
## 1) notifications |
||||
|
||||
### app (key: app) |
||||
|
||||
- unfocus `[]` |
||||
- focus `[]` |
||||
|
||||
### compiler (key: compiler) |
||||
|
||||
- compilationFinished `[success (bool), data (obj), source (obj)]` |
||||
- compilationData `[compilationResult]` |
||||
|
||||
### transaction listener (key: txlistener) |
||||
|
||||
- newTransaction `tx (obj)` |
||||
|
||||
## 2) interactions |
||||
|
||||
### app |
||||
|
||||
- getExecutionContextProvider `@return {String} provider (injected | web3 | vm)` |
||||
- getProviderEndpoint `@return {String} url` |
||||
- updateTitle `@param {String} title` |
||||
- detectNetWork `@return {Object} {name, id}` |
||||
- addProvider `@param {String} name, @param {String} url` |
||||
- removeProvider `@return {String} name` |
||||
|
||||
### config |
||||
|
||||
- setConfig `@param {String} path, @param {String} content` |
||||
- getConfig `@param {String} path` |
||||
- removeConfig `@param {String} path` |
||||
|
||||
### compiler |
||||
- getCompilationResult `@return {Object} compilation result` |
||||
|
||||
### udapp (only VM) |
||||
- runTx `@param {Object} tx` |
||||
- getAccounts `@return {Array} acccounts` |
||||
- createVMAccount `@param {String} privateKey, @param {String} balance (hex)` |
||||
|
||||
### editor |
||||
- getFilesFromPath `@param {Array} [path]` |
||||
- getCurrentFile `@return {String} path` |
||||
- getFile `@param {String} path` |
||||
- setFile `@param {String} path, @param {String} content` |
||||
- highlight `@param {Object} lineColumnPos {start: {line, column}, end: {line, column}}, @param {String} path, @param {String} hexColor` |
||||
- discardHighlight |
||||
|
@ -1,150 +0,0 @@ |
||||
'use strict' |
||||
var executionContext = require('../../execution-context') |
||||
var SourceHighlighter = require('../editor/sourceHighlighter') |
||||
/* |
||||
Defines available API. `key` / `type` |
||||
*/ |
||||
module.exports = (pluginManager, fileProviders, fileManager, compilesrArtefacts, udapp) => { |
||||
let highlighters = {} |
||||
return { |
||||
app: { |
||||
getExecutionContextProvider: (mod, cb) => { |
||||
cb(null, executionContext.getProvider()) |
||||
}, |
||||
getProviderEndpoint: (mod, cb) => { |
||||
if (executionContext.getProvider() === 'web3') { |
||||
cb(null, executionContext.web3().currentProvider.host) |
||||
} else { |
||||
cb('no endpoint: current provider is either injected or vm') |
||||
} |
||||
}, |
||||
updateTitle: (mod, title, cb) => { |
||||
pluginManager.plugins[mod].modal.setTitle(title) |
||||
if (cb) cb() |
||||
}, |
||||
detectNetWork: (mod, cb) => { |
||||
executionContext.detectNetwork((error, network) => { |
||||
cb(error, network) |
||||
}) |
||||
}, |
||||
addProvider: (mod, name, url, cb) => { |
||||
executionContext.addProvider({ name, url }) |
||||
cb() |
||||
}, |
||||
removeProvider: (mod, name, cb) => { |
||||
executionContext.removeProvider(name) |
||||
cb() |
||||
} |
||||
}, |
||||
config: { |
||||
setConfig: (mod, path, content, cb) => { |
||||
fileProviders['config'].set(mod + '/' + path, content) |
||||
cb() |
||||
}, |
||||
getConfig: (mod, path, cb) => { |
||||
cb(null, fileProviders['config'].get(mod + '/' + path)) |
||||
}, |
||||
removeConfig: (mod, path, cb) => { |
||||
cb(null, fileProviders['config'].remove(mod + '/' + path)) |
||||
if (cb) cb() |
||||
} |
||||
}, |
||||
compiler: { |
||||
getCompilationResult: (mod, cb) => { |
||||
cb(null, compilesrArtefacts['__last']) |
||||
}, |
||||
sendCompilationResult: (mod, file, source, languageVersion, data, cb) => { |
||||
pluginManager.receivedDataFrom('sendCompilationResult', mod, [file, source, languageVersion, data]) |
||||
} |
||||
}, |
||||
udapp: { |
||||
runTx: (mod, tx, cb) => { |
||||
executionContext.detectNetwork((error, network) => { |
||||
if (error) return cb(error) |
||||
if (network.name === 'Main' && network.id === '1') { |
||||
return cb('It is not allowed to make this action against mainnet') |
||||
} |
||||
udapp.silentRunTx(tx, (error, result) => { |
||||
if (error) return cb(error) |
||||
cb(null, { |
||||
transactionHash: result.transactionHash, |
||||
status: result.result.status, |
||||
gasUsed: '0x' + result.result.gasUsed.toString('hex'), |
||||
error: result.result.vm.exceptionError, |
||||
return: result.result.vm.return ? '0x' + result.result.vm.return.toString('hex') : '0x', |
||||
createdAddress: result.result.createdAddress ? '0x' + result.result.createdAddress.toString('hex') : undefined |
||||
}) |
||||
}) |
||||
}) |
||||
}, |
||||
getAccounts: (mod, cb) => { |
||||
executionContext.detectNetwork((error, network) => { |
||||
if (error) return cb(error) |
||||
if (network.name === 'Main' && network.id === '1') { |
||||
return cb('It is not allowed to make this action against mainnet') |
||||
} |
||||
udapp.getAccounts(cb) |
||||
}) |
||||
}, |
||||
createVMAccount: (mod, privateKey, balance, cb) => { |
||||
if (executionContext.getProvider() !== 'vm') return cb('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed') |
||||
udapp.createVMAccount(privateKey, balance, (error, address) => { |
||||
cb(error, address) |
||||
}) |
||||
} |
||||
}, |
||||
editor: { |
||||
getFilesFromPath: (mod, path, cb) => { |
||||
fileManager.filesFromPath(path, cb) |
||||
}, |
||||
getCurrentFile: (mod, cb) => { |
||||
var path = fileManager.currentFile() |
||||
if (!path) { |
||||
cb('no file selected') |
||||
} else { |
||||
cb(null, path) |
||||
} |
||||
}, |
||||
getFile: (mod, path, cb) => { |
||||
var provider = fileManager.fileProviderOf(path) |
||||
if (provider) { |
||||
// TODO add approval to user for external plugin to get the content of the given `path`
|
||||
provider.get(path, (error, content) => { |
||||
cb(error, content) |
||||
}) |
||||
} else { |
||||
cb(path + ' not available') |
||||
} |
||||
}, |
||||
setFile: (mod, path, content, cb) => { |
||||
var provider = fileManager.fileProviderOf(path) |
||||
if (provider) { |
||||
// TODO add approval to user for external plugin to set the content of the given `path`
|
||||
provider.set(path, content, (error) => { |
||||
if (error) return cb(error) |
||||
fileManager.syncEditor(path) |
||||
cb() |
||||
}) |
||||
} else { |
||||
cb(path + ' not available') |
||||
} |
||||
}, |
||||
highlight: (mod, lineColumnPos, filePath, hexColor, cb) => { |
||||
var position |
||||
try { |
||||
position = JSON.parse(lineColumnPos) |
||||
} catch (e) { |
||||
return cb(e.message) |
||||
} |
||||
if (!highlighters[mod]) highlighters[mod] = new SourceHighlighter() |
||||
highlighters[mod].currentSourceLocation(null) |
||||
highlighters[mod].currentSourceLocationFromfileName(position, filePath, hexColor) |
||||
cb() |
||||
}, |
||||
discardHighlight: (mod, cb) => { |
||||
if (highlighters[mod]) highlighters[mod].currentSourceLocation(null) |
||||
cb() |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,179 +0,0 @@ |
||||
'use strict' |
||||
var remixLib = require('remix-lib') |
||||
var EventManager = remixLib.EventManager |
||||
const PluginAPI = require('./pluginAPI') |
||||
/** |
||||
* Register and Manage plugin: |
||||
* |
||||
* Plugin registration is done in the settings tab, |
||||
* using the following format: |
||||
* { |
||||
* "title": "<plugin name>", |
||||
* "url": "<plugin url>" |
||||
* } |
||||
* |
||||
* structure of messages: |
||||
* |
||||
* - Notification sent by Remix: |
||||
*{ |
||||
* action: 'notification', |
||||
* key: <string>, |
||||
* type: <string>, |
||||
* value: <array> |
||||
*} |
||||
* |
||||
* - Request sent by the plugin: |
||||
*{ |
||||
* id: <number>, |
||||
* action: 'request', |
||||
* key: <string>, |
||||
* type: <string>, |
||||
* value: <array> |
||||
*} |
||||
* |
||||
* - Response sent by Remix and receive by the plugin: |
||||
*{ |
||||
* id: <number>, |
||||
* action: 'response', |
||||
* key: <string>, |
||||
* type: <string>, |
||||
* value: <array>, |
||||
* error: (see below) |
||||
*} |
||||
* => The `error` property is `undefined` if no error happened. |
||||
* => In case of error (due to permission, system error, API error, etc...): |
||||
* error: { code, msg (optional), data (optional), stack (optional) |
||||
* => possible error code are still to be defined, but the generic one would be 500. |
||||
* |
||||
* Plugin receive 4 types of message: |
||||
* - focus (when he get focus) |
||||
* - unfocus (when he loose focus - is hidden) |
||||
* - compilationData (that is triggered just after a focus - and send the current compilation data or null) |
||||
* - compilationFinished (that is only sent to the plugin that has focus) |
||||
* |
||||
* Plugin can emit messages and receive response. |
||||
* |
||||
* CONFIG: |
||||
* - getConfig(filename). The data to send should be formatted like: |
||||
* { |
||||
* id: <requestid>, |
||||
* action: 'request', |
||||
* key: 'config', |
||||
* type: 'getConfig', |
||||
* value: ['filename.ext'] |
||||
* } |
||||
* the plugin will reveice a response like: |
||||
* { |
||||
* id: <requestid>, |
||||
* action: 'response', |
||||
* key: 'config', |
||||
* type: 'getConfig', |
||||
* error, |
||||
* value: ['content of filename.ext'] |
||||
* } |
||||
* same apply for the other call |
||||
* - setConfig(filename, content) |
||||
* - removeConfig |
||||
* |
||||
* See index.html and remix.js in test-browser folder for sample |
||||
* |
||||
*/ |
||||
module.exports = class PluginManager { |
||||
constructor (app, compilersArtefacts, txlistener, fileProviders, fileManager, udapp) { |
||||
const self = this |
||||
self.event = new EventManager() |
||||
var pluginAPI = new PluginAPI( |
||||
this, |
||||
fileProviders, |
||||
fileManager, |
||||
compilersArtefacts, |
||||
udapp |
||||
) |
||||
self._components = { pluginAPI } |
||||
self.plugins = {} |
||||
self.origins = {} |
||||
self.inFocus |
||||
fileManager.event.register('currentFileChanged', (file, provider) => { |
||||
self.broadcast(JSON.stringify({ |
||||
action: 'notification', |
||||
key: 'editor', |
||||
type: 'currentFileChanged', |
||||
value: [ file ] |
||||
})) |
||||
}) |
||||
|
||||
txlistener.event.register('newTransaction', (tx) => { |
||||
self.broadcast(JSON.stringify({ |
||||
action: 'notification', |
||||
key: 'txlistener', |
||||
type: 'newTransaction', |
||||
value: [tx] |
||||
})) |
||||
}) |
||||
|
||||
window.addEventListener('message', (event) => { |
||||
if (event.type !== 'message') return |
||||
var extension = self.origins[event.origin] |
||||
if (!extension) return |
||||
|
||||
function response (key, type, callid, error, result) { |
||||
self.postToOrigin(event.origin, JSON.stringify({ |
||||
id: callid, |
||||
action: 'response', |
||||
key: key, |
||||
type: type, |
||||
error: error, |
||||
value: [ result ] |
||||
})) |
||||
} |
||||
var data = JSON.parse(event.data) |
||||
data.value.unshift(extension) |
||||
data.value.push((error, result) => { |
||||
response(data.key, data.type, data.id, error, result) |
||||
}) |
||||
if (pluginAPI[data.key] && pluginAPI[data.key][data.type]) { |
||||
pluginAPI[data.key][data.type].apply({}, data.value) |
||||
} else { |
||||
response(data.key, data.type, data.id, `Endpoint ${data.key}/${data.type} not present`, null) |
||||
} |
||||
}, false) |
||||
} |
||||
unregister (desc) { |
||||
const self = this |
||||
self._components.pluginAPI.editor.discardHighlight(desc.title, () => {}) |
||||
delete self.plugins[desc.title] |
||||
delete self.origins[desc.url] |
||||
} |
||||
register (desc, modal, content) { |
||||
const self = this |
||||
self.plugins[desc.title] = { content, modal, origin: desc.url } |
||||
self.origins[desc.url] = desc.title |
||||
} |
||||
broadcast (value) { |
||||
for (var plugin in this.plugins) { |
||||
this.post(plugin, value) |
||||
} |
||||
} |
||||
postToOrigin (origin, value) { |
||||
if (this.origins[origin]) { |
||||
this.post(this.origins[origin], value) |
||||
} |
||||
} |
||||
receivedDataFrom (methodName, mod, argumentsArray) { |
||||
// TODO check whether 'mod' as right to do that
|
||||
console.log(argumentsArray) |
||||
this.event.trigger(methodName, argumentsArray) // forward to internal modules
|
||||
this.broadcast(JSON.stringify({ // forward to plugins
|
||||
action: 'notification', |
||||
key: mod, |
||||
type: methodName, |
||||
value: argumentsArray |
||||
})) |
||||
} |
||||
post (name, value) { |
||||
const self = this |
||||
if (self.plugins[name]) { |
||||
self.plugins[name].content.querySelector('iframe').contentWindow.postMessage(value, self.plugins[name].origin) |
||||
} |
||||
} |
||||
} |
@ -1,38 +0,0 @@ |
||||
'use strict' |
||||
|
||||
module.exports = { |
||||
'oraclize': { |
||||
url: 'https://remix-plugin.oraclize.it', |
||||
title: 'Oraclize' |
||||
}, |
||||
'solium': { |
||||
url: 'https://two-water.surge.sh', |
||||
title: 'Solium' |
||||
}, |
||||
'ethdoc': { |
||||
url: 'https://30400.swarm-gateways.net/bzz:/ethdoc.remixide.eth', |
||||
title: 'Ethdoc' |
||||
}, |
||||
'openzeppelin snippet': { |
||||
url: 'https://left-edge.surge.sh', |
||||
title: 'Openzeppelin snippet' |
||||
}, |
||||
'vyper': { |
||||
url: 'https://plugin.vyper.live', |
||||
title: 'Vyper' |
||||
}, |
||||
'slither/mythril': { |
||||
url: 'http://jittery-space.surge.sh', |
||||
title: 'Slither/Mythril' |
||||
}, |
||||
'pipeline': { |
||||
url: 'https://pipeline.pipeos.one', |
||||
title: 'Pipeline' |
||||
} |
||||
/* |
||||
'etherscan-general': { |
||||
url: 'http://127.0.0.1:8081', |
||||
title: 'Etherscan-general' |
||||
} |
||||
*/ |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,105 @@ |
||||
const async = require('async') |
||||
const EventEmitter = require('events') |
||||
var remixTests = require('remix-tests') |
||||
var Compiler = require('remix-solidity').Compiler |
||||
var CompilerImport = require('../../compiler/compiler-imports') |
||||
|
||||
// TODO: move this to the UI
|
||||
const addTooltip = require('../../ui/tooltip') |
||||
|
||||
class CompileTab { |
||||
|
||||
constructor (queryParams, fileManager, editor, config, fileProviders) { |
||||
this.event = new EventEmitter() |
||||
this.queryParams = queryParams |
||||
this.compilerImport = new CompilerImport() |
||||
this.compiler = new Compiler((url, cb) => this.importFileCb(url, cb)) |
||||
this.fileManager = fileManager |
||||
this.editor = editor |
||||
this.config = config |
||||
this.fileProviders = fileProviders |
||||
} |
||||
|
||||
init () { |
||||
this.optimize = this.queryParams.get().optimize |
||||
this.optimize = this.optimize === 'true' |
||||
this.queryParams.update({ optimize: this.optimize }) |
||||
this.compiler.setOptimize(this.optimize) |
||||
} |
||||
|
||||
setOptimize (newOptimizeValue) { |
||||
this.optimize = newOptimizeValue |
||||
this.queryParams.update({ optimize: this.optimize }) |
||||
this.compiler.setOptimize(this.optimize) |
||||
} |
||||
|
||||
runCompiler () { |
||||
this.fileManager.saveCurrentFile() |
||||
this.editor.clearAnnotations() |
||||
var currentFile = this.config.get('currentFile') |
||||
if (!currentFile) return |
||||
if (!/\.sol/.exec(currentFile)) return |
||||
// only compile *.sol file.
|
||||
var target = currentFile |
||||
var sources = {} |
||||
var provider = this.fileManager.fileProviderOf(currentFile) |
||||
if (!provider) return console.log('cannot compile ' + currentFile + '. Does not belong to any explorer') |
||||
provider.get(target, (error, content) => { |
||||
if (error) return console.log(error) |
||||
sources[target] = { content } |
||||
this.event.emit('startingCompilation') |
||||
setTimeout(() => { |
||||
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
|
||||
this.compiler.compile(sources, target) |
||||
}, 100) |
||||
}) |
||||
} |
||||
|
||||
importExternal (url, cb) { |
||||
this.compilerImport.import(url, |
||||
|
||||
// TODO: move to an event that is generated, the UI shouldn't be here
|
||||
(loadingMsg) => { addTooltip(loadingMsg) }, |
||||
(error, content, cleanUrl, type, url) => { |
||||
if (error) return cb(error) |
||||
|
||||
if (this.fileProviders[type]) { |
||||
this.fileProviders[type].addReadOnly(cleanUrl, content, url) |
||||
} |
||||
cb(null, content) |
||||
}) |
||||
} |
||||
|
||||
importFileCb (url, filecb) { |
||||
if (url.indexOf('/remix_tests.sol') !== -1) return filecb(null, remixTests.assertLibCode) |
||||
|
||||
var provider = this.fileManager.fileProviderOf(url) |
||||
if (provider) { |
||||
if (provider.type === 'localhost' && !provider.isConnected()) { |
||||
return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`) |
||||
} |
||||
return provider.exists(url, (error, exist) => { |
||||
if (error) return filecb(error) |
||||
if (exist) { |
||||
return provider.get(url, filecb) |
||||
} |
||||
this.importExternal(url, filecb) |
||||
}) |
||||
} |
||||
if (this.compilerImport.isRelativeImport(url)) { |
||||
// try to resolve localhost modules (aka truffle imports)
|
||||
var splitted = /([^/]+)\/(.*)$/g.exec(url) |
||||
return async.tryEach([ |
||||
(cb) => { this.importFileCb('localhost/installed_contracts/' + url, cb) }, |
||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } }, |
||||
(cb) => { this.importFileCb('localhost/node_modules/' + url, cb) }, |
||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }], |
||||
(error, result) => { filecb(error, result) } |
||||
) |
||||
} |
||||
this.importExternal(url, filecb) |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = CompileTab |
@ -0,0 +1,267 @@ |
||||
/* global Worker */ |
||||
const yo = require('yo-yo') |
||||
var minixhr = require('minixhr') |
||||
var helper = require('../../../lib/helper') |
||||
const addTooltip = require('../../ui/tooltip') |
||||
|
||||
var css = require('../styles/compile-tab-styles') |
||||
|
||||
class CompilerContainer { |
||||
|
||||
constructor (compileTabLogic, editor, config, queryParams) { |
||||
this._view = {} |
||||
this.compileTabLogic = compileTabLogic |
||||
this.editor = editor |
||||
this.config = config |
||||
this.queryParams = queryParams |
||||
|
||||
this.data = { |
||||
hideWarnings: config.get('hideWarnings') || false, |
||||
autoCompile: config.get('autoCompile'), |
||||
compileTimeout: null, |
||||
timeout: 300, |
||||
allversions: null, |
||||
selectedVersion: null, |
||||
defaultVersion: 'soljson-v0.5.1+commit.c8a2cb62.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler
|
||||
baseurl: 'https://solc-bin.ethereum.org/bin' |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Update the compilation button with the name of the current file |
||||
*/ |
||||
set currentFile (name = '') { |
||||
if (!this._view.compilationButton) return |
||||
const button = this.compilationButton(name.split('/').pop()) |
||||
yo.update(this._view.compilationButton, button) |
||||
} |
||||
|
||||
deactivate () { |
||||
} |
||||
|
||||
activate () { |
||||
this.currentFile = this.config.get('currentFile') |
||||
this.listenToEvents() |
||||
} |
||||
|
||||
listenToEvents () { |
||||
this.editor.event.register('contentChanged', this.scheduleCompilation.bind(this)) |
||||
this.editor.event.register('sessionSwitched', this.scheduleCompilation.bind(this)) |
||||
|
||||
this.compileTabLogic.event.on('startingCompilation', () => { |
||||
if (!this._view.compileIcon) return |
||||
this._view.compileIcon.setAttribute('title', 'compiling...') |
||||
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) |
||||
this._view.compileIcon.classList.add(`${css.spinningIcon}`) |
||||
}) |
||||
|
||||
this.compileTabLogic.compiler.event.register('compilationDuration', (speed) => { |
||||
if (!this._view.warnCompilationSlow) return |
||||
if (speed > 1000) { |
||||
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.` |
||||
this._view.warnCompilationSlow.setAttribute('title', msg) |
||||
this._view.warnCompilationSlow.style.visibility = 'visible' |
||||
} else { |
||||
this._view.warnCompilationSlow.style.visibility = 'hidden' |
||||
} |
||||
}) |
||||
|
||||
this.editor.event.register('contentChanged', () => { |
||||
if (!this._view.compileIcon) return |
||||
this._view.compileIcon.classList.add(`${css.bouncingIcon}`) // @TODO: compileView tab
|
||||
}) |
||||
|
||||
this.compileTabLogic.compiler.event.register('loadingCompiler', () => { |
||||
if (!this._view.compileIcon) return |
||||
this._view.compileIcon.setAttribute('title', 'compiler is loading, please wait a few moments.') |
||||
this._view.compileIcon.classList.add(`${css.spinningIcon}`) |
||||
this._view.warnCompilationSlow.style.visibility = 'hidden' |
||||
}) |
||||
|
||||
this.compileTabLogic.compiler.event.register('compilerLoaded', () => { |
||||
if (!this._view.compileIcon) return |
||||
this._view.compileIcon.setAttribute('title', '') |
||||
this._view.compileIcon.classList.remove(`${css.spinningIcon}`) |
||||
}) |
||||
|
||||
this.compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => { |
||||
if (!this._view.compileIcon) return |
||||
this._view.compileIcon.setAttribute('title', 'idle') |
||||
this._view.compileIcon.classList.remove(`${css.spinningIcon}`) |
||||
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) |
||||
}) |
||||
} |
||||
|
||||
/************** |
||||
* SUBCOMPONENT |
||||
*/ |
||||
compilationButton (name) { |
||||
if (!name) name = '' |
||||
var displayed = name === '' ? '<no file selected>' : name |
||||
var el = yo` |
||||
<div class="${css.compilerArticle}"> |
||||
<button class="btn btn-primary btn-block ${name === '' ? 'disabled' : ''}" title="Compile" onclick="${this.compile.bind(this)}"> |
||||
<span>${this._view.compileIcon} Compile ${displayed}</span> |
||||
</button> |
||||
</div>` |
||||
if (name === '') { |
||||
el.setAttribute('disabled', 'true') |
||||
} |
||||
return el |
||||
} |
||||
|
||||
render () { |
||||
this.compileTabLogic.compiler.event.register('compilerLoaded', (version) => this.setVersionText(version)) |
||||
this.fetchAllVersion((allversions, selectedVersion) => { |
||||
this.data.allversions = allversions |
||||
this.data.selectedVersion = selectedVersion |
||||
if (this._view.versionSelector) this._updateVersionSelector() |
||||
}) |
||||
|
||||
this._view.warnCompilationSlow = yo`<i title="Compilation Slow" style="visibility:hidden" class="${css.warnCompilationSlow} fas fa-exclamation-triangle" aria-hidden="true"></i>` |
||||
this._view.compileIcon = yo`<i class="fas fa-sync ${css.icon}" aria-hidden="true"></i>` |
||||
this._view.autoCompile = yo`<input class="${css.autocompile}" onchange=${this.updateAutoCompile.bind(this)} id="autoCompile" type="checkbox" title="Auto compile">` |
||||
this._view.hideWarningsBox = yo`<input class="${css.autocompile}" onchange=${this.hideWarnings.bind(this)} id="hideWarningsBox" type="checkbox" title="Hide warnings">` |
||||
if (this.data.autoCompile) this._view.autoCompile.setAttribute('checked', '') |
||||
if (this.data.hideWarnings) this._view.hideWarningsBox.setAttribute('checked', '') |
||||
|
||||
this._view.optimize = yo`<input onchange=${this.onchangeOptimize.bind(this)} id="optimize" type="checkbox">` |
||||
if (this.compileTabLogic.optimize) this._view.optimize.setAttribute('checked', '') |
||||
|
||||
this._view.versionSelector = yo` |
||||
<select onchange="${this.onchangeLoadVersion.bind(this)}" class="custom-select" id="versionSelector" disabled> |
||||
<option disabled selected>Select new compiler version</option> |
||||
</select>` |
||||
this._view.version = yo`<span id="version"></span>` |
||||
|
||||
this._view.compilationButton = this.compilationButton() |
||||
|
||||
this._view.compileContainer = yo` |
||||
<section> |
||||
<!-- Select Compiler Version --> |
||||
<article> |
||||
<header class="navbar navbar-light bg-light input-group mb-3"> |
||||
<div class="input-group-prepend"> |
||||
<label class="input-group-text border-0" for="versionSelector">Compiler</label> |
||||
</div> |
||||
${this._view.versionSelector} |
||||
</header> |
||||
${this._view.compilationButton} |
||||
</article> |
||||
<!-- Config --> |
||||
<article> |
||||
<small class="${css.compilerSm}">Compiler Configuration</small> |
||||
<ul class="list-group list-group-flush"> |
||||
<li class="list-group-item form-group ${css.compilerConfig}"> |
||||
${this._view.autoCompile} |
||||
<label for="autoCompile">Auto compile</label> |
||||
</li> |
||||
<li class="list-group-item form-group ${css.compilerConfig}"> |
||||
${this._view.optimize} |
||||
<label for="optimize">Enable Optimization</label> |
||||
</li> |
||||
<li class="list-group-item form-group ${css.compilerConfig}"> |
||||
${this._view.hideWarningsBox} |
||||
<label for="hideWarningsBox">Hide warnings</label> |
||||
</li> |
||||
</ul> |
||||
</article> |
||||
</section>` |
||||
|
||||
return this._view.compileContainer |
||||
} |
||||
|
||||
updateAutoCompile (event) { |
||||
this.config.set('autoCompile', this._view.autoCompile.checked) |
||||
} |
||||
|
||||
compile (event) { |
||||
this.compileTabLogic.runCompiler() |
||||
} |
||||
|
||||
hideWarnings (event) { |
||||
this.config.set('hideWarnings', this._view.hideWarningsBox.checked) |
||||
this.compile() |
||||
} |
||||
|
||||
onchangeOptimize () { |
||||
this.compileTabLogic.setOptimize(!!this._view.optimize.checked) |
||||
this.compileTabLogic.runCompiler() |
||||
} |
||||
|
||||
onchangeLoadVersion (event) { |
||||
this.data.selectedVersion = this._view.versionSelector.value |
||||
this._updateVersionSelector() |
||||
} |
||||
|
||||
_updateVersionSelector () { |
||||
this._view.versionSelector.innerHTML = '' |
||||
this.data.allversions.forEach(build => { |
||||
const option = build.path === this.data.selectedVersion |
||||
? yo`<option value="${build.path}" selected>${build.longVersion}</option>` |
||||
: yo`<option value="${build.path}">${build.longVersion}</option>` |
||||
this._view.versionSelector.appendChild(option) |
||||
}) |
||||
this._view.versionSelector.removeAttribute('disabled') |
||||
this.queryParams.update({ version: this.data.selectedVersion }) |
||||
let url |
||||
if (this.data.selectedVersion === 'builtin') { |
||||
let location = window.document.location |
||||
location = `${location.protocol}//${location.host}/${location.pathname}` |
||||
if (location.endsWith('index.html')) location = location.substring(0, location.length - 10) |
||||
if (!location.endsWith('/')) location += '/' |
||||
url = location + 'soljson.js' |
||||
} else { |
||||
if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) { |
||||
return console.log('loading ' + this.data.selectedVersion + ' not allowed') |
||||
} |
||||
url = `${this.data.baseurl}/${this.data.selectedVersion}` |
||||
} |
||||
const isFirefox = typeof InstallTrigger !== 'undefined' |
||||
if (document.location.protocol !== 'file:' && Worker !== undefined && isFirefox) { |
||||
// Workers cannot load js on "file:"-URLs and we get a
|
||||
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
|
||||
// resort to non-worker version in that case.
|
||||
this.compileTabLogic.compiler.loadVersion(true, url) |
||||
this.setVersionText('(loading using worker)') |
||||
} else { |
||||
this.compileTabLogic.compiler.loadVersion(false, url) |
||||
this.setVersionText('(loading)') |
||||
} |
||||
} |
||||
|
||||
setVersionText (text) { |
||||
this.data.version = text |
||||
if (this._view.version) this._view.version.innerText = text |
||||
} |
||||
|
||||
fetchAllVersion (callback) { |
||||
minixhr(`${this.data.baseurl}/list.json`, (json, event) => { |
||||
// @TODO: optimise and cache results to improve app loading times
|
||||
var allversions, selectedVersion |
||||
if (event.type !== 'error') { |
||||
try { |
||||
const data = JSON.parse(json) |
||||
allversions = data.builds.slice().reverse() |
||||
selectedVersion = this.data.defaultVersion |
||||
if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version |
||||
} catch (e) { |
||||
addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload.') |
||||
} |
||||
} else { |
||||
allversions = [{ path: 'builtin', longVersion: 'latest local version' }] |
||||
selectedVersion = 'builtin' |
||||
} |
||||
callback(allversions, selectedVersion) |
||||
}) |
||||
} |
||||
|
||||
scheduleCompilation () { |
||||
if (!this.config.get('autoCompile')) return |
||||
if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout) |
||||
this.data.compileTimeout = window.setTimeout(() => this.compileTabLogic.runCompiler(), this.data.timeout) |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = CompilerContainer |
@ -0,0 +1,66 @@ |
||||
const executionContext = require('../../execution-context') |
||||
import { NetworkApi } from 'remix-plugin' |
||||
|
||||
export const profile = { |
||||
name: 'network', |
||||
description: 'Manage the network (mainnet, ropsten, goerli...) and the provider (web3, vm, injected)' |
||||
} |
||||
|
||||
// Network API has :
|
||||
// - events: ['providerChanged']
|
||||
// - methods: ['getNetworkProvider', 'getEndpoint', 'detectNetwork', 'addNetwork', 'removeNetwork']
|
||||
|
||||
export class NetworkModule extends NetworkApi { |
||||
constructor () { |
||||
super(profile) |
||||
// TODO: See with remix-lib to make sementic coherent
|
||||
executionContext.event.register('contextChanged', (provider) => { |
||||
this.events.emit('providerChanged', provider) |
||||
}) |
||||
/* |
||||
// Events that could be implemented later
|
||||
executionContext.event.register('removeProvider', (provider) => { |
||||
this.events.emit('networkRemoved', provider) |
||||
}) |
||||
executionContext.event.register('addProvider', (provider) => { |
||||
this.events.emit('networkAdded', provider) |
||||
}) |
||||
executionContext.event.register('web3EndpointChanged', (provider) => { |
||||
this.events.emit('web3EndpointChanged', provider) |
||||
}) |
||||
*/ |
||||
} |
||||
|
||||
/** Return the current network provider (web3, vm, injected) */ |
||||
getNetworkProvider () { |
||||
return executionContext.getProvider() |
||||
} |
||||
|
||||
/** Return the current network */ |
||||
detectNetwork () { |
||||
return new Promise((resolve, reject) => { |
||||
executionContext.detectNetwork((error, network) => { |
||||
error ? reject(error) : resolve(network) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** Return the url only if network provider is 'web3' */ |
||||
getEndpoint () { |
||||
const provider = executionContext.getProvider() |
||||
if (provider !== 'web3') { |
||||
throw new Error('no endpoint: current provider is either injected or vm') |
||||
} |
||||
return provider.web3().currentProvider.host |
||||
} |
||||
|
||||
/** Add a custom network to the list of available networks */ |
||||
addNetwork (customNetwork) { |
||||
executionContext.addProvider(customNetwork) |
||||
} |
||||
|
||||
/** Remove a network to the list of availble networks */ |
||||
removeNetwork (name) { |
||||
executionContext.removeProvider(name) |
||||
} |
||||
} |
@ -1,318 +1,157 @@ |
||||
var yo = require('yo-yo') |
||||
var csjs = require('csjs-inject') |
||||
var remixLib = require('remix-lib') |
||||
|
||||
const defaultPlugins = require('../plugin/plugins') |
||||
var globalRegistry = require('../../global/registry') |
||||
const globalRegistry = require('../../global/registry') |
||||
var tooltip = require('../ui/tooltip') |
||||
var copyToClipboard = require('../ui/copy-to-clipboard') |
||||
var styleGuide = require('../ui/styles-guide/theme-chooser') |
||||
var styles = styleGuide.chooser() |
||||
var Storage = remixLib.Storage |
||||
var EventManager = require('../../lib/events') |
||||
var css = require('./styles/settings-tab-styles') |
||||
import { BaseApi } from 'remix-plugin' |
||||
|
||||
module.exports = class SettingsTab { |
||||
constructor (localRegistry) { |
||||
const self = this |
||||
self._components = {} |
||||
self._components.registry = localRegistry || globalRegistry |
||||
// dependencies
|
||||
self._deps = { |
||||
config: self._components.registry.get('config').api, |
||||
editorPanel: self._components.registry.get('editorpanel').api, |
||||
editor: self._components.registry.get('editor').api |
||||
const profile = { |
||||
name: 'settings', |
||||
displayName: 'Settings', |
||||
methods: [], |
||||
events: [], |
||||
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMTUyIDg5NnEwLTEwNi03NS0xODF0LTE4MS03NS0xODEgNzUtNzUgMTgxIDc1IDE4MSAxODEgNzUgMTgxLTc1IDc1LTE4MXptNTEyLTEwOXYyMjJxMCAxMi04IDIzdC0yMCAxM2wtMTg1IDI4cS0xOSA1NC0zOSA5MSAzNSA1MCAxMDcgMTM4IDEwIDEyIDEwIDI1dC05IDIzcS0yNyAzNy05OSAxMDh0LTk0IDcxcS0xMiAwLTI2LTlsLTEzOC0xMDhxLTQ0IDIzLTkxIDM4LTE2IDEzNi0yOSAxODYtNyAyOC0zNiAyOGgtMjIycS0xNCAwLTI0LjUtOC41dC0xMS41LTIxLjVsLTI4LTE4NHEtNDktMTYtOTAtMzdsLTE0MSAxMDdxLTEwIDktMjUgOS0xNCAwLTI1LTExLTEyNi0xMTQtMTY1LTE2OC03LTEwLTctMjMgMC0xMiA4LTIzIDE1LTIxIDUxLTY2LjV0NTQtNzAuNXEtMjctNTAtNDEtOTlsLTE4My0yN3EtMTMtMi0yMS0xMi41dC04LTIzLjV2LTIyMnEwLTEyIDgtMjN0MTktMTNsMTg2LTI4cTE0LTQ2IDM5LTkyLTQwLTU3LTEwNy0xMzgtMTAtMTItMTAtMjQgMC0xMCA5LTIzIDI2LTM2IDk4LjUtMTA3LjV0OTQuNS03MS41cTEzIDAgMjYgMTBsMTM4IDEwN3E0NC0yMyA5MS0zOCAxNi0xMzYgMjktMTg2IDctMjggMzYtMjhoMjIycTE0IDAgMjQuNSA4LjV0MTEuNSAyMS41bDI4IDE4NHE0OSAxNiA5MCAzN2wxNDItMTA3cTktOSAyNC05IDEzIDAgMjUgMTAgMTI5IDExOSAxNjUgMTcwIDcgOCA3IDIyIDAgMTItOCAyMy0xNSAyMS01MSA2Ni41dC01NCA3MC41cTI2IDUwIDQxIDk4bDE4MyAyOHExMyAyIDIxIDEyLjV0OCAyMy41eiIvPjwvc3ZnPg==', |
||||
description: 'Remix-IDE settings', |
||||
kind: 'settings', |
||||
location: 'swapPanel' |
||||
} |
||||
|
||||
module.exports = class SettingsTab extends BaseApi { |
||||
constructor (config, editor, appManager) { |
||||
super(profile) |
||||
this.config = config |
||||
this.editor = editor |
||||
this.appManager = appManager |
||||
this._components = {} |
||||
this._deps = { |
||||
themeModule: globalRegistry.get('themeModule').api |
||||
} |
||||
self._view = { /* eslint-disable */ |
||||
this._view = { /* eslint-disable */ |
||||
el: null, |
||||
optionVM: null, personal: null, warnPersonalMode: null, generateContractMetadata: null, |
||||
pluginInput: null, versionSelector: null, version: null, |
||||
theme: { dark: null, light: null, clean: null }, |
||||
plugins: {}, |
||||
config: { |
||||
general: null, themes: null, |
||||
plugin: null |
||||
general: null, themes: null |
||||
} |
||||
} /* eslint-enable */ |
||||
self.data = {} |
||||
self.event = new EventManager() |
||||
self._components.themeStorage = new Storage('style:') |
||||
self.data.currentTheme = self._components.themeStorage.get('theme') || 'light' |
||||
this.event = new EventManager() |
||||
} |
||||
|
||||
createThemeCheckies () { |
||||
let themes = this._deps.themeModule.getThemes() |
||||
const onswitchTheme = (event, name) => { |
||||
this._deps.themeModule.switchTheme(name) |
||||
} |
||||
if (themes) { |
||||
return yo`<div class="card-text themes-container">
|
||||
${themes.map((aTheme) => { |
||||
let el = yo`<div class="${css.frow} form-check ${css.crow}">
|
||||
<input type="radio" onchange=${event => { onswitchTheme(event, aTheme.name) }} class="align-middle form-check-input" name="theme" id="${aTheme.name}" > |
||||
<label class="form-check-label" for="${aTheme.name}">${aTheme.name} (${aTheme.quality})</label> |
||||
</div>` |
||||
if (this._deps.themeModule.active === aTheme.name) el.querySelector('input').setAttribute('checked', 'checked') |
||||
return el |
||||
})} |
||||
</div>` |
||||
} |
||||
} |
||||
|
||||
render () { |
||||
const self = this |
||||
if (self._view.el) return self._view.el |
||||
|
||||
// Gist settings
|
||||
var gistAccessToken = yo`<input id="gistaccesstoken" type="password">` |
||||
var token = self._deps.config.get('settings/gist-access-token') |
||||
var gistAccessToken = yo`<input id="gistaccesstoken" type="password" class="form-control mb-2 ${css.inline}" placeholder="Token">` |
||||
var token = this.config.get('settings/gist-access-token') |
||||
if (token) gistAccessToken.value = token |
||||
var gistAddToken = yo`<input class="${css.savegisttoken}" id="savegisttoken" onclick=${() => { self._deps.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">` |
||||
var gistRemoveToken = yo`<input id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; self._deps.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">` |
||||
self._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => self._deps.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>` |
||||
//
|
||||
self._view.optionVM = yo`<input onchange=${onchangeOption} id="alwaysUseVM" type="checkbox">` |
||||
if (self._deps.config.get('settings/always-use-vm')) self._view.optionVM.setAttribute('checked', '') |
||||
self._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox">` |
||||
if (self._deps.config.get('settings/personal-mode')) self._view.personal.setAttribute('checked', '') |
||||
var gistAddToken = yo`<input class="${css.savegisttoken} btn btn-sm btn-primary" id="savegisttoken" onclick=${() => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">` |
||||
var gistRemoveToken = yo`<input class="btn btn-sm btn-primary" id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; this.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">` |
||||
this._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => this.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>` |
||||
this._view.optionVM = yo`<input onchange=${onchangeOption} class="align-middle form-check-input" id="alwaysUseVM" type="checkbox">` |
||||
if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '') |
||||
this._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="align-middle form-check-input">` |
||||
if (this.config.get('settings/personal-mode')) this._view.personal.setAttribute('checked', '') |
||||
var warnText = `Transaction sent over Web3 will use the web3.personal API - be sure the endpoint is opened before enabling it.
|
||||
This mode allows to provide the passphrase in the Remix interface without having to unlock the account. |
||||
Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). |
||||
It is not recommended (and also most likely not relevant) to use this mode with an injected provider (Mist, Metamask, ...) or with JavaScript VM. |
||||
Remix never persist any passphrase.`.split('\n').map(s => s.trim()).join(' ')
|
||||
self._view.warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fa fa-exclamation-triangle" aria-hidden="true"></i>` |
||||
self._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox">` |
||||
if (self._deps.config.get('settings/generate-contract-metadata')) self._view.generateContractMetadata.setAttribute('checked', '') |
||||
self._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>` |
||||
this._view.warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>` |
||||
this._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" type="checkbox" class="form-check-input">` |
||||
|
||||
if (this.config.get('settings/generate-contract-metadata')) this._view.generateContractMetadata.setAttribute('checked', '') |
||||
|
||||
this._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>` |
||||
|
||||
self._view.theme.light = yo`<input onchange=${onswitch2lightTheme} class="${css.col1}" name="theme" id="themeLight" type="radio">` |
||||
self._view.theme.dark = yo`<input onchange=${onswitch2darkTheme} class="${css.col1}" name="theme" id="themeDark" type="radio">` |
||||
self._view.theme.clean = yo`<input onchange=${onswitch2cleanTheme} class="${css.col1}" name="theme" id="themeClean" type="radio">` |
||||
self._view.theme[self.data.currentTheme].setAttribute('checked', 'checked') |
||||
this._view.themes = this._deps.themeModule.getThemes() |
||||
this._view.themesCheckBoxes = this.createThemeCheckies() |
||||
this._view.config.homePage = yo` |
||||
|
||||
self._view.config.general = yo` |
||||
<div class="${css.info}"> |
||||
<div class=${css.title}>General settings</div> |
||||
<div class="${css.crow}"> |
||||
<div>${self._view.generateContractMetadata}</div> |
||||
<span class="${css.checkboxText}">Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.</span> |
||||
<div class="${css.info} card"> |
||||
<div class="card-body"> |
||||
<h6 class="${css.title} card-title">Have a question?</h6> |
||||
<button class="btn btn-primary sm-1" onclick="${() => { window.open('https://gitter.im/ethereum/remix') }}">Gitter Channel</button> |
||||
</div> |
||||
</div>` |
||||
this._view.config.general = yo` |
||||
<div class="${css.info} card"> |
||||
<div class="card-body"> |
||||
<h6 class="${css.title} card-title">General settings</h6> |
||||
<div class="form-check ${css.frow}"> |
||||
<div>${this._view.generateContractMetadata}</div> |
||||
<label class="form-check-label align-middle" for="generatecontractmetadata">Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.</label> |
||||
</div> |
||||
<div class="${css.crow}"> |
||||
<div>${self._view.optionVM}</div> |
||||
<span class="${css.checkboxText}">Always use Ethereum VM at Load</span> |
||||
<div class="form-check ${css.frow}"> |
||||
<div>${this._view.optionVM}</div> |
||||
<label class="form-check-label align-middle" for="alwaysUseVM">Always use Ethereum VM at Load</label> |
||||
</div> |
||||
<div class="${css.crow}"> |
||||
<div><input id="editorWrap" type="checkbox" onchange=${function () { self._deps.editor.resize(this.checked) }}></div> |
||||
<span class="${css.checkboxText}">Text Wrap</span> |
||||
<div class="form-check ${css.frow}"> |
||||
<div><input id="editorWrap" class="form-check-input align-middle" type="checkbox" onchange=${function () { this.editor.resize(this.checked) }}></div> |
||||
<label class="form-check-label align-middle" for="editorWrap">Text Wrap</label> |
||||
</div> |
||||
<div class="${css.crow}"> |
||||
<div>${self._view.personal}></div> |
||||
<span class="${css.checkboxText}">Enable Personal Mode ${self._view.warnPersonalMode}></span> |
||||
<div class="form-check ${css.frow}"> |
||||
<div>${this._view.personal}></div> |
||||
<label class="form-check-label align-middle" for="personal">Enable Personal Mode ${this._view.warnPersonalMode}></label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
` |
||||
self._view.gistToken = yo` |
||||
<div class="${css.info}"> |
||||
<div class=${css.title}>Gist Access Token</div> |
||||
<div class="${css.crowNoFlex}">Manage the access token used to publish to Gist and retrieve Github contents.</div> |
||||
<div class="${css.crowNoFlex}">Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only 'create gist' permission.</div> |
||||
<div class="${css.crowNoFlex}"><a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></div>
|
||||
<div class="${css.crowNoFlex}">${self._view.gistToken}</div> |
||||
</div>` |
||||
self._view.config.themes = yo` |
||||
<div class="${css.info}"> |
||||
<div class=${css.title}>Themes</div> |
||||
<div class=${css.attention}> |
||||
<i title="Select the theme" class="${css.icon} fa fa-exclamation-triangle" aria-hidden="true"></i> |
||||
<span>Selecting a theme will trigger a page reload</span> |
||||
</div> |
||||
<div class="${css.crow}"> |
||||
${self._view.theme.light} |
||||
<label for="themeLight">Light Theme</label> |
||||
</div> |
||||
<div class="${css.crow}"> |
||||
${self._view.theme.dark} |
||||
<label for="themeDark">Dark Theme</label> |
||||
</div> |
||||
<div class="${css.crow}"> |
||||
${self._view.theme.clean} |
||||
<label for="themeClean">Clean Theme</label> |
||||
this._view.gistToken = yo` |
||||
<div class="${css.info} card"> |
||||
<div class="card-body"> |
||||
<h6 class="${css.title} card-title">Gist Access Token</h6> |
||||
<p class="">Manage the access token used to publish to Gist and retrieve Github contents.</p> |
||||
<p class="">Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only 'create gist' permission.</p> |
||||
<p class="${css.crowNoFlex}"><a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
|
||||
<div class="${css.crowNoFlex}">${this._view.gistToken}</div> |
||||
</div> |
||||
</div>` |
||||
self._view.config.plugins = yo`<div></div>` |
||||
self._view.config.plugin = yo` |
||||
<div class="${css.info}"> |
||||
<div class=${css.title}>Plugin <i title="This section is still under heavy development, please use it carefully" class="${css.icon} fa fa-exclamation-triangle" aria-hidden="true"></i> </div> |
||||
<div class="${css.crowNoFlex}"> |
||||
<div>Load plugin from JSON description: </div> |
||||
${self._view.pluginInput} |
||||
<input onclick=${onloadPluginJson} type="button" value="Load" class="${css.initPlugin}"> |
||||
${self._view.config.plugins} |
||||
this._view.config.themes = yo` |
||||
<div class="${css.info} card"> |
||||
<div class="card-body"> |
||||
<h6 class="${css.title} card-title">Themes</h6> |
||||
${this._view.themesCheckBoxes} |
||||
</div> |
||||
</div>` |
||||
self._view.el = yo` |
||||
this._view.el = yo` |
||||
<div class="${css.settingsTabView}" id="settingsView"> |
||||
${self._view.config.general} |
||||
${self._view.config.plugin} |
||||
${self._view.gistToken} |
||||
${self._view.config.themes} |
||||
${this._view.config.homePage} |
||||
${this._view.config.general} |
||||
${this._view.gistToken} |
||||
${this._view.config.themes} |
||||
</div>` |
||||
|
||||
function loadPlugins (plugins, opt) { |
||||
for (var k in plugins) { |
||||
(function (plugin) { |
||||
if (!self._view.plugins[plugin.title]) self._view.plugins[plugin.title] = {} |
||||
self._view.plugins[plugin.title].json = plugin |
||||
self._view.plugins[plugin.title].el = yo`<div title=${plugin.title} class="${css.pluginLoad}">
|
||||
<div class="${css.aPlugin}" onclick=${() => { onLoadPlugin(plugin.title) }}>${plugin.title}</div> |
||||
${opt.removable ? yo`<span class="${css.removePlugin}" onclick=${() => { onRemovePlugin(plugin.title) }}><i class="fa fa-close"></i></span>` : yo`<span></span>`} |
||||
</div>` |
||||
self._view.config.plugins.appendChild(self._view.plugins[plugin.title].el) |
||||
})(plugins[k]) |
||||
} |
||||
} |
||||
|
||||
function getSavedPlugin () { |
||||
var savedPlugin = self._deps.config.get('settings/plugins-list') |
||||
return savedPlugin ? JSON.parse(savedPlugin) : {} |
||||
} |
||||
function setSavedPlugin (savedPlugins) { |
||||
self._deps.config.set('settings/plugins-list', JSON.stringify(savedPlugins)) |
||||
} |
||||
loadPlugins(defaultPlugins, {removable: false}) |
||||
loadPlugins(getSavedPlugin(), {removable: true}) |
||||
|
||||
function onLoadPlugin (name) { |
||||
self.event.trigger('plugin-loadRequest', [self._view.plugins[name].json]) |
||||
} |
||||
function onRemovePlugin (name) { |
||||
var savedPlugin = getSavedPlugin() |
||||
delete savedPlugin[name] |
||||
setSavedPlugin(savedPlugin) |
||||
if (self._view.plugins[name]) { |
||||
self._view.plugins[name].el.parentNode.removeChild(self._view.plugins[name].el) |
||||
delete self._view.plugins[name] |
||||
} |
||||
} |
||||
function onloadPluginJson (event) { |
||||
try { |
||||
var json = JSON.parse(self._view.pluginInput.value) |
||||
} catch (e) { |
||||
return tooltip('cannot parse the plugin definition to JSON') |
||||
} |
||||
var savedPlugin = getSavedPlugin() |
||||
if (self._view.plugins[json.title]) return tooltip('Plugin already loaded') |
||||
savedPlugin[json.title] = json |
||||
setSavedPlugin(savedPlugin) |
||||
loadPlugins([json], {removable: true}) |
||||
} |
||||
|
||||
function onchangeGenerateContractMetadata (event) { |
||||
self._deps.config.set('settings/generate-contract-metadata', !self._deps.config.get('settings/generate-contract-metadata')) |
||||
self.config.set('settings/generate-contract-metadata', !self.config.get('settings/generate-contract-metadata')) |
||||
} |
||||
function onchangeOption (event) { |
||||
self._deps.config.set('settings/always-use-vm', !self._deps.config.get('settings/always-use-vm')) |
||||
} |
||||
function onswitch2darkTheme (event) { |
||||
styleGuide.switchTheme('dark') |
||||
window.location.reload() |
||||
} |
||||
function onswitch2lightTheme (event) { |
||||
styleGuide.switchTheme('light') |
||||
window.location.reload() |
||||
} |
||||
function onswitch2cleanTheme (event) { |
||||
styleGuide.switchTheme('clean') |
||||
window.location.reload() |
||||
self.config.set('settings/always-use-vm', !self.config.get('settings/always-use-vm')) |
||||
} |
||||
function onchangePersonal (event) { |
||||
self._deps.config.set('settings/personal-mode', !self._deps.config.get('settings/personal-mode')) |
||||
self.config.set('settings/personal-mode', !self.config.get('settings/personal-mode')) |
||||
} |
||||
return self._view.el |
||||
} |
||||
} |
||||
|
||||
const css = csjs` |
||||
.settingsTabView { |
||||
padding: 2%; |
||||
display: flex; |
||||
} |
||||
.info { |
||||
${styles.rightPanel.settingsTab.box_SolidityVersionInfo}; |
||||
margin-bottom: 1em; |
||||
word-break: break-word; |
||||
} |
||||
.title { |
||||
font-size: 1.1em; |
||||
font-weight: bold; |
||||
margin-bottom: 1em; |
||||
} |
||||
.crow { |
||||
display: flex; |
||||
overflow: auto; |
||||
clear: both; |
||||
padding: .2em; |
||||
} |
||||
.checkboxText { |
||||
font-weight: normal; |
||||
} |
||||
.crow label { |
||||
cursor:pointer; |
||||
} |
||||
.crowNoFlex { |
||||
overflow: auto; |
||||
clear: both; |
||||
} |
||||
.attention { |
||||
margin-bottom: 1em; |
||||
padding: .5em; |
||||
font-weight: bold; |
||||
} |
||||
.heading { |
||||
margin-bottom: 0; |
||||
} |
||||
.explaination { |
||||
margin-top: 3px; |
||||
margin-bottom: 3px; |
||||
} |
||||
input { |
||||
margin-right: 5px; |
||||
cursor: pointer; |
||||
width: inherit; |
||||
this._deps.themeModule.switchTheme() |
||||
return this._view.el |
||||
} |
||||
input[type=radio] { |
||||
margin-top: 2px; |
||||
} |
||||
.pluginTextArea { |
||||
font-family: unset; |
||||
} |
||||
.pluginLoad { |
||||
vertical-align: top; |
||||
${styles.rightPanel.settingsTab.button_LoadPlugin}; |
||||
width: inherit; |
||||
display: inline-block; |
||||
} |
||||
.initPlugin { |
||||
vertical-align: top; |
||||
${styles.rightPanel.settingsTab.button_initPlugin}; |
||||
width: inherit; |
||||
display: block; |
||||
max-height: inherit; |
||||
padding:7px; |
||||
} |
||||
|
||||
.removePlugin { |
||||
cursor: pointer; |
||||
} |
||||
i.warnIt { |
||||
color: ${styles.appProperties.warningText_Color}; |
||||
} |
||||
.icon { |
||||
margin-right: .5em; |
||||
} |
||||
.savegisttoken { |
||||
margin-left: 5px; |
||||
} |
||||
.aPlugin { |
||||
display: inline-block; |
||||
padding-left: 10px; |
||||
padding-top: 4px; |
||||
padding-bottom: 6px; |
||||
max-width: 100px; |
||||
text-overflow: ellipsis; |
||||
overflow: hidden; |
||||
white-space: nowrap; |
||||
vertical-align: middle; |
||||
} |
||||
.pluginLoad { |
||||
vertical-align: top; |
||||
max-height: inherit; |
||||
margin: 2px; |
||||
|
||||
} |
||||
.removePlugin{ |
||||
padding-left: 7px; |
||||
padding-right: 7px; |
||||
border-left: 2px solid ${styles.appProperties.primary_BackgroundColor}; |
||||
margin-left: 10px; |
||||
} |
||||
` |
||||
} |
||||
|
@ -0,0 +1,219 @@ |
||||
const csjs = require('csjs-inject') |
||||
|
||||
const css = csjs` |
||||
.compilerArticle { |
||||
padding: 10px; |
||||
} |
||||
.title { |
||||
font-size: 1.1em; |
||||
font-weight: bold; |
||||
margin-bottom: 1em; |
||||
} |
||||
.panicError { |
||||
color: red; |
||||
font-size: 20px; |
||||
} |
||||
.crow { |
||||
display: flex; |
||||
overflow: auto; |
||||
clear: both; |
||||
padding: .2em; |
||||
} |
||||
.checkboxText { |
||||
font-weight: normal; |
||||
} |
||||
.crow label { |
||||
cursor:pointer; |
||||
} |
||||
.crowNoFlex { |
||||
overflow: auto; |
||||
clear: both; |
||||
} |
||||
.info { |
||||
padding: 10px; |
||||
word-break: break-word; |
||||
} |
||||
.contract { |
||||
display: block; |
||||
margin: 3% 0; |
||||
} |
||||
.autocompileContainer { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.hideWarningsContainer { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.autocompile {} |
||||
.autocompileTitle { |
||||
font-weight: bold; |
||||
margin: 1% 0; |
||||
} |
||||
.autocompileText { |
||||
margin: 1% 0; |
||||
font-size: 12px; |
||||
overflow: hidden; |
||||
word-break: normal; |
||||
line-height: initial; |
||||
} |
||||
.warnCompilationSlow { |
||||
margin-left: 1%; |
||||
} |
||||
.compilerConfig { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.compilerConfig label { |
||||
margin: 0; |
||||
} |
||||
.compilerSm { |
||||
padding-left: 1.25rem; |
||||
} |
||||
.name { |
||||
display: flex; |
||||
} |
||||
.size { |
||||
display: flex; |
||||
} |
||||
.checkboxes { |
||||
display: flex; |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
flex-wrap: wrap; |
||||
} |
||||
.compileButton { |
||||
width: 100%; |
||||
margin: 15px 0 10px 0; |
||||
font-size: 12px; |
||||
} |
||||
.container { |
||||
margin: 0; |
||||
margin-bottom: 2%; |
||||
} |
||||
.optimizeContainer { |
||||
display: flex; |
||||
} |
||||
.noContractAlert { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
.contractHelperButtons { |
||||
margin-top: 10px; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
float: right; |
||||
} |
||||
.copyToClipboard { |
||||
font-size: 1rem; |
||||
} |
||||
.copyIcon { |
||||
margin-right: 5px; |
||||
} |
||||
.log { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-bottom: 5%; |
||||
overflow: visible; |
||||
} |
||||
.key { |
||||
margin-right: 5px; |
||||
text-transform: uppercase; |
||||
width: 100%; |
||||
} |
||||
.value { |
||||
display: flex; |
||||
width: 100%; |
||||
margin-top: 1.5%; |
||||
} |
||||
.questionMark { |
||||
margin-left: 2%; |
||||
cursor: pointer; |
||||
} |
||||
.questionMark:hover { |
||||
} |
||||
.detailsJSON { |
||||
padding: 8px 0; |
||||
border: none; |
||||
} |
||||
.icon { |
||||
margin-right: 0.3em; |
||||
} |
||||
.errorBlobs { |
||||
padding-left: 5px; |
||||
padding-right: 5px; |
||||
} |
||||
|
||||
.spinningIcon { |
||||
display: inline-block; |
||||
position: relative; |
||||
animation: spin 2s infinite linear; |
||||
-moz-animation: spin 2s infinite linear; |
||||
-o-animation: spin 2s infinite linear; |
||||
-webkit-animation: spin 2s infinite linear; |
||||
} |
||||
@keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-webkit-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-moz-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-o-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-ms-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
|
||||
.bouncingIcon { |
||||
display: inline-block; |
||||
position: relative; |
||||
-moz-animation: bounce 2s infinite linear; |
||||
-o-animation: bounce 2s infinite linear; |
||||
-webkit-animation: bounce 2s infinite linear; |
||||
animation: bounce 2s infinite linear; |
||||
}
|
||||
|
||||
@-webkit-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@-moz-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@-o-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@-ms-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
` |
||||
|
||||
module.exports = css |
@ -1,77 +0,0 @@ |
||||
const yo = require('yo-yo') |
||||
var css = require('./styles/support-tab-styles') |
||||
|
||||
class SupportTab { |
||||
|
||||
constructor (localRegistry) { |
||||
this.el = null |
||||
this.gitterIframe = '' |
||||
this.gitterIsLoaded = false |
||||
} |
||||
|
||||
loadTab () { |
||||
if (this.gitterIsLoaded) return |
||||
|
||||
const iframe = yo`<iframe class="${css.chatIframe}" src='https://gitter.im/ethereum/remix/~embed'>` |
||||
this.gitterIframe.parentNode.replaceChild(iframe, this.gitterIframe) |
||||
this.gitterIframe = iframe |
||||
this.el.style.display = 'block' |
||||
this.gitterIsLoaded = true |
||||
} |
||||
|
||||
render () { |
||||
if (this.el) return this.el |
||||
|
||||
this.gitterIframe = yo`<div></div>` |
||||
|
||||
const remixd = yo` |
||||
<div class="${css.info}"> |
||||
<div class=${css.title}>Accessing local files</div> |
||||
<div class="${css.crow}"> |
||||
Remixd is a tool which allow Remix IDE to access files located in your local computer. |
||||
it can also be used to setup a development environment. |
||||
</div> |
||||
<div class="${css.crow}">More infos:</div> |
||||
<div class="${css.crow}"><a target="_blank" href="https://github.com/ethereum/remixd"> https://github.com/ethereum/remixd</a></div>
|
||||
<div class="${css.crow}"><a target="_blank" href="https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem">http://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html</a></div>
|
||||
<div class="${css.crow}">Installation: <pre class=${css.remixdinstallation}>npm install remixd -g</pre></div> |
||||
</div>` |
||||
|
||||
const localremixd = yo` |
||||
<div class="${css.info}"> |
||||
<div class=${css.title}>Running Remix locally</div> |
||||
<div class="${css.crow}"> |
||||
as a NPM module: |
||||
</div> |
||||
<a target="_blank" href="https://www.npmjs.com/package/remix-ide">https://www.npmjs.com/package/remix-ide</a>
|
||||
<pre class=${css.remixdinstallation}>npm install remix-ide -g</pre> |
||||
<div class="${css.crow}"> |
||||
as an electron app: |
||||
</div> |
||||
<a target="_blank" href="https://github.com/horizon-games/remix-app">https://github.com/horizon-games/remix-app</a>
|
||||
</div>` |
||||
|
||||
this.el = yo` |
||||
<div class="${css.supportTabView}" id="supportView"> |
||||
<div class="${css.infoBox}"> |
||||
Have a question, found a bug or want to propose a feature? Have a look at the |
||||
<a target="_blank" href='https://github.com/ethereum/remix-ide/issues'> issues</a> or check out |
||||
<a target="_blank" href='https://remix.readthedocs.io/en/latest/'> the documentation page on Remix</a> or |
||||
<a target="_blank" href='https://solidity.readthedocs.io/en/latest/'> Solidity</a>. |
||||
</div> |
||||
<div class="${css.chat}"> |
||||
<div class="${css.chatTitle}" onclick=${() => { window.open('https://gitter.im/ethereum/remix') }} title='Click to open chat in Gitter'> |
||||
<div class="${css.chatTitleText}">ethereum/remix community chat</div> |
||||
</div> |
||||
${this.gitterIframe} |
||||
</div> |
||||
${remixd} |
||||
${localremixd} |
||||
</div>` |
||||
|
||||
return this.el |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = SupportTab |
@ -1,93 +0,0 @@ |
||||
var yo = require('yo-yo') |
||||
var css = require('./styles/tabbed-menu-styles') |
||||
|
||||
var globalRegistry = require('../../global/registry') |
||||
var helper = require('../../lib/helper') |
||||
|
||||
var EventManager = require('../../lib/events') |
||||
|
||||
class TabbedMenu { |
||||
constructor (localRegistry) { |
||||
const self = this |
||||
self.event = new EventManager() |
||||
self._components = {} |
||||
self._components.registry = localRegistry || globalRegistry |
||||
self._deps = { |
||||
app: self._components.registry.get('app').api |
||||
} |
||||
self._view = { el: null, viewport: null, tabs: {}, contents: {} } |
||||
self._deps.app.event.register('debuggingRequested', () => { |
||||
self.selectTabByTitle('Debugger') |
||||
}) |
||||
} |
||||
render () { |
||||
const self = this |
||||
if (self._view.el) return self._view.el |
||||
self._view.el = yo`<ul class=${css.menu}>${Object.values(self._view.tabs)}</ul>` |
||||
return self._view.el |
||||
} |
||||
renderViewport () { |
||||
const self = this |
||||
if (self._view.viewport) return self._view.viewport |
||||
self._view.viewport = yo` |
||||
<div id="optionViews" class=${css.optionViews}> |
||||
${Object.values(self._view.contents)} |
||||
</div>` |
||||
return self._view.viewport |
||||
} |
||||
addTab (title, cssClass, content) { |
||||
const self = this |
||||
if (helper.checkSpecialChars(title)) return |
||||
if (self._view.contents[title] || self._view.tabs[title]) throw new Error('tab already exists') |
||||
self._view.contents[title] = content |
||||
self._view.tabs[title] = yo`<li class="${css.options} ${cssClass}" onclick=${function (ev) { self.selectTab(this) }} title=${title}>${title}</li>` |
||||
if (self._view.el) self._view.el.appendChild(self._view.tabs[title]) |
||||
if (self._view.viewport) self._view.viewport.appendChild(self._view.contents[title]) |
||||
} |
||||
removeTabByTitle (title) { |
||||
const self = this |
||||
if (self._view.tabs[title]) { |
||||
self._view.tabs[title].parentNode.removeChild(self._view.tabs[title]) |
||||
} |
||||
if (self._view.contents[title]) { |
||||
self._view.contents[title].parentNode.removeChild(self._view.contents[title]) |
||||
} |
||||
delete self._view.contents[title] |
||||
delete self._view.tabs[title] |
||||
} |
||||
getTabByClass (tabClass) { |
||||
const self = this |
||||
return self._view.el.querySelector(`li.${tabClass}`) |
||||
} |
||||
updateTabTitle (tabClass, title) { |
||||
const self = this |
||||
var tab = self.getTabByClass(tabClass) |
||||
if (tab) tab.innerHTML = title |
||||
} |
||||
selectTabByTitle (title) { |
||||
const self = this |
||||
self.selectTab(self._view.tabs[title]) |
||||
} |
||||
selectTabByClassName (tabClass) { |
||||
const self = this |
||||
var tab = self.getTabByClass(tabClass) |
||||
if (tab) self.selectTab(tab) |
||||
return tab |
||||
} |
||||
selectTab (el) { |
||||
const self = this |
||||
if (!el.classList.contains(css.active)) { |
||||
var nodes = Object.values(self._view.tabs) |
||||
for (var i = 0; i < nodes.length; ++i) { |
||||
nodes[i].classList.remove(css.active) |
||||
self._view.contents[nodes[i].getAttribute('title')].style.display = 'none' |
||||
} |
||||
} |
||||
var title = el.getAttribute('title') |
||||
self._view.contents[el.getAttribute('title')].style.display = 'block' |
||||
el.classList.add(css.active) |
||||
self._deps.app.event.trigger('tabChanged', [title]) |
||||
} |
||||
} |
||||
|
||||
module.exports = TabbedMenu |
@ -0,0 +1,85 @@ |
||||
var helper = require('../../../lib/helper.js') |
||||
var modalDialogCustom = require('../../ui/modal-dialog-custom') |
||||
|
||||
class TestTabLogic { |
||||
|
||||
constructor (fileManager) { |
||||
this.fileManager = fileManager |
||||
} |
||||
|
||||
generateTestFile () { |
||||
var path = this.fileManager.currentPath() |
||||
var fileProvider = this.fileManager.fileProviderOf(path) |
||||
if (!fileProvider) return |
||||
helper.createNonClashingNameWithPrefix(path + '/test.sol', fileProvider, '_test', (error, newFile) => { |
||||
if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error) |
||||
if (!fileProvider.set(newFile, this.generateTestContractSample())) return modalDialogCustom.alert('Failed to create test file ' + newFile) |
||||
this.fileManager.switchFile(newFile) |
||||
}) |
||||
} |
||||
|
||||
async getTests (cb) { |
||||
var path = this.fileManager.currentPath() |
||||
if (!path) return cb(null, []) |
||||
var provider = this.fileManager.fileProviderOf(path) |
||||
if (!provider) return cb(null, []) |
||||
var tests = [] |
||||
let files |
||||
try { |
||||
files = await this.fileManager.getFolder(path) |
||||
} catch (e) { |
||||
cb(e.message) |
||||
} |
||||
for (var file in files) { |
||||
if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file) |
||||
} |
||||
cb(null, tests) |
||||
} |
||||
|
||||
generateTestContractSample () { |
||||
return `pragma solidity >=0.4.0 <0.6.0;
|
||||
import "remix_tests.sol"; // this import is automatically injected by Remix.
|
||||
|
||||
// file name has to end with '_test.sol'
|
||||
contract test_1 { |
||||
|
||||
function beforeAll() public { |
||||
// here should instantiate tested contract
|
||||
Assert.equal(uint(4), uint(3), "error in before all function"); |
||||
} |
||||
|
||||
function check1() public { |
||||
// use 'Assert' to test the contract
|
||||
Assert.equal(uint(2), uint(1), "error message"); |
||||
Assert.equal(uint(2), uint(2), "error message"); |
||||
} |
||||
|
||||
function check2() public view returns (bool) { |
||||
// use the return value (true or false) to test the contract
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
contract test_2 { |
||||
|
||||
function beforeAll() public { |
||||
// here should instantiate tested contract
|
||||
Assert.equal(uint(4), uint(3), "error in before all function"); |
||||
} |
||||
|
||||
function check1() public { |
||||
// use 'Assert' to test the contract
|
||||
Assert.equal(uint(2), uint(1), "error message"); |
||||
Assert.equal(uint(2), uint(2), "error message"); |
||||
} |
||||
|
||||
function check2() public view returns (bool) { |
||||
// use the return value (true or false) to test the contract
|
||||
return true; |
||||
} |
||||
}` |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = TestTabLogic |
@ -0,0 +1,63 @@ |
||||
import { BaseApi } from 'remix-plugin' |
||||
import { EventEmitter } from 'events' |
||||
|
||||
const themes = [ |
||||
{name: 'Cerulean', quality: 'light', url: 'https://bootswatch.com/4/cerulean/bootstrap.min.css'}, |
||||
{name: 'Flatly', quality: 'light', url: 'https://bootswatch.com/4/flatly/bootstrap.min.css'}, |
||||
{name: 'Lumen', quality: 'light', url: 'https://bootswatch.com/4/lumen/bootstrap.min.css'}, |
||||
{name: 'Minty', quality: 'light', url: 'https://bootswatch.com/4/minty/bootstrap.min.css'}, |
||||
{name: 'Pulse', quality: 'light', url: 'https://bootswatch.com/4/pulse/bootstrap.min.css'}, |
||||
{name: 'Sandstone', quality: 'light', url: 'https://bootswatch.com/4/sandstone/bootstrap.min.css'}, |
||||
{name: 'Spacelab', quality: 'light', url: 'https://bootswatch.com/4/spacelab/bootstrap.min.css'}, |
||||
{name: 'Yeti', quality: 'light', url: 'https://bootswatch.com/4/yeti/bootstrap.min.css'}, |
||||
{name: 'Cyborg', quality: 'dark', url: 'https://bootswatch.com/4/cyborg/bootstrap.min.css'}, |
||||
{name: 'Darkly', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/darkly/bootstrap.min.css'}, |
||||
{name: 'Slate', quality: 'light', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/slate/bootstrap.min.css'}, |
||||
{name: 'Superhero', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/superhero/bootstrap.min.css'} |
||||
] |
||||
|
||||
const profile = { |
||||
name: 'theme', |
||||
events: ['themeChanged'], |
||||
methods: ['switchTheme', 'getThemes', 'currentTheme'] |
||||
} |
||||
|
||||
export class ThemeModule extends BaseApi { |
||||
|
||||
constructor (registry) { |
||||
super(profile) |
||||
this.events = new EventEmitter() |
||||
this._deps = { |
||||
config: registry.get('config').api |
||||
} |
||||
this.themes = themes.reduce((acc, theme) => ({ ...acc, [theme.name]: theme }), {}) |
||||
this.active = this._deps.config.get('settings/theme') ? this._deps.config.get('settings/theme') : 'Flatly' |
||||
} |
||||
|
||||
/** Return the active theme */ |
||||
currentTheme () { |
||||
return this.themes[this.active] |
||||
} |
||||
|
||||
/** Returns all themes as an array */ |
||||
getThemes () { |
||||
return Object.keys(this.themes).map(key => this.themes[key]) |
||||
} |
||||
|
||||
/** |
||||
* Change the current theme |
||||
* @param {string} [themeName] - The name of the theme |
||||
*/ |
||||
switchTheme (themeName) { |
||||
if (themeName && !Object.keys(this.themes).includes(themeName)) { |
||||
throw new Error(`Theme ${themeName} doesn't exist`) |
||||
} |
||||
const next = themeName || this.active // Name
|
||||
const nextTheme = this.themes[next] // Theme
|
||||
this._deps.config.set('settings/theme', next) |
||||
document.getElementById('theme-link').setAttribute('href', nextTheme.url) |
||||
document.documentElement.style.setProperty('--theme', nextTheme.quality) |
||||
if (themeName) this.active = themeName |
||||
this.events.emit('themeChanged', nextTheme) |
||||
} |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,70 @@ |
||||
let yo = require('yo-yo') |
||||
let csjs = require('csjs-inject') |
||||
|
||||
var css = csjs` |
||||
.text { |
||||
cursor: pointer; |
||||
font-weight: normal; |
||||
max-width: 300px; |
||||
user-select: none; |
||||
padding-left: 14px; |
||||
} |
||||
.text:hover { |
||||
font-weight: bold; |
||||
} |
||||
.link { |
||||
cursor: pointer; |
||||
font-weight: normal; |
||||
text-decoration : none; |
||||
user-select: none; |
||||
padding-left: 14px; |
||||
} |
||||
.link:hover { |
||||
font-weight: bold; |
||||
text-decoration : none; |
||||
} |
||||
` |
||||
|
||||
class Section { |
||||
constructor (title, actions) { |
||||
this.title = title |
||||
this.actions = actions |
||||
} |
||||
|
||||
render () { |
||||
let sectionLook = yo` |
||||
<div class="card border-0 bg-light text-dark p-1" style="min-width: 300px; min-height: 180px;"> |
||||
<div class="card-header h5 font-weight-bold" style="user-select: none;">${this.title}</div> |
||||
<p></p> |
||||
</div> |
||||
` |
||||
for (var i = 0; i < this.actions.length; i++) { |
||||
if (this.actions[i].type === `callback`) { |
||||
sectionLook.appendChild(yo` |
||||
<div> |
||||
<span class="${css.text} h6 text-dark" onclick=${this.actions[i].payload} > |
||||
${this.actions[i].label} |
||||
</span> |
||||
</div> |
||||
`)
|
||||
} else if (this.actions[i].type === `link`) { |
||||
sectionLook.appendChild(yo` |
||||
<div > |
||||
<a class="${css.link} text-dark h6 text-decoration-none" href=${this.actions[i].payload} target="_blank" > |
||||
${this.actions[i].label} |
||||
</a> |
||||
</div> |
||||
`)
|
||||
} |
||||
} |
||||
|
||||
if (!this._view) { |
||||
this._view = sectionLook |
||||
} |
||||
|
||||
return this._view |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = Section |
@ -0,0 +1,44 @@ |
||||
let globalRegistry = require('../../../global/registry') |
||||
|
||||
export class Workspace { |
||||
constructor (title, description, isMain, activate, deactivate) { |
||||
this.title = title |
||||
this.description = description |
||||
this.isMain = isMain |
||||
this.activate = activate |
||||
this.deactivate = deactivate |
||||
} |
||||
} |
||||
|
||||
export const defaultWorkspaces = (appManager) => { |
||||
return [ |
||||
new Workspace( |
||||
'Solidity', |
||||
'Writing smart contracts. It is used for implementing smart contracts on various blockchain platforms', |
||||
true, |
||||
() => { |
||||
appManager.ensureActivated('solidity') |
||||
appManager.ensureActivated('run') |
||||
appManager.ensureActivated('solidityStaticAnalysis') |
||||
appManager.ensureActivated('solidityUnitTesting') |
||||
globalRegistry.get('verticalicon').api.select('solidity') |
||||
}, () => {}), |
||||
new Workspace( |
||||
'Vyper', |
||||
'Vyper is a contract-oriented, pythonic programming language that targets the Ethereum Virtual Machine (EVM)', |
||||
true, |
||||
() => { |
||||
appManager.ensureActivated('vyper') |
||||
appManager.ensureActivated('run') |
||||
globalRegistry.get('verticalicon').api.select('vyper') |
||||
}, () => {}), |
||||
new Workspace('Debugger', 'Debug transactions with remix', false, () => { |
||||
appManager.ensureActivated('debugger') |
||||
}, () => {}), |
||||
new Workspace('Pipeline', '', false, () => { |
||||
appManager.ensureActivated('solidity') |
||||
appManager.ensureActivated('pipeline') |
||||
appManager.ensureActivated('run') |
||||
}) |
||||
] |
||||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,36 +0,0 @@ |
||||
var styleGuideLight = require('./style-guide') |
||||
var styleGuideDark = require('./styleGuideDark') |
||||
var styleGuideClean = require('./styleGuideClean') |
||||
var Storage = require('remix-lib').Storage |
||||
|
||||
module.exports = { |
||||
|
||||
chooser: function () { |
||||
var themeStorage = new Storage('style:') |
||||
if (themeStorage.exists('theme')) { |
||||
if (themeStorage.get('theme') === 'dark') { |
||||
return styleGuideDark() |
||||
} else if (themeStorage.get('theme') === 'clean') { |
||||
return styleGuideClean() |
||||
} else { |
||||
return styleGuideLight() |
||||
} |
||||
} else { |
||||
return styleGuideLight() |
||||
} |
||||
}, |
||||
|
||||
switchTheme: function (theme) { |
||||
var themeStorage = new Storage('style:') |
||||
themeStorage.set('theme', theme) |
||||
if (theme === 'dark') { |
||||
return styleGuideDark() |
||||
} else if (theme === 'light') { |
||||
return styleGuideLight() |
||||
} else if (theme === 'clean') { |
||||
return styleGuideClean() |
||||
} else { |
||||
return styleGuideLight() |
||||
} |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue