diff --git a/apps/remix-ide-e2e/src/tests/solidityImport.test.ts b/apps/remix-ide-e2e/src/tests/solidityImport.test.ts index afbf1fe84f..ab4e4331b1 100644 --- a/apps/remix-ide-e2e/src/tests/solidityImport.test.ts +++ b/apps/remix-ide-e2e/src/tests/solidityImport.test.ts @@ -84,12 +84,12 @@ module.exports = { 'Test NPM Import (with unpkg.com)': function (browser: NightwatchBrowser) { browser - // .setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') + .setSolidityCompilerVersion('soljson-v0.8.1+commit.df193b15.js') .clickLaunchIcon('fileExplorers') .click('li[data-id="treeViewLitreeViewItemREADME.txt"') .addFile('Untitled9.sol', sources[8]['Untitled9.sol']) .clickLaunchIcon('fileExplorers') - .verifyContracts(['test13', 'ERC20', 'SafeMath'], { wait: 30000 }) + .verifyContracts(['test13', 'ERC20'], { wait: 30000 }) .end() }, tearDown: sauce @@ -122,6 +122,6 @@ const sources = [ 'Untitled8.sol': { content: 'import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"; contract test12 {}' } }, { - 'Untitled9.sol': { content: 'pragma solidity >=0.6.0 <0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract test13 {}' } + 'Untitled9.sol': { content: 'pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract test13 {}' } } ] diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 5e8b7ac863..829a635104 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -18,7 +18,7 @@ import { LandingPage } from './app/ui/landing-page/landing-page' import { MainPanel } from './app/components/main-panel' import FetchAndCompile from './app/compiler/compiler-sourceVerifier-fetchAndCompile' -import migrateFileSystem, { migrateToWorkspace } from './migrateFileSystem' +import migrateFileSystem from './migrateFileSystem' const isElectron = require('is-electron') const csjs = require('csjs-inject') @@ -325,11 +325,11 @@ Please make a backup of your contracts and start using http://remix.ethereum.org // those views depend on app_manager const menuicons = new VerticalIcons(appManager) - const landingPage = new LandingPage(appManager, menuicons) const sidePanel = new SidePanel(appManager, menuicons) const hiddenPanel = new HiddenPanel() const pluginManagerComponent = new PluginManagerComponent(appManager, engine) const filePanel = new FilePanel(appManager) + const landingPage = new LandingPage(appManager, menuicons, fileManager, filePanel) const settings = new SettingsTab( registry.get('config').api, editor, @@ -494,7 +494,5 @@ Please make a backup of your contracts and start using http://remix.ethereum.org // get the file list from the parent iframe loadFileFromParent(fileManager) - migrateToWorkspace(fileManager, filePanel) - if (params.embed) framingService.embed() } diff --git a/apps/remix-ide/src/app/files/fileProvider.js b/apps/remix-ide/src/app/files/fileProvider.js index 20a7135c12..544a1b0bab 100644 --- a/apps/remix-ide/src/app/files/fileProvider.js +++ b/apps/remix-ide/src/app/files/fileProvider.js @@ -199,21 +199,24 @@ class FileProvider { * copy the folder recursively (internal use) * @param {string} path is the folder to be copied over * @param {Function} visitFile is a function called for each visited files + * @param {Function} visitFolder is a function called for each visited folders */ - _copyFolderToJsonInternal (path, visitFile) { + _copyFolderToJsonInternal (path, visitFile, visitFolder) { visitFile = visitFile || (() => {}) + visitFolder = visitFolder || (() => {}) return new Promise((resolve, reject) => { const json = {} path = this.removePrefix(path) if (window.remixFileSystem.existsSync(path)) { try { const items = window.remixFileSystem.readdirSync(path) + visitFolder({ path }) if (items.length !== 0) { items.forEach(async (item, index) => { const file = {} const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}` if (window.remixFileSystem.statSync(curPath).isDirectory()) { - file.children = await this._copyFolderToJsonInternal(curPath, visitFile) + file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder) } else { file.content = window.remixFileSystem.readFileSync(curPath, 'utf8') visitFile({ path: curPath, content: file.content }) @@ -234,10 +237,12 @@ class FileProvider { * copy the folder recursively * @param {string} path is the folder to be copied over * @param {Function} visitFile is a function called for each visited files + * @param {Function} visitFolder is a function called for each visited folders */ - copyFolderToJson (path, visitFile) { + copyFolderToJson (path, visitFile, visitFolder) { visitFile = visitFile || (() => {}) - return this._copyFolderToJsonInternal(path, visitFile) + visitFolder = visitFolder || (() => {}) + return this._copyFolderToJsonInternal(path, visitFile, visitFolder) } removeFile (path) { diff --git a/apps/remix-ide/src/app/files/workspaceFileProvider.js b/apps/remix-ide/src/app/files/workspaceFileProvider.js index 2b1a795d0b..fe4e6825cb 100644 --- a/apps/remix-ide/src/app/files/workspaceFileProvider.js +++ b/apps/remix-ide/src/app/files/workspaceFileProvider.js @@ -48,11 +48,14 @@ class WorkspaceFileProvider extends FileProvider { }) } - async copyFolderToJson (directory, visitFile) { + async copyFolderToJson (directory, visitFile, visitFolder) { visitFile = visitFile || (() => {}) + visitFolder = visitFolder || (() => {}) const regex = new RegExp(`.workspaces/${this.workspace}/`, 'g') let json = await super._copyFolderToJsonInternal(directory, ({ path, content }) => { visitFile({ path: path.replace(regex, ''), content }) + }, ({ path }) => { + visitFolder({ path: path.replace(regex, '') }) }) json = JSON.stringify(json).replace(regex, '') return JSON.parse(json) diff --git a/apps/remix-ide/src/app/ui/landing-page/landing-page.js b/apps/remix-ide/src/app/ui/landing-page/landing-page.js index bf89d166d8..844778ac82 100644 --- a/apps/remix-ide/src/app/ui/landing-page/landing-page.js +++ b/apps/remix-ide/src/app/ui/landing-page/landing-page.js @@ -1,11 +1,14 @@ import * as packageJson from '../../../../../../package.json' import { ViewPlugin } from '@remixproject/engine-web' +import { migrateToWorkspace } from '../../../migrateFileSystem' +import JSZip from 'jszip' const yo = require('yo-yo') const csjs = require('csjs-inject') const globalRegistry = require('../../../global/registry') const CompilerImport = require('../../compiler/compiler-imports') const modalDialogCustom = require('../modal-dialog-custom') +const modalDialog = require('../modaldialog') const tooltip = require('../tooltip') const GistHandler = require('../../../lib/gist-handler') const QueryParams = require('../../../lib/query-params.js') @@ -112,9 +115,11 @@ const profile = { } export class LandingPage extends ViewPlugin { - constructor (appManager, verticalIcons) { + constructor (appManager, verticalIcons, fileManager, filePanel) { super(profile) this.profile = profile + this.fileManager = fileManager + this.filePanel = filePanel this.appManager = appManager this.verticalIcons = verticalIcons this.gistHandler = new GistHandler() @@ -297,6 +302,44 @@ export class LandingPage extends ViewPlugin { this.call('fileExplorers', 'createNewFile') } + const saveAs = (blob, name) => { + const node = document.createElement('a') + node.download = name + node.rel = 'noopener' + node.href = URL.createObjectURL(blob) + setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s + setTimeout(function () { + try { + node.dispatchEvent(new MouseEvent('click')) + } catch (e) { + var evt = document.createEvent('MouseEvents') + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, + 20, false, false, false, false, 0, null) + node.dispatchEvent(evt) + } + }, 0) // 40s + } + + const downloadFiles = async () => { + try { + tooltip('preparing files, please wait..') + const fileProviders = globalRegistry.get('fileproviders').api + const zip = new JSZip() + await fileProviders.browser.copyFolderToJson('/', ({ path, content }) => { + zip.file(path, content) + }, ({ path, content }) => { + zip.folder(path, content) + }) + zip.generateAsync({ type: 'blob' }).then(function (blob) { + saveAs(blob, 'remixdbackup.zip') + }).catch((e) => { + tooltip(e.message) + }) + } catch (e) { + tooltip(e.message) + } + } + const uploadFile = (target) => { this.call('fileExplorers', 'uploadFile', target) } @@ -357,6 +400,37 @@ export class LandingPage extends ViewPlugin { query.update({ appVersion: '0.7.7' }) document.location.reload() } + + const migrate = async () => { + tooltip('migrating workspace...') + try { + const workspaceName = await migrateToWorkspace(this.fileManager, this.filePanel) + tooltip('done. ' + workspaceName + ' created.') + } catch (e) { + return tooltip(e.message) + } + } + + const migrateWorkspace = async () => { + modalDialog( + 'File system Migration', + yo`Do you want to download your files to local device first?`, + { + label: 'Download and Migrate', + fn: async () => { + await downloadFiles() + migrate() + } + }, + { + label: 'Migrate', + fn: () => { + migrate() + } + } + ) + } + const img = yo`` const playRemi = async () => { await document.getElementById('remiAudio').play() } // to retrieve medium posts @@ -408,6 +482,10 @@ export class LandingPage extends ViewPlugin { connectToLocalhost()}>Connect to Localhost

+

+ + downloadFiles()}>Download all Files +

@@ -431,10 +509,14 @@ export class LandingPage extends ViewPlugin { ${this.websiteIcon} Featuring website

-

+

switchToPreviousVersion()}>Old experience

+

+ + migrateWorkspace()}>Migrate old filesystem to workspace +

diff --git a/apps/remix-ide/src/migrateFileSystem.js b/apps/remix-ide/src/migrateFileSystem.js index 0a64ba5b29..797d581eaa 100644 --- a/apps/remix-ide/src/migrateFileSystem.js +++ b/apps/remix-ide/src/migrateFileSystem.js @@ -24,19 +24,27 @@ export default (fileProvider) => { export async function migrateToWorkspace (fileManager, filePanel) { const browserProvider = fileManager.getProvider('browser') const workspaceProvider = fileManager.getProvider('workspace') - const flag = 'status' - const fileStorageBrowserWorkspace = new Storage('remix_browserWorkspace_migration:') - if (fileStorageBrowserWorkspace.get(flag) === 'done') return const files = await browserProvider.copyFolderToJson('/') console.log(files) - if (Object.keys(files).length > 0) { - const workspaceName = 'default_workspace' - const workspacePath = joinPath('browser', workspaceProvider.workspacesPath, workspaceName) - await filePanel.processCreateWorkspace(workspaceName) - filePanel.getWorkspaces() // refresh list - await populateWorkspace(workspacePath, files, browserProvider) + + if (Object.keys(files).length === 0) { + // we don't have any root file, only .workspaces + // don't need to create a workspace + throw new Error('No file to migrate') } - fileStorageBrowserWorkspace.set(flag, 'done') + + if (Object.keys(files).length === 1 && files['/.workspaces']) { + // we don't have any root file, only .workspaces + // don't need to create a workspace + throw new Error('No file to migrate') + } + + const workspaceName = 'workspace_migrated_' + Date.now() + await filePanel.processCreateWorkspace(workspaceName) + filePanel.getWorkspaces() // refresh list + const workspacePath = joinPath('browser', workspaceProvider.workspacesPath, workspaceName) + await populateWorkspace(workspacePath, files, browserProvider) + return workspaceName } const populateWorkspace = async (workspace, json, browserProvider) => { diff --git a/package-lock.json b/package-lock.json index abfa63097f..ee3b44b89c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24165,6 +24165,51 @@ "object.assign": "^4.1.0" } }, + "jszip": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", @@ -24722,6 +24767,21 @@ "webpack-sources": "^1.2.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + }, + "dependencies": { + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + } + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -33929,8 +33989,7 @@ "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parallel-transform": { "version": "1.2.0", @@ -37502,6 +37561,11 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", diff --git a/package.json b/package.json index 77d6b9e62e..f7d5243a2e 100644 --- a/package.json +++ b/package.json @@ -153,6 +153,7 @@ "fs-extra": "^3.0.1", "http-server": "^0.11.1", "isbinaryfile": "^3.0.2", + "jszip": "^3.6.0", "merge": "^1.2.0", "npm-install-version": "^6.0.2", "react": "16.13.1",