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. 2
      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. 182
      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. 17
      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: 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: sudo apt update && sudo apt install python3-pip -y
- when:
condition:
equal: [ "chrome", << parameters.browser >> ]

@ -28,8 +28,8 @@ module.exports = {
'Should start with ERC20 workspace #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-path="home"')
.waitForElementVisible('*[data-id="homeTabGetStartedERC20"]')
.click('*[data-id="homeTabGetStartedERC20"')
.waitForElementVisible('*[data-id="homeTabGetStartedozerc20"]')
.click('*[data-id="homeTabGetStartedozerc20"')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[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'
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',
JSON: 'https://github.com/ethereum/remix-project/blob/master/package.json'
JSON: 'https://github.com/remix-project-org/git-hometab-test.git'
}
module.exports = {
@ -22,63 +22,53 @@ module.exports = {
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]')
.pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from GitHub')
.waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.waitForElementVisible('*[data-id="fileSystemModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="fileSystemModalDialogModalTitle-react"]', 'Clone Git Repository')
.waitForElementVisible('*[data-id="fileSystemModalDialogModalBody-react"]')
.waitForElementVisible('input[data-id="modalDialogCustomPromptTextClone"]')
},
'Display Error Message For Invalid GitHub URL Modal #group1': function (browser: NightwatchBrowser) {
browser
.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)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]') // submitted
.setValue('input[data-id="modalDialogCustomPromptTextClone"]', testData.invalidURL)
.waitForElementVisible('*[data-id="fileSystemModalDialogModalFooter-react"]')
.click('[data-id="fileSystem-modal-footer-ok-react"]') // submitted
//.waitForElementVisible('*[data-shared="tooltipPopup"]')
//.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
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.click('div[data-id="verticalIconsHomeIcon"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]').pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.waitForElementVisible('input[data-id="modalDialogCustomPromptTextClone"]')
.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)
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.validURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol')
.clearValue('input[data-id="modalDialogCustomPromptTextClone"]').pause(1000)
.setValue('input[data-id="modalDialogCustomPromptTextClone"]', testData.validURL)
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.openFile('Roles.sol')
.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'
})
.getEditorValue((content) => {
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
.click('div[data-id="verticalIconsHomeIcon"]')
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000)
.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'")
.openFile('package.json')
.waitForElementVisible("*[data-path='git-hometab-test.git/package.json'")
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('"name": "remix-project",') !== -1, 'content does contain "name": "remix-project"')
})

@ -267,6 +267,40 @@ module.exports = {
.pause(2000)
.clickLaunchIcon('dgit')
.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)
}
}
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')
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.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 })
// .openFile('deployWithEthersJs.js')
.pause(1000)

@ -392,7 +392,7 @@ class AppComponent {
this.pinnedPanel = new PinnedPanel()
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)
const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager)

@ -624,8 +624,7 @@ class DGitProvider extends Plugin {
...await this.addIsomorphicGitConfig(input),
...await this.addIsomorphicGitConfigFS()
}
this.call('terminal', 'logHtml', `Cloning ${input.url}...`)
this.call('terminal', 'logHtml', `Cloning ${input.url}... please wait...`)
const result = await git.clone(cmd)
if (!input.workspaceExists) {
setTimeout(async () => {
@ -633,6 +632,12 @@ class DGitProvider extends Plugin {
}, 1000)
}
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
}
}

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

@ -68,7 +68,7 @@
"filePanel.createNewFolder": "Create new folder",
"filePanel.publishToGist": "Publish to 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.updateGist": "Update Gist",
"filePanel.workspace.updateGist": "Publish Gist update",

@ -66,6 +66,9 @@
"home.resources": "Resources",
"home.connectToLocalhost": "Connect to Localhost",
"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)
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="w-100 d-flex flex-column rounded-3 remix_ui-carouselbox">
<ThemeContext.Provider value={themeFilter}>
@ -109,7 +109,7 @@ function HomeTabFeatured() {
</div>
<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">
<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>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: '1' }}>
<h5>

@ -1,31 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState, useRef, useReducer, useEffect } from 'react'
import { FormattedMessage } from 'react-intl'
import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
const _paq = (window._paq = window._paq || []) // eslint-disable-line
import { CustomTooltip } from '@remix-ui/helper'
import { TEMPLATE_NAMES } from '@remix-ui/workspace'
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
@ -48,10 +31,6 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
recentWorkspaces: [],
})
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const inputValue = useRef(null)
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', async () => {
let recents = JSON.parse(localStorage.getItem('recentWorkspaces'))
@ -59,8 +38,11 @@ function HomeTabFile({ plugin }: HomeTabFileProps) {
if (!recents) {
recents = []
} else {
const filtered = recents.filter((workspace) => {
return workspace !== null
})
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])
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) => {
setState((prevState) => {
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 }) // calling once is not working.
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
*/
function print() public pure returns (string memory) {
return "Hello World!";
}
}
}
`
if (createFile) {
const { newPath } = await plugin.call('fileManager', 'writeFileNoRewrite', '/contracts/HelloWorld.sol', content)
@ -180,76 +127,20 @@ contract HelloWorld {
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) => {
e.preventDefault()
_paq.push(['trackEvent', 'hometab', 'filesSection', 'loadRecentWorkspace'])
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 (
<>
<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} />
<div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection">
<div className="mb-3">
<div className="justify-content-start p-2 d-flex flex-column" id="hTFileSection">
<div className="mb-1">
{(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
<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
</label>
{state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && (
@ -270,58 +161,59 @@ contract HelloWorld {
</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' }}>
<FormattedMessage id="home.files" />
</label>
<div className="d-flex flex-column">
<div className="d-flex flex-row">
<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" data-id="homeTabNewFile" style={{ width: 'fit-content' }} onClick={async () => await plugin.call('filePanel', 'createNewFile')}>
<div className="d-flex flex-row flex-wrap">
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.newFileTooltip" />} 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 () => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'newFile'])
await plugin.call('menuicons', 'select', 'filePanel')
await plugin.call('filePanel', 'createNewFile')
}}>
<i className="far fa-file pl-1 pr-2"></i>
<FormattedMessage id="home.newFile" />
</button>
</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">
<span>
<label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<label className="btn text-nowrap p-2 mr-2 border my-1 mb-2" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<i className="far fa-upload pl-1 pr-2"></i>
<FormattedMessage id="home.openFile" />
</label>
<input
title="open file"
type="file"
id="openFileInput"
onChange={(event) => {
onChange={async (event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
await plugin.call('menuicons', '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 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'])}>
Git Clone
</button>
<button className="btn text-nowrap p-2 mr-2 border my-1" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>
<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
className="btn text-nowrap p-2 mr-2 border my-1"
onClick={() =>
showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])
}
</CustomTooltip>
<CustomTooltip placement={'top'} tooltipId="overlay-tooltip" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="home.gitCloneTooltip" />} tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
HTTPS
<button className="btn text-nowrap p-2 mr-2 border my-1 mb-2" data-id="landingPageImportFromGitHubButton" onClick={async () => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'Git Clone'])
await plugin.call('filePanel', 'clone')
}}>
<i className="fa-brands fa-github-alt pl-1 pr-2"></i>
Git Clone
</button>
</div>
</div>
<div className="d-flex mt-2 align-items-end w-100">
</CustomTooltip>
<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>
<FormattedMessage id="home.accessFileSystem" />
</button>

@ -32,24 +32,17 @@ const workspaceTemplates: WorkspaceTemplate[] = [
{
gsID: 'sUTLogo',
workspaceTitle: 'Start Coding',
description: 'Create a new project using this template.',
description: 'Start coding using the default template.',
projectLogo: 'assets/img/remixverticaltextLogo.png',
templateName: 'remixDefault',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'Circom',
workspaceTitle: 'ZK Semaphore',
description: 'Create a new ZK Project with Circom using this template.',
projectLogo: 'assets/img/circom.webp',
templateName: 'semaphore',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'Uniswap',
description: 'Create a new MultiSig wallet using this template.',
projectLogo: 'assets/img/gnosissafeLogo.png',
templateName: 'uniswapV4Template',
},
{
gsID: 'sUTLogo',
workspaceTitle: 'ERC20',
@ -57,6 +50,13 @@ const workspaceTemplates: WorkspaceTemplate[] = [
projectLogo: 'assets/img/oxprojectLogo.png',
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',
workspaceTitle: 'NFT / ERC721',
@ -157,12 +157,12 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
<div ref={carouselRefDiv} className="w-100 d-flex flex-column pt-1">
<ThemeContext.Provider value={themeFilter}>
<div className="pt-3">
<div className="d-flex flex-row align-items-center mb-3 flex-nowrap">
{workspaceTemplates.slice(0, 3).map((template, index) => (
<div className="d-flex flex-row align-items-center mb-3 flex-wrap">
{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}`}>
<button
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) => {
createWorkspace(template.templateName)
}}
@ -173,22 +173,6 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
</CustomTooltip>
))}
</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>
</ThemeContext.Provider>
</div>

@ -126,7 +126,7 @@ function HomeTabTitle() {
openLink(button.urlLink)
_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>
</CustomTooltip>
))}

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

@ -73,10 +73,10 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
// border-right
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}>
<div className="d-flex flex-row w-100 h-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="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: 'inherit' }}>
<HomeTabTitle />
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
{!(platform === appPlatformTypes.desktop) ?

@ -721,11 +721,11 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
Slither
{slitherWarnings.length > 0 ? (
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 ? (
<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}
</span>

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

@ -35,9 +35,9 @@ export default function AIStatus(props: AIStatusProps) {
<CustomTooltip
tooltipText={copilotActive ? "Remix Copilot activated" : "Remix Copilot disabled."}
>
<div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center">
<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 ? "small mx-1 text-danger semi-bold" : "small mx-1 semi-bold" }>Remix Copilot</span>
<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-white" : "fa-regular fa-microchip-ai ml-1"}></span>
<span className={copilotActive === false ? "small mx-1 text-white semi-bold" : "small mx-1 semi-bold" }>Remix Copilot</span>
</div>
</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
import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar'
import '../../css/statusbar.css'
@ -11,10 +11,12 @@ export interface GitStatusProps {
}
export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: GitStatusProps) {
const [isLocalHost, setIsLocalHost] = useState(false)
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', async (workspace) => {
const isGit = await plugin.call('fileManager', 'isGitRepo')
setIsLocalHost(workspace.isLocalhost)
if (isGit) {
setGitBranchName(workspace.name)
} else {
@ -37,6 +39,7 @@ export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: G
setGitBranchName(workspace)
}
})
}, [])
const lightDgitUp = async () => {
@ -51,10 +54,14 @@ export default function GitStatus({ plugin, gitBranchName, setGitBranchName }: G
const initializeNewGitRepo = async () => {
await plugin.call('dGitProvider', 'init')
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (isLocalHost === false) {
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
// plugin.verticalIcons.select('dgit')
}
}
const checkBranchName = ()=> {
return gitBranchName && gitBranchName !== 'Not a git repo' && gitBranchName.length > 0
}
return (
<CustomTooltip
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"
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>}
{gitBranchName.length > 0 && gitBranchName !== 'Not a git repo' && <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="ml-1">{gitBranchName}</span>}
{checkBranchName() && isLocalHost === false && <span className="fa-solid fa-arrows-rotate fa-1 ml-1"></span>}
</div>
</CustomTooltip>
)

@ -15,7 +15,7 @@ export default function ScamAlertStatus ({ refs, getReferenceProps }: ScamAlertS
<CustomTooltip
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="text-white font-semibold small">
<FormattedMessage id="home.scamAlert" />

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

@ -1,5 +1,5 @@
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 { Placement } from 'react-bootstrap/esm/Overlay'
import { FileExplorerMenuProps } from '../types'
@ -39,6 +39,20 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
icon: 'far fa-folder-upload',
placement: 'top',
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(
(item) =>
@ -49,6 +63,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
),
actions: {}
})
const enableDirUpload = { directory: '', webkitdirectory: '' }
return (
@ -143,6 +158,12 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
props.createNewFolder()
} else if (action === 'publishToGist' || action == 'updateGist') {
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 {
state.actions[action]()
}

@ -383,6 +383,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
publishToGist={publishToGist}
uploadFile={uploadFile}
uploadFolder={uploadFolder}
importFromIpfs={props.importFromIpfs}
importFromHttps={props.importFromHttps}
/>
</div>
</span>
@ -412,4 +414,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
)
}
export const MessageWrapper = () => {
return (
<p>e.g ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH</p>
)
}
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 { Dropdown } from 'react-bootstrap'
import { CustomIconsToggle, CustomMenu, CustomToggle, CustomTooltip, extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CopyToClipboard } from '@remix-ui/clipboard'
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 './css/remix-ui-workspace.css'
import { ROOT_PATH, TEMPLATE_NAMES } from './utils/constants'
@ -31,6 +32,7 @@ export function Workspace() {
const [showDropdown, setShowDropdown] = useState<boolean>(false)
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false)
const [showBranches, setShowBranches] = useState<boolean>(false)
const [highlightUpdateSubmodules, setHighlightUpdateSubmodules] = useState<boolean>(false)
const [branchFilter, setBranchFilter] = useState<string>('')
const displayOzCustomRef = useRef<HTMLDivElement>()
const mintableCheckboxRef = useRef()
@ -105,6 +107,136 @@ export function Workspace() {
}
}, [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(() => {
let workspaceName = localStorage.getItem('currentWorkspace')
if (!workspaceName && global.fs.browser.workspaces.length) {
@ -124,6 +256,10 @@ export function Workspace() {
cloneGitRepository()
}
}
global.plugin.on('dGitProvider', 'repositoryWithSubmodulesCloned', () => {
setHighlightUpdateSubmodules(true)
})
}, [])
useEffect(() => {
@ -678,6 +814,7 @@ export function Workspace() {
const updateSubModules = async () => {
try {
setHighlightUpdateSubmodules(false)
await global.dispatchUpdateGitSubmodules()
} catch (e) {
console.error(e)
@ -1081,7 +1218,8 @@ export function Workspace() {
<FileExplorer
fileState={global.fs.browser.fileState}
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}
removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems}
files={global.fs.browser.files}
@ -1133,6 +1271,8 @@ export function Workspace() {
createNewFolder={handleNewFolderInput}
deletePath={deletePath}
renamePath={editModeOn}
importFromIpfs={importFromUrl}
importFromHttps={importFromUrl}
/>
)}
@ -1197,6 +1337,8 @@ export function Workspace() {
deletePath={deletePath}
renamePath={editModeOn}
dragStatus={dragStatus}
importFromIpfs={importFromUrl}
importFromHttps={importFromUrl}
/>
)}
</div>
@ -1206,11 +1348,11 @@ export function Workspace() {
{selectedWorkspace && (
<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="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?
<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> :
<div style={{ height: 30 }} onClick={updateSubModules} data-id='updatesubmodules' className='btn btn-sm border text-muted small'>update 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, minWidth: 165 }} onClick={updateSubModules} data-id='updatesubmodules' className={`btn btn-sm border small ${highlightUpdateSubmodules ? 'text-warning' : 'text-muted'}`}>update submodules</div>}
</div>
: null}
<div className="pt-1 mr-1" data-id="workspaceGitBranchesDropdown">
@ -1329,6 +1471,38 @@ export function Workspace() {
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>
)
}

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

Loading…
Cancel
Save