diff --git a/apps/remix-ide-e2e/src/tests/homeTab.test.ts b/apps/remix-ide-e2e/src/tests/homeTab.test.ts index 46faaef303..810ae1d2cd 100644 --- a/apps/remix-ide-e2e/src/tests/homeTab.test.ts +++ b/apps/remix-ide-e2e/src/tests/homeTab.test.ts @@ -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"]') diff --git a/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts b/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts index 5799955a1e..d2dbe89f21 100644 --- a/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts +++ b/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts @@ -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"') }) diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index fdbc8fb8b1..69e4cdc946 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -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) diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index d767019df4..18ec31d8c2 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -389,7 +389,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) @@ -516,7 +516,7 @@ class AppComponent { ) await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solcoder']) - await this.appManager.activatePlugin(['filePanel']) + await this.appManager.activatePlugin(['filePanel']) // Set workspace after initial activation this.appManager.on('editor', 'editorMounted', () => { diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index a709692452..02d233d5bb 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -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 diff --git a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json index 5f033c3aad..8159675a7e 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json +++ b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json @@ -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", diff --git a/apps/remix-ide/src/app/tabs/locales/en/home.json b/apps/remix-ide/src/app/tabs/locales/en/home.json index 9d78937c91..99389282e1 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/home.json +++ b/apps/remix-ide/src/app/tabs/locales/en/home.json @@ -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" } diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx index 03e787e7ef..bc5fb49f62 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx @@ -12,7 +12,7 @@ function HomeTabFeatured() { const themeFilter = useContext(ThemeContext) return ( -
+
@@ -109,7 +109,7 @@ function HomeTabFeatured() {
- +
diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx index 2e277f395e..fbd4190c5d 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx @@ -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; - -contract HelloWorld { - /** - * @dev Prints Hello World string - */ - function print() public pure returns (string memory) { - return "Hello World!"; - } -} + pragma solidity >=0.6.12 <0.9.0; + + 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,77 +127,21 @@ contract HelloWorld { plugin.verticalIcons.select('filePanel') } - const showFullMessage = (title: string, loadItem: string, examples: Array, 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) => ( -
- {urlEl} -
- )) - return ( <> - hideFullMessage()} okFn={() => processLoading(state.modalInfo.title)}> -
- {state.modalInfo.loadItem !== '' && Enter the {state.modalInfo.loadItem} you would like to load.} - {state.modalInfo.examples.length !== 0 && ( - <> -
e.g
-
{examples}
- - )} -
- {state.modalInfo.prefix && ipfs://} - { - setState((prevState) => { - return { ...prevState, importSource: inputValue.current.value } - }) - }} - /> -
-
-
-
-
+
+
{(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
-
)}
-
+
-
-
- } tooltipTextClasses="border bg-light text-dark p-1 pr-3"> - - - } tooltipTextClasses="border bg-light text-dark p-1 pr-3"> - - - { - event.stopPropagation() - plugin.verticalIcons.select('filePanel') - uploadFile(event.target) - }} - multiple - /> - - - - - - - -
-
-
+ } tooltipTextClasses="border bg-light text-dark p-1 pr-3"> - diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx index a986cebbfb..645e2f3346 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx @@ -30,24 +30,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', @@ -55,6 +48,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', @@ -149,12 +149,12 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {
-
- {workspaceTemplates.slice(0, 3).map((template, index) => ( +
+ {workspaceTemplates.map((template, index) => (
-
- {workspaceTemplates.slice(3, workspaceTemplates.length).map((template, index) => ( - - - - ))} -
diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx index 721efa5512..c6273a8545 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx @@ -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}`} > ))} diff --git a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css index efdb012c7d..b62c511f34 100644 --- a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css +++ b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css @@ -125,6 +125,12 @@ width: 20rem; } +.remixui_carouselImage_remixbeta { + flex: 1; + height: 20rem; + width: 18rem; +} + .remixui_carouselbox { min-height: 25.12rem; } diff --git a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx index fac0ec5bab..3153b7ac79 100644 --- a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx +++ b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx @@ -73,10 +73,10 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { // border-right return ( -
+
-
-
+
+
{!(platform === appPlatformTypes.desktop) ? diff --git a/libs/remix-ui/statusbar/src/css/statusbar.css b/libs/remix-ui/statusbar/src/css/statusbar.css index 15976801a2..5d2f461026 100644 --- a/libs/remix-ui/statusbar/src/css/statusbar.css +++ b/libs/remix-ui/statusbar/src/css/statusbar.css @@ -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; } diff --git a/libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx b/libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx index 5490d5f9d9..d96a8fdace 100644 --- a/libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx +++ b/libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx @@ -35,9 +35,9 @@ export default function AIStatus(props: AIStatusProps) { -
- - Remix Copilot +
+ + Remix Copilot
) diff --git a/libs/remix-ui/statusbar/src/lib/components/scamAlertStatus.tsx b/libs/remix-ui/statusbar/src/lib/components/scamAlertStatus.tsx index 2fe18b909f..aab3dd31c8 100644 --- a/libs/remix-ui/statusbar/src/lib/components/scamAlertStatus.tsx +++ b/libs/remix-ui/statusbar/src/lib/components/scamAlertStatus.tsx @@ -15,7 +15,7 @@ export default function ScamAlertStatus ({ refs, getReferenceProps }: ScamAlertS -
+
diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index 8b1f828e78..b4480e2019 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -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' diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx index ba158f7033..63d8c92024 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx @@ -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]() } diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index 9b90deb035..e3b301901c 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -383,6 +383,8 @@ export const FileExplorer = (props: FileExplorerProps) => { publishToGist={publishToGist} uploadFile={uploadFile} uploadFolder={uploadFolder} + importFromIpfs={props.importFromIpfs} + importFromHttps={props.importFromHttps} />
@@ -412,4 +414,10 @@ export const FileExplorer = (props: FileExplorerProps) => { ) } +export const MessageWrapper = () => { + return ( +

e.g ipfs://QmQQfBMkpDgmxKzYaoAtqfaybzfgGm9b2LWYyT56Chv6xH

+ ) +} + export default FileExplorer diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 85860d03a5..3546caa951 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -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' @@ -105,6 +106,136 @@ export function Workspace() { } }, [canPaste]) + const [modalState, setModalState] = useState<{ + searchInput: string + showModalDialog: boolean + // modalValidation?: ValidationResult + modalInfo: { + title: string + loadItem: string + examples: Array + prefix?: string + } + importSource: string + toasterMsg: string + }>({ + searchInput: '', + showModalDialog: false, + // modalValidation: {} as ValidationResult, + modalInfo: { title: '', loadItem: '', examples: [], prefix: '' }, + importSource: '', + toasterMsg: '' + }) + + const [validationResult, setValidationResult] = useState({ 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, 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) => ( +
+ {urlEl} +
+ )) + + 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, 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) { @@ -1086,7 +1217,8 @@ export function Workspace() { )} @@ -1202,6 +1336,8 @@ export function Workspace() { deletePath={deletePath} renamePath={editModeOn} dragStatus={dragStatus} + importFromIpfs={importFromUrl} + importFromHttps={importFromUrl} /> )}
@@ -1334,6 +1470,38 @@ export function Workspace() { downloadPath={downloadPath} /> )} + + hideFullMessage()} + okFn={() => processLoading(modalState.modalInfo.title)} validationFn={validateUrlForImport} + > +
+ {modalState.modalInfo.loadItem !== '' && Enter the {modalState.modalInfo.loadItem} you would like to load.} + {modalState.modalInfo.examples.length !== 0 && ( + <> +
e.g
+
{examples}
+ + )} +
+ {modalState.modalInfo.prefix && ipfs://} + { + setModalState((prevState) => { + return { ...prevState, importSource: inputValue.current.value } + }) + }} + /> +
+
+
) } diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index 7d812c75ec..1d8b515759 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -153,6 +153,8 @@ export interface FileExplorerProps { createNewFolder:(parentFolder?: string) => Promise renamePath:(path: string, type: string, isNew?: boolean) => void dragStatus: (status: boolean) => void + importFromIpfs: any + importFromHttps: any } export interface FileExplorerMenuProps { @@ -163,6 +165,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 {