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' |
|
||||||
} |
|
||||||
*/ |
|
||||||
} |
|
@ -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) |
||||||
|
} |
||||||
|
} |
@ -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