Merge branch 'master' into terminal-dragbar

pull/5370/head
David Disu 3 years ago committed by GitHub
commit 5a4645bedc
  1. 66
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  2. 3
      apps/remix-ide/src/app/editor/editor.js
  3. 571
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  4. BIN
      apps/remix-ide/src/assets/img/solhintLogo.png
  5. BIN
      apps/remix-ide/src/assets/img/solhintLogo.webp
  6. 133
      libs/remix-ui/editor/src/lib/cairoSyntax.ts
  7. 11
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  8. 3
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  9. 4
      libs/remix-ui/home-tab/.babelrc
  10. 248
      libs/remix-ui/home-tab/.eslintrc
  11. 7
      libs/remix-ui/home-tab/README.md
  12. 1
      libs/remix-ui/home-tab/src/index.ts
  13. 27
      libs/remix-ui/home-tab/src/lib/components/pluginButton.tsx
  14. 82
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  15. 370
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  16. 16
      libs/remix-ui/home-tab/src/lib/themeContext.tsx
  17. 16
      libs/remix-ui/home-tab/tsconfig.json
  18. 13
      libs/remix-ui/home-tab/tsconfig.lib.json
  19. 4
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  20. 2
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  21. 1
      libs/remix-ui/plugin-manager/src/lib/components/permissionsSettings.tsx
  22. 1
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  23. 1
      libs/remix-ui/toaster/src/lib/toaster.tsx
  24. 3
      nx.json
  25. 4242
      package-lock.json
  26. 1
      package.json
  27. 1
      tsconfig.base.json
  28. 17
      workspace.json

@ -0,0 +1,66 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const testData = {
validURL: 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol',
invalidURL: 'https://github.com/Oppelin/Roles.sol'
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Import from GitHub Modal': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.click('div[title="home"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]')
.pause(1000)
.scrollAndClick('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from Github')
.waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.refresh()
},
'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus()
}, [], () => {})
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.invalidURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]') // submitted
.waitForElementVisible('*[data-shared="tooltipPopup"]')
.assert.containsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL)
},
'Import From Github For Valid URL': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.clearValue('*[data-id="homeTabModalDialogCustomPromptText"]')
.execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus()
}, [], () => {})
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.validURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol')
.waitForElementVisible("div[title='default_workspace/github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol'")
.end()
}
}

@ -46,7 +46,8 @@ class Editor extends Plugin {
txt: 'text', txt: 'text',
json: 'json', json: 'json',
abi: 'json', abi: 'json',
rs: 'rust' rs: 'rust',
cairo: 'cairo'
} }
this.activated = false this.activated = false

@ -1,110 +1,11 @@
/* global */
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import * as packageJson from '../../../../../../package.json' import * as packageJson from '../../../../../../package.json'
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import { migrateToWorkspace } from '../../../migrateFileSystem' import { RemixUiHomeTab } from '@remix-ui/home-tab' // eslint-disable-line
import JSZip from 'jszip'
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const globalRegistry = require('../../../global/registry')
const modalDialogCustom = require('../modal-dialog-custom')
const modalDialog = require('../modaldialog')
const tooltip = require('../tooltip')
const GistHandler = require('../../../lib/gist-handler') const GistHandler = require('../../../lib/gist-handler')
const QueryParams = require('../../../lib/query-params.js')
const _paq = window._paq = window._paq || []
const css = csjs`
.text {
cursor: pointer;
font-weight: normal;
max-width: 300px;
user-select: none;
}
.text:hover {
cursor: pointer;
text-decoration: underline;
}
.homeContainer {
user-select: none;
overflow-y: hidden;
}
.mainContent {
overflow-y: auto;
flex-grow: 3;
}
.hpLogoContainer {
margin: 30px;
padding-right: 90px;
}
.mediaBadge {
font-size: 2em;
height: 2em;
width: 2em;
}
.mediaBadge:focus {
outline: none;
}
.image {
height: 1em;
width: 1em;
text-align: center;
}
.logoImg {
height: 10em;
}
.hpSections {
}
.rightPanel {
right: 0;
position: absolute;
z-index: 3;
}
.remixHomeMedia {
overflow-y: auto;
overflow-x: hidden;
max-height: 720px;
}
.panels {
box-shadow: 0px 0px 13px -7px;
}
.labelIt {
margin-bottom: 0;
}
.bigLabelSize {
font-size: 13px;
}
.seeAll {
margin-top: 7px;
white-space: nowrap;
}
.importFrom p {
margin-right: 10px;
}
.logoContainer img{
height: 150px;
opacity: 0.7;
}
.envLogo {
height: 16px;
}
.cursorStyle {
cursor: pointer;
}
.envButton {
width: 120px;
height: 70px;
}
.media {
overflow: hidden;
width: 400px;
transition: .5s ease-out;
z-index: 1000;
}
.migrationBtn {
width: 100px;
}
}
`
const profile = { const profile = {
name: 'home', name: 'home',
@ -116,7 +17,6 @@ const profile = {
location: 'mainPanel', location: 'mainPanel',
version: packageJson.version version: packageJson.version
} }
export class LandingPage extends ViewPlugin { export class LandingPage extends ViewPlugin {
constructor (appManager, verticalIcons, fileManager, filePanel, contentImport) { constructor (appManager, verticalIcons, fileManager, filePanel, contentImport) {
super(profile) super(profile)
@ -127,459 +27,22 @@ export class LandingPage extends ViewPlugin {
this.appManager = appManager this.appManager = appManager
this.verticalIcons = verticalIcons this.verticalIcons = verticalIcons
this.gistHandler = new GistHandler() this.gistHandler = new GistHandler()
const themeQuality = globalRegistry.get('themeModule').api.currentTheme().quality this.el = document.createElement('div')
window.addEventListener('resize', () => this.adjustMediaPanel()) this.el.setAttribute('id', 'landingPageHomeContainer')
window.addEventListener('click', (e) => this.hideMediaPanel(e)) this.el.setAttribute('class', 'remixui_homeContainer justify-content-between bg-light d-flex')
this.twitterFrame = yo` this.el.setAttribute('data-id', 'landingPageHomeContainer')
<div class="px-2 ${css.media}">
<a class="twitter-timeline"
data-width="350"
data-theme="${themeQuality}"
data-chrome="nofooter noheader transparent"
data-tweet-limit="8"
href="https://twitter.com/EthereumRemix"
>
</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
`
this.badgeTwitter = yo`<button
class="btn-info p-2 m-1 border rounded-circle ${css.mediaBadge} fab fa-twitter"
id="remixIDEHomeTwitterbtn"
onclick=${(e) => this.showMediaPanel(e)}
></button>`
this.badgeMedium = yo`<button
class="btn-danger p-2 m-1 border rounded-circle ${css.mediaBadge} fab fa-medium"
id="remixIDEHomeMediumbtn"
onclick=${(e) => this.showMediaPanel(e)}
></button>`
this.twitterPanel = yo`
<div id="remixIDE_TwitterBlock" class="p-2 mx-0 mb-0 d-none ${css.remixHomeMedia}">
${this.twitterFrame}
</div>
`
this.mediumPanel = yo`
<div id="remixIDE_MediumBlock" class="p-2 mx-0 mb-0 d-none ${css.remixHomeMedia}">
<div id="medium-widget" class="p-3 ${css.media}">
<div
id="retainable-rss-embed"
data-rss="https://medium.com/feed/remix-ide"
data-maxcols="1"
data-layout="grid"
data-poststyle="external"
data-readmore="More..."
data-buttonclass="btn mb-3"
data-offset="-100"
>
- </div>
</div>
</div>
`
this.adjustMediaPanel()
globalRegistry.get('themeModule').api.events.on('themeChanged', (theme) => {
this.onThemeChanged(theme.quality)
})
}
adjustMediaPanel () {
this.twitterPanel.style.maxHeight = Math.max(window.innerHeight - 150, 200) + 'px'
this.mediumPanel.style.maxHeight = Math.max(window.innerHeight - 150, 200) + 'px'
}
hideMediaPanel (e) {
const mediaPanelsTitle = document.getElementById('remixIDEMediaPanelsTitle')
const mediaPanels = document.getElementById('remixIDEMediaPanels')
if (!mediaPanelsTitle || !mediaPanels) return
if (!mediaPanelsTitle.contains(e.target) && !mediaPanels.contains(e.target)) {
this.mediumPanel.classList.remove('d-block')
this.mediumPanel.classList.add('d-none')
this.twitterPanel.classList.remove('d-block')
this.twitterPanel.classList.add('d-none')
}
}
onThemeChanged (themeQuality) {
const twitterFrame = yo`
<div class="px-2 ${css.media}">
<a class="twitter-timeline"
data-width="350"
data-theme="${themeQuality}"
data-chrome="nofooter noheader transparent"
data-tweet-limit="8"
href="https://twitter.com/EthereumRemix"
>
</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
`
yo.update(this.twitterFrame, twitterFrame)
const invertNum = (themeQuality === 'dark') ? 1 : 0
if (this.solEnv.getElementsByTagName('img')[0]) this.solEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.optimismEnv.getElementsByTagName('img')[0]) this.optimismEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.solhintEnv.getElementsByTagName('img')[0]) this.solhintEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.learnEthEnv.getElementsByTagName('img')[0]) this.learnEthEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.sourcifyEnv.getElementsByTagName('img')[0]) this.sourcifyEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.moreEnv.getElementsByTagName('img')[0]) this.moreEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
if (this.websiteIcon) this.websiteIcon.style.filter = `invert(${invertNum})`
}
showMediaPanel (e) {
if (e.target.id === 'remixIDEHomeTwitterbtn') {
this.mediumPanel.classList.remove('d-block')
this.mediumPanel.classList.add('d-none')
this.twitterPanel.classList.toggle('d-block')
_paq.push(['trackEvent', 'pluginManager', 'media', 'twitter'])
} else {
this.twitterPanel.classList.remove('d-block')
this.twitterPanel.classList.add('d-none')
this.mediumPanel.classList.toggle('d-block')
_paq.push(['trackEvent', 'pluginManager', 'media', 'medium'])
}
} }
render () { render () {
const load = (service, item, examples, info) => { this.renderComponent()
const contentImport = this.contentImport return this.el
const fileProviders = globalRegistry.get('fileproviders').api }
const msg = yo`
<div class="p-2">
<span>Enter the ${item} you would like to load.</span>
<div>${info}</div>
<div>e.g ${examples.map((url) => { return yo`<div class="p-1"><a>${url}</a></div>` })}</div>
</div>`
const title = `Import from ${service}`
modalDialogCustom.prompt(title, msg, null, (target) => {
if (target !== '') {
contentImport.import(
target,
(loadingMsg) => { tooltip(loadingMsg) },
(error, content, cleanUrl, type, url) => {
if (error) {
modalDialogCustom.alert(title, error.message || error)
} else {
try {
fileProviders.workspace.addExternal(type + '/' + cleanUrl, content, url)
this.verticalIcons.select('filePanel')
} catch (e) {
modalDialogCustom.alert(title, e.message)
}
}
}
)
}
})
}
const startSolidity = async () => {
await this.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting'])
this.verticalIcons.select('solidity')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidity'])
}
const startOptimism = async () => {
await this.appManager.activatePlugin('optimism-compiler')
this.verticalIcons.select('optimism-compiler')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'optimism-compiler'])
}
const startSolhint = async () => {
await this.appManager.activatePlugin(['solidity', 'solhint'])
this.verticalIcons.select('solhint')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solhint'])
}
const startLearnEth = async () => {
await this.appManager.activatePlugin(['solidity', 'LearnEth', 'solidityUnitTesting'])
this.verticalIcons.select('LearnEth')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'learnEth'])
}
const startSourceVerify = async () => {
await this.appManager.activatePlugin(['solidity', 'source-verification'])
this.verticalIcons.select('source-verification')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'source-verification'])
}
const startPluginManager = async () => {
await this.appManager.activatePlugin('pluginManager')
this.verticalIcons.select('pluginManager')
}
const startRestoreBackupZip = async () => {
await this.appManager.activatePlugin(['restorebackupzip'])
this.verticalIcons.select('restorebackupzip')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'restorebackupzip'])
}
const createNewFile = () => {
this.call('filePanel', 'createNewFile')
}
const saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
var evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}
const downloadFiles = async () => {
try {
tooltip('preparing files for download, please wait..')
const fileProviders = globalRegistry.get('fileproviders').api
const zip = new JSZip()
await fileProviders.browser.copyFolderToJson('/', ({ path, content }) => {
zip.file(`remixbackup${path}`, content)
})
zip.generateAsync({ type: 'blob' }).then(function (blob) {
saveAs(blob, 'remixbackup.zip')
}).catch((e) => {
tooltip(e.message)
})
} catch (e) {
tooltip(e.message)
}
}
const uploadFile = (target) => {
this.call('filePanel', 'uploadFile', target)
}
const connectToLocalhost = () => {
this.appManager.activatePlugin('remixd')
}
const importFromGist = () => {
this.gistHandler.loadFromGist({ gist: '' }, globalRegistry.get('filemanager').api)
this.verticalIcons.select('filePanel')
}
globalRegistry.get('themeModule').api.events.on('themeChanged', (theme) => {
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('remixLogo'))
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('solidityLogo'))
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('debuggerLogo'))
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('learnEthLogo'))
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('workshopLogo'))
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('moreLogo'))
globalRegistry.get('themeModule').api.fixInvert(document.getElementById('solhintLogo'))
})
const createLargeButton = (imgPath, envID, envText, callback) => {
return yo`
<button
class="btn border-secondary d-flex mr-3 text-nowrap justify-content-center flex-column align-items-center ${css.envButton}"
data-id="landingPageStartSolidity"
onclick=${() => callback()}
>
<img class="m-2 align-self-center ${css.envLogo}" id=${envID} src="${imgPath}">
<label class="text-uppercase text-dark ${css.cursorStyle}">${envText}</label>
</button>
`
}
// main
this.solEnv = createLargeButton('assets/img/solidityLogo.webp', 'solidityLogo', 'Solidity', startSolidity)
// Featured
this.optimismEnv = createLargeButton('assets/img/optimismLogo.webp', 'optimismLogo', 'Optimism', startOptimism)
this.solhintEnv = createLargeButton('assets/img/solhintLogo.png', 'solhintLogo', 'Solhint linter', startSolhint)
this.learnEthEnv = createLargeButton('assets/img/learnEthLogo.webp', 'learnEthLogo', 'LearnEth', startLearnEth)
this.sourcifyEnv = createLargeButton('assets/img/sourcifyLogo.webp', 'sourcifyLogo', 'Sourcify', startSourceVerify)
this.moreEnv = createLargeButton('assets/img/moreLogo.webp', 'moreLogo', 'More', startPluginManager)
this.websiteIcon = yo`<img id='remixHhomeWebsite' class="mr-1 ${css.image}" src="${profile.icon}"></img>`
const themeQuality = globalRegistry.get('themeModule').api.currentTheme().quality
const invertNum = (themeQuality === 'dark') ? 1 : 0
this.solEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.optimismEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.solhintEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.learnEthEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.sourcifyEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.moreEnv.getElementsByTagName('img')[0].style.filter = `invert(${invertNum})`
this.websiteIcon.style.filter = `invert(${invertNum})`
const switchToPreviousVersion = () => {
const query = new QueryParams()
query.update({ appVersion: '0.7.7' })
_paq.push(['trackEvent', 'LoadingType', 'oldExperience_0.7.7'])
document.location.reload()
}
const migrate = async () => {
try {
setTimeout(() => {
tooltip('migrating workspace...')
}, 500)
const workspaceName = await migrateToWorkspace(this.fileManager, this.filePanel)
tooltip('done. ' + workspaceName + ' created.')
} catch (e) {
setTimeout(() => {
tooltip(e.message)
}, 1000)
}
}
const onAcceptDownloadn = async () => {
await downloadFiles()
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
migrate()
}
const onDownload = () => {
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
migrate()
}
const onCancel = () => {
const el = document.getElementById('modal-dialog')
el.parentElement.removeChild(el)
}
const migrateWorkspace = async () => {
modalDialog(
'File system Migration',
yo`
<span>Do you want to download your files to local device first?</span>
<div class="d-flex justify-content-around pt-3 mt-3 border-top">
<button class="btn btn-sm btn-primary" onclick=${async () => onAcceptDownloadn()}>Download and Migrate</button>
<button class="btn btn-sm btn-secondary ${css.migrationBtn}" onclick=${() => onDownload()}>Migrate</button>
<button class="btn btn-sm btn-secondary ${css.migrationBtn}" onclick=${() => onCancel()}>Cancel</button>
</div>
`,
{
label: '',
fn: null
},
{
label: '',
fn: null
}
)
}
const img = yo`<img class="m-4 ${css.logoImg}" src="assets/img/guitarRemiCroped.webp" onclick="${() => playRemi()}"></img>`
const playRemi = async () => { await document.getElementById('remiAudio').play() }
// to retrieve medium posts
document.body.appendChild(yo`<script src="https://www.twilik.com/assets/retainable/rss-embed/retainable-rss-embed.js"></script>`)
const container = yo`
<div class="${css.homeContainer} d-flex" data-id="landingPageHomeContainer">
<div class="${css.mainContent} bg-light">
<div class="d-flex justify-content-between">
<div class="d-flex flex-column">
<div class="border-bottom d-flex justify-content-between clearfix py-3 mb-4">
<div class="mx-4 w-100 d-flex">
${img}
<audio id="remiAudio" muted=false src="assets/audio/remiGuitar-single-power-chord-A-minor.wav"></audio>
<div class="w-80 pl-5 ml-5">
<h5 class="mb-1">Quicklinks</h5>
<a class="${css.text} mr-1" target="__blank" href="https://medium.com/remix-ide/migrating-files-to-workspaces-8e34737c751c?source=friends_link&sk=b75cfd9093aa23c78be13cce49e4a5e8">Guide </a>for migrating the old File System
<p class="font-weight-bold mb-0 py-1">Migration tools:</p>
<li class="pl-1">
<spam class="pl-0">
<u class="${css.text} pr-1" onclick=${() => migrateWorkspace()}>Basic migration</u>
</spam>
</li>
<li class="pl-1">
<u class="${css.text} pr-1" onclick=${() => downloadFiles()}>Download all Files</u>
as a backup zip
</li>
<li class="pl-1">
<u class="${css.text} pr-1" onclick=${() => startRestoreBackupZip()}>Restore files</u>from backup zip
</li>
<p class="font-weight-bold mb-0 mt-2">Help:</p>
<dir class="d-flex flex-column mt-1 pl-0">
<a class="${css.text} mx-1" target="__blank" href="https://gitter.im/ethereum/remix">Gitter channel</a>
<a class="${css.text} mx-1" target="__blank" href="https://github.com/ethereum/remix-project/issues">Report on Github</a>
</dir>
</div>
</div>
</div>
<div class="row ${css.hpSections} mx-4" data-id="landingPageHpSections">
<div class="ml-3">
<div class="plugins mb-5">
<h4>Featured Plugins</h4>
<div class="d-flex flex-row pt-2">
${this.solEnv}
${this.optimismEnv}
${this.learnEthEnv}
${this.solhintEnv}
${this.sourcifyEnv}
${this.moreEnv}
</div>
</div>
<div class="d-flex">
<div class="file">
<h4>File</h4>
<p class="mb-1">
<i class="mr-1 far fa-file"></i>
<span class="ml-1 mb-1 ${css.text}" onclick=${() => createNewFile()}>New File</span>
</p>
<p class="mb-1">
<i class="mr-1 far fa-file-alt"></i>
<label class="ml-1 ${css.labelIt} ${css.bigLabelSize} ${css.text}">
Open Files
<input title="open file" type="file" onchange="${(event) => {
event.stopPropagation()
uploadFile(event.target)
}
}" multiple />
</label>
</p>
<p class="mb-1">
<i class="far fa-hdd"></i>
<span class="ml-1 ${css.text}" onclick=${() => connectToLocalhost()}>Connect to Localhost</span>
</p>
<p class="mt-3 mb-0"><label>LOAD FROM:</label></p>
<div class="btn-group">
<button class="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onclick="${() => importFromGist()}">Gist</button>
<button class="btn mx-1 btn-secondary" onclick="${() => load('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}">GitHub</button>
<button class="btn mx-1 btn-secondary" onclick="${() => load('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>'])}">Ipfs</button>
<button class="btn mx-1 btn-secondary" onclick="${() => load('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])}">https</button>
</div><!-- end of btn-group -->
</div><!-- end of div.file -->
<div class="ml-4 pl-4">
<h4>Resources</h4>
<p class="mb-1">
<i class="mr-1 fas fa-book"></i>
<a class="${css.text}" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/#">Documentation</a>
</p>
<p class="mb-1">
<i class="mr-1 fab fa-gitter"></i>
<a class="${css.text}" target="__blank" href="https://gitter.im/ethereum/remix">Gitter channel</a>
</p>
<p class="mb-1">
${this.websiteIcon}
<a class="${css.text}" target="__blank" href="https://remix-project.org">Featuring website</a>
</p>
<p class="mb-1">
<i class="fab fa-ethereum ${css.image}"></i>
<span class="${css.text}" onclick=${() => switchToPreviousVersion()}>Old experience</span>
</p>
</div>
</div>
</div>
</div><!-- end of hpSections -->
</div>
<div class="d-flex flex-column ${css.rightPanel}">
<div class="d-flex pr-3 py-2 align-self-end" id="remixIDEMediaPanelsTitle">
${this.badgeTwitter}
${this.badgeMedium}
</div>
<div class="mr-3 d-flex bg-light ${css.panels}" id="remixIDEMediaPanels">
${this.mediumPanel}
${this.twitterPanel}
</div>
</div>
</div>
</div>
</div>
`
return container renderComponent () {
ReactDOM.render(
<RemixUiHomeTab
plugin={this}
/>
, this.el)
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,133 @@
/* eslint-disable */
export const cairoConf = {
comments: {
lineComment: '#'
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')'],
['%{', '%}']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '%{', close: '%}' },
{ open: "'", close: "'", notIn: ['string', 'comment'] }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '%{', close: '%}' },
{ open: "'", close: "'" }
]
}
export const cairoLang = {
defaultToken: '',
tokenPostfix: '.cairo',
brackets: [
{ token: 'delimiter.curly', open: '{', close: '}' },
{ token: 'delimiter.parenthesis', open: '(', close: ')' },
{ token: 'delimiter.square', open: '[', close: ']' },
{ token: 'delimiter.curly', open: '%{', close: '%}' }
],
keywords: [
// control
'if',
'else',
'end',
// meta
'alloc_locals',
'as',
'assert',
'cast',
'const',
'dw',
'felt',
'from',
'func',
'import',
'let',
'local',
'member',
'nondet',
'return',
'static_assert',
'struct',
'tempvar',
'with_attr',
'with',
// register
'ap',
'fp',
// opcode
'call',
'jmp',
'ret',
'abs',
'rel'
],
operators: ['=', ':', '==', '++', '+', '-', '*', '**', '/', '&', '%', '_'],
// we include these common regular expressions
symbols: /[=><!~?:&|+\-*\/\^%]+/,
numberDecimal: /[+-]?[0-9]+/,
numberHex: /[+-]?0x[0-9a-fA-F]+/,
// The main tokenizer for our languages
tokenizer: {
root: [
// identifiers and keywords
[
/[a-zA-Z_]\w*/,
{
cases: {
'@keywords': { token: 'keyword.$0' },
'@default': 'identifier'
}
}
],
// whitespace
{ include: '@whitespace' },
// directives
[/^%[a-zA-Z]\w*/, 'tag'],
// delimiters and operators
[/[{}()\[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[
/@symbols/,
{
cases: {
'@operators': 'delimiter',
'@default': ''
}
}
],
// numbers
[/(@numberHex)/, 'number.hex'],
[/(@numberDecimal)/, 'number'],
// strings
[/'[^']*'/, 'string']
],
whitespace: [
[/\s+/, 'white'],
[/(^#.*$)/, 'comment']
]
}
}

@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint
import Editor, { loader } from '@monaco-editor/react' import Editor, { loader } from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor' import { reducerActions, reducerListener, initialState } from './actions/editor'
import { language, conf } from './syntax' import { language, conf } from './syntax'
import { cairoLang, cairoConf } from './cairoSyntax'
import './remix-ui-editor.css' import './remix-ui-editor.css'
@ -273,7 +274,11 @@ export const EditorUI = (props: EditorUIProps) => {
const file = editorModelsState[props.currentFile] const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model) editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly }) editorRef.current.updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly })
if (file.language === 'sol') monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity') if (file.language === 'sol') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
} else if (file.language === 'cairo') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo')
}
setAnnotationsbyFile(props.currentFile) setAnnotationsbyFile(props.currentFile)
setMarkerbyFile(props.currentFile) setMarkerbyFile(props.currentFile)
}, [props.currentFile]) }, [props.currentFile])
@ -362,9 +367,13 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current = monaco monacoRef.current = monaco
// Register a new language // Register a new language
monacoRef.current.languages.register({ id: 'remix-solidity' }) monacoRef.current.languages.register({ id: 'remix-solidity' })
monacoRef.current.languages.register({ id: 'remix-cairo' })
// Register a tokens provider for the language // Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', language) monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', language)
monacoRef.current.languages.setLanguageConfiguration('remix-solidity', conf) monacoRef.current.languages.setLanguageConfiguration('remix-solidity', conf)
monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoLang)
monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoConf)
} }
return ( return (

@ -59,5 +59,6 @@ export const getPathIcon = (path: string) => {
? 'fas fa-brackets-curly' : path.endsWith('.vy') ? 'fas fa-brackets-curly' : path.endsWith('.vy')
? 'fak fa-vyper-mono' : path.endsWith('.lex') ? 'fak fa-vyper-mono' : path.endsWith('.lex')
? 'fak fa-lexon' : path.endsWith('.contract') ? 'fak fa-lexon' : path.endsWith('.contract')
? 'fab fa-ethereum' : 'far fa-file' ? 'fab fa-ethereum' : path.endsWith('.cairo')
? 'fab fa-ethereum' : 'far fa-file' // TODO: add cairo icon
} }

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

@ -0,0 +1,248 @@
{
"rules": {
"array-callback-return": "warn",
"dot-location": ["warn", "property"],
"eqeqeq": ["warn", "smart"],
"new-parens": "warn",
"no-caller": "warn",
"no-cond-assign": ["warn", "except-parens"],
"no-const-assign": "warn",
"no-control-regex": "warn",
"no-delete-var": "warn",
"no-dupe-args": "warn",
"no-dupe-keys": "warn",
"no-duplicate-case": "warn",
"no-empty-character-class": "warn",
"no-empty-pattern": "warn",
"no-eval": "warn",
"no-ex-assign": "warn",
"no-extend-native": "warn",
"no-extra-bind": "warn",
"no-extra-label": "warn",
"no-fallthrough": "warn",
"no-func-assign": "warn",
"no-implied-eval": "warn",
"no-invalid-regexp": "warn",
"no-iterator": "warn",
"no-label-var": "warn",
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }],
"no-lone-blocks": "warn",
"no-loop-func": "warn",
"no-mixed-operators": [
"warn",
{
"groups": [
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": false
}
],
"no-multi-str": "warn",
"no-native-reassign": "warn",
"no-negated-in-lhs": "warn",
"no-new-func": "warn",
"no-new-object": "warn",
"no-new-symbol": "warn",
"no-new-wrappers": "warn",
"no-obj-calls": "warn",
"no-octal": "warn",
"no-octal-escape": "warn",
"no-redeclare": "warn",
"no-regex-spaces": "warn",
"no-restricted-syntax": ["warn", "WithStatement"],
"no-script-url": "warn",
"no-self-assign": "warn",
"no-self-compare": "warn",
"no-sequences": "warn",
"no-shadow-restricted-names": "warn",
"no-sparse-arrays": "warn",
"no-template-curly-in-string": "warn",
"no-this-before-super": "warn",
"no-throw-literal": "warn",
"no-restricted-globals": [
"error",
"addEventListener",
"blur",
"close",
"closed",
"confirm",
"defaultStatus",
"defaultstatus",
"event",
"external",
"find",
"focus",
"frameElement",
"frames",
"history",
"innerHeight",
"innerWidth",
"length",
"location",
"locationbar",
"menubar",
"moveBy",
"moveTo",
"name",
"onblur",
"onerror",
"onfocus",
"onload",
"onresize",
"onunload",
"open",
"opener",
"opera",
"outerHeight",
"outerWidth",
"pageXOffset",
"pageYOffset",
"parent",
"print",
"removeEventListener",
"resizeBy",
"resizeTo",
"screen",
"screenLeft",
"screenTop",
"screenX",
"screenY",
"scroll",
"scrollbars",
"scrollBy",
"scrollTo",
"scrollX",
"scrollY",
"self",
"status",
"statusbar",
"stop",
"toolbar",
"top"
],
"no-unexpected-multiline": "warn",
"no-unreachable": "warn",
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true,
"allowTaggedTemplates": true
}
],
"no-unused-labels": "warn",
"no-useless-computed-key": "warn",
"no-useless-concat": "warn",
"no-useless-escape": "warn",
"no-useless-rename": [
"warn",
{
"ignoreDestructuring": false,
"ignoreImport": false,
"ignoreExport": false
}
],
"no-with": "warn",
"no-whitespace-before-property": "warn",
"react-hooks/exhaustive-deps": "warn",
"require-yield": "warn",
"rest-spread-spacing": ["warn", "never"],
"strict": ["warn", "never"],
"unicode-bom": ["warn", "never"],
"use-isnan": "warn",
"valid-typeof": "warn",
"no-restricted-properties": [
"error",
{
"object": "require",
"property": "ensure",
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
},
{
"object": "System",
"property": "import",
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
}
],
"getter-return": "warn",
"import/first": "error",
"import/no-amd": "error",
"import/no-webpack-loader-syntax": "error",
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }],
"react/jsx-no-comment-textnodes": "warn",
"react/jsx-no-duplicate-props": "warn",
"react/jsx-no-target-blank": "warn",
"react/jsx-no-undef": "error",
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }],
"react/jsx-uses-react": "warn",
"react/jsx-uses-vars": "warn",
"react/no-danger-with-children": "warn",
"react/no-direct-mutation-state": "warn",
"react/no-is-mounted": "warn",
"react/no-typos": "error",
"react/react-in-jsx-scope": "error",
"react/require-render-return": "error",
"react/style-prop-object": "warn",
"react/jsx-no-useless-fragment": "warn",
"jsx-a11y/accessible-emoji": "warn",
"jsx-a11y/alt-text": "warn",
"jsx-a11y/anchor-has-content": "warn",
"jsx-a11y/anchor-is-valid": [
"warn",
{ "aspects": ["noHref", "invalidHref"] }
],
"jsx-a11y/aria-activedescendant-has-tabindex": "warn",
"jsx-a11y/aria-props": "warn",
"jsx-a11y/aria-proptypes": "warn",
"jsx-a11y/aria-role": "warn",
"jsx-a11y/aria-unsupported-elements": "warn",
"jsx-a11y/heading-has-content": "warn",
"jsx-a11y/iframe-has-title": "warn",
"jsx-a11y/img-redundant-alt": "warn",
"jsx-a11y/no-access-key": "warn",
"jsx-a11y/no-distracting-elements": "warn",
"jsx-a11y/no-redundant-roles": "warn",
"jsx-a11y/role-has-required-aria-props": "warn",
"jsx-a11y/role-supports-aria-props": "warn",
"jsx-a11y/scope": "warn",
"react-hooks/rules-of-hooks": "error",
"default-case": "off",
"no-dupe-class-members": "off",
"no-undef": "off",
"@typescript-eslint/consistent-type-assertions": "warn",
"no-array-constructor": "off",
"@typescript-eslint/no-array-constructor": "warn",
"@typescript-eslint/no-namespace": "error",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": [
"warn",
{
"functions": false,
"classes": false,
"variables": false,
"typedefs": false
}
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{ "args": "none", "ignoreRestSiblings": true }
],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "warn"
},
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"jest": true,
"node": true
},
"settings": { "react": { "version": "detect" } },
"plugins": ["import", "jsx-a11y", "react", "react-hooks"],
"extends": ["../../../.eslintrc"],
"ignorePatterns": ["!**/*"]
}

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

@ -0,0 +1 @@
export * from './lib/remix-ui-home-tab'

@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useContext } from 'react'
import { ThemeContext } from '../themeContext'
interface PluginButtonProps {
imgPath: string,
envID: string,
envText: string,
callback: any
}
function PluginButton ({ imgPath, envID, envText, callback }: PluginButtonProps) {
const themeFilter = useContext(ThemeContext)
return (
<button
className="btn border-secondary d-flex mr-3 text-nowrap justify-content-center flex-column align-items-center remixui_envButton"
data-id={'landingPageStart' + envText}
onClick={() => callback()}
>
<img className="m-2 align-self-center remixui_envLogo" id={envID} src={imgPath} alt="" style={ { filter: themeFilter.filter } } />
<label className="text-uppercase text-dark remixui_cursorStyle">{envText}</label>
</button>
)
}
export default PluginButton

@ -0,0 +1,82 @@
.remixui_text {
cursor: pointer;
font-weight: normal;
max-width: 300px;
}
.remixui_text:hover {
cursor: pointer;
text-decoration: underline;
}
.remixui_homeContainer {
overflow-y: hidden;
overflow-y: auto;
flex-grow: 3;
}
.remixui_hpLogoContainer {
margin: 30px;
padding-right: 90px;
}
.remixui_mediaBadge {
font-size: 2em;
height: 2em;
width: 2em;
}
.remixui_mediaBadge:focus {
outline: none;
}
.remixui_image {
height: 1em;
width: 1em;
text-align: center;
}
.remixui_logoImg {
height: 10em;
}
.remixui_rightPanel {
right: 0;
position: absolute;
z-index: 3;
}
.remixui_remixHomeMedia {
overflow-y: auto;
overflow-x: hidden;
}
.remixui_panels {
box-shadow: 0px 0px 13px -7px;
}
.remixui_labelIt {
margin-bottom: 0;
}
.remixui_bigLabelSize {
font-size: 13px;
}
.remixui_seeAll {
margin-top: 7px;
white-space: nowrap;
}
.remixui_importFrom p {
margin-right: 10px;
}
.remixui_logoContainer img{
height: 150px;
opacity: 0.7;
}
.remixui_envLogo {
height: 16px;
}
.remixui_cursorStyle {
cursor: pointer;
}
.remixui_envButton {
width: 120px;
height: 70px;
}
.remixui_media {
overflow: hidden;
max-width: 400px;
transition: .5s ease-out;
z-index: 1000;
}
.remixui_migrationBtn {
width: 100px;
}

@ -0,0 +1,370 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import './remix-ui-home-tab.css'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import PluginButton from './components/pluginButton' // eslint-disable-line
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import { ThemeContext, themes } from './themeContext'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
/* eslint-disable-next-line */
export interface RemixUiHomeTabProps {
plugin: any
}
const loadingInitialState = {
tooltip: '',
showModalDialog: false,
importSource: ''
}
const loadingReducer = (state = loadingInitialState, action) => {
return { ...state, tooltip: action.tooltip, showModalDialog: false, importSource: '' }
}
export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
const { plugin } = props
const fileManager = plugin.fileManager
const [state, setState] = useState<{
themeQuality: { filter: string, name: string },
showMediaPanel: 'none' | 'twitter' | 'medium',
showModalDialog: boolean,
modalInfo: { title: string, loadItem: string, examples: Array<string> },
importSource: string,
toasterMsg: string
}>({
themeQuality: themes.light,
showMediaPanel: 'none',
showModalDialog: false,
modalInfo: { title: '', loadItem: '', examples: [] },
importSource: '',
toasterMsg: ''
})
const processLoading = () => {
const contentImport = plugin.contentImport
const workspace = fileManager.getProvider('workspace')
contentImport.import(
state.importSource,
(loadingMsg) => dispatch({ tooltip: loadingMsg }),
(error, content, cleanUrl, type, url) => {
if (error) {
toast(error.message || error)
} else {
try {
workspace.addExternal(type + '/' + cleanUrl, content, url)
plugin.call('menuicons', 'select', 'filePanel')
} catch (e) {
toast(e.message)
}
}
}
)
setState(prevState => {
return { ...prevState, showModalDialog: false, importSource: '' }
})
}
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const playRemi = async () => {
remiAudioEl.current.play()
}
const remiAudioEl = useRef(null)
const inputValue = useRef(null)
useEffect(() => {
plugin.call('theme', 'currentTheme').then((theme) => {
// update theme quality. To be used for for images
setState(prevState => {
return { ...prevState, themeQuality: theme.quality === 'dark' ? themes.dark : themes.light }
})
})
plugin.on('theme', 'themeChanged', (theme) => {
// update theme quality. To be used for for images
setState(prevState => {
return { ...prevState, themeQuality: theme.quality === 'dark' ? themes.dark : themes.light }
})
})
window.addEventListener('click', (event) => {
const target = event.target as Element
const id = target.id
if (id !== 'remixIDEHomeTwitterbtn' && id !== 'remixIDEHomeMediumbtn') {
// todo check event.target
setState(prevState => { return { ...prevState, showMediaPanel: 'none' } })
}
})
// to retrieve twitter feed
const scriptTwitter = document.createElement('script')
scriptTwitter.src = 'https://platform.twitter.com/widgets.js'
scriptTwitter.async = true
document.body.appendChild(scriptTwitter)
// to retrieve medium publications
const scriptMedium = document.createElement('script')
scriptMedium.src = 'https://www.twilik.com/assets/retainable/rss-embed/retainable-rss-embed.js'
scriptMedium.async = true
document.body.appendChild(scriptMedium)
return () => {
document.body.removeChild(scriptTwitter)
document.body.removeChild(scriptMedium)
}
}, [])
const toast = (message: string) => {
setState(prevState => {
return { ...prevState, toasterMsg: message }
})
}
const createNewFile = async () => {
await plugin.call('filePanel', 'createNewFile')
}
const uploadFile = async (target) => {
await plugin.call('filePanel', 'uploadFile', target)
}
const connectToLocalhost = () => {
plugin.appManager.activatePlugin('remixd')
}
const importFromGist = () => {
plugin.gistHandler.loadFromGist({ gist: '' }, fileManager)
plugin.verticalIcons.select('filePanel')
}
const switchToPreviousVersion = () => {
const query = new QueryParams()
query.update({ appVersion: '0.7.7' })
_paq.push(['trackEvent', 'LoadingType', 'oldExperience_0.7.7'])
document.location.reload()
}
const startSolidity = async () => {
await plugin.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting'])
plugin.verticalIcons.select('solidity')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidity'])
}
const startOptimism = async () => {
await plugin.appManager.activatePlugin('optimism-compiler')
plugin.verticalIcons.select('optimism-compiler')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'optimism-compiler'])
}
const startSolhint = async () => {
await plugin.appManager.activatePlugin(['solidity', 'solhint'])
plugin.verticalIcons.select('solhint')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solhint'])
}
const startLearnEth = async () => {
await plugin.appManager.activatePlugin(['solidity', 'LearnEth', 'solidityUnitTesting'])
plugin.verticalIcons.select('LearnEth')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'learnEth'])
}
const startSourceVerify = async () => {
await plugin.appManager.activatePlugin(['solidity', 'source-verification'])
plugin.verticalIcons.select('source-verification')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'source-verification'])
}
const startPluginManager = async () => {
await plugin.appManager.activatePlugin('pluginManager')
plugin.verticalIcons.select('pluginManager')
}
const showFullMessage = (title: string, loadItem: string, examples: Array<string>) => {
setState(prevState => {
return { ...prevState, showModalDialog: true, modalInfo: { title: title, loadItem: loadItem, examples: examples } }
})
}
const hideFullMessage = () => { //eslint-disable-line
setState(prevState => {
return { ...prevState, showModalDialog: false, importSource: '' }
})
}
const maxHeight = Math.max(window.innerHeight - 150, 250) + 'px'
const examples = state.modalInfo.examples.map((urlEl, key) => (<div key={key} className="p-1 user-select-auto"><a>{urlEl}</a></div>))
const elHeight = '4000px'
return (
<>
<ModalDialog
id='homeTab'
title={ 'Import from ' + state.modalInfo.title }
okLabel='Import'
hide={ !state.showModalDialog }
handleHide={ () => hideFullMessage() }
okFn={ () => processLoading() }
>
<div className="p-2 user-select-auto">
{ state.modalInfo.loadItem !== '' && <span>Enter the { state.modalInfo.loadItem } you would like to load.</span> }
{ state.modalInfo.examples.length !== 0 &&
<>
<div>e.g</div>
<div>
{ examples }
</div>
</> }
<input
ref={inputValue}
type='text'
name='prompt_text'
id='inputPrompt_text'
className="w-100 mt-1 form-control"
data-id="homeTabModalDialogCustomPromptText"
value={state.importSource}
onInput={(e) => {
setState(prevState => {
return { ...prevState, importSource: inputValue.current.value }
})
}}
/>
</div>
</ModalDialog>
<Toaster message={state.toasterMsg} />
<div className="d-flex flex-column ml-4" id="remixUiRightPanel">
<div className="border-bottom d-flex justify-content-between mr-4 pb-3 mb-3">
<div className="mx-4 my-4 d-flex">
<label style={ { fontSize: 'xxx-large', height: 'auto', alignSelf: 'flex-end' } }>Remix IDE</label>
</div>
<div className="mr-4 d-flex">
<img className="mt-4 mb-2 remixui_logoImg" src="assets/img/guitarRemiCroped.webp" onClick={ () => playRemi() } alt=""></img>
<audio
id="remiAudio"
muted={false}
src="assets/audio/remiGuitar-single-power-chord-A-minor.wav"
ref={remiAudioEl}
></audio>
</div>
</div>
<div className="row remixui_hpSections mx-2 mr-4" data-id="landingPageHpSections">
<div className="ml-3">
<div className="mb-5">
<h4>Featured Plugins</h4>
<div className="d-flex flex-row pt-2">
<ThemeContext.Provider value={ state.themeQuality }>
<PluginButton imgPath="assets/img/solidityLogo.webp" envID="solidityLogo" envText="Solidity" callback={() => startSolidity()} />
<PluginButton imgPath="assets/img/optimismLogo.webp" envID="optimismLogo" envText="Optimism" callback={() => startOptimism()} />
<PluginButton imgPath="assets/img/solhintLogo.webp" envID="solhintLogo" envText="Solhint linter" callback={() => startSolhint()} />
<PluginButton imgPath="assets/img/learnEthLogo.webp" envID="learnEthLogo" envText="LearnEth" callback={() => startLearnEth()} />
<PluginButton imgPath="assets/img/sourcifyLogo.webp" envID="sourcifyLogo" envText="Sourcify" callback={() => startSourceVerify()} />
<PluginButton imgPath="assets/img/moreLogo.webp" envID="moreLogo" envText="More" callback={startPluginManager} />
</ThemeContext.Provider>
</div>
</div>
<div className="d-flex">
<div className="file">
<h4>File</h4>
<p className="mb-1">
<i className="mr-2 far fa-file"></i>
<span className="ml-1 mb-1 remixui_text" onClick={() => createNewFile()}>New File</span>
</p>
<p className="mb-1">
<i className="mr-2 far fa-file-alt"></i>
<span className="ml-1 remixui_labelIt remixui_bigLabelSize} remixui_text">
Open Files
<input title="open file" type="file" onChange={(event) => {
event.stopPropagation()
uploadFile(event.target)
}} multiple />
</span>
</p>
<p className="mb-1">
<i className="mr-1 far fa-hdd"></i>
<span className="ml-1 remixui_text" onClick={() => connectToLocalhost()}>Connect to Localhost</span>
</p>
<p className="mt-3 mb-0"><label>LOAD FROM:</label></p>
<div className="btn-group">
<button className="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button>
<button className="btn mx-1 btn-secondary" data-id="landingPageImportFromGitHubButton" onClick={() => showFullMessage('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}>GitHub</button>
<button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>'])}>Ipfs</button>
<button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])}>https</button>
</div>
</div>
<div className="ml-4 pl-4">
<h4>Resources</h4>
<p className="mb-1">
<i className="mr-2 fas fa-book"></i>
<a className="remixui_text" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/#">Documentation</a>
</p>
<p className="mb-1">
<i className="mr-2 fab fa-gitter"></i>
<a className="remixui_text" target="__blank" href="https://gitter.im/ethereum/remix">Gitter channel</a>
</p>
<p className="mb-1">
<img id='remixHhomeWebsite' className="mr-2 remixui_image" src={ plugin.profile.icon } style={ { filter: state.themeQuality.filter } } alt=''></img>
<a className="remixui_text" target="__blank" href="https://remix-project.org">Featuring website</a>
</p>
<p className="mb-1">
<i className="mr-2 fab fa-ethereum remixui_image"></i>
<span className="remixui_text" onClick={() => switchToPreviousVersion()}>Old experience</span>
</p>
</div>
</div>
</div>
</div>
<div className="d-flex flex-column remixui_rightPanel">
<div className="d-flex pr-3 py-2 align-self-end" id="remixIDEMediaPanelsTitle">
<button
className="btn-info p-2 m-1 border rounded-circle remixui_mediaBadge fab fa-twitter"
id="remixIDEHomeTwitterbtn"
title="Twitter"
onClick={(e) => {
setState(prevState => {
return { ...prevState, showMediaPanel: state.showMediaPanel === 'twitter' ? 'none' : 'twitter' }
})
_paq.push(['trackEvent', 'pluginManager', 'media', 'twitter'])
}}
></button>
<button
className="btn-danger p-2 m-1 border rounded-circle remixui_mediaBadge fab fa-medium"
id="remixIDEHomeMediumbtn"
title="Medium blogs"
onClick={(e) => {
setState(prevState => {
return { ...prevState, showMediaPanel: state.showMediaPanel === 'medium' ? 'none' : 'medium' }
})
_paq.push(['trackEvent', 'pluginManager', 'media', 'medium'])
}}
></button>
</div>
<div className="mr-3 d-flex bg-light remixui_panels" style={ { visibility: state.showMediaPanel === 'none' ? 'hidden' : 'visible' } } id="remixIDEMediaPanels">
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_remixHomeMedia" style={ { maxHeight: maxHeight } }>
<div id="medium-widget" className="px-3 remixui_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: elHeight } }>
<div
id="retainable-rss-embed"
data-rss="https://medium.com/feed/remix-ide"
data-maxcols="1"
data-layout="grid"
data-poststyle="external"
data-readmore="More..."
data-buttonclass="btn mb-3"
data-offset="-100"
>
</div>
</div>
</div>
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } >
<div className="remixui_media" style={ { minHeight: elHeight } } >
<a className="twitter-timeline"
data-width="330"
data-theme={ state.themeQuality.name }
data-chrome="nofooter noheader transparent"
data-tweet-limit="18"
href="https://twitter.com/EthereumRemix"
>
</a>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default RemixUiHomeTab

@ -0,0 +1,16 @@
import React from 'react' // eslint-disable-line
export const themes = {
light: {
filter: 'invert(0)',
name: 'light'
},
dark: {
filter: 'invert(1)',
name: 'dark'
}
}
export const ThemeContext = React.createContext(
themes.dark // default value
)

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

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -34,7 +34,9 @@ export const ModalDialog = (props: ModalDialogProps) => {
modal.current.addEventListener('blur', handleBlur) modal.current.addEventListener('blur', handleBlur)
return () => { return () => {
modal.current.removeEventListener('blur', handleBlur) if (modal.current) {
modal.current.removeEventListener('blur', handleBlur)
}
} }
} }
}, [modal.current]) }, [modal.current])

@ -1,6 +1,6 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
export interface ModalDialogProps { export interface ModalDialogProps {
id?: string id: string
title?: string, title?: string,
message?: string | JSX.Element, message?: string | JSX.Element,
okLabel?: string, okLabel?: string,

@ -106,6 +106,7 @@ function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
return ( return (
<Fragment> <Fragment>
<ModalDialog <ModalDialog
id='permissionsSettings'
handleHide={closeModal} handleHide={closeModal}
cancelFn={cancel} cancelFn={cancel}
hide={modalVisibility} hide={modalVisibility}

@ -521,6 +521,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
</div> </div>
<ModalDialog <ModalDialog
id='terminal'
title={ modalState.title } title={ modalState.title }
message={ modalState.message } message={ modalState.message }
hide={ modalState.hide } hide={ modalState.hide }

@ -92,6 +92,7 @@ export const Toaster = (props: ToasterProps) => {
return ( return (
<> <>
<ModalDialog <ModalDialog
id='toaster'
message={props.message} message={props.message}
cancelLabel='Close' cancelLabel='Close'
cancelFn={() => {}} cancelFn={() => {}}

@ -139,6 +139,9 @@
"remix-ui-vertical-icons-panel": { "remix-ui-vertical-icons-panel": {
"tags": [] "tags": []
}, },
"remix-ui-home-tab": {
"tags": []
},
"remix-ui-tabs": { "remix-ui-tabs": {
"tags": [] "tags": []
}, },

4242
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -87,6 +87,7 @@
"nightwatch_local_fileExplorer": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/fileExplorer.test.js --env=chrome", "nightwatch_local_fileExplorer": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/fileExplorer.test.js --env=chrome",
"nightwatch_local_debugger": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/debugger_*.test.js --env=chrome", "nightwatch_local_debugger": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/debugger_*.test.js --env=chrome",
"nightwatch_local_editor": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/editor.test.js --env=chrome", "nightwatch_local_editor": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/editor.test.js --env=chrome",
"nightwatch_local_importFromGithub": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/importFromGithub.test.js --env=chrome",
"nightwatch_local_compiler": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/compiler_api.test.js --env=chrome", "nightwatch_local_compiler": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/compiler_api.test.js --env=chrome",
"nightwatch_local_txListener": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/txListener.test.js --env=chrome", "nightwatch_local_txListener": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/txListener.test.js --env=chrome",
"nightwatch_local_fileManager": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/fileManager_api.test.js --env=chrome", "nightwatch_local_fileManager": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/fileManager_api.test.js --env=chrome",

@ -64,6 +64,7 @@
"@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"], "@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"],
"@remix-ui/terminal": ["libs/remix-ui/terminal/src/index.ts"], "@remix-ui/terminal": ["libs/remix-ui/terminal/src/index.ts"],
"@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"], "@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"],
"@remix-ui/home-tab": ["libs/remix-ui/home-tab/src/index.ts"],
"@remix-ui/editor": ["libs/remix-ui/editor/src/index.ts"], "@remix-ui/editor": ["libs/remix-ui/editor/src/index.ts"],
"@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"], "@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"],
"@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"], "@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"],

@ -1007,6 +1007,23 @@
} }
} }
}, },
"remix-ui-home-tab": {
"root": "libs/remix-ui/home-tab",
"sourceRoot": "libs/remix-ui/home-tab/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/home-tab/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/home-tab/**/*"]
}
}
}
},
"remix-ui-editor": { "remix-ui-editor": {
"root": "libs/remix-ui/editor", "root": "libs/remix-ui/editor",
"sourceRoot": "libs/remix-ui/editor/src", "sourceRoot": "libs/remix-ui/editor/src",

Loading…
Cancel
Save