Merge branch 'master' into intl

pull/2581/head
drafish 2 years ago
commit 898fb99625
  1. 2
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  2. 64
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  3. 11
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  4. BIN
      apps/remix-ide/src/assets/img/StarkNetLogo.png
  5. BIN
      apps/remix-ide/src/assets/img/bgRemi.webp
  6. BIN
      apps/remix-ide/src/assets/img/home.webp
  7. 1
      apps/remix-ide/src/assets/img/logoicon.svg
  8. BIN
      apps/remix-ide/src/assets/img/remixRewardBetaTester.webp
  9. BIN
      apps/remix-ide/src/assets/img/remixRewardUser.webp
  10. BIN
      apps/remix-ide/src/assets/img/remix_logo_light.webp
  11. 14
      libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
  12. 14
      libs/remix-ui/home-tab/src/lib/components/customButtonGroupAsArrows.tsx
  13. 20
      libs/remix-ui/home-tab/src/lib/components/customNavButtons.tsx
  14. 73
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  15. 125
      libs/remix-ui/home-tab/src/lib/components/homeTabFeaturedPlugins.tsx
  16. 161
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  17. 88
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  18. 76
      libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx
  19. 28
      libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx
  20. 148
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  21. 30
      libs/remix-ui/home-tab/src/lib/components/pluginButton.tsx
  22. 12
      libs/remix-ui/home-tab/src/lib/components/rssFeed.css
  23. 42
      libs/remix-ui/home-tab/src/lib/components/rssFeed.tsx
  24. 116
      libs/remix-ui/home-tab/src/lib/components/types/carouselTypes.ts
  25. 28
      libs/remix-ui/home-tab/src/lib/components/workspaceTemplate.tsx
  26. 35
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  27. 354
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  28. 1
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.css
  29. 22
      libs/remix-ui/vertical-icons-panel/src/lib/components/BasicLogo.tsx
  30. 2
      libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx
  31. 2
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  32. 4
      libs/remix-ui/workspace/src/lib/components/file-render.tsx
  33. 40
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  34. 341
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  35. 2
      libs/remix-ui/workspace/src/lib/types/index.ts
  36. 4
      libs/remix-ws-templates/src/templates/ozerc1155/index.ts
  37. 4
      libs/remix-ws-templates/src/templates/ozerc20/index.ts
  38. 4
      libs/remix-ws-templates/src/templates/ozerc721/index.ts
  39. 1
      package.json
  40. 5
      yarn.lock

@ -28,7 +28,7 @@ module.exports = {
'Loads Main View': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
.waitForElementVisible('div[data-id="landingPageHomeContainer"]')
.waitForElementVisible('div[data-id="landingPageHpSections"]')
.waitForElementVisible('div[data-id="remixUIHTAll"]')
.waitForElementVisible('div[data-id="terminalContainer"]')
},

@ -345,7 +345,15 @@ module.exports = {
'Should rename a workspace #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceRename"]') // rename workspace_name
.useXpath()
.waitForElementPresent({
selector: '//i[@data-icon="workspaceDropdownMenuIcon"]',
locateStrategy: 'xpath',
})
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[3]') // rename workspace_name
.useCss()
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextRename"]')
.click('*[data-id="modalDialogCustomPromptTextRename"]')
@ -365,12 +373,15 @@ module.exports = {
'Should delete a workspace #group1': function (browser: NightwatchBrowser) {
browser
.switchWorkspace('workspace_name_1')
.click('*[data-id="workspaceDelete"]') // delete workspace_name_1
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementVisible('[data-id="workspacesSelect"]')
.click('[data-id="workspacesSelect"]')
.switchWorkspace('workspace_name_1')//*[@id="workspacesMenuDropdown"]/span
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[2]') // delete workspace_name_1
.waitForElementVisible('//*[@id="fileExplorerView"]/div[2]/div/div/div[2]')
.click('//*[@id="fileExplorerView"]/div[2]/div/div/div[3]/button')
.waitForElementVisible('//*[@id="workspacesSelect"]')
.click('//*[@id="workspacesSelect"]')
.useCss()
.waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`)
},
@ -379,8 +390,11 @@ module.exports = {
'Should clone a repository #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-id="cloneGitRepository"]')
.click('[data-id="cloneGitRepository"]')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -403,8 +417,11 @@ module.exports = {
'Should display non-clashing names for duplicate clone #group2': '' + function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="cloneGitRepository"]')
.click('[data-id="cloneGitRepository"]')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -412,8 +429,11 @@ module.exports = {
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix1')
.waitForElementVisible('[data-id="cloneGitRepository"]')
.click('[data-id="cloneGitRepository"]')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -421,8 +441,10 @@ module.exports = {
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000)
.waitForElementContainsText('[data-id="workspacesSelect"]', 'awesome-remix2')
.waitForElementVisible('[data-id="cloneGitRepository"]')
.click('[data-id="cloneGitRepository"]')
.useXpath()
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -438,8 +460,15 @@ module.exports = {
'Should display error message in modal for failed clone #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="cloneGitRepository"]')
.click('[data-id="cloneGitRepository"]')
.useXpath()
.waitForElementPresent({
selector: '//i[@data-icon="workspaceDropdownMenuIcon"]',
locateStrategy: 'xpath',
})
.click('//*[@id="workspacesMenuDropdown"]/span/i')
.waitForElementVisible('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.click('//*[@id="workspacesMenuDropdown"]/div/ul/a[5]')
.useCss()
.waitForElementVisible('[data-id="fileSystemModalDialogModalBody-react"]')
.click('[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="modalDialogCustomPromptTextClone"]')
@ -456,3 +485,4 @@ module.exports = {
tearDown: sauce
}

@ -10,7 +10,7 @@ const profile = {
methods: [],
events: [],
description: 'Remix home tab ',
icon: 'assets/img/remixLogo.webp',
icon: 'assets/img/home.webp',
location: 'mainPanel',
version: packageJson.version
}
@ -30,9 +30,8 @@ export class LandingPage extends ViewPlugin {
}
render () {
return <div id='landingPageHomeContainer' className='remixui_homeContainer justify-content-between bg-light d-flex' data-id='landingPageHomeContainer'><RemixUiHomeTab
plugin={this}
/></div>
}
return <div id='landingPageHomeContainer' className='remixui_homeContainer justify-content-between bg-light d-flex' data-id='landingPageHomeContainer'>
<RemixUiHomeTab plugin={this} />
</div>
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="165" height="165" viewBox="0 0 165 165" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M91.993 45C83.181 45 76 37.8204 76 28.9921C76 20.1779 83.181 13 91.993 13C100.821 13 108 20.1779 108 28.9921C108 37.8204 100.821 45 91.993 45M82.5 0C36.9366 0 0 36.937 0 82.4991C0 101.872 6.69319 119.672 17.8711 133.749C26.8781 121.109 36.4975 109.188 48.5698 99.2582C48.9162 98.9725 49.7499 98.3619 50.8086 97.5906C55.8308 93.8271 59.3497 88.3158 60.3048 82.1153V82.0117C63.3559 61.898 71.6113 54.9655 94.5527 54.9655C96.5862 54.9655 98.7608 55.0262 101.039 55.1262C112.774 55.6761 119.546 59.0325 120.338 60.7804C120.786 61.7373 120.767 62.7746 120.563 63.769L119.647 63.6476C112.408 62.7532 101.242 64.9706 99.6963 72.86C98.8233 77.3555 99.8605 82.2974 100.285 86.8321C100.734 91.5098 101.141 96.2284 101.101 100.947C101.08 101.313 100.816 103.143 101.06 103.345C88.5737 91.3883 59.6942 106.396 50.6033 112.986C51.5406 112.68 52.4761 112.334 53.4117 111.989C62.0741 109.04 88.3898 101.149 98.5376 109.874C107.139 120.409 99.3928 139.852 92.987 149.491C89.1486 155.288 84.5549 160.374 79.456 164.923C80.4683 164.961 81.4788 165 82.5 165C128.063 165 165 128.065 165 82.4991C165 36.937 128.063 0 82.5 0" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

@ -20,6 +20,20 @@ export const CustomToggle = React.forwardRef(({ children, onClick, icon, classNa
</button>
))
export const CustomIconsToggle = React.forwardRef(({ onClick, icon, className = '' }: { children?: React.ReactNode, onClick: () => void, icon: string, className: string }, ref: Ref<HTMLSpanElement>) => (
<span
ref={ref}
onClick={(e) => {
e.preventDefault()
onClick()
}}
className={`${className.replace('dropdown-toggle', '')} mb-0 pb-0 d-flex justify-content-end align-items-end remixuimenuicon_shadow fs-3`}
data-id="workspaceMenuDropdown"
>
{ icon && <i style={{ fontSize: 'large' }} className={`${icon}`} data-icon="workspaceDropdownMenuIcon"></i> }
</span>
))
// forwardRef again here!
// Dropdown needs access to the DOM of the Menu to measure it
export const CustomMenu = React.forwardRef(

@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
function CustomButtonGroupAsArrows ({ next, previous }) {
return (
<div style={{ textAlign: "center" }}>
<h4>These buttons can be positioned anywhere you want on the screen</h4>
<button onClick={previous}>Prev</button>
<button onClick={next}>Next</button>
</div>
)
}
export default CustomButtonGroupAsArrows

@ -0,0 +1,20 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
const CustomNavButtons = ({ next, previous, goToSlide, ...rest }) => {
const { carouselState: { currentSlide, totalItems } } = rest
return (
<div className="mt-1 d-flex justify-content-end carousel-button-group">
<button className={currentSlide === 0 ? 'disable py-1 border btn' : 'py-1 border btn'} onClick={() => previous()}>
<i className="fas fa-angle-left"></i>
</button>
<button className={currentSlide + 1 === totalItems ? 'disable py-1 border btn' : 'py-1 border btn'} onClick={() => {
if (currentSlide + 1 < totalItems) goToSlide(currentSlide + 1)
}} >
<i className="fas fa-angle-right"></i>
</button>
</div>
)
}
export default CustomNavButtons

@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useRef, useContext } from 'react'
import { ThemeContext, themes } from '../themeContext'
import Carousel from 'react-multi-carousel'
import 'react-multi-carousel/lib/styles.css'
import CustomNavButtons from './customNavButtons'
function HomeTabFeatured() {
const themeFilter = useContext(ThemeContext)
useEffect(() => {
return () => {
}
}, [])
return (
<div className="pt-3 pl-2" id="hTFeaturedeSection">
<label style={{ fontSize: "1.2rem" }}>Featured</label>
<div className="mb-2">
<div className="w-100 d-flex flex-column" style={{ height: "200px" }}>
<ThemeContext.Provider value={ themeFilter }>
<Carousel
customButtonGroup={<CustomNavButtons next={undefined} previous={undefined} goToSlide={undefined} />}
arrows={false}
swipeable={false}
draggable={true}
showDots={true}
responsive={{ desktop: { breakpoint: { max: 2000, min: 1024 }, items: 1 } }}
renderDotsOutside={true}
ssr={true} // means to render carousel on server-side.
infinite={true}
centerMode={false}
autoPlay={true}
keyBoardControl={true}
containerClass="border carousel-container"
sliderClass="px-2 h-100 justify-content-between"
deviceType={"desktop"}
itemClass="px-2 carousel-item-padding-10-px"
autoPlaySpeed={15000}
dotListClass="position-relative mt-2"
>
<div className="d-flex">
<img src={"assets/img/bgRemi.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px"}} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1"}}>
<h5>JUMP INTO WEB3</h5>
<span>The Remix Project is a rich toolset which can be used for the entire journey of contract development by users of any knowledge level, and as a learning lab for teaching and experimenting with Ethereum.</span>
</div>
</div>
<div className="d-flex">
<img src={"/assets/img/remixRewardUser.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 p-4" style={{ flex: "1"}}>
<h5>REMIX REWARDS</h5>
<p style={{fontStyle: 'italic'}}>NFTs for our users!</p>
<span>Remix Project rewards contributors, beta testers, and UX research participants with NFTs deployed on Optimism. Remix Reward holders are able to mint a second Remixer user NFT badge to give to any other user of their choice</span>
</div>
</div>
<div className="d-flex">
<img src={"/assets/img/remixRewardBetaTester.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 p-4" style={{ flex: "1"}}>
<h5>BETA TESTING</h5>
<p style={{fontStyle: 'italic'}}>Our community supports us.</p>
<span>You can join Beta Testing before each release of Remix IDE. Help us test now and get a handle on new features!</span>
</div>
</div>
</Carousel>
</ThemeContext.Provider>
</div>
</div>
</div>
)
}
export default HomeTabFeatured

@ -0,0 +1,125 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useRef, useContext } from 'react'
import PluginButton from './pluginButton'
import { ThemeContext, themes } from '../themeContext'
import Carousel from 'react-multi-carousel'
import 'react-multi-carousel/lib/styles.css'
import CustomNavButtons from './customNavButtons'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
interface HomeTabFeaturedPluginsProps {
plugin: any
}
function HomeTabFeaturedPlugins ({plugin}: HomeTabFeaturedPluginsProps) {
const themeFilter = useContext(ThemeContext)
const carouselRef = useRef(null)
// Todo doesn't work
useEffect(() => {
window.addEventListener("scroll", handleScroll)
return () => {
window.removeEventListener("scroll", handleScroll)
}
}, [])
const handleScroll = (e) => {
}
const startSolidity = async () => {
await plugin.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting'])
plugin.verticalIcons.select('solidity')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidity'])
}
const startStarkNet = async () => {
await plugin.appManager.activatePlugin('starkNet_compiler')
plugin.verticalIcons.select('starkNet_compiler')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'starkNet_compiler'])
}
const startSolhint = async () => {
await plugin.appManager.activatePlugin(['solidity', 'solhint'])
plugin.verticalIcons.select('solhint')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solhint'])
}
const startSourceVerify = async () => {
await plugin.appManager.activatePlugin(['solidity', 'sourcify'])
plugin.verticalIcons.select('sourcify')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'sourcify'])
}
const startSolidityUnitTesting = async () => {
await plugin.appManager.activatePlugin(['solidity', 'solidityUnitTesting'])
plugin.verticalIcons.select('solidityUnitTesting')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidityUnitTesting'])
}
return (
<div className="pl-2 w-100" id="hTFeaturedPlugins">
<label className="" style={{fontSize: "1.2rem"}}>Featured Plugins</label>
<div className="w-100 d-flex flex-column">
<ThemeContext.Provider value={ themeFilter }>
<Carousel
ref={carouselRef}
focusOnSelect
customButtonGroup={<CustomNavButtons next={undefined} previous={undefined} goToSlide={undefined} />}
arrows={false}
swipeable={false}
draggable={true}
showDots={false}
responsive={{ desktop: { breakpoint: { max: 3000, min: 1024 }, items: 5} }}
renderButtonGroupOutside={true}
ssr={true} // means to render carousel on server-side.
keyBoardControl={true}
containerClass="carousel-container"
deviceType={"desktop"}
itemClass="w-100"
>
<PluginButton
imgPath="assets/img/solidityLogo.webp"
envID="solidityLogo"
envText="Solidity"
description="Compile, test and analyse smart contract."
remixMaintained={true}
callback={() => startSolidity()}
/>
<PluginButton
imgPath="assets/img/starkNetLogo.webp"
envID="starkNetLogo"
envText="StarkNet"
description="Compile and deploy contracts with Cairo, a native language for StarkNet."
l2={true}
callback={() => startStarkNet()}
/>
<PluginButton
imgPath="assets/img/solhintLogo.webp"
envID="solhintLogo" envText="Solhint linter"
description="Solhint is an open source project for linting Solidity code."
callback={() => startSolhint()}
/>
<PluginButton
imgPath="assets/img/sourcifyNewLogo.webp"
envID="sourcifyLogo"
envText="Sourcify"
description="Solidity contract and metadata verification service."
callback={() => startSourceVerify()}
/>
<PluginButton
imgPath="assets/img/unitTesting.webp"
envID="sUTLogo"
envText="Solidity unit testing"
description="Write and run unit tests for your contracts in Solidity."
callback={() => startSolidityUnitTesting()}
/>
</Carousel>
</ThemeContext.Provider>
</div>
</div>
)
}
export default HomeTabFeaturedPlugins

@ -0,0 +1,161 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState, useRef, useReducer } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
interface HomeTabFileProps {
plugin: any
}
const loadingInitialState = {
tooltip: '',
showModalDialog: false,
importSource: ''
}
const loadingReducer = (state = loadingInitialState, action) => {
return { ...state, tooltip: action.tooltip, showModalDialog: false, importSource: '' }
}
function HomeTabFile ({plugin}: HomeTabFileProps) {
const [state, setState] = useState<{
searchInput: string,
showModalDialog: boolean,
modalInfo: { title: string, loadItem: string, examples: Array<string> },
importSource: string,
toasterMsg: string
}>({
searchInput: '',
showModalDialog: false,
modalInfo: { title: '', loadItem: '', examples: [] },
importSource: '',
toasterMsg: ''
})
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const inputValue = useRef(null)
const processLoading = () => {
const contentImport = plugin.contentImport
const workspace = plugin.fileManager.getProvider('workspace')
contentImport.import(
state.importSource,
(loadingMsg) => dispatch({ tooltip: loadingMsg }),
async (error, content, cleanUrl, type, url) => {
if (error) {
toast(error.message || error)
} else {
try {
if (await workspace.exists(type + '/' + cleanUrl)) toast('File already exists in workspace')
else {
workspace.addExternal(type + '/' + cleanUrl, content, url)
plugin.call('menuicons', 'select', 'filePanel')
}
} catch (e) {
toast(e.message)
}
}
}
)
setState(prevState => {
return { ...prevState, showModalDialog: false, importSource: '' }
})
}
const toast = (message: string) => {
setState(prevState => {
return { ...prevState, toasterMsg: message }
})
}
const createNewFile = async () => {
plugin.verticalIcons.select('filePanel')
await plugin.call('filePanel', 'createNewFile')
}
const uploadFile = async (target) => {
await plugin.call('filePanel', 'uploadFile', target)
}
const connectToLocalhost = () => {
plugin.appManager.activatePlugin('remixd')
}
const importFromGist = () => {
plugin.call('gistHandler', 'load', '')
plugin.verticalIcons.select('filePanel')
}
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 examples = state.modalInfo.examples.map((urlEl, key) => (<div key={key} className="p-1 user-select-auto"><a>{urlEl}</a></div>))
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="justify-content-start mt-1 p-2 border-bottom d-flex flex-column" id="hTFileSection">
<label style={{fontSize: "1rem"}}>Files</label>
<button className="btn btn-primary p-2 border my-1" data-id="homeTabNewFile" style={{width: 'fit-content'}} onClick={() => createNewFile()}>New File</button>
<label className="btn p-2 border my-1" style={{width: 'fit-content'}} htmlFor="openFileInput">Open File</label>
<input title="open file" type="file" id="openFileInput" onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}} multiple />
<button className="btn p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}>Connect to Localhost</button>
<label className="pt-2">Load From</label>
<div className="d-flex">
<button className="btn p-2 border mr-2" 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 p-2 border mr-2" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button>
<button className="btn p-2 border mr-2" onClick={() => showFullMessage('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>'])}>IPFS</button>
<button className="btn p-2 border" onClick={() => showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])}>HTTPS</button>
</div>
</div>
</>
)
}
export default HomeTabFile

@ -0,0 +1,88 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useRef, useContext } from 'react'
import { ThemeContext, themes } from '../themeContext'
import Carousel from 'react-multi-carousel'
import WorkspaceTemplate from './workspaceTemplate'
import 'react-multi-carousel/lib/styles.css'
import CustomNavButtons from './customNavButtons'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
interface HomeTabGetStartedProps {
plugin: any
}
function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
const themeFilter = useContext(ThemeContext)
const createWorkspace = async (templateName) => {
await plugin.appManager.activatePlugin('filePanel')
const timeStamp = Date.now()
await plugin.call('filePanel', 'createWorkspace', templateName + "_" + timeStamp, templateName)
await plugin.call('filePanel', 'setWorkspace', templateName + "_" + timeStamp)
plugin.verticalIcons.select('filePanel')
_paq.push(['trackEvent', 'homeGetStarted', templateName])
}
return (
<div className="pl-2" id="hTGetStartedSection">
<label style={{fontSize: "1.2rem"}}>
<span className="mr-2" style={{fontWeight: "bold"}}>
Get Started
</span>
- Project Templates
</label>
<div className="w-100 d-flex flex-column">
<ThemeContext.Provider value={ themeFilter }>
<Carousel
focusOnSelect
customButtonGroup={<CustomNavButtons next={undefined} previous={undefined} goToSlide={undefined} />}
arrows={false}
swipeable={false}
draggable={true}
showDots={false}
responsive={{ desktop: { breakpoint: { max: 3000, min: 1024 }, items: 5} }}
renderButtonGroupOutside={true}
ssr={true} // means to render carousel on server-side.
keyBoardControl={true}
containerClass="carousel-container"
deviceType={"desktop"}
itemClass="w-100"
>
<WorkspaceTemplate
gsID="starkNetLogo"
workspaceTitle="Blank"
description="Create an empty workspace."
callback={() => createWorkspace("blank")} />
<WorkspaceTemplate
gsID="solhintLogo"
workspaceTitle="Remix Default"
description="Create a workspace with sample files."
callback={() => createWorkspace("remixDefault")} />
<WorkspaceTemplate
gsID="sourcifyLogo"
workspaceTitle="OpenZeppelin ERC20"
description="Create an ERC20 token by importing OpenZeppelin library."
callback={() => createWorkspace("ozerc20")} />
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="OpenZeppelin ERC721"
description="Create an NFT token by importing OpenZeppelin library."
callback={() => createWorkspace("ozerc721")} />
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="0xProject ERC20"
description="Create an ERC20 token by importing 0xProject contract."
callback={() => createWorkspace("zeroxErc20")} />
</Carousel>
</ThemeContext.Provider>
</div>
</div>
)
}
export default HomeTabGetStarted

@ -0,0 +1,76 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useContext } from 'react'
import { ThemeContext } from '../themeContext'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
enum VisibleTutorial {
Basics,
Intermediate,
Advanced
}
interface HomeTabLearnProps {
plugin: any
}
function HomeTabLearn ({plugin}: HomeTabLearnProps) {
const [state, setState] = useState<{
visibleTutorial: VisibleTutorial
}>({
visibleTutorial: VisibleTutorial.Basics
})
const themeFilter = useContext(ThemeContext)
const openLink = () => {
window.open("https://remix-ide.readthedocs.io/en/latest/remix_tutorials_learneth.html?highlight=learneth#learneth-tutorial-repos", '_blank')
}
const startLearnEthTutorial = async (tutorial) => {
await plugin.appManager.activatePlugin(['solidity', 'LearnEth', 'solidityUnitTesting'])
plugin.call('LearnEth', 'startTutorial', 'ethereum/remix-workshops', 'master', tutorial)
plugin.verticalIcons.select('LearnEth')
_paq.push(['trackEvent', 'homeTab', 'startLearnEthTutorial', tutorial])
}
return (
<div className="d-flex px-2 pb-2 pt-2 d-flex flex-column" id="hTLearnSection">
<div className="d-flex justify-content-between">
<label className="py-2 align-self-center m-0" style={{fontSize: "1.2rem"}}>Learn</label>
<button
onClick={ ()=> openLink()}
className="h-100 px-2 pt-0 btn"
>
<img className="align-self-center" src="assets/img/learnEthLogo.webp" alt="" style={ { filter: themeFilter.filter, width: "1rem", height: "1ren" } } />
</button>
</div>
<div className="d-flex flex-column">
<button className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Basics }})}>
<label className="float-left" style={{fontSize: "1rem"}}>Remix Basics</label>
{(state.visibleTutorial === VisibleTutorial.Basics) && <div className="pt-2 d-flex flex-column text-left">
<span>Introduction to Remix's interface and concepts used in Ethereum, as well as the basics of Solidity.</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('basics')}>Get Started</button>
</div>}
</button>
<button className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Intermediate }})}>
<label className="float-left" style={{fontSize: "1rem"}}>Remix Intermediate</label>
{(state.visibleTutorial === VisibleTutorial.Intermediate) && <div className="pt-2 d-flex flex-column text-left">Using the web3.js to interact with a contract. Using Recorder tool.
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('useofweb3js')}>Get Started</button>
</div>}
</button>
<button className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Advanced }})}>
<label className="float-left" style={{fontSize: "1rem"}}>Remix Advanced</label>
{(state.visibleTutorial === VisibleTutorial.Advanced) && <div className="pt-2 d-flex flex-column text-left">Learn the Proxy Pattern and working with Libraries in Remix. Learn to use the Debugger.
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('deploylibraries')}>Get Started</button>
</div>}
</button>
</div>
</div>
)
}
export default HomeTabLearn

@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
function HomeTabScamAlert () {
return (
<div className="" id="hTScamAlertSection">
<label className="pl-2 text-danger" style={{fontSize: "1.2rem"}}>Scam Alert</label>
<div className="py-2 ml-2 mb-1 align-self-end mb-2 d-flex flex-column border border-danger">
<span className="pl-4 mt-2">
<i className="pr-2 text-danger fas fa-exclamation-triangle"></i>
<b>Scam Alerts:</b>
</span>
<span className="pl-4 mt-1">
The only URL Remix uses is remix.ethereum.org
</span>
<span className="pl-4 mt-1">
Beware of online videos promoting "liquidity front runner bots":
<a className="pl-2 remixui_home_text" target="__blank" href="https://medium.com/remix-ide/remix-in-youtube-crypto-scams-71c338da32d">Learn more</a>
</span>
<span className="pl-4 mt-1">
Additional safety tips: &nbsp;<a className="remixui_home_text" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/security.html">here</a>
</span>
</div>
</div>
)
}
export default HomeTabScamAlert

@ -0,0 +1,148 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import BasicLogo from 'libs/remix-ui/vertical-icons-panel/src/lib/components/BasicLogo'
import { ThemeContext } from '../themeContext'
import React, { useEffect, useState, useRef, useContext } from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'// eslint-disable-line
function HomeTabTitle() {
useEffect(() => {
document.addEventListener("keyup", (e) => handleSearchKeyDown(e))
return () => {
document.removeEventListener("keyup", handleSearchKeyDown)
}
}, [])
const [state, setState] = useState<{
searchDisable: boolean
}>({
searchDisable: true
})
const themeFilter = useContext(ThemeContext)
const searchInputRef = useRef(null)
const remiAudioEl = useRef(null)
const playRemi = async () => {
remiAudioEl.current.play()
}
const handleSearchKeyDown = (e: KeyboardEvent) => {
if (e.target !== searchInputRef.current) return
if (e.key === "Enter") {
openLink()
searchInputRef.current.value = ""
} else {
setState(prevState => {
return { ...prevState, searchDisable: searchInputRef.current.value === "" }
})
}
}
const openLink = (url = "") => {
if (url === "") {
window.open("https://remix-ide.readthedocs.io/en/latest/search.html?q=" + searchInputRef.current.value + "&check_keywords=yes&area=default", '_blank')
} else {
window.open(url, '_blank')
}
}
return (
<div className="px-2 pb-2 pt-2 d-flex flex-column border-bottom" id="hTTitleSection">
<div className="mr-4 d-flex">
<div onClick={() => playRemi()} style={{ filter: themeFilter.filter}} >
<BasicLogo classList="align-self-end remixui_home_logoImg" solid={false} />
</div>
<audio
id="remiAudio"
muted={false}
src="assets/audio/remiGuitar-single-power-chord-A-minor.wav"
ref={remiAudioEl}
></audio>
</div>
<div className="d-flex justify-content-between">
<span className="h-80 text-uppercase" style={{ fontSize: 'xx-large', fontFamily: "Noah, sans-serif" }}>Remix</span>
<span>
<OverlayTrigger placement={'top'} overlay={
<Tooltip className="text-nowrap" id="overlay-tooltip">
<span className="border bg-light text-dark p-1 pr-3">Remix Youtube Playlist</span>
</Tooltip>
}>
<button
onClick={() => openLink("https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA")}
className="border-0 h-100 btn fab fa-youtube">
</button>
</OverlayTrigger>
<OverlayTrigger placement={'top'} overlay={
<Tooltip className="text-nowrap" id="overlay-tooltip">
<span className="border bg-light text-dark p-1 pr-3">Remix Twitter Profile</span>
</Tooltip>
}>
<button
onClick={() => openLink("https://twitter.com/EthereumRemix")}
className="border-0 h-100 pl-2 btn fab fa-twitter">
</button>
</OverlayTrigger>
<OverlayTrigger placement={'top'} overlay={
<Tooltip className="text-nowrap" id="overlay-tooltip">
<span className="border bg-light text-dark p-1 pr-3">Remix Linkedin Profile</span>
</Tooltip>
}>
<button
onClick={() => openLink("https://www.linkedin.com/company/ethereum-remix/")}
className="border-0 h-100 pl-2 btn fa fa-linkedin">
</button>
</OverlayTrigger>
<OverlayTrigger placement={'top'} overlay={
<Tooltip className="text-nowrap" id="overlay-tooltip">
<span className="border bg-light text-dark p-1 pr-3">Remix Medium Posts</span>
</Tooltip>
}>
<button
onClick={() => openLink("https://medium.com/remix-ide")}
className="border-0 h-100 pl-2 btn fab fa-medium">
</button>
</OverlayTrigger>
<OverlayTrigger placement={'top'} overlay={
<Tooltip className="text-nowrap" id="overlay-tooltip">
<span className="border bg-light text-dark p-1 pr-3">Remix Gitter channel</span>
</Tooltip>
}>
<button
onClick={() => openLink("https://gitter.im/ethereum/remix")}
className="border-0 h-100 pl-2 btn fab fa-gitter">
</button>
</OverlayTrigger>
</span>
</div>
<b className="pb-1 text-dark" style={{ fontStyle: 'italic' }}>The Native IDE for Web3 Development.</b>
<div className="pb-1" id="hTGeneralLinks">
<a className="remixui_home_text" target="__blank" href="https://remix-project.org">Website</a>
<a className="pl-2 remixui_home_text" target="__blank" href="https://remix-ide.readthedocs.io/en/latest">Documentation</a>
<a className="pl-2 remixui_home_text" target="__blank" href="https://remix-plugin-docs.readthedocs.io/en/latest/">Remix Plugin</a>
<a className="pl-2 remixui_home_text" target="__blank" href="https://github.com/ethereum/remix-desktop/releases">Remix Desktop</a>
</div>
<div className="d-flex pb-1 align-items-center">
<input
ref={searchInputRef}
type="text"
className="border form-control border-right-0"
id="searchInput"
placeholder="Search Documentation"
data-id="terminalInputSearch"
/>
<button
className="form-control border d-flex align-items-center p-2 justify-content-center fas fa-search bg-light"
onClick={(e) => openLink()}
disabled={state.searchDisable}
style={{ width: "3rem" }}
>
</button>
</div>
</div>
)
}
export default HomeTabTitle

@ -1,29 +1,43 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useContext } from 'react'
import { ThemeContext } from '../themeContext'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
interface PluginButtonProps {
imgPath: string,
envID: string,
envText: string,
callback: any,
l2?: boolean
l2?: boolean,
description: string,
remixMaintained?: boolean
}
function PluginButton ({ imgPath, envID, envText, callback, l2 }: PluginButtonProps) {
function PluginButton ({ imgPath, envID, envText, callback, l2, description, remixMaintained }: PluginButtonProps) {
const themeFilter = useContext(ThemeContext)
return (
<div>
<div className="d-flex remixui_home_envButton">
<button
className="btn border-secondary d-flex mr-3 text-nowrap justify-content-center flex-column align-items-center remixui_home_envButton"
className="btn border-secondary d-flex flex-column pb-2 text-nowrap justify-content-center align-items-center mr-2 remixui_home_envButton"
data-id={'landingPageStart' + envText}
onClick={() => callback()}
>
<img className="m-2 align-self-center remixui_home_envLogo" id={envID} src={imgPath} alt="" style={ { filter: themeFilter.filter } } />
<label className="text-uppercase text-dark remixui_home_cursorStyle">{envText}</label>
<img className="px-2 mb-2 align-self-center remixui_home_envLogo" id={envID} src={imgPath} alt="" style={ { filter: themeFilter.filter } } />
<div className="h-100 d-flex flex-column">
<label className="text-uppercase text-dark remixui_home_cursorStyle">{envText}</label>
<div className="remixui_home_envLogoDescription">{description}</div>
</div>
</button>
{ l2 && <label className="bg-light mx-1 px-1 mb-0 mx-2 position-relative remixui_home_l2Label">L2</label> }
{ l2 && <label className="bg-light mx-1 px-1 mb-0 mx-2 position-absolute remixui_home_l2Label">L2</label> }
{ remixMaintained &&
<OverlayTrigger placement="bottom" overlay={
<Tooltip id="overlay-tooltip-run-script">
<span>Maintained by Remix</span>
</Tooltip>
}>
<i className="bg-light text-success mx-1 px-1 mb-0 mx-2 position-absolute remixui_home_maintainedLabel fas fa-check"></i>
</OverlayTrigger>
}
</div>
)

@ -1,12 +0,0 @@
.RSSFeed-item img {
width: 100%;
}
.RSSFeed-item .truncate {
max-height: 500px;
overflow: hidden;
}
.RSSFeed-item .more-button {
}

@ -1,42 +0,0 @@
import React, { useState, useEffect } from "react";
import Parser from "rss-parser";
import './rssFeed.css';
interface RSSFeedProps {
feedUrl: string,
maxItems: number,
}
export function RSSFeed({ feedUrl, maxItems }: RSSFeedProps) {
const [feed, setFeed] = useState(null);
useEffect(() => {
const fetchData = async () => {
const parser = new Parser()
const feed = await parser.parseURL(feedUrl);
for (const item of feed.items) {
item.content = item['content:encoded']
item.date = new Date(item.pubDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
setFeed(feed);
};
fetchData();
}, [feedUrl]);
return (<>
{feed && feed.items.slice(0, maxItems).map((item: any, index: any) => (
<div className='RSSFeed-item' key={index}>
<a target="_blank" href={item.link}><h3>{item.title}</h3></a>
<p>Author: {item.creator}</p>
<h4>{item.date}</h4>
<div className="truncate" dangerouslySetInnerHTML={{ __html: item.content }} />
<a className="more-button btn mb-3" target="_blank" href={item.link}>READ MORE</a>
<hr></hr>
</div>
))}
</>)
}

@ -0,0 +1,116 @@
import * as React from "react";
export interface ResponsiveType {
[key: string]: {
breakpoint: { max: number; min: number };
items: number;
partialVisibilityGutter?: number; // back-ward compatible, because previously there has been a typo
paritialVisibilityGutter?: number;
slidesToSlide?: number;
};
}
export function isMouseMoveEvent(
e: React.MouseEvent | React.TouchEvent
): e is React.MouseEvent {
return "clientX" && "clientY" in e;
}
export interface CarouselProps {
responsive: ResponsiveType;
deviceType?: string;
ssr?: boolean;
slidesToSlide?: number;
draggable?: boolean;
arrows?: boolean; // show or hide arrows.
renderArrowsWhenDisabled?: boolean; // Allow for the arrows to have a disabled attribute instead of not showing them
swipeable?: boolean;
removeArrowOnDeviceType?: string | Array<string>;
children: any;
customLeftArrow?: React.ReactElement<any> | null;
customRightArrow?: React.ReactElement<any> | null;
customDot?: React.ReactElement<any> | null;
customButtonGroup?: React.ReactElement<any> | null;
infinite?: boolean;
minimumTouchDrag?: number; // default 50px. The amount of distance to drag / swipe in order to move to the next slide.
afterChange?: (previousSlide: number, state: StateCallBack) => void; // Change callback after sliding everytime. `(previousSlide, currentState) => ...`
beforeChange?: (nextSlide: number, state: StateCallBack) => void; // Change callback before sliding everytime. `(previousSlide, currentState) => ...`
sliderClass?: string; // Use this to style your own track list.
itemClass?: string; // Use this to style your own Carousel item. For example add padding-left and padding-right
itemAriaLabel?: string; // Use this to add your own Carousel item aria-label.if it is not defined the child aria label will be applied if the child dont have one than a default empty string will be applied
containerClass?: string; // Use this to style the whole container. For example add padding to allow the "dots" or "arrows" to go to other places without being overflown.
className?: string; // Use this to style the whole container with styled-components
dotListClass?: string; // Use this to style the dot list.
keyBoardControl?: boolean;
centerMode?: boolean; // show previous and next set of items partially
autoPlay?: boolean;
autoPlaySpeed?: number; // default 3000ms
showDots?: boolean;
renderDotsOutside?: boolean; // show dots outside of the container for custom styling.
renderButtonGroupOutside?: boolean; // show buttonGroup outside of the container for custom styling.
// Show next/previous item partially
// partialVisible has to be used in conjunction with the responsive props, details are in documentation.
// it shows the next set of items partially, different from centerMode as it shows both.
partialVisible?: boolean;
partialVisbile?: boolean; // old typo - deprecated (will be remove in 3.0)
customTransition?: string;
transitionDuration?: number;
// if you are using customTransition, make sure to put the duration here.
// for example, customTransition="all .5" then put transitionDuration as 500.
// this is needed for the resizing to work.
focusOnSelect?: boolean;
additionalTransfrom?: number; // this is only used if you want to add additional transfrom to the current transform
pauseOnHover?: boolean;
shouldResetAutoplay?: boolean;
rewind?: boolean;
rewindWithAnimation?: boolean;
rtl?: boolean;
}
export type StateCallBack = CarouselInternalState;
export type Direction = "left" | "right" | "" | undefined;
export type SkipCallbackOptions =
| boolean
| { skipBeforeChange?: boolean; skipAfterChange?: boolean };
export interface ButtonGroupProps {
previous?: () => void;
next?: () => void;
goToSlide?: (index: number, skipCallbacks?: SkipCallbackOptions) => void;
carouselState?: StateCallBack;
}
export interface ArrowProps {
onClick?: () => void;
carouselState?: StateCallBack;
}
export interface DotProps {
index?: number;
active?: boolean;
onClick?: () => void;
carouselState?: StateCallBack;
}
export interface CarouselInternalState {
itemWidth: number;
containerWidth: number;
slidesToShow: number;
currentSlide: number;
totalItems: number;
domLoaded: boolean;
deviceType?: string;
transform: number;
}
export default class Carousel extends React.Component<CarouselProps> {
previous: (slidesHavePassed: number) => void;
next: (slidesHavePassed: number) => void;
goToSlide: (slide: number, skipCallbacks?: SkipCallbackOptions) => void;
state: CarouselInternalState;
setClones: (
slidesToShow: number,
itemWidth?: number,
forResizing?: boolean
) => void; // reset carousel in infinite mode.
setItemsToShow: (shouldCorrectItemPosition?: boolean) => void; // reset carousel in non-infinite mode.
correctClonesPosition: ({ domLoaded }: { domLoaded: boolean }) => void;
onMove: boolean;
direction: Direction;
containerRef: React.RefObject<HTMLDivElement>;
}

@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useContext } from 'react'
interface WorkspaceTemplateProps {
gsID: string,
workspaceTitle: string,
callback: any,
description: string,
}
function WorkspaceTemplate ({ gsID, workspaceTitle, description, callback }: WorkspaceTemplateProps) {
return (
<div className="d-flex remixui_home_workspaceTemplate">
<button
className="btn border-secondary p-1 d-flex flex-column text-nowrap justify-content-center align-items-center mr-2 remixui_home_workspaceTemplate"
data-id={'landingPageStart' + gsID}
onClick={() => callback()}
>
<div className="w-100 p-2 h-100 align-items-start d-flex flex-column">
<label className="h6 pb-1 text-uppercase text-dark remixui_home_cursorStyle">{workspaceTitle}</label>
<div className="remixui_home_gtDescription">{description}</div>
</div>
</button>
</div>
)
}
export default WorkspaceTemplate

@ -31,7 +31,7 @@
text-align: center;
}
.remixui_home_logoImg {
height: 10em;
height: 4rem;
}
.remixui_home_rightPanel {
right: 0;
@ -58,19 +58,37 @@
.remixui_home_importFrom p {
margin-right: 10px;
}
.remixui_home_logoContainer img{
.remixui_home_logoContainer img {
height: 150px;
opacity: 0.7;
}
.remixui_home_envLogo {
height: 16px;
height: 2.5rem;
}
.remixui_home_envLogoDescription {
white-space: pre-wrap;
font-size: small;
line-height: 0.9rem;
text-align: left;
}
.remixui_home_gtDescription {
white-space: pre-wrap;
font-size: small;
line-height: 1.1rem;
text-align: left;
}
.remixui_home_cursorStyle {
cursor: pointer;
font-size: 0.8rem;
}
.remixui_home_envButton {
width: 120px;
height: 70px;
width: 220px;
cursor: pointer;
height: 130px;
}
.remixui_home_workspaceTemplate {
width: 220px;
height: 80px;
}
.remixui_home_media {
overflow: hidden;
@ -82,5 +100,10 @@
width: 100px;
}
.remixui_home_l2Label {
bottom: 10px;
top: 120px;
right: 180px;
}
.remixui_home_maintainedLabel {
top: 120px;
right: 180px;
}

@ -1,90 +1,34 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import React, { useState, useEffect } 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 { ThemeContext, themes } from './themeContext'
import { RSSFeed } from './components/rssFeed'
import HomeTabTitle from './components/homeTabTitle'
import HomeTabFile from './components/homeTabFile'
import HomeTabLearn from './components/homeTabLearn'
import HomeTabScamAlert from './components/homeTabScamAlert'
import HomeTabGetStarted from './components/homeTabGetStarted'
import HomeTabFeatured from './components/homeTabFeatured'
import HomeTabFeaturedPlugins from './components/homeTabFeaturedPlugins'
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 }),
async (error, content, cleanUrl, type, url) => {
if (error) {
toast(error.message || error)
} else {
try {
if (await workspace.exists(type + '/' + cleanUrl)) toast('File already exists in workspace')
else {
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)
const rightPanel = useRef(null)
useEffect(() => {
plugin.call('theme', 'currentTheme').then((theme) => {
// update theme quality. To be used for for images
@ -98,276 +42,24 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
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' && !rightPanel.current.contains(event.target)) {
// 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)
return () => {
document.body.removeChild(scriptTwitter)
}
}, [])
const toast = (message: string) => {
setState(prevState => {
return { ...prevState, toasterMsg: message }
})
}
const createNewFile = async () => {
plugin.verticalIcons.select('filePanel')
await plugin.call('filePanel', 'createNewFile')
}
const uploadFile = async (target) => {
await plugin.call('filePanel', 'uploadFile', target)
}
const connectToLocalhost = () => {
plugin.appManager.activatePlugin('remixd')
}
const importFromGist = () => {
plugin.call('gistHandler', 'load', '')
plugin.verticalIcons.select('filePanel')
}
const startSolidity = async () => {
await plugin.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting'])
plugin.verticalIcons.select('solidity')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidity'])
}
const startStarkNet = async () => {
await plugin.appManager.activatePlugin('starkNet_compiler')
plugin.verticalIcons.select('starkNet_compiler')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'starkNet_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', 'sourcify'])
plugin.verticalIcons.select('sourcify')
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'sourcify'])
}
const startPluginManager = async () => {
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 flex-column mr-4 pb-3 mb-3">
<div className="pt-2 d-flex justify-content-between">
<div>
<div className="mx-4 my-4 pt-4 d-flex">
<label style={ { fontSize: 'xxx-large' } }>Remix IDE</label>
</div>
<div className="pt-4 align-self-end mb-2 d-flex flex-column">
<span className="pl-4 text-danger mt-2">
<i className="pr-2 text-danger fas fa-exclamation-triangle"></i>
<b><FormattedMessage id='home.scamAlert' defaultMessage='Scam Alerts' />:</b>
</span>
<span className="pl-4 text-danger mt-1">
<FormattedMessage id='home.scamAlertText' defaultMessage='The only URL Remix uses is remix.ethereum.org' />
</span>
<span className="pl-4 text-danger mt-1">
<FormattedMessage id='home.scamAlertText2' defaultMessage='Beware of online videos promoting "liquidity front runner bots"' />:
<a className="pl-2 remixui_home_text" target="__blank" href="https://medium.com/remix-ide/remix-in-youtube-crypto-scams-71c338da32d"><FormattedMessage id='home.learnMore' defaultMessage='Learn more' /></a>
</span>
<span className="pl-4 text-danger mt-1">
<FormattedMessage id='home.scamAlertText3' defaultMessage='Additional safety tips' />: &nbsp;<a className="remixui_home_text" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/security.html"><FormattedMessage id='home.here' defaultMessage='here' /></a>
</span>
</div>
</div>
<div className="mr-4 d-flex">
<img className="align-self-end remixui_home_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>
<div className="row mx-2 mr-4" data-id="landingPageHpSections">
<div className="ml-3">
<div className="mb-3">
<h4><FormattedMessage id='home.featuredPlugins' defaultMessage='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/starkNetLogo.webp" envID="starkNetLogo" envText="StarkNet" l2={true} callback={() => startStarkNet()} />
<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/sourcifyNewLogo.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><FormattedMessage id='home.file' defaultMessage='File' /></h4>
<p className="mb-1">
<i className="mr-2 far fa-file"></i>
<label className="ml-1 mb-1 remixui_home_text" data-id="homeTabNewFile" onClick={() => createNewFile()}><FormattedMessage id='home.newFile' defaultMessage='New File' /></label>
</p>
<p className="mb-1">
<i className="mr-2 far fa-file-alt"></i>
<label className="ml-1 remixui_home_labelIt remixui_home_bigLabelSize remixui_home_text" htmlFor="openFileInput">
<FormattedMessage id='home.openFiles' defaultMessage='Open Files' />
</label>
<input title="open file" type="file" id="openFileInput" onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}} multiple />
</p>
<p className="mb-1">
<i className="mr-1 far fa-hdd"></i>
<label className="ml-1 remixui_home_text" onClick={() => connectToLocalhost()}><FormattedMessage id='home.connectToLocalhost' defaultMessage='Connect to Localhost' /></label>
</p>
<p className="mt-3 mb-0"><label><FormattedMessage id='home.loadFrom' defaultMessage='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><FormattedMessage id='home.resources' defaultMessage='Resources' /></h4>
<p className="mb-1">
<i className="mr-2 fas fa-book"></i>
<a className="remixui_home_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_home_text" target="__blank" href="https://gitter.im/ethereum/remix">Gitter channel</a>
</p>
<p className="mb-1">
<img id='remixHhomeWebsite' className="mr-2 remixui_home_image" src={ plugin.profile.icon } style={ { filter: state.themeQuality.filter } } alt=''></img>
<a className="remixui_home_text" target="__blank" href="https://remix-project.org">Featuring website</a>
</p>
</div>
</div>
</div>
<div className="d-flex flex-row w-100" data-id="remixUIHTAll">
<ThemeContext.Provider value={ state.themeQuality }>
<div className="px-2 pl-3 justify-content-start d-flex border-right flex-column" id="remixUIHTLeft" style={{flex: 2, minWidth: "35%"}}>
<HomeTabTitle />
<HomeTabFile plugin={plugin} />
<HomeTabLearn plugin={plugin} />
</div>
<div className="d-flex flex-column remixui_home_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_home_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_home_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_home_panels"
style={ { visibility: state.showMediaPanel === 'none' ? 'hidden' : 'visible' } }
id="remixIDEMediaPanels"
ref={rightPanel}
>
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }>
<div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }>
<RSSFeed feedUrl='https://rss.remixproject.org/' maxItems={10} />
</div>
</div>
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } >
<div className="remixui_home_media" style={ { minHeight: elHeight } } >
<a className="twitter-timeline"
data-width="375"
data-theme={ state.themeQuality.name }
data-chrome="nofooter noheader transparent"
data-tweet-limit="18"
href="https://twitter.com/EthereumRemix"
>
</a>
</div>
</div>
</div>
<div className="pl-2 pr-3 justify-content-start d-flex flex-column" style={{width: "65%"}} id="remixUIHTRight">
<HomeTabFeatured></HomeTabFeatured>
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
<HomeTabFeaturedPlugins plugin={plugin}></HomeTabFeaturedPlugins>
<HomeTabScamAlert></HomeTabScamAlert>
</div>
</div>
</>
</ThemeContext.Provider>
</div>
)
}

@ -33,6 +33,7 @@
.iconImage {
width: 1rem;
height: 1rem;
align-self: start;
}
.active {
border: 1px solid transparent;

@ -1,12 +1,20 @@
import React from 'react'
interface BasicLogoProps {
classList?: string,
solid?: boolean
}
function BasicLogo () {
return (<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/>
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/>
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/>
</svg>
)
function BasicLogo ({classList = "", solid = true}: BasicLogoProps) {
if (solid) {
return (<svg id="Ebene_2" className={classList} data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/>
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/>
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/>
</svg>
)
} else {
return (<img className="" src="assets/img/remix_logo_light.webp" style={{height: "3rem"}} alt=""></img>)
}
}
export default BasicLogo

@ -31,8 +31,6 @@ const RemixUiVerticalIconsPanel = ({
const [activateScroll, dispatchScrollAction] = useReducer(verticalScrollReducer, initialState)
const [theme, setTheme] = useState<string>('dark')
const evaluateScrollability = () => {
dispatchScrollAction({
type: 'resize',

@ -460,6 +460,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
handleClickFolder={handleClickFolder}
handleContextMenu={handleContextMenu}
key={index}
showIconsMenu={props.showIconsMenu}
hideIconsMenu={props.hideIconsMenu}
/>)
}

@ -20,6 +20,8 @@ export interface RenderFileProps {
focusContext: { element: string, x: number, y: number, type: string },
ctrlKey: boolean,
expandPath: string[],
hideIconsMenu?: React.Dispatch<React.SetStateAction<boolean>>,
showIconsMenu?: boolean,
editModeOff: (content: string) => void,
handleClickFolder: (path: string, type: string) => void,
handleClickFile: (path: string, type: string) => void,
@ -52,11 +54,13 @@ export const FileRender = (props: RenderFileProps) => {
const handleFolderClick = (event: SyntheticEvent) => {
event.stopPropagation()
if (props.focusEdit.element !== file.path) props.handleClickFolder(file.path, file.type)
if (props.showIconsMenu === true) props.hideIconsMenu(!props.showIconsMenu)
}
const handleFileClick = (event: SyntheticEvent) => {
event.stopPropagation()
if (props.focusEdit.element !== file.path) props.handleClickFile(file.path, file.type)
if (props.showIconsMenu === true) props.hideIconsMenu(!props.showIconsMenu)
}
const handleContextMenu = (event: PointerEvent) => {

@ -51,7 +51,7 @@
word-break: break-word;
}
.remixui_menuicon {
padding-right : 10px;
padding-right : 0px;
}
.remixui_menuicon:hover {
transform: scale(1.3);
@ -95,3 +95,41 @@
color: var(--text);
}
.remixuimenuicon_shadow {
}
.remixuimenuicon_shadow:hover {
box-shadow: 0px 0px 14px -7px;
}
.remixui_topmenu {
padding-bottom: 0.1rem;
}
.remixui_menuwidth {
width: 8rem;
}
#workspacesMenuDropdown > div.custom-dropdown-items {
min-width: 8rem;
}
.remixui_menuhr{
}
#workspacesMenuDropdown>div>ul>a:nth-child(6):hover {
background-color: rgba(0,0,0,0)
}
#workspacesMenuDropdown>div>ul>a:nth-child(4):hover {
background-color: rgba(0, 0, 0, 0)
}
#workspacesMenuDropdown > div > ul > a:hover {
background-color: var(--secondary);
/* border: 1px solid var(--secondary); */
border-radius: 2px;
color: var(--text)
}

@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import React, { useState, useEffect, useRef, useContext, SyntheticEvent } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import { Dropdown } from 'react-bootstrap'
import { CustomMenu, CustomToggle } from '@remix-ui/helper'
import { Dropdown, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle } from '@remix-ui/helper'
import { FileExplorer } from './components/file-explorer' // eslint-disable-line
import { FileSystemContext } from './contexts'
import './css/remix-ui-workspace.css'
@ -16,6 +16,7 @@ export function Workspace () {
const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE)
const [selectedWorkspace, setSelectedWorkspace] = useState<{ name: string, isGitRepo: boolean}>(null)
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false)
const displayOzCustomRef = useRef<HTMLDivElement>()
const mintableCheckboxRef = useRef()
const burnableCheckboxRef = useRef()
@ -312,91 +313,267 @@ export function Workspace () {
)
}
const workspaceMenuIcons = [
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Create</span>
</Tooltip>
}
>
<div
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={currentWorkspace === LOCALHOST}
id='workspaceCreate'
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
hideIconsMenu(!showIconsMenu)
}}
className='far fa-plus pl-2'
>
</span>
<span className="pl-3">Create</span>
</div>
</OverlayTrigger>,
<OverlayTrigger
placement="right-start"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Delete Workspace</span>
</Tooltip>
}
>
<div
data-id='workspaceDelete'
onClick={(e) => {
e.stopPropagation()
deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceDelete'
data-id='workspaceDelete'
onClick={(e) => {
e.stopPropagation()
deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
hideIconsMenu(!showIconsMenu)
}}
className='far fa-trash pl-2'
>
</span>
<span className="pl-3">{'Delete'}</span>
</div>
</OverlayTrigger>,
<OverlayTrigger
placement='right-start'
overlay={
<Tooltip id="workspaceRenametooltip">
<span>Rename Workspace</span>
</Tooltip>
}
>
<div onClick={(e) => {
e.stopPropagation()
renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
hideIconsMenu(!showIconsMenu)
}}
data-id='workspaceRename'
>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceRename'
data-id='workspaceRename'
onClick={(e) => {
e.stopPropagation()
renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
hideIconsMenu(!showIconsMenu)
}}
className='far fa-edit pl-2'>
</span>
<span className="pl-3">{'Rename'}</span>
</div>
</OverlayTrigger>,
<Dropdown.Divider className="border mb-0 mt-0" />,
<OverlayTrigger
placement="right-start"
overlay={
<Tooltip id="cloneWorkspaceTooltip" className="text-nowrap">
<span>Clone Git Repository</span>
</Tooltip>
}
>
<div
data-id='cloneGitRepository'
onClick={(e) => {
e.stopPropagation()
cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={currentWorkspace === LOCALHOST}
id='cloneGitRepository'
data-id='cloneGitRepository'
onClick={(e) => {
e.stopPropagation()
cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
hideIconsMenu(!showIconsMenu)
}}
className='fab fa-github pl-2'
>
</span>
<span className="pl-3">{'Clone'}</span>
</div>
</OverlayTrigger>,
<Dropdown.Divider className="border mt-0 mb-0 remixui_menuhr" style={{ pointerEvents: 'none' }}/>,
<OverlayTrigger
placement="right-start"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Download Workspace</span>
</Tooltip>
}
>
<div
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspacesDownload'
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
hideIconsMenu(!showIconsMenu)
}}
className='far fa-download pl-2 '
>
</span>
<span className="pl-3">{'Download'}</span>
</div>
</OverlayTrigger>,
<OverlayTrigger
placement="right-start"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Restore Workspace Backup</span>
</Tooltip>
}
>
<div
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
hideIconsMenu(!showIconsMenu)
}}
>
<span
hidden={currentWorkspace === LOCALHOST}
id='workspacesRestore'
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
hideIconsMenu(!showIconsMenu)
}}
className='far fa-upload pl-2'
>
</span>
<span className="pl-3">{'Restore'}</span>
</div>
</OverlayTrigger>,
]
return (
<div className='remixui_container'>
<div className='d-flex flex-column w-100 remixui_fileexplorer' data-id="remixUIWorkspaceExplorer" onClick={resetFocus}>
<div>
<header>
<div className="mx-2 mb-2">
<label className="pl-1 form-check-label" htmlFor="workspacesSelect">
<FormattedMessage id='filePanel.workspace' defaultMessage='Workspaces' />
</label>
<span className="remixui_menu">
<span
hidden={currentWorkspace === LOCALHOST}
id='workspaceCreate'
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
}}
className='far fa-plus-square remixui_menuicon'
title={intl.formatMessage({id: 'filePanel.create', defaultMessage: 'Create'})}>
</span>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceRename'
data-id='workspaceRename'
onClick={(e) => {
e.stopPropagation()
renameCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceRename'])
}}
className='far fa-edit remixui_menuicon'
title={intl.formatMessage({id: 'filePanel.rename', defaultMessage: 'Rename'})}>
</span>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspaceDelete'
data-id='workspaceDelete'
onClick={(e) => {
e.stopPropagation()
deleteCurrentWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceDelete'])
}}
className='far fa-trash remixui_menuicon'
title={intl.formatMessage({id: 'filePanel.delete', defaultMessage: 'Delete'})}>
</span>
<span
hidden={currentWorkspace === LOCALHOST || currentWorkspace === NO_WORKSPACE}
id='workspacesDownload'
data-id='workspacesDownload'
onClick={(e) => {
e.stopPropagation()
downloadWorkspaces()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesDownload'])
}}
className='far fa-download remixui_menuicon'
title={intl.formatMessage({id: 'filePanel.workspace.download', defaultMessage: 'Download Workspaces'})}>
</span>
<span
hidden={currentWorkspace === LOCALHOST}
id='workspacesRestore'
data-id='workspacesRestore'
onClick={(e) => {
e.stopPropagation()
restoreBackup()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspacesRestore'])
}}
className='far fa-upload remixui_menuicon'
title={intl.formatMessage({id: 'filePanel.workspace.restore', defaultMessage: 'Restore Workspaces Backup'})}>
<div className="mx-2 mb-2 d-flex flex-column">
<div className="d-flex justify-content-between">
<span className="d-flex align-items-end">
<label className="pl-1 form-check-label" htmlFor="workspacesSelect">
WORKSPACES
</label>
</span>
<span
hidden={currentWorkspace === LOCALHOST}
id='cloneGitRepository'
data-id='cloneGitRepository'
onClick={(e) => {
e.stopPropagation()
cloneGitRepository()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'cloneGitRepository'])
}}
className='far fa-clone remixui_menuicon'
title={intl.formatMessage({id: 'filePanel.workspace.clone', defaultMessage: 'Clone Git Repository'})}>
<span className="remixui_menu remixui_topmenu d-flex justify-content-between align-items-end w-75">
<OverlayTrigger
placement="top-end"
overlay={
<Tooltip id="createWorkspaceTooltip" className="text-nowrap">
<span>Create</span>
</Tooltip>
}
>
<span
hidden={currentWorkspace === LOCALHOST}
id='workspaceCreate'
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
createWorkspace()
_paq.push(['trackEvent', 'fileExplorer', 'workspaceMenu', 'workspaceCreate'])
}}
style={{ fontSize: 'large' }}
className='far fa-plus remixui_menuicon d-flex align-self-end'
>
</span>
</OverlayTrigger>
<Dropdown id="workspacesMenuDropdown" data-id="workspacesMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}>
<Dropdown.Toggle
as={CustomIconsToggle}
onClick={() => {
hideIconsMenu(!showIconsMenu)
}}
icon={'fas fa-bars'}
></Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} data-id="wsdropdownMenu" className='custom-dropdown-items remixui_menuwidth' rootCloseEvent="click">
{
workspaceMenuIcons.map(m => {
return (
<Dropdown.Item>
{m}
</Dropdown.Item>
)
})
}
</Dropdown.Menu>
</Dropdown>
</span>
</span>
</div>
<Dropdown id="workspacesSelect" data-id="workspacesSelect" onToggle={toggleDropdown} show={showDropdown}>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" icon={selectedWorkspace && selectedWorkspace.isGitRepo && !(currentWorkspace === LOCALHOST) ? 'far fa-code-branch' : null}>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control mt-1" icon={selectedWorkspace && selectedWorkspace.isGitRepo && !(currentWorkspace === LOCALHOST) ? 'far fa-code-branch' : null}>
{ selectedWorkspace ? selectedWorkspace.name : currentWorkspace === LOCALHOST ? 'localhost' : NO_WORKSPACE }
</Dropdown.Toggle>
@ -452,6 +629,8 @@ export function Workspace () {
expandPath={global.fs.browser.expandPath}
focusEdit={global.fs.focusEdit}
focusElement={global.fs.focusElement}
hideIconsMenu={hideIconsMenu}
showIconsMenu={showIconsMenu}
dispatchCreateNewFile={global.dispatchCreateNewFile}
modal={global.modal}
dispatchCreateNewFolder={global.dispatchCreateNewFolder}
@ -489,6 +668,8 @@ export function Workspace () {
expandPath={global.fs.localhost.expandPath}
focusEdit={global.fs.focusEdit}
focusElement={global.fs.focusElement}
hideIconsMenu={hideIconsMenu}
showIconsMenu={showIconsMenu}
dispatchCreateNewFile={global.dispatchCreateNewFile}
modal={global.modal}
dispatchCreateNewFolder={global.dispatchCreateNewFolder}

@ -77,6 +77,8 @@ export interface FileExplorerProps {
fileState: fileDecoration[],
expandPath: string[],
focusEdit: string,
hideIconsMenu: React.Dispatch<React.SetStateAction<boolean>>,
showIconsMenu: boolean,
focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[],
dispatchCreateNewFile: (path: string, rootDir: string) => Promise<void>,
// eslint-disable-next-line no-undef

@ -8,7 +8,7 @@ export default async (opts) => {
}
const filesObj = {
'contracts/MyToken.sol': erc1155.print({ ...erc1155.defaults, upgradeable: opts.upgradeable}),
'contracts/MyToken.sol': erc1155.print({ ...erc1155.defaults, upgradeable: opts && opts.upgradeable ? opts.upgradeable : false}),
// @ts-ignore
'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default,
// @ts-ignore
@ -21,6 +21,6 @@ export default async (opts) => {
// If no options is selected, opts.upgradeable will be undefined
// We do not show test file for upgradeable contract
// @ts-ignore
if (opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default
if (!opts || opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default
return filesObj
}

@ -8,7 +8,7 @@ export default async (opts) => {
}
const filesObj = {
'contracts/MyToken.sol': erc20.print({ ...erc20.defaults, upgradeable: opts.upgradeable}),
'contracts/MyToken.sol': erc20.print({ ...erc20.defaults, upgradeable: opts && opts.upgradeable ? opts.upgradeable : false }),
// @ts-ignore
'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default,
// @ts-ignore
@ -22,6 +22,6 @@ export default async (opts) => {
// If no options is selected, opts.upgradeable will be undefined
// We do not show test file for upgradeable contract
// @ts-ignore
if (opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default
if (!opts || opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default
return filesObj
}

@ -8,7 +8,7 @@ export default async (opts) => {
}
const filesObj = {
'contracts/MyToken.sol': erc721.print({ ...erc721.defaults, upgradeable: opts.upgradeable}),
'contracts/MyToken.sol': erc721.print({ ...erc721.defaults, upgradeable: opts && opts.upgradeable ? opts.upgradeable : false }),
// @ts-ignore
'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default,
// @ts-ignore
@ -22,6 +22,6 @@ export default async (opts) => {
// If no options is selected, opts.upgradeable will be undefined
// We do not show test file for upgradeable contract
// @ts-ignore
if (opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default
if (!opts || opts.upgradeable === undefined || !opts.upgradeable) filesObj['tests/MyToken_test.sol'] = (await import('raw-loader!./tests/MyToken_test.sol')).default
return filesObj
}

@ -210,6 +210,7 @@
"react-draggable": "^4.4.4",
"react-intl": "^6.0.4",
"react-json-view": "^1.21.3",
"react-multi-carousel": "^2.8.2",
"react-router-dom": "^6.3.0",
"react-tabs": "^3.2.2",
"regenerator-runtime": "0.13.7",

@ -19808,6 +19808,11 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-multi-carousel@^2.8.2:
version "2.8.2"
resolved "https://registry.yarnpkg.com/react-multi-carousel/-/react-multi-carousel-2.8.2.tgz#4bbd7a9656d8e49e081745331593e5500eefdbe4"
integrity sha512-M9Y7DfAp8bA/r6yexttU6RLA7uyppje4c0ELRuCHZWswH+u7nr0uVP6qHNPjc4XGOEY1MYFOb5nBg7JvoKutuQ==
react-overlays@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.1.1.tgz#2e7cf49744b56537c7828ccb94cfc63dd778ae4f"

Loading…
Cancel
Save