pull/4877/head
Your Name 7 months ago
commit 8bf9abd3ae
  1. 1
      .circleci/config.yml
  2. 4
      apps/remix-ide-e2e/src/tests/homeTab.test.ts
  3. 54
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  4. 58
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  5. 2
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  6. 4
      apps/remix-ide/src/app.js
  7. 9
      apps/remix-ide/src/app/files/dgitProvider.ts
  8. 3
      apps/remix-ide/src/app/panels/file-panel.js
  9. 2
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  10. 5
      apps/remix-ide/src/app/tabs/locales/en/home.json
  11. 4
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  12. 236
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  13. 40
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  14. 2
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  15. 6
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  16. 6
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  17. 6
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  18. 12
      libs/remix-ui/statusbar/src/css/statusbar.css
  19. 6
      libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx
  20. 19
      libs/remix-ui/statusbar/src/lib/components/gitStatus.tsx
  21. 2
      libs/remix-ui/statusbar/src/lib/components/scamAlertStatus.tsx
  22. 2
      libs/remix-ui/workspace/src/lib/actions/index.ts
  23. 23
      libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx
  24. 8
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  25. 184
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  26. 4
      libs/remix-ui/workspace/src/lib/types/index.ts

@ -364,6 +364,7 @@ jobs:
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules || yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules - run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules || yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: mkdir node_modules/hardhat && wget https://unpkg.com/hardhat/console.sol -O node_modules/hardhat/console.sol - run: mkdir node_modules/hardhat && wget https://unpkg.com/hardhat/console.sol -O node_modules/hardhat/console.sol
- run: ls -la ./dist/apps/remix-ide/assets/js - run: ls -la ./dist/apps/remix-ide/assets/js
- run: sudo apt update && sudo apt install python3-pip -y
- when: - when:
condition: condition:
equal: [ "chrome", << parameters.browser >> ] equal: [ "chrome", << parameters.browser >> ]

@ -28,8 +28,8 @@ module.exports = {
'Should start with ERC20 workspace #group1': function (browser: NightwatchBrowser) { 'Should start with ERC20 workspace #group1': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-path="home"') .click('*[data-path="home"')
.waitForElementVisible('*[data-id="homeTabGetStartedERC20"]') .waitForElementVisible('*[data-id="homeTabGetStartedozerc20"]')
.click('*[data-id="homeTabGetStartedERC20"') .click('*[data-id="homeTabGetStartedozerc20"')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemtests/MyToken_test.sol"]') .waitForElementVisible('*[data-id="treeViewDivtreeViewItemtests/MyToken_test.sol"]')
.click('*[data-id="treeViewDivtreeViewItemtests/MyToken_test.sol"]') .click('*[data-id="treeViewDivtreeViewItemtests/MyToken_test.sol"]')

@ -3,9 +3,9 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
const testData = { const testData = {
validURL: 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol', validURL: 'https://github.com/remix-project-org/git-hometab-test.git',
invalidURL: 'https://github.com/Oppelin/Roles.sol', invalidURL: 'https://github.com/Oppelin/Roles.sol',
JSON: 'https://github.com/ethereum/remix-project/blob/master/package.json' JSON: 'https://github.com/remix-project-org/git-hometab-test.git'
} }
module.exports = { module.exports = {
@ -22,63 +22,53 @@ module.exports = {
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]') .waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]')
.pause(1000) .pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]') .click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]') .waitForElementVisible('*[data-id="fileSystemModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from GitHub') .assert.containsText('*[data-id="fileSystemModalDialogModalTitle-react"]', 'Clone Git Repository')
.waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="fileSystemModalDialogModalBody-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.') .waitForElementVisible('input[data-id="modalDialogCustomPromptTextClone"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
}, },
'Display Error Message For Invalid GitHub URL Modal #group1': function (browser: NightwatchBrowser) { 'Display Error Message For Invalid GitHub URL Modal #group1': function (browser: NightwatchBrowser) {
browser browser
.execute(() => { .execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus() (document.querySelector('input[data-id="modalDialogCustomPromptTextClone"]') as any).focus()
}, [], () => { }) }, [], () => { })
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.invalidURL) .setValue('input[data-id="modalDialogCustomPromptTextClone"]', testData.invalidURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]') .waitForElementVisible('*[data-id="fileSystemModalDialogModalFooter-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]') // submitted .click('[data-id="fileSystem-modal-footer-ok-react"]') // submitted
//.waitForElementVisible('*[data-shared="tooltipPopup"]') //.waitForElementVisible('*[data-shared="tooltipPopup"]')
//.waitForElementContainsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL) //.waitForElementContainsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL)
}, },
'Import From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) { 'Clone From GitHub with Valid URL #group2': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('div[data-id="verticalIconsHomeIcon"]') .click('div[data-id="verticalIconsHomeIcon"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]').pause(1000) .waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]').pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]') .click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]') .waitForElementVisible('input[data-id="modalDialogCustomPromptTextClone"]')
.execute(() => { .execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus() (document.querySelector('input[data-id="modalDialogCustomPromptTextClone"]') as any).focus()
}, [], () => { }) }, [], () => { })
.clearValue('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000) .clearValue('input[data-id="modalDialogCustomPromptTextClone"]').pause(1000)
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.validURL) .setValue('input[data-id="modalDialogCustomPromptTextClone"]', testData.validURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]') .waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]') .click('[data-id="fileSystem-modal-footer-ok-react"]')
.openFile('github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol') .openFile('Roles.sol')
.waitForElementVisible({ .waitForElementVisible({
selector: `//*[@data-id='tab-active' and @data-path="default_workspace/github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol"]`, selector: `//*[@data-id='tab-active' and @data-path="git-hometab-test.git/Roles.sol"]`,
locateStrategy: 'xpath' locateStrategy: 'xpath'
}) })
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf('library Roles {') !== -1, 'content does contain "library Roles {"') browser.assert.ok(content.indexOf('library Roles {') !== -1, 'content does contain "library Roles {"')
}) })
}, },
'Import JSON From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) { 'Confirm JSON After Cloning From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) {
browser browser
.click('div[data-id="verticalIconsHomeIcon"]') .click('div[data-id="verticalIconsHomeIcon"]')
.click('button[data-id="landingPageImportFromGitHubButton"]') .openFile('package.json')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000) .waitForElementVisible("*[data-path='git-hometab-test.git/package.json'")
.execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus()
}, [], () => { })
.clearValue('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000)
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.JSON)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/ethereum/remix-project/package.json')
.waitForElementVisible("div[data-path='default_workspace/github/ethereum/remix-project/package.json'")
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf('"name": "remix-project",') !== -1, 'content does contain "name": "remix-project"') browser.assert.ok(content.indexOf('"name": "remix-project",') !== -1, 'content does contain "name": "remix-project"')
}) })

@ -267,6 +267,40 @@ module.exports = {
.pause(2000) .pause(2000)
.clickLaunchIcon('dgit') .clickLaunchIcon('dgit')
.waitForElementNotPresent('*[data-id="disabled"]') .waitForElementNotPresent('*[data-id="disabled"]')
},
'Should install slither #group6': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
await installSlither()
done()
})
},
'Should perform slither analysis #group6': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
try {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) {
console.error(err)
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
.openFile('ballot.sol')
.pause(2000)
.clickLaunchIcon('solidityStaticAnalysis')
.useXpath()
.click('//*[@id="staticAnalysisRunBtn"]')
.waitForElementPresent('//*[@id="staticanalysisresult"]', 5000)
.waitForElementVisible({
selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '3')]",
locateStrategy: 'xpath',
timeout: 5000
})
.waitForElementVisible({
selector: "//div[@data-id='block']/span[contains(text(), '3 warnings found.')]",
locateStrategy: 'xpath',
timeout: 5000
})
} }
} }
@ -500,3 +534,27 @@ async function buildFoundryProject(): Promise<void> {
console.log(e) console.log(e)
} }
} }
async function installSlither(): Promise<void> {
console.log('installSlither', process.cwd())
try {
const server = spawn('node', ['./dist/libs/remixd/src/scripts/installSlither.js'], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) {
console.log(data.toString())
if (
data.toString().includes("Slither is ready to use")
) {
console.log('resolving')
resolve()
}
})
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
} catch (e) {
console.log(e)
}
}

@ -100,7 +100,7 @@ module.exports = {
.switchEnvironment('vm-london') .switchEnvironment('vm-london')
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('*[data-id="treeViewDivMenu"]') // make sure we create the file at the root folder .click('*[data-id="treeViewUltreeViewMenu"]') // make sure we create the file at the root folder
.addFile('deployWithEthersJs.js', { content: deployWithEthersJs }) .addFile('deployWithEthersJs.js', { content: deployWithEthersJs })
// .openFile('deployWithEthersJs.js') // .openFile('deployWithEthersJs.js')
.pause(1000) .pause(1000)

@ -392,7 +392,7 @@ class AppComponent {
this.pinnedPanel = new PinnedPanel() this.pinnedPanel = new PinnedPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine) const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine)
const filePanel = new FilePanel(appManager) const filePanel = new FilePanel(appManager, contentImport)
this.statusBar = new StatusBar(filePanel, this.menuicons) this.statusBar = new StatusBar(filePanel, this.menuicons)
const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport) const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager) this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager)
@ -519,7 +519,7 @@ class AppComponent {
) )
await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solidity-script'])
await this.appManager.activatePlugin(['solcoder']) await this.appManager.activatePlugin(['solcoder'])
await this.appManager.activatePlugin(['filePanel']) await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation // Set workspace after initial activation
this.appManager.on('editor', 'editorMounted', () => { this.appManager.on('editor', 'editorMounted', () => {

@ -624,8 +624,7 @@ class DGitProvider extends Plugin {
...await this.addIsomorphicGitConfig(input), ...await this.addIsomorphicGitConfig(input),
...await this.addIsomorphicGitConfigFS() ...await this.addIsomorphicGitConfigFS()
} }
this.call('terminal', 'logHtml', `Cloning ${input.url}... please wait...`)
this.call('terminal', 'logHtml', `Cloning ${input.url}...`)
const result = await git.clone(cmd) const result = await git.clone(cmd)
if (!input.workspaceExists) { if (!input.workspaceExists) {
setTimeout(async () => { setTimeout(async () => {
@ -633,6 +632,12 @@ class DGitProvider extends Plugin {
}, 1000) }, 1000)
} }
this.emit('clone') this.emit('clone')
this.call('fileManager', 'hasGitSubmodules').then((submodules) => {
if (submodules) {
this.call('terminal', 'log', { type: 'warn', value: 'This repository has submodules. Please update submodules to pull all the dependencies.' })
this.emit('repositoryWithSubmodulesCloned')
}
})
return result return result
} }
} }

@ -59,7 +59,7 @@ const profile = {
maintainedBy: 'Remix' maintainedBy: 'Remix'
} }
module.exports = class Filepanel extends ViewPlugin { module.exports = class Filepanel extends ViewPlugin {
constructor(appManager) { constructor(appManager, contentImport) {
super(profile) super(profile)
this.registry = Registry.getInstance() this.registry = Registry.getInstance()
this.fileProviders = this.registry.get('fileproviders').api this.fileProviders = this.registry.get('fileproviders').api
@ -73,6 +73,7 @@ module.exports = class Filepanel extends ViewPlugin {
this.foundryHandle = new FoundryHandle() this.foundryHandle = new FoundryHandle()
this.truffleHandle = new TruffleHandle() this.truffleHandle = new TruffleHandle()
this.slitherHandle = new SlitherHandle() this.slitherHandle = new SlitherHandle()
this.contentImport = contentImport
this.workspaces = [] this.workspaces = []
this.appManager = appManager this.appManager = appManager
this.currentWorkspaceMetadata = null this.currentWorkspaceMetadata = null

@ -68,7 +68,7 @@
"filePanel.createNewFolder": "Create new folder", "filePanel.createNewFolder": "Create new folder",
"filePanel.publishToGist": "Publish to Gist", "filePanel.publishToGist": "Publish to Gist",
"filePanel.workspace.publishToGist": "Publish workspace to GitHub gist", "filePanel.workspace.publishToGist": "Publish workspace to GitHub gist",
"filePanel.uploadFile": "Upload files", "filePanel.uploadFile": "Open a File from your File System",
"filePanel.uploadFolder": "Upload folder", "filePanel.uploadFolder": "Upload folder",
"filePanel.updateGist": "Update Gist", "filePanel.updateGist": "Update Gist",
"filePanel.workspace.updateGist": "Publish Gist update", "filePanel.workspace.updateGist": "Publish Gist update",

@ -66,6 +66,9 @@
"home.resources": "Resources", "home.resources": "Resources",
"home.connectToLocalhost": "Connect to Localhost", "home.connectToLocalhost": "Connect to Localhost",
"home.seeAllTutorials": "See all tutorials", "home.seeAllTutorials": "See all tutorials",
"home.maintainedByRemix": "Maintained by Remix" "home.maintainedByRemix": "Maintained by Remix",
"home.gitCloneTooltip": "Clone a Github repo to a new workspace",
"home.gistTooltip": "Open Gist repo",
"home.newFileTooltip": "Add a new file to a workspace"
} }

@ -12,7 +12,7 @@ function HomeTabFeatured() {
const themeFilter = useContext(ThemeContext) const themeFilter = useContext(ThemeContext)
return ( return (
<div className="pt-1 pl-2" id="hTFeaturedeSection"> <div className="pt-1 pl-2 h-100" id="hTFeaturedeSection">
<div className="mb-2 remix_ui-carousel-container"> <div className="mb-2 remix_ui-carousel-container">
<div className="w-100 d-flex flex-column rounded-3 remix_ui-carouselbox"> <div className="w-100 d-flex flex-column rounded-3 remix_ui-carouselbox">
<ThemeContext.Provider value={themeFilter}> <ThemeContext.Provider value={themeFilter}>
@ -109,7 +109,7 @@ function HomeTabFeatured() {
</div> </div>
<div className="mr-1 pr-1 d-flex align-items-center justify-content-center h-100"> <div className="mr-1 pr-1 d-flex align-items-center justify-content-center h-100">
<a href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform" target="__blank"> <a href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform" target="__blank">
<img src={'/assets/img/remixRewardBetaTester_small.webp'} className="remixui_carouselImage" alt=""></img> <img src={'/assets/img/remixRewardBetaTester_small.webp'} className="remixui_carouselImage_remixbeta" alt=""></img>
</a> </a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}> <div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5> <h5>

@ -1,31 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState, useRef, useReducer, useEffect } from 'react' import React, { useState, useRef, useReducer, useEffect } from 'react'
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl'
import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
const _paq = (window._paq = window._paq || []) // eslint-disable-line const _paq = (window._paq = window._paq || []) // eslint-disable-line
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import { TEMPLATE_NAMES } from '@remix-ui/workspace'
interface HomeTabFileProps { interface HomeTabFileProps {
plugin: any 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) { function HomeTabFile({ plugin }: HomeTabFileProps) {
const [state, setState] = useState<{ const [state, setState] = useState<{
searchInput: string searchInput: string
@ -48,10 +31,6 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
recentWorkspaces: [], recentWorkspaces: [],
}) })
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const inputValue = useRef(null)
useEffect(() => { useEffect(() => {
plugin.on('filePanel', 'setWorkspace', async () => { plugin.on('filePanel', 'setWorkspace', async () => {
let recents = JSON.parse(localStorage.getItem('recentWorkspaces')) let recents = JSON.parse(localStorage.getItem('recentWorkspaces'))
@ -59,8 +38,11 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
if (!recents) { if (!recents) {
recents = [] recents = []
} else { } else {
const filtered = recents.filter((workspace) => {
return workspace !== null
})
setState((prevState) => { setState((prevState) => {
return { ...prevState, recentWorkspaces: recents.slice(0, recents.length <= 3 ? recents.length : 3) } return { ...prevState, recentWorkspaces: filtered.slice(0, filtered.length <= 3 ? filtered.length : 3) }
}) })
} }
}) })
@ -91,41 +73,6 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
} }
}, [plugin]) }, [plugin])
const processLoading = (type: string) => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'importFrom' + type])
const contentImport = plugin.contentImport
const workspace = plugin.fileManager.getProvider('workspace')
const startsWith = state.importSource.substring(0, 4)
if ((type === 'ipfs' || type === 'IPFS') && startsWith !== 'ipfs' && startsWith !== 'IPFS') {
setState((prevState) => {
return { ...prevState, importSource: startsWith + state.importSource }
})
}
contentImport.import(
state.modalInfo.prefix + 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) => { const toast = (message: string) => {
setState((prevState) => { setState((prevState) => {
return { ...prevState, toasterMsg: message } return { ...prevState, toasterMsg: message }
@ -146,16 +93,16 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
await plugin.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false }) await plugin.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false })
await plugin.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false }) // calling once is not working. await plugin.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false }) // calling once is not working.
const content = `// SPDX-License-Identifier: MIT const content = `// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <0.9.0; pragma solidity >=0.6.12 <0.9.0;
contract HelloWorld { contract HelloWorld {
/** /**
* @dev Prints Hello World string * @dev Prints Hello World string
*/ */
function print() public pure returns (string memory) { function print() public pure returns (string memory) {
return "Hello World!"; return "Hello World!";
} }
} }
` `
if (createFile) { if (createFile) {
const { newPath } = await plugin.call('fileManager', 'writeFileNoRewrite', '/contracts/HelloWorld.sol', content) const { newPath } = await plugin.call('fileManager', 'writeFileNoRewrite', '/contracts/HelloWorld.sol', content)
@ -180,77 +127,21 @@ contract HelloWorld {
plugin.verticalIcons.select('filePanel') plugin.verticalIcons.select('filePanel')
} }
const showFullMessage = (title: string, loadItem: string, examples: Array<string>, prefix = '') => {
setState((prevState) => {
return {
...prevState,
showModalDialog: true,
modalInfo: {
title: title,
loadItem: loadItem,
examples: examples,
prefix,
},
}
})
}
const hideFullMessage = () => {
//eslint-disable-line
setState((prevState) => {
return { ...prevState, showModalDialog: false, importSource: '' }
})
}
const handleSwichToRecentWorkspace = async (e, workspaceName) => { const handleSwichToRecentWorkspace = async (e, workspaceName) => {
e.preventDefault() e.preventDefault()
_paq.push(['trackEvent', 'hometab', 'filesSection', 'loadRecentWorkspace']) _paq.push(['trackEvent', 'hometab', 'filesSection', 'loadRecentWorkspace'])
await plugin.call('filePanel', 'switchToWorkspace', { name: workspaceName, isLocalhost: false }) await plugin.call('filePanel', 'switchToWorkspace', { name: workspaceName, isLocalhost: false })
} }
const examples = state.modalInfo.examples.map((urlEl, key) => (
<div key={key} className="p-1 user-select-auto">
<a>{urlEl}</a>
</div>
))
return ( return (
<> <>
<ModalDialog id="homeTab" title={'Import from ' + state.modalInfo.title} okLabel="Import" hide={!state.showModalDialog} handleHide={() => hideFullMessage()} okFn={() => processLoading(state.modalInfo.title)}>
<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>
</>
)}
<div className="d-flex flex-row">
{state.modalInfo.prefix && <span className="text-nowrap align-self-center mr-2">ipfs://</span>}
<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>
</div>
</ModalDialog>
<Toaster message={state.toasterMsg} /> <Toaster message={state.toasterMsg} />
<div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection"> <div className="justify-content-start p-2 d-flex flex-column" id="hTFileSection">
<div className="mb-3"> <div className="mb-1">
{(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && ( {(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
<div className="d-flex flex-column mb-5 remixui_recentworkspace"> <div className="d-flex flex-column mb-5 remixui_recentworkspace">
<label style={{ fontSize: '0.8rem' }} className="mt-3"> <label style={{ fontSize: '0.8rem' }} className="mt-1">
Recent Workspaces Recent Workspaces
</label> </label>
{state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && ( {state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[0])}> <a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[0])}>
@ -270,58 +161,59 @@ contract HelloWorld {
</div> </div>
)} )}
</div> </div>
<div className="d-flex flex-column flex-nowrap pt-3"> <div className="d-flex flex-column flex-nowrap mt-4">
<label style={{ fontSize: '1.2rem' }}> <label style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.files" /> <FormattedMessage id="home.files" />
</label> </label>
<div className="d-flex flex-column"> <div className="d-flex flex-row flex-wrap">
<div className="d-flex flex-row"> <CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.newFileTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.startCodingPlayground" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3"> <button className="btn btn-primary text-nowrap p-2 mr-2 border my-1 mb-2" data-id="homeTabNewFile" style={{ width: 'fit-content' }} onClick={async () => {
<button className="btn btn-primary text-nowrap p-2 mr-2 border my-1" data-id="homeTabNewFile" style={{ width: 'fit-content' }} onClick={async () => await plugin.call('filePanel', 'createNewFile')}> _paq.push(['trackEvent', 'hometab', 'filesSection', 'newFile'])
<FormattedMessage id="home.newFile" /> await plugin.call('menuicons', 'select', 'filePanel')
</button> await plugin.call('filePanel', 'createNewFile')
</CustomTooltip> }}>
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.openFileTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3"> <i className="far fa-file pl-1 pr-2"></i>
<span> <FormattedMessage id="home.newFile" />
<label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<FormattedMessage id="home.openFile" />
</label>
<input
title="open file"
type="file"
id="openFileInput"
onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}}
multiple
/>
</span>
</CustomTooltip>
<button className="btn text-nowrap p-2 mr-2 border my-1" onClick={() => showFullMessage('Ipfs', 'ipfs hash', ['ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH'], 'ipfs://')}>
IPFS
</button> </button>
<button className="btn text-nowrap p-2 mr-2 border my-1" 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'])}> </CustomTooltip>
Git Clone <CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.openFileTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
</button> <span>
<button className="btn text-nowrap p-2 mr-2 border my-1" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}> <label className="btn text-nowrap p-2 mr-2 border my-1 mb-2" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
Gist <i className="far fa-upload pl-1 pr-2"></i>
<FormattedMessage id="home.openFile" />
</label>
<input
title="open file"
type="file"
id="openFileInput"
onChange={async (event) => {
event.stopPropagation()
await plugin.call('menuicons', 'select', 'filePanel')
uploadFile(event.target)
}}
multiple
/>
</span>
</CustomTooltip>
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.gistTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button className="btn text-nowrap p-2 mr-2 border my-1 mb-2" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>
<i className="fab fa-github pl-1 pr-2"></i>
Gist
</button> </button>
</CustomTooltip>
<button <CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.gitCloneTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3"
className="btn text-nowrap p-2 mr-2 border my-1" >
onClick={() => <button className="btn text-nowrap p-2 mr-2 border my-1 mb-2" data-id="landingPageImportFromGitHubButton" onClick={async () => {
showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol']) _paq.push(['trackEvent', 'hometab', 'filesSection', 'Git Clone'])
} await plugin.call('filePanel', 'clone')
> }}>
HTTPS <i className="fa-brands fa-github-alt pl-1 pr-2"></i>
Git Clone
</button> </button>
</div> </CustomTooltip>
</div>
<div className="d-flex mt-2 align-items-end w-100">
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.connectToLocalhost" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3"> <CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.connectToLocalhost" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3">
<button className="btn btn-block text-nowrap p-2 border my-1" onClick={() => connectToLocalhost()}> <button className="btn text-nowrap p-2 border my-1 mb-2" onClick={() => connectToLocalhost()}>
<i className="fa-regular fa-desktop pr-2"></i> <i className="fa-regular fa-desktop pr-2"></i>
<FormattedMessage id="home.accessFileSystem" /> <FormattedMessage id="home.accessFileSystem" />
</button> </button>

@ -32,24 +32,17 @@ const workspaceTemplates: WorkspaceTemplate[] = [
{ {
gsID: 'sUTLogo', gsID: 'sUTLogo',
workspaceTitle: 'Start Coding', workspaceTitle: 'Start Coding',
description: 'Create a new project using this template.', description: 'Start coding using the default template.',
projectLogo: 'assets/img/remixverticaltextLogo.png', projectLogo: 'assets/img/remixverticaltextLogo.png',
templateName: 'remixDefault', templateName: 'remixDefault',
}, },
{ {
gsID: 'sUTLogo', gsID: 'sUTLogo',
workspaceTitle: 'Circom', workspaceTitle: 'ZK Semaphore',
description: 'Create a new ZK Project with Circom using this template.', description: 'Create a new ZK Project with Circom using this template.',
projectLogo: 'assets/img/circom.webp', projectLogo: 'assets/img/circom.webp',
templateName: 'semaphore', templateName: 'semaphore',
}, },
{
gsID: 'sUTLogo',
workspaceTitle: 'Uniswap',
description: 'Create a new MultiSig wallet using this template.',
projectLogo: 'assets/img/gnosissafeLogo.png',
templateName: 'uniswapV4Template',
},
{ {
gsID: 'sUTLogo', gsID: 'sUTLogo',
workspaceTitle: 'ERC20', workspaceTitle: 'ERC20',
@ -57,6 +50,13 @@ const workspaceTemplates: WorkspaceTemplate[] = [
projectLogo: 'assets/img/oxprojectLogo.png', projectLogo: 'assets/img/oxprojectLogo.png',
templateName: 'ozerc20', templateName: 'ozerc20',
}, },
{
gsID: 'sUTLogo',
workspaceTitle: 'Uniswap V4 Hooks',
description: 'Create a new workspace based on this template.',
projectLogo: 'assets/img/gnosissafeLogo.png',
templateName: 'uniswapV4Template',
},
{ {
gsID: 'sUTLogo', gsID: 'sUTLogo',
workspaceTitle: 'NFT / ERC721', workspaceTitle: 'NFT / ERC721',
@ -157,12 +157,12 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
<div ref={carouselRefDiv} className="w-100 d-flex flex-column pt-1"> <div ref={carouselRefDiv} className="w-100 d-flex flex-column pt-1">
<ThemeContext.Provider value={themeFilter}> <ThemeContext.Provider value={themeFilter}>
<div className="pt-3"> <div className="pt-3">
<div className="d-flex flex-row align-items-center mb-3 flex-nowrap"> <div className="d-flex flex-row align-items-center mb-3 flex-wrap">
{workspaceTemplates.slice(0, 3).map((template, index) => ( {workspaceTemplates.map((template, index) => (
<CustomTooltip tooltipText={template.description} tooltipId={template.gsID} tooltipClasses="text-nowrap" tooltipTextClasses="border bg-light text-dark p-1 pr-3" placement="top-start" key={`${template.gsID}-${template.workspaceTitle}-${index}`}> <CustomTooltip tooltipText={template.description} tooltipId={template.gsID} tooltipClasses="text-nowrap" tooltipTextClasses="border bg-light text-dark p-1 pr-3" placement="top-start" key={`${template.gsID}-${template.workspaceTitle}-${index}`}>
<button <button
key={index} key={index}
className={index === 0 ? 'btn btn-primary border p-2 text-nowrap mr-3' : index === workspaceTemplates.length - 1 ? 'btn border p-2 text-nowrap mr-2' : 'btn border p-2 text-nowrap mr-3'} className={index === 0 ? 'btn btn-primary border p-2 text-nowrap mr-3 mb-3' : index === workspaceTemplates.length - 1 ? 'btn border p-2 text-nowrap mr-2 mb-3' : 'btn border p-2 text-nowrap mr-3 mb-3'}
onClick={(e) => { onClick={(e) => {
createWorkspace(template.templateName) createWorkspace(template.templateName)
}} }}
@ -173,22 +173,6 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
</CustomTooltip> </CustomTooltip>
))} ))}
</div> </div>
<div className="d-flex flex-row align-items-center mb-2 flex-nowrap">
{workspaceTemplates.slice(3, workspaceTemplates.length).map((template, index) => (
<CustomTooltip tooltipText={template.description} tooltipId={template.gsID} tooltipClasses="text-nowrap" tooltipTextClasses="border bg-light text-dark p-1 pr-3" placement="bottom-start" key={`${template.gsID}-${template.workspaceTitle}-${index}`}>
<button
key={index}
className={'btn border p-2 text-nowrap mr-3'}
onClick={() => {
createWorkspace(template.templateName)
}}
data-id={`homeTabGetStarted${template.workspaceTitle}`}
>
{template.workspaceTitle}
</button>
</CustomTooltip>
))}
</div>
</div> </div>
</ThemeContext.Provider> </ThemeContext.Provider>
</div> </div>

@ -126,7 +126,7 @@ function HomeTabTitle() {
openLink(button.urlLink) openLink(button.urlLink)
_paq.push(button.matomoTrackingEntry) _paq.push(button.matomoTrackingEntry)
}} }}
className={`border-0 h-100 pl-1 pr-0 btn fab ${button.iconClass}`} className={`border-0 h-100 px-1 btn fab ${button.iconClass}`}
></button> ></button>
</CustomTooltip> </CustomTooltip>
))} ))}

@ -125,6 +125,12 @@
width: 20rem; width: 20rem;
} }
.remixui_carouselImage_remixbeta {
flex: 1;
height: 20rem;
width: 18rem;
}
.remixui_carouselbox { .remixui_carouselbox {
min-height: 25.12rem; min-height: 25.12rem;
} }

@ -73,10 +73,10 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
// border-right // border-right
return ( return (
<div className="d-flex flex-column w-100 h-100" data-id="remixUIHTAll"> <div className="d-flex flex-column w-100" data-id="remixUIHTAll">
<ThemeContext.Provider value={state.themeQuality}> <ThemeContext.Provider value={state.themeQuality}>
<div className="d-flex flex-row w-100 h-100 custom_home_bg"> <div className="d-flex flex-row w-100 custom_home_bg">
<div className="px-2 pl-3 justify-content-start border-right d-flex flex-column" id="remixUIHTLeft" style={{ width: `${100 - carouselWidth}%` }}> <div className="px-2 pl-3 justify-content-start border-right d-flex flex-column" id="remixUIHTLeft" style={{ width: 'inherit' }}>
<HomeTabTitle /> <HomeTabTitle />
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted> <HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
{!(platform === appPlatformTypes.desktop) ? {!(platform === appPlatformTypes.desktop) ?

@ -721,11 +721,11 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
Slither Slither
{slitherWarnings.length > 0 ? ( {slitherWarnings.length > 0 ? (
hideWarnings ? ( hideWarnings ? (
<span className="badge badge-warning badge-pill mx-1 ml-1">{slitherErrors.length}</span> <span data-id='slitherErrors' className="badge badge-warning badge-pill mx-1 ml-1">{slitherErrors.length}</span>
) : showLibsWarning && !hideWarnings ? ( ) : showLibsWarning && !hideWarnings ? (
<span className={`badge ${slitherErrors.length > 0 ? `badge-danger` : 'badge-warning'} badge-pill mx-1 ml-1 text-center`}>{slitherWarnings.length}</span> <span data-id='slitherWarnings' className={`badge ${slitherErrors.length > 0 ? `badge-danger` : 'badge-warning'} badge-pill mx-1 ml-1 text-center`}>{slitherWarnings.length}</span>
) : ( ) : (
<span className={`badge ${slitherErrors.length > 0 ? `badge-danger` : 'badge-warning'} badge-pill mx-1 ml-1 text-center`}>{noLibSlitherWarnings.length}</span> <span data-id='nolibslitherwarnings' className={`badge ${slitherErrors.length > 0 ? `badge-danger` : 'badge-warning'} badge-pill mx-1 ml-1 text-center`}>{noLibSlitherWarnings.length}</span>
) )
) : null} ) : null}
</span> </span>

@ -1,10 +1,16 @@
remixui_statusbar:hover { .remixui_statusbar_gitstatus
.remixui_statusbar_gitstatus:hover {
cursor: pointer; cursor: pointer;
} }
.remixui_statusbar_gitstatus .remixui_statusbar_scamAlert {}
.remixui_statusbar_gitstatus:hover { .remixui_statusbar_scamAlert:hover {
cursor: pointer;
}
.remixui_statusbar_aistatus {}
.remixui_statusbar_aistatus:hover {
cursor: pointer; cursor: pointer;
} }

@ -35,9 +35,9 @@ export default function AIStatus(props: AIStatusProps) {
<CustomTooltip <CustomTooltip
tooltipText={copilotActive ? "Remix Copilot activated" : "Remix Copilot disabled."} tooltipText={copilotActive ? "Remix Copilot activated" : "Remix Copilot disabled."}
> >
<div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center"> <div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center remixui_statusbar_aistatus">
<span className={copilotActive === false ? "fa-regular fa-microchip-ai ml-1 text-danger" : "fa-regular fa-microchip-ai ml-1"}></span> <span className={copilotActive === false ? "fa-regular fa-microchip-ai ml-1 text-white" : "fa-regular fa-microchip-ai ml-1"}></span>
<span className={copilotActive === false ? "small mx-1 text-danger semi-bold" : "small mx-1 semi-bold" }>Remix Copilot</span> <span className={copilotActive === false ? "small mx-1 text-white semi-bold" : "small mx-1 semi-bold" }>Remix Copilot</span>
</div> </div>
</CustomTooltip> </CustomTooltip>
) )

@ -1,4 +1,4 @@
import React, { useEffect, Dispatch } from 'react' import React, { useEffect, Dispatch, useState } from 'react'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar' import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar'
import '../../css/statusbar.css' import '../../css/statusbar.css'
@ -11,10 +11,12 @@ export interface GitStatusProps {
} }
export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: GitStatusProps) { export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: GitStatusProps) {
const [isLocalHost, setIsLocalHost] = useState(false)
useEffect(() => { useEffect(() => {
plugin.on('filePanel', 'setWorkspace', async (workspace) => { plugin.on('filePanel', 'setWorkspace', async (workspace) => {
const isGit = await plugin.call('fileManager', 'isGitRepo') const isGit = await plugin.call('fileManager', 'isGitRepo')
setIsLocalHost(workspace.isLocalhost)
if (isGit) { if (isGit) {
setGitBranchName(workspace.name) setGitBranchName(workspace.name)
} else { } else {
@ -37,6 +39,7 @@ export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: G
setGitBranchName(workspace) setGitBranchName(workspace)
} }
}) })
}, []) }, [])
const lightDgitUp = async () => { const lightDgitUp = async () => {
@ -51,10 +54,14 @@ export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: G
const initializeNewGitRepo = async () => { const initializeNewGitRepo = async () => {
await plugin.call('dGitProvider', 'init') await plugin.call('dGitProvider', 'init')
const isActive = await plugin.call('manager', 'isActive', 'dgit') const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (isLocalHost === false) {
// plugin.verticalIcons.select('dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
}
} }
const checkBranchName = ()=> {
return gitBranchName && gitBranchName !== 'Not a git repo' && gitBranchName.length > 0
}
return ( return (
<CustomTooltip <CustomTooltip
tooltipText={`${gitBranchName === 'Not a git repo' ? 'Initialize as a git repo' : gitBranchName} (Git)`} tooltipText={`${gitBranchName === 'Not a git repo' ? 'Initialize as a git repo' : gitBranchName} (Git)`}
@ -63,10 +70,10 @@ export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: G
className="d-flex flex-row pl-3 text-white justify-content-center align-items-center remixui_statusbar_gitstatus" className="d-flex flex-row pl-3 text-white justify-content-center align-items-center remixui_statusbar_gitstatus"
onClick={async () => await lightDgitUp()} onClick={async () => await lightDgitUp()}
> >
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' ? <span className="fa-regular fa-code-branch ml-1"></span> {checkBranchName() && isLocalHost === false ? <span className="fa-regular fa-code-branch ml-1"></span>
: <span className=" ml-1" onClick={initializeNewGitRepo}> Initialize as git repo</span>} : <span className=" ml-1" onClick={initializeNewGitRepo}> Initialize as git repo</span>}
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' && <span className="ml-1">{gitBranchName}</span>} {checkBranchName() && isLocalHost === false && <span className="ml-1">{gitBranchName}</span>}
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' && <span className="fa-solid fa-arrows-rotate fa-1 ml-1"></span>} {checkBranchName() && isLocalHost === false && <span className="fa-solid fa-arrows-rotate fa-1 ml-1"></span>}
</div> </div>
</CustomTooltip> </CustomTooltip>
) )

@ -15,7 +15,7 @@ export default function ScamAlertStatus ({ refs, getReferenceProps }: ScamAlertS
<CustomTooltip <CustomTooltip
tooltipText={"Scam Alerts"} tooltipText={"Scam Alerts"}
> >
<div className="mr-2 d-flex align-items-center justify-content-center" id="hTScamAlertSection" ref={refs.setReference} {...getReferenceProps()}> <div className="mr-2 d-flex align-items-center justify-content-center remixui_statusbar_scamAlert" id="hTScamAlertSection" ref={refs.setReference} {...getReferenceProps()}>
<span className="pr-2 far fa-exclamation-triangle text-white"></span> <span className="pr-2 far fa-exclamation-triangle text-white"></span>
<span className="text-white font-semibold small"> <span className="text-white font-semibold small">
<FormattedMessage id="home.scamAlert" /> <FormattedMessage id="home.scamAlert" />

@ -10,6 +10,8 @@ import { fetchContractFromEtherscan, fetchContractFromBlockscout } from '@remix-
import JSZip from 'jszip' import JSZip from 'jszip'
import { Actions, FileTree } from '../types' import { Actions, FileTree } from '../types'
import IpfsHttpClient from 'ipfs-http-client' import IpfsHttpClient from 'ipfs-http-client'
import { AppModal } from '@remix-ui/app'
import { MessageWrapper } from '../components/file-explorer'
export * from './events' export * from './events'
export * from './workspace' export * from './workspace'

@ -1,5 +1,5 @@
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import React, {useState, useEffect, useContext} from 'react' //eslint-disable-line import React, {useState, useEffect, useContext, useRef, useReducer} from 'react' //eslint-disable-line
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl'
import { Placement } from 'react-bootstrap/esm/Overlay' import { Placement } from 'react-bootstrap/esm/Overlay'
import { FileExplorerMenuProps } from '../types' import { FileExplorerMenuProps } from '../types'
@ -39,6 +39,20 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
icon: 'far fa-folder-upload', icon: 'far fa-folder-upload',
placement: 'top', placement: 'top',
platforms:[appPlatformTypes.web] platforms:[appPlatformTypes.web]
},
{
action: 'importFromIpfs',
title: 'Import files from ipfs',
icon: 'fa-regular fa-cube',
placement: 'top',
platforms: [appPlatformTypes.web, appPlatformTypes.desktop]
},
{
action: 'importFromHttps',
title: 'Import files with https',
icon: 'fa-solid fa-link',
placement: 'top',
platforms: [appPlatformTypes.web, appPlatformTypes.desktop]
} }
].filter( ].filter(
(item) => (item) =>
@ -49,6 +63,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
), ),
actions: {} actions: {}
}) })
const enableDirUpload = { directory: '', webkitdirectory: '' } const enableDirUpload = { directory: '', webkitdirectory: '' }
return ( return (
@ -143,6 +158,12 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
props.createNewFolder() props.createNewFolder()
} else if (action === 'publishToGist' || action == 'updateGist') { } else if (action === 'publishToGist' || action == 'updateGist') {
props.publishToGist() props.publishToGist()
} else if (action === 'importFromIpfs') {
_paq.push(['trackEvent', 'fileExplorer', 'fileAction', action])
props.importFromIpfs('Ipfs', 'ipfs hash', ['ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH'], 'ipfs://')
} else if (action === 'importFromHttps') {
_paq.push(['trackEvent', 'fileExplorer', 'fileAction', action])
props.importFromHttps('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])
} else { } else {
state.actions[action]() state.actions[action]()
} }

@ -383,6 +383,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
publishToGist={publishToGist} publishToGist={publishToGist}
uploadFile={uploadFile} uploadFile={uploadFile}
uploadFolder={uploadFolder} uploadFolder={uploadFolder}
importFromIpfs={props.importFromIpfs}
importFromHttps={props.importFromHttps}
/> />
</div> </div>
</span> </span>
@ -412,4 +414,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
) )
} }
export const MessageWrapper = () => {
return (
<p>e.g ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH</p>
)
}
export default FileExplorer export default FileExplorer

@ -1,9 +1,10 @@
import React, {useState, useEffect, useRef, useContext, ChangeEvent} from 'react' // eslint-disable-line import React, {useState, useEffect, useRef, useContext, ChangeEvent, useReducer} from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { Dropdown } from 'react-bootstrap' import { Dropdown } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle, CustomTooltip, extractNameFromKey, extractParentFromKey } from '@remix-ui/helper' import { CustomIconsToggle, CustomMenu, CustomToggle, CustomTooltip, extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
import {FileExplorer} from './components/file-explorer' // eslint-disable-line import {FileExplorer} from './components/file-explorer' // eslint-disable-line
import {ModalDialog, ValidationResult} from '@remix-ui/modal-dialog' // eslint-disable-line
import { FileSystemContext } from './contexts' import { FileSystemContext } from './contexts'
import './css/remix-ui-workspace.css' import './css/remix-ui-workspace.css'
import { ROOT_PATH, TEMPLATE_NAMES } from './utils/constants' import { ROOT_PATH, TEMPLATE_NAMES } from './utils/constants'
@ -31,6 +32,7 @@ export function Workspace() {
const [showDropdown, setShowDropdown] = useState<boolean>(false) const [showDropdown, setShowDropdown] = useState<boolean>(false)
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false) const [showIconsMenu, hideIconsMenu] = useState<boolean>(false)
const [showBranches, setShowBranches] = useState<boolean>(false) const [showBranches, setShowBranches] = useState<boolean>(false)
const [highlightUpdateSubmodules, setHighlightUpdateSubmodules] = useState<boolean>(false)
const [branchFilter, setBranchFilter] = useState<string>('') const [branchFilter, setBranchFilter] = useState<string>('')
const displayOzCustomRef = useRef<HTMLDivElement>() const displayOzCustomRef = useRef<HTMLDivElement>()
const mintableCheckboxRef = useRef() const mintableCheckboxRef = useRef()
@ -105,6 +107,136 @@ export function Workspace() {
} }
}, [canPaste]) }, [canPaste])
const [modalState, setModalState] = useState<{
searchInput: string
showModalDialog: boolean
// modalValidation?: ValidationResult
modalInfo: {
title: string
loadItem: string
examples: Array<string>
prefix?: string
}
importSource: string
toasterMsg: string
}>({
searchInput: '',
showModalDialog: false,
// modalValidation: {} as ValidationResult,
modalInfo: { title: '', loadItem: '', examples: [], prefix: '' },
importSource: '',
toasterMsg: ''
})
const [validationResult, setValidationResult] = useState<ValidationResult>({ valid: true, message: '' })
const loadingInitialState = {
tooltip: '',
showModalDialog: false,
importSource: '',
}
const loadingReducer = (state = loadingInitialState, action) => {
return {
...state,
tooltip: action.tooltip,
showModalDialog: false,
importSource: '',
}
}
const inputValue = useRef(null)
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const toast = (message: string) => {
setModalState((prevState) => {
return { ...prevState, toasterMsg: message }
})
}
const showFullMessage = async (title: string, loadItem: string, examples: Array<string>, prefix = '') => {
setModalState((prevState) => {
return {
...prevState,
showModalDialog: true,
modalInfo: {
title: title,
loadItem: loadItem,
examples: examples,
prefix,
},
}
})
}
const hideFullMessage = () => {
//eslint-disable-line
setModalState((prevState) => {
return { ...prevState, showModalDialog: false, importSource: '' }
})
}
const examples = modalState.modalInfo.examples.map((urlEl, key) => (
<div key={key} className="p-1 user-select-auto">
<a>{urlEl}</a>
</div>
))
const processLoading = (type: string) => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'importFrom' + type])
const contentImport = global.plugin.contentImport
const workspace = global.plugin.fileManager.getProvider('workspace')
const startsWith = modalState.importSource.substring(0, 4)
if ((type === 'ipfs' || type === 'IPFS') && startsWith !== 'ipfs' && startsWith !== 'IPFS') {
setModalState((prevState) => {
return { ...prevState, importSource: startsWith + modalState.importSource }
})
}
contentImport.import(
modalState.modalInfo.prefix + modalState.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)
global.plugin.call('menuicons', 'select', 'filePanel')
}
} catch (e) {
toast(e.message)
}
}
}
)
setModalState((prevState) => {
return { ...prevState, showModalDialog: false, importSource: '' }
})
}
/**
* show modal for either ipfs or https icons in file explorer menu
* @returns void
*/
const importFromUrl = (title: string, loadItem: string, examples: Array<string>, prefix = '') => {
showFullMessage(title, loadItem, examples, prefix)
}
/**
* Validate the url fed into the modal for ipfs and https imports
* @returns {ValidationResult}
*/
const validateUrlForImport = (input: any) => {
if ((input.trim().startsWith('ipfs://') && input.length > 7) || input.trim().startsWith('https://') || input.trim() !== '') {
return { valid: true, message: '' }
} else {
global.plugin.call('notification', 'alert', { id: 'homeTabAlert', message: 'The provided value is invalid!' })
return { valid: false, message: 'The provided value is invalid!' }
}
}
useEffect(() => { useEffect(() => {
let workspaceName = localStorage.getItem('currentWorkspace') let workspaceName = localStorage.getItem('currentWorkspace')
if (!workspaceName && global.fs.browser.workspaces.length) { if (!workspaceName && global.fs.browser.workspaces.length) {
@ -124,6 +256,10 @@ export function Workspace() {
cloneGitRepository() cloneGitRepository()
} }
} }
global.plugin.on('dGitProvider', 'repositoryWithSubmodulesCloned', () => {
setHighlightUpdateSubmodules(true)
})
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -678,6 +814,7 @@ export function Workspace() {
const updateSubModules = async () => { const updateSubModules = async () => {
try { try {
setHighlightUpdateSubmodules(false)
await global.dispatchUpdateGitSubmodules() await global.dispatchUpdateGitSubmodules()
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -1081,7 +1218,8 @@ export function Workspace() {
<FileExplorer <FileExplorer
fileState={global.fs.browser.fileState} fileState={global.fs.browser.fileState}
name={currentWorkspace} name={currentWorkspace}
menuItems={['createNewFile', 'createNewFolder', selectedWorkspace && selectedWorkspace.isGist ? 'updateGist' : 'publishToGist', canUpload ? 'uploadFile' : '', canUpload ? 'uploadFolder' : '']} menuItems={['createNewFile', 'createNewFolder', selectedWorkspace && selectedWorkspace.isGist ? 'updateGist' : 'publishToGist', canUpload ? 'uploadFile' : '', canUpload ? 'uploadFolder' : '', 'importFromIpfs',
'importFromHttps']}
contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems} contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems}
removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems} removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems}
files={global.fs.browser.files} files={global.fs.browser.files}
@ -1133,6 +1271,8 @@ export function Workspace() {
createNewFolder={handleNewFolderInput} createNewFolder={handleNewFolderInput}
deletePath={deletePath} deletePath={deletePath}
renamePath={editModeOn} renamePath={editModeOn}
importFromIpfs={importFromUrl}
importFromHttps={importFromUrl}
/> />
)} )}
@ -1197,6 +1337,8 @@ export function Workspace() {
deletePath={deletePath} deletePath={deletePath}
renamePath={editModeOn} renamePath={editModeOn}
dragStatus={dragStatus} dragStatus={dragStatus}
importFromIpfs={importFromUrl}
importFromHttps={importFromUrl}
/> />
)} )}
</div> </div>
@ -1206,11 +1348,11 @@ export function Workspace() {
{selectedWorkspace && ( {selectedWorkspace && (
<div className={`bg-light border-top ${selectedWorkspace.isGitRepo && currentBranch ? 'd-block' : 'd-none'}`} data-id="workspaceGitPanel"> <div className={`bg-light border-top ${selectedWorkspace.isGitRepo && currentBranch ? 'd-block' : 'd-none'}`} data-id="workspaceGitPanel">
<div className="d-flex justify-space-between p-1"> <div className="d-flex justify-space-between p-1">
<div className="mr-auto text-uppercase text-dark pt-2 pl-2">GIT</div> <div className="mr-auto text-uppercase text-dark pt-2 px-1">GIT</div>
{selectedWorkspace.hasGitSubmodules? {selectedWorkspace.hasGitSubmodules?
<div className="pt-1 mr-1"> <div className="pt-1 mr-1">
{global.fs.browser.isRequestingCloning ? <div style={{ height: 30 }} className='btn btn-sm border text-muted small'><i className="fad fa-spinner fa-spin"></i> updating submodules</div> : {global.fs.browser.isRequestingCloning ? <div style={{ height: 30, minWidth: 165 }} className='btn btn-sm border text-muted small'><i className="fad fa-spinner fa-spin"></i> updating submodules</div> :
<div style={{ height: 30 }} onClick={updateSubModules} data-id='updatesubmodules' className='btn btn-sm border text-muted small'>update submodules</div>} <div style={{ height: 30, minWidth: 165 }} onClick={updateSubModules} data-id='updatesubmodules' className={`btn btn-sm border small ${highlightUpdateSubmodules ? 'text-warning' : 'text-muted'}`}>update submodules</div>}
</div> </div>
: null} : null}
<div className="pt-1 mr-1" data-id="workspaceGitBranchesDropdown"> <div className="pt-1 mr-1" data-id="workspaceGitBranchesDropdown">
@ -1329,6 +1471,38 @@ export function Workspace() {
downloadPath={downloadPath} downloadPath={downloadPath}
/> />
)} )}
<ModalDialog id="homeTab" title={'Import from ' + modalState.modalInfo.title}
okLabel="Import" hide={!modalState.showModalDialog} handleHide={() => hideFullMessage()}
okFn={() => processLoading(modalState.modalInfo.title)} validationFn={validateUrlForImport}
>
<div className="p-2 user-select-auto">
{modalState.modalInfo.loadItem !== '' && <span>Enter the {modalState.modalInfo.loadItem} you would like to load.</span>}
{modalState.modalInfo.examples.length !== 0 && (
<>
<div>e.g</div>
<div>{examples}</div>
</>
)}
<div className="d-flex flex-row">
{modalState.modalInfo.prefix && <span className="text-nowrap align-self-center mr-2">ipfs://</span>}
<input
ref={inputValue}
type="text"
name="prompt_text"
id="inputPrompt_text"
className="w-100 mt-1 form-control"
data-id="homeTabModalDialogCustomPromptText"
value={modalState.importSource}
onInput={(e) => {
setModalState((prevState) => {
return { ...prevState, importSource: inputValue.current.value }
})
}}
/>
</div>
</div>
</ModalDialog>
</div> </div>
) )
} }

@ -154,6 +154,8 @@ export interface FileExplorerProps {
createNewFolder:(parentFolder?: string) => Promise<void> createNewFolder:(parentFolder?: string) => Promise<void>
renamePath:(path: string, type: string, isNew?: boolean) => void renamePath:(path: string, type: string, isNew?: boolean) => void
dragStatus: (status: boolean) => void dragStatus: (status: boolean) => void
importFromIpfs: any
importFromHttps: any
} }
export interface FileExplorerMenuProps { export interface FileExplorerMenuProps {
@ -164,6 +166,8 @@ export interface FileExplorerMenuProps {
publishToGist: (path?: string) => void publishToGist: (path?: string) => void
uploadFile: (target: EventTarget & HTMLInputElement) => void uploadFile: (target: EventTarget & HTMLInputElement) => void
uploadFolder: (target: EventTarget & HTMLInputElement) => void uploadFolder: (target: EventTarget & HTMLInputElement) => void
importFromIpfs: any
importFromHttps: any
tooltipPlacement?: Placement tooltipPlacement?: Placement
} }
export interface FileExplorerContextMenuProps { export interface FileExplorerContextMenuProps {

Loading…
Cancel
Save