Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/remix-ide-e2e/src/tests/gist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ module.exports = {
.setValue('*[data-id="modalDialogCustomPromptText"]', testData.invalidGistId)
.modalFooterOKClick()
.waitForElementVisible('*[data-id="modalDialogModalBody"]')
.assert.containsText('*[data-id="modalDialogModalBody"]', 'Gist load error: Not Found')
.assert.containsText('*[data-id="modalDialogModalBody"]', 'Not Found')
.modalFooterOKClick()
},

Expand Down
1 change: 1 addition & 0 deletions apps/remix-ide-e2e/src/tests/solidityImport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
.addFile('Untitled2.sol', sources[1]['Untitled2.sol'])
.openFile('Untitled1.sol')
.verifyContracts(['test6', 'test4', 'test5'])
.pause(1000)
},

'Test Failed Import': function (browser: NightwatchBrowser) {
Expand Down
23 changes: 23 additions & 0 deletions apps/remix-ide-e2e/src/tests/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ module.exports = {
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
},

'Should rename a workspace': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspaceRename"]') // rename workspace_name
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextRename"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextRename"]')['value'] = 'workspace_name_renamed' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
},

'Should delete a workspace': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.click('*[data-id="workspaceDelete"]') // delete workspace_name_1
.waitForElementVisible('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementNotPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.end()
},

Expand Down
14 changes: 12 additions & 2 deletions apps/remix-ide/src/app/files/fileManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@ class FileManager extends Plugin {
if (file.startsWith('browser')) {
return this._deps.filesProviders.browser
}
const provider = this._deps.filesProviders.workspace
if (!provider.isReady()) throw createError({ code: 'ECONNRESET', message: 'No workspace has been opened.' })
return this._deps.filesProviders.workspace
}

Expand Down Expand Up @@ -579,7 +581,11 @@ class FileManager extends Plugin {

async.each(Object.keys(filesSet), (file, callback) => {
if (override) {
self._deps.filesProviders[fileProvider].set(file, filesSet[file].content)
try {
self._deps.filesProviders[fileProvider].set(file, filesSet[file].content)
} catch (e) {
return callback(e.message || e)
}
self.syncEditor(fileProvider + file)
return callback()
}
Expand All @@ -591,7 +597,11 @@ class FileManager extends Plugin {
} else if (helper.checkSpecialChars(name)) {
modalDialogCustom.alert('Special characters are not allowed')
} else {
self._deps.filesProviders[fileProvider].set(name, filesSet[file].content)
try {
self._deps.filesProviders[fileProvider].set(name, filesSet[file].content)
} catch (e) {
return callback(e.message || e)
}
self.syncEditor(fileProvider + name)
}
callback()
Expand Down
12 changes: 12 additions & 0 deletions apps/remix-ide/src/app/files/workspaceFileProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,28 @@ class WorkspaceFileProvider extends FileProvider {
constructor () {
super('')
this.workspacesPath = '.workspaces'
this.workspace = null
}

setWorkspace (workspace) {
workspace = workspace.replace(/^\/|\/$/g, '') // remove first and last slash
this.workspace = workspace
}

getWorkspace () {
return this.workspace
}

isReady () {
return this.workspace !== null
}

clearWorkspace () {
this.workspace = null
}

removePrefix (path) {
if (!this.workspace) throw new Error('No workspace has been opened.')
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
if (path.startsWith(this.workspace)) return this.workspacesPath + '/' + this.workspace
Expand All @@ -27,6 +37,7 @@ class WorkspaceFileProvider extends FileProvider {
}

resolveDirectory (path, callback) {
if (!this.workspace) throw new Error('No workspace has been opened.')
super.resolveDirectory(path, (error, files) => {
if (error) return callback(error)
const unscoped = {}
Expand All @@ -38,6 +49,7 @@ class WorkspaceFileProvider extends FileProvider {
}

_normalizePath (path) {
if (!this.workspace) throw new Error('No workspace has been opened.')
return path.replace(this.workspacesPath + '/' + this.workspace + '/', '')
}
}
Expand Down
13 changes: 13 additions & 0 deletions apps/remix-ide/src/app/panels/file-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { Workspace } from '@remix-ui/workspace' // eslint-disable-line
import * as ethutil from 'ethereumjs-util'
import { checkSpecialChars, checkSlash } from '../../lib/helper'
var EventManager = require('../../lib/events')
var { RemixdHandle } = require('../files/remixd-handle.js')
var { GitHandle } = require('../files/git-handle.js')
Expand Down Expand Up @@ -74,6 +75,7 @@ module.exports = class Filepanel extends ViewPlugin {
ReactDOM.render(
<Workspace
createWorkspace={this.createWorkspace.bind(this)}
renameWorkspace={this.renameWorkspace.bind(this)}
setWorkspace={this.setWorkspace.bind(this)}
workspaceRenamed={this.workspaceRenamed.bind(this)}
workspaceDeleted={this.workspaceDeleted.bind(this)}
Expand Down Expand Up @@ -186,6 +188,8 @@ module.exports = class Filepanel extends ViewPlugin {
}

async createWorkspace (workspaceName) {
if (!workspaceName) throw new Error('name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
if (await this.workspaceExists(workspaceName)) throw new Error('workspace already exists')
const browserProvider = this._deps.fileProviders.browser
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
Expand All @@ -199,6 +203,15 @@ module.exports = class Filepanel extends ViewPlugin {
}
}

async renameWorkspace (oldName, workspaceName) {
if (!workspaceName) throw new Error('name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
if (await this.workspaceExists(workspaceName)) throw new Error('workspace already exists')
const browserProvider = this._deps.fileProviders.browser
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
}

/** these are called by the react component, action is already finished whent it's called */
async setWorkspace (workspace) {
this._deps.fileManager.removeTabsOf(this._deps.fileProviders.workspace)
Expand Down
13 changes: 9 additions & 4 deletions apps/remix-ide/src/app/ui/landing-page/landing-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,17 +240,22 @@ export class LandingPage extends ViewPlugin {
<div>e.g ${examples.map((url) => { return yo`<div class="p-1"><a>${url}</a></div>` })}</div>
</div>`

modalDialogCustom.prompt(`Import from ${service}`, msg, null, (target) => {
const title = `Import from ${service}`
modalDialogCustom.prompt(title, msg, null, (target) => {
if (target !== '') {
compilerImport.import(
target,
(loadingMsg) => { tooltip(loadingMsg) },
(error, content, cleanUrl, type, url) => {
if (error) {
modalDialogCustom.alert(error)
modalDialogCustom.alert(title, error.message || error)
} else {
fileProviders.browser.addExternal(type + '/' + cleanUrl, content, url)
this.verticalIcons.select('fileExplorers')
try {
fileProviders.workspace.addExternal(type + '/' + cleanUrl, content, url)
this.verticalIcons.select('fileExplorers')
} catch (e) {
modalDialogCustom.alert(title, e.message)
}
}
}
)
Expand Down
2 changes: 1 addition & 1 deletion apps/remix-ide/src/app/ui/modal-dialog-custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var css = require('./styles/modal-dialog-custom-styles')
module.exports = {
alert: function (title, text) {
if (text) return modal(title, yo`<div>${text}</div>`, null, { label: null })
return modal('', yo`<div>${title}</div>`, null, { label: null })
return modal('Alert', yo`<div>${title}</div>`, null, { label: null })
},
prompt: function (title, text, inputValue, ok, cancel, focus) {
return prompt(title, text, false, inputValue, ok, cancel, focus)
Expand Down
6 changes: 4 additions & 2 deletions apps/remix-ide/src/lib/gist-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function GistHandler (_window) {
if (gistId) {
cb(gistId)
} else {
modalDialogCustom.alert('Error while loading gist. Please provide a valid Gist ID or URL.')
modalDialogCustom.alert('Gist load error', 'Error while loading gist. Please provide a valid Gist ID or URL.')
}
}
})
Expand Down Expand Up @@ -49,7 +49,7 @@ function GistHandler (_window) {
json: true
}, async (error, response, data = {}) => {
if (error || !data.files) {
modalDialogCustom.alert(`Gist load error: ${error || data.message}`)
modalDialogCustom.alert('Gist load error', error || data.message)
return
}
const obj = {}
Expand All @@ -60,6 +60,8 @@ function GistHandler (_window) {
if (!errorLoadingFile) {
const provider = fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile)
}
})
})
Expand Down
3 changes: 3 additions & 0 deletions apps/remix-ide/src/lib/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ module.exports = {
checkSpecialChars (name) {
return name.match(/[:*?"<>\\'|]/) != null
},
checkSlash (name) {
return name.match(/\//) != null
},
isHexadecimal (value) {
return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0)
},
Expand Down
4 changes: 2 additions & 2 deletions libs/remix-ui/file-explorer/src/lib/file-explorer-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
},
{
action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
title: 'Publish all the current workspace files (only root) to a github gist',
icon: 'fab fa-github'
},
{
action: 'uploadFile',
title: 'Load a local file into Remix\'s browser folder',
title: 'Load a local file into current workspace',
icon: 'fa fa-upload'
},
{
Expand Down
37 changes: 21 additions & 16 deletions libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
export interface WorkspaceProps {
setWorkspace: ({ name: string, isLocalhost: boolean }) => void,
createWorkspace: (name: string) => void,
renameWorkspace: (oldName: string, newName: string) => void
workspaceRenamed: ({ name: string }) => void,
workspaceCreated: ({ name: string }) => void,
workspaceDeleted: ({ name: string }) => void,
Expand Down Expand Up @@ -142,7 +143,7 @@ export const Workspace = (props: WorkspaceProps) => {
}

const deleteCurrentWorkspace = () => {
modal('Remove Workspace', 'Please choose a name for the workspace', {
modal('Remove Workspace', 'Are you sure to delete the current workspace?', {
label: 'OK',
fn: onFinishDeleteWorkspace
}, {
Expand All @@ -152,13 +153,12 @@ export const Workspace = (props: WorkspaceProps) => {
}

const modalMessage = (title: string, body: string) => {
modal(title, body, {
label: 'OK',
fn: () => {}
}, {
label: null,
fn: null
})
setTimeout(() => { // wait for any previous modal a chance to close
modal(title, body, {
label: 'OK',
fn: () => {}
}, null)
}, 200)
}

const workspaceRenameInput = useRef()
Expand All @@ -168,10 +168,15 @@ export const Workspace = (props: WorkspaceProps) => {
if (workspaceRenameInput.current === undefined) return
// @ts-ignore: Object is possibly 'null'.
const workspaceName = workspaceRenameInput.current.value
const workspacesPath = props.workspace.workspacesPath
await props.fileManager.rename('browser/' + workspacesPath + '/' + state.currentWorkspace, 'browser/' + workspacesPath + '/' + workspaceName)
setWorkspace(workspaceName)
props.workspaceRenamed({ name: state.currentWorkspace })

try {
await props.renameWorkspace(state.currentWorkspace, workspaceName)
setWorkspace(workspaceName)
props.workspaceRenamed({ name: workspaceName })
} catch (e) {
modalMessage('Rename Workspace', e.message)
console.error(e)
}
}

const onFinishCreateWorkspace = async () => {
Expand All @@ -181,11 +186,11 @@ export const Workspace = (props: WorkspaceProps) => {

try {
await props.createWorkspace(workspaceName)
await setWorkspace(workspaceName)
} catch (e) {
modalMessage('Workspace Creation', e.message)
modalMessage('Create Workspace', e.message)
console.error(e)
}
await setWorkspace(workspaceName)
}

const onFinishDeleteWorkspace = async () => {
Expand Down Expand Up @@ -261,7 +266,7 @@ export const Workspace = (props: WorkspaceProps) => {
return (
<>
<span>{ state.modal.message }</span>
<input type="text" data-id="modalDialogCustomPromptTextCreate" placeholder={`workspace_${Date.now()}`} ref={workspaceCreateInput} className="form-control" />
<input type="text" data-id="modalDialogCustomPromptTextCreate" defaultValue={`workspace_${Date.now()}`} ref={workspaceCreateInput} className="form-control" />
</>
)
}
Expand All @@ -270,7 +275,7 @@ export const Workspace = (props: WorkspaceProps) => {
return (
<>
<span>{ state.modal.message }</span>
<input type="text" data-id="modalDialogCustomPromptTextRename" placeholder={ state.currentWorkspace } ref={workspaceRenameInput} className="form-control" />
<input type="text" data-id="modalDialogCustomPromptTextRename" defaultValue={ state.currentWorkspace } ref={workspaceRenameInput} className="form-control" />
</>
)
}
Expand Down