From 97ccd2fbd6ab54741d38baea315dfb699d9f96a4 Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Wed, 14 Jul 2021 13:13:33 +0100 Subject: [PATCH 01/87] Extract props --- .../workspace/src/lib/remix-ui-workspace.tsx | 60 +++++++++++++------ 1 file changed, 41 insertions(+), 19 deletions(-) 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 ad8f1cc717..c61c826f68 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -7,27 +7,49 @@ import { MenuItems } from 'libs/remix-ui/file-explorer/src/lib/types' /* eslint-disable-next-line */ export interface WorkspaceProps { - setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void, - createWorkspace: (name: string) => void, - renameWorkspace: (oldName: string, newName: string) => void - workspaceRenamed: ({ name: string }) => void, - workspaceCreated: ({ name: string }) => void, - workspaceDeleted: ({ name: string }) => void, - workspace: any // workspace provider, - browser: any // browser provider - localhost: any // localhost provider - fileManager : any - registry: any // registry - plugin: any // plugin call and resetFocus - request: any // api request, - workspaces: any, - registeredMenuItems: MenuItems // menu items - removedMenuItems: MenuItems - initialWorkspace: string + plugin: { + setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void, + createWorkspace: (name: string) => void, + renameWorkspace: (oldName: string, newName: string) => void + workspaceRenamed: ({ name: string }) => void, + workspaceCreated: ({ name: string }) => void, + workspaceDeleted: ({ name: string }) => void, + workspace: any // workspace provider, + browser: any // browser provider + localhost: any // localhost provider + fileManager : any + registry: any // registry + request: any // api request, + workspaces: any, + registeredMenuItems: MenuItems // menu items + removedMenuItems: MenuItems + initialWorkspace: string + } } var canUpload = window.File || window.FileReader || window.FileList || window.Blob export const Workspace = (props: WorkspaceProps) => { + const { + plugin, + plugin: { + setWorkspace, + createWorkspace, + renameWorkspace, + workspaceRenamed, + workspaceCreated, + workspaceDeleted, + workspace, + browser, + localhost, + fileManager, + registry, + request, + workspaces, + registeredMenuItems, + removedMenuItems, + initialWorkspace + } + } = props const LOCALHOST = ' - connect to localhost - ' const NO_WORKSPACE = ' - none - ' @@ -409,7 +431,7 @@ export const Workspace = (props: WorkspaceProps) => { registry={props.registry} filesProvider={props.workspace} menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} - plugin={props.plugin} + plugin={plugin} focusRoot={state.reset} contextMenuItems={props.registeredMenuItems} removedContextMenuItems={props.removedMenuItems} @@ -427,7 +449,7 @@ export const Workspace = (props: WorkspaceProps) => { registry={props.registry} filesProvider={props.localhost} menuItems={['createNewFile', 'createNewFolder']} - plugin={props.plugin} + plugin={plugin} focusRoot={state.reset} contextMenuItems={props.registeredMenuItems} removedContextMenuItems={props.removedMenuItems} From 26db081037afa30d740845dcc3f6ae10fa46ffcd Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Wed, 14 Jul 2021 15:00:56 +0100 Subject: [PATCH 02/87] Move top-level functions --- apps/remix-ide/src/app/panels/file-panel.js | 14 ++++++++++++++ .../workspace/src/lib/remix-ui-workspace.tsx | 15 --------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index ea71d98c63..7b4b933d43 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -300,5 +300,19 @@ module.exports = class Filepanel extends ViewPlugin { workspaceCreated (workspace) { this.emit('createWorkspace', workspace) } + + resetFocus (reset) { + // setState(prevState => { + // return { ...prevState, reset } + // }) + } + + resetNewFile () { + // setState(prevState => { + // return { ...prevState, displayNewFile: !state.displayNewFile } + // }) + } + + resetUploadFile = () => {} /** end section */ } 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 c61c826f68..204ef3bdcc 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -53,21 +53,6 @@ export const Workspace = (props: WorkspaceProps) => { const LOCALHOST = ' - connect to localhost - ' const NO_WORKSPACE = ' - none - ' - /* extends the parent 'plugin' with some function needed by the file explorer */ - props.plugin.resetFocus = (reset) => { - setState(prevState => { - return { ...prevState, reset } - }) - } - - props.plugin.resetNewFile = () => { - setState(prevState => { - return { ...prevState, displayNewFile: !state.displayNewFile } - }) - } - - props.plugin.resetUploadFile = () => {} - /* implement an external API, consumed by the parent */ props.request.createWorkspace = () => { return createWorkspace() From 0881f25d574ddb4de8ed28c7aa08baa761d16dff Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Tue, 20 Jul 2021 03:57:48 +0100 Subject: [PATCH 03/87] Initialize workspace in reducer. --- apps/remix-ide/src/app/panels/file-panel.js | 14 -- .../workspace/src/lib/actions/workspace.ts | 112 ++++++++++++++ .../workspace/src/lib/reducers/workspace.ts | 30 ++++ .../workspace/src/lib/remix-ui-workspace.tsx | 137 +++++++++--------- 4 files changed, 211 insertions(+), 82 deletions(-) create mode 100644 libs/remix-ui/workspace/src/lib/actions/workspace.ts create mode 100644 libs/remix-ui/workspace/src/lib/reducers/workspace.ts diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 7b4b933d43..ea71d98c63 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -300,19 +300,5 @@ module.exports = class Filepanel extends ViewPlugin { workspaceCreated (workspace) { this.emit('createWorkspace', workspace) } - - resetFocus (reset) { - // setState(prevState => { - // return { ...prevState, reset } - // }) - } - - resetNewFile () { - // setState(prevState => { - // return { ...prevState, displayNewFile: !state.displayNewFile } - // }) - } - - resetUploadFile = () => {} /** end section */ } diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts new file mode 100644 index 0000000000..a09ec15ea8 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -0,0 +1,112 @@ +import { bufferToHex, keccakFromString } from 'ethereumjs-util' +import { checkSpecialChars, checkSlash } from '../../../../../../apps/remix-ide/src/lib/helper' + +const GistHandler = require('../../../../../../apps/remix-ide/src/lib/gist-handler') +const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params') +const examples = require('../../../../../../apps/remix-ide/src/app/editor/examples') +let plugin + +export const setCurrentWorkspace = (workspace: string) => { + return { + type: 'SET_CURRENT_WORKSPACE', + payload: workspace + } +} + +export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatch) => { + plugin = filePanelPlugin + const queryParams = new QueryParams() + const gistHandler = new GistHandler() + const params = queryParams.get() + // get the file from gist + let loadedFromGist = false + + if (params.gist) { + await processCreateWorkspace('gist-sample') + plugin.initialWorkspace = 'gist-sample' + loadedFromGist = gistHandler.loadFromGist(params, plugin.fileManager) + } + if (loadedFromGist) return + + if (params.code) { + try { + await processCreateWorkspace('code-sample') + const hash = bufferToHex(keccakFromString(params.code)) + const fileName = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' + const path = fileName + + await plugin.fileProviders.workspace.set(path, atob(params.code)) + plugin.initialWorkspace = 'code-sample' + await plugin.fileManager.openFile(fileName) + } catch (e) { + console.error(e) + } + return + } + + return new Promise((resolve, reject) => { + plugin.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { + if (error) return reject(error) + if (Object.keys(filesList).length === 0) { + await createWorkspace('default_workspace') + return resolve('default_workspace') + } else { + plugin.fileProviders.browser.resolveDirectory('.workspaces', async (error, filesList) => { + if (error) return reject(error) + if (Object.keys(filesList).length > 0) { + const workspacePath = Object.keys(filesList)[0].split('/').filter(val => val) + const workspaceName = workspacePath[workspacePath.length - 1] + + plugin.fileProviders.workspace.setWorkspace(workspaceName) + return resolve(workspaceName) + } + return reject(new Error('Can\'t find available workspace.')) + }) + } + }) + }) +} + +const processCreateWorkspace = async (name: string) => { + const workspaceProvider = plugin.fileProviders.workspace + const browserProvider = plugin.fileProviders.browser + const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name + const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath + const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath) + const workspacePathExists = await browserProvider.exists(workspacePath) + + if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath) + if (!workspacePathExists) browserProvider.createDir(workspacePath) + plugin.fileProviders.workspace.setWorkspace(name) +} + +const createWorkspace = async (workspaceName, setDefaults = true) => { + if (!workspaceName) throw new Error('name cannot be empty') + if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') + if (await workspaceExists(workspaceName)) throw new Error('workspace already exists') + else { + const workspaceProvider = plugin.fileProviders.workspace + + await processCreateWorkspace(workspaceName) + workspaceProvider.setWorkspace(workspaceName) + plugin.workspaceName = workspaceName + if (setDefaults) { + // insert example contracts if there are no files to show + for (const file in examples) { + try { + await workspaceProvider.set(examples[file].name, examples[file].content) + } catch (error) { + console.error(error) + } + } + } + } +} + +const workspaceExists = async (name) => { + const workspaceProvider = plugin.fileProviders.workspace + const browserProvider = plugin.fileProviders.browser + const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name + + return browserProvider.exists(workspacePath) +} diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts new file mode 100644 index 0000000000..bb376b64c5 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -0,0 +1,30 @@ +interface Action { + type: string + payload: Record | string +} + +export const browserInitialState = { + browser: { + currentWorkspace: '', + workspaces: [], + isRequesting: false, + isSuccessful: false, + error: null + } +} + +export const browserReducer = (state = browserInitialState, action: Action) => { + switch (action.type) { + case 'SET_CURRENT_WORKSPACE': { + return { + ...state, + browser: { + ...state.browser, + currentWorkspace: typeof action.payload === 'string' ? action.payload : '' + } + } + } + default: + throw new Error() + } +} 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 204ef3bdcc..a18d6426c5 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -1,58 +1,53 @@ -import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line +import React, { useState, useEffect, useRef, useReducer } from 'react' // eslint-disable-line import { FileExplorer } from '@remix-ui/file-explorer' // eslint-disable-line import './remix-ui-workspace.css' import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line import { Toaster } from '@remix-ui/toaster'// eslint-disable-line import { MenuItems } from 'libs/remix-ui/file-explorer/src/lib/types' +import { initWorkspace } from './actions/workspace' +import { browserReducer, browserInitialState } from './reducers/workspace' /* eslint-disable-next-line */ export interface WorkspaceProps { - plugin: { - setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void, - createWorkspace: (name: string) => void, - renameWorkspace: (oldName: string, newName: string) => void - workspaceRenamed: ({ name: string }) => void, - workspaceCreated: ({ name: string }) => void, - workspaceDeleted: ({ name: string }) => void, - workspace: any // workspace provider, - browser: any // browser provider - localhost: any // localhost provider - fileManager : any - registry: any // registry - request: any // api request, - workspaces: any, - registeredMenuItems: MenuItems // menu items - removedMenuItems: MenuItems - initialWorkspace: string - } + setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void, + createWorkspace: (name: string) => void, + renameWorkspace: (oldName: string, newName: string) => void + workspaceRenamed: ({ name: string }) => void, + workspaceCreated: ({ name: string }) => void, + workspaceDeleted: ({ name: string }) => void, + workspace: any // workspace provider, + browser: any // browser provider + localhost: any // localhost provider + fileManager : any + registry: any // registry + plugin: any // plugin call and resetFocus + request: any // api request, + workspaces: any, + registeredMenuItems: MenuItems // menu items + removedMenuItems: MenuItems + initialWorkspace: string } var canUpload = window.File || window.FileReader || window.FileList || window.Blob export const Workspace = (props: WorkspaceProps) => { - const { - plugin, - plugin: { - setWorkspace, - createWorkspace, - renameWorkspace, - workspaceRenamed, - workspaceCreated, - workspaceDeleted, - workspace, - browser, - localhost, - fileManager, - registry, - request, - workspaces, - registeredMenuItems, - removedMenuItems, - initialWorkspace - } - } = props const LOCALHOST = ' - connect to localhost - ' const NO_WORKSPACE = ' - none - ' + /* extends the parent 'plugin' with some function needed by the file explorer */ + props.plugin.resetFocus = (reset) => { + setState(prevState => { + return { ...prevState, reset } + }) + } + + props.plugin.resetNewFile = () => { + setState(prevState => { + return { ...prevState, displayNewFile: !state.displayNewFile } + }) + } + + props.plugin.resetUploadFile = () => {} + /* implement an external API, consumed by the parent */ props.request.createWorkspace = () => { return createWorkspace() @@ -112,32 +107,36 @@ export const Workspace = (props: WorkspaceProps) => { } } - useEffect(() => { - props.localhost.event.off('disconnected', localhostDisconnect) - props.localhost.event.on('disconnected', localhostDisconnect) - props.localhost.event.on('connected', () => { - remixdExplorer.show() - setWorkspace(LOCALHOST) - }) - - props.localhost.event.on('disconnected', () => { - remixdExplorer.hide() - }) - - props.localhost.event.on('loading', () => { - remixdExplorer.loading() - }) - - props.workspace.event.on('createWorkspace', (name) => { - createNewWorkspace(name) - }) + // useEffect(() => { + // props.localhost.event.off('disconnected', localhostDisconnect) + // props.localhost.event.on('disconnected', localhostDisconnect) + // props.localhost.event.on('connected', () => { + // remixdExplorer.show() + // setWorkspace(LOCALHOST) + // }) + + // props.localhost.event.on('disconnected', () => { + // remixdExplorer.hide() + // }) + + // props.localhost.event.on('loading', () => { + // remixdExplorer.loading() + // }) + + // props.workspace.event.on('createWorkspace', (name) => { + // createNewWorkspace(name) + // }) + + // if (props.initialWorkspace) { + // props.workspace.setWorkspace(props.initialWorkspace) + // setState(prevState => { + // return { ...prevState, currentWorkspace: props.initialWorkspace } + // }) + // } + // }, []) - if (props.initialWorkspace) { - props.workspace.setWorkspace(props.initialWorkspace) - setState(prevState => { - return { ...prevState, currentWorkspace: props.initialWorkspace } - }) - } + useEffect(() => { + initWorkspace(props.plugin)(dispatch) }, []) const createNewWorkspace = async (workspaceName) => { @@ -174,6 +173,8 @@ export const Workspace = (props: WorkspaceProps) => { toasterMsg: '' }) + const [fs, dispatch] = useReducer(browserReducer, browserInitialState) + const toast = (message: string) => { setState(prevState => { return { ...prevState, toasterMsg: message } @@ -394,7 +395,7 @@ export const Workspace = (props: WorkspaceProps) => { title='Delete'> - setWorkspace(e.target.value)} className="form-control custom-select"> { state.workspaces .map((folder, index) => { @@ -416,7 +417,7 @@ export const Workspace = (props: WorkspaceProps) => { registry={props.registry} filesProvider={props.workspace} menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} - plugin={plugin} + plugin={props.plugin} focusRoot={state.reset} contextMenuItems={props.registeredMenuItems} removedContextMenuItems={props.removedMenuItems} @@ -434,7 +435,7 @@ export const Workspace = (props: WorkspaceProps) => { registry={props.registry} filesProvider={props.localhost} menuItems={['createNewFile', 'createNewFolder']} - plugin={plugin} + plugin={props.plugin} focusRoot={state.reset} contextMenuItems={props.registeredMenuItems} removedContextMenuItems={props.removedMenuItems} From d41396e18549e0f0fd74dcb08992d754b22293a6 Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Fri, 23 Jul 2021 17:11:34 +0100 Subject: [PATCH 04/87] Modified dependencies --- apps/remix-ide/src/app/panels/file-panel.js | 57 +++++++++---------- .../workspace/src/lib/actions/workspace.ts | 12 ++-- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index ea71d98c63..17bbc6ce48 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -47,17 +47,14 @@ const profile = { module.exports = class Filepanel extends ViewPlugin { constructor (appManager) { super(profile) - this._components = {} - this._components.registry = globalRegistry - this._deps = { - fileProviders: this._components.registry.get('fileproviders').api, - fileManager: this._components.registry.get('filemanager').api - } + this.registry = globalRegistry + this.fileProviders = this.registry.get('fileproviders').api + this.fileManager = this.registry.get('filemanager').api this.el = document.createElement('div') this.el.setAttribute('id', 'fileExplorerView') - this.remixdHandle = new RemixdHandle(this._deps.fileProviders.localhost, appManager) + this.remixdHandle = new RemixdHandle(this.fileProviders.localhost, appManager) this.gitHandle = new GitHandle() this.hardhatHandle = new HardhatHandle() this.slitherHandle = new SlitherHandle() @@ -83,11 +80,11 @@ module.exports = class Filepanel extends ViewPlugin { workspaceRenamed={this.workspaceRenamed.bind(this)} workspaceDeleted={this.workspaceDeleted.bind(this)} workspaceCreated={this.workspaceCreated.bind(this)} - workspace={this._deps.fileProviders.workspace} - browser={this._deps.fileProviders.browser} - localhost={this._deps.fileProviders.localhost} - fileManager={this._deps.fileManager} - registry={this._components.registry} + workspace={this.fileProviders.workspace} + browser={this.fileProviders.browser} + localhost={this.fileProviders.localhost} + fileManager={this.fileManager} + registry={this.registry} plugin={this} request={this.request} workspaces={this.workspaces} @@ -131,8 +128,8 @@ module.exports = class Filepanel extends ViewPlugin { async getWorkspaces () { const result = new Promise((resolve, reject) => { - const workspacesPath = this._deps.fileProviders.workspace.workspacesPath - this._deps.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => { + const workspacesPath = this.fileProviders.workspace.workspacesPath + this.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => { if (error) { console.error(error) return reject(error) @@ -161,7 +158,7 @@ module.exports = class Filepanel extends ViewPlugin { let loadedFromGist = false if (params.gist) { await this.processCreateWorkspace('gist-sample') - this._deps.fileProviders.workspace.setWorkspace('gist-sample') + this.fileProviders.workspace.setWorkspace('gist-sample') this.initialWorkspace = 'gist-sample' loadedFromGist = gistHandler.loadFromGist(params, this._deps.fileManager) } @@ -170,23 +167,23 @@ module.exports = class Filepanel extends ViewPlugin { if (params.code || params.url) { try { await this.processCreateWorkspace('code-sample') - this._deps.fileProviders.workspace.setWorkspace('code-sample') + this.fileProviders.workspace.setWorkspace('code-sample') let path = '' let content = '' if (params.code) { var hash = bufferToHex(keccakFromString(params.code)) path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' content = atob(params.code) - await this._deps.fileProviders.workspace.set(path, content) + await this.fileProviders.workspace.set(path, content) } if (params.url) { const data = await this.call('contentImport', 'resolve', params.url) path = data.cleanUrl content = data.content - await this._deps.fileProviders.workspace.set(path, content) + await this.fileProviders.workspace.set(path, content) } this.initialWorkspace = 'code-sample' - await this._deps.fileManager.openFile(path) + await this.fileManager.openFile(path) } catch (e) { console.error(e) } @@ -197,19 +194,19 @@ module.exports = class Filepanel extends ViewPlugin { this.appManager.on('manager', 'pluginDeactivated', self.removePluginActions.bind(this)) // insert example contracts if there are no files to show return new Promise((resolve, reject) => { - this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { + this.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { if (error) return reject(error) if (Object.keys(filesList).length === 0) { await this.createWorkspace('default_workspace') resolve('default_workspace') } else { - this._deps.fileProviders.browser.resolveDirectory('.workspaces', async (error, filesList) => { + this.fileProviders.browser.resolveDirectory('.workspaces', async (error, filesList) => { if (error) return reject(error) if (Object.keys(filesList).length > 0) { const workspacePath = Object.keys(filesList)[0].split('/').filter(val => val) const workspaceName = workspacePath[workspacePath.length - 1] - this._deps.fileProviders.workspace.setWorkspace(workspaceName) + this.fileProviders.workspace.setWorkspace(workspaceName) return resolve(workspaceName) } return reject(new Error('Can\'t find available workspace.')) @@ -228,8 +225,8 @@ module.exports = class Filepanel extends ViewPlugin { } async processCreateWorkspace (name) { - const workspaceProvider = this._deps.fileProviders.workspace - const browserProvider = this._deps.fileProviders.browser + const workspaceProvider = this.fileProviders.workspace + const browserProvider = this.fileProviders.browser const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath) @@ -240,8 +237,8 @@ module.exports = class Filepanel extends ViewPlugin { } async workspaceExists (name) { - const workspaceProvider = this._deps.fileProviders.workspace - const browserProvider = this._deps.fileProviders.browser + const workspaceProvider = this.fileProviders.workspace + const browserProvider = this.fileProviders.browser const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name return browserProvider.exists(workspacePath) } @@ -251,7 +248,7 @@ module.exports = class Filepanel extends ViewPlugin { if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') if (await this.workspaceExists(workspaceName)) throw new Error('workspace already exists') else { - const workspaceProvider = this._deps.fileProviders.workspace + const workspaceProvider = this.fileProviders.workspace await this.processCreateWorkspace(workspaceName) workspaceProvider.setWorkspace(workspaceName) await this.request.setWorkspace(workspaceName) // tells the react component to switch to that workspace @@ -271,8 +268,8 @@ module.exports = class Filepanel extends ViewPlugin { 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 + const browserProvider = this.fileProviders.browser + const workspacesPath = this.fileProviders.workspace.workspacesPath browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true) } @@ -284,7 +281,7 @@ module.exports = class Filepanel extends ViewPlugin { this.call('manager', 'deactivatePlugin', 'remixd') } if (setEvent) { - this._deps.fileManager.setMode(workspace.isLocalhost ? 'localhost' : 'browser') + this.fileManager.setMode(workspace.isLocalhost ? 'localhost' : 'browser') this.emit('setWorkspace', workspace) } } diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index a09ec15ea8..2ad2ec9503 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -18,15 +18,16 @@ export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatc const queryParams = new QueryParams() const gistHandler = new GistHandler() const params = queryParams.get() + let initialWorkspace = '' // get the file from gist let loadedFromGist = false if (params.gist) { await processCreateWorkspace('gist-sample') - plugin.initialWorkspace = 'gist-sample' + initialWorkspace = 'gist-sample' loadedFromGist = gistHandler.loadFromGist(params, plugin.fileManager) } - if (loadedFromGist) return + if (loadedFromGist) return dispatch(setCurrentWorkspace(initialWorkspace)) if (params.code) { try { @@ -36,15 +37,15 @@ export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatc const path = fileName await plugin.fileProviders.workspace.set(path, atob(params.code)) - plugin.initialWorkspace = 'code-sample' + initialWorkspace = 'code-sample' await plugin.fileManager.openFile(fileName) } catch (e) { console.error(e) } - return + return dispatch(setCurrentWorkspace(initialWorkspace)) } - return new Promise((resolve, reject) => { + initialWorkspace = await new Promise((resolve, reject) => { plugin.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { if (error) return reject(error) if (Object.keys(filesList).length === 0) { @@ -65,6 +66,7 @@ export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatc } }) }) + return dispatch(setCurrentWorkspace(initialWorkspace)) } const processCreateWorkspace = async (name: string) => { From 65f2e75995b1b98618807e2014e04b13fdb6ae56 Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Mon, 26 Jul 2021 14:50:53 +0100 Subject: [PATCH 05/87] Manage workspaces from reducer --- apps/remix-ide/src/app/panels/file-panel.js | 71 ----------- .../workspace/src/lib/actions/workspace.ts | 101 ++++++++------- .../workspace/src/lib/reducers/workspace.ts | 14 ++- .../workspace/src/lib/remix-ui-workspace.tsx | 115 +++++------------- 4 files changed, 104 insertions(+), 197 deletions(-) diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 17bbc6ce48..578193c8e2 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -4,7 +4,6 @@ import * as packageJson from '../../../../../package.json' import React from 'react' // eslint-disable-line import ReactDOM from 'react-dom' import { Workspace } from '@remix-ui/workspace' // eslint-disable-line -import { bufferToHex, keccakFromString } from 'ethereumjs-util' import { checkSpecialChars, checkSlash } from '../../lib/helper' const { RemixdHandle } = require('../files/remixd-handle.js') const { GitHandle } = require('../files/git-handle.js') @@ -12,8 +11,6 @@ const { HardhatHandle } = require('../files/hardhat-handle.js') const { SlitherHandle } = require('../files/slither-handle.js') const globalRegistry = require('../../global/registry') const examples = require('../editor/examples') -const GistHandler = require('../../lib/gist-handler') -const QueryParams = require('../../lib/query-params') const modalDialogCustom = require('../ui/modal-dialog-custom') /* Overview of APIs: @@ -67,7 +64,6 @@ module.exports = class Filepanel extends ViewPlugin { } render () { - this.on('editor', 'editorMounted', () => this.initWorkspace().then(() => this.getWorkspaces()).catch(console.error)) return this.el } @@ -149,73 +145,6 @@ module.exports = class Filepanel extends ViewPlugin { return this.workspaces } - async initWorkspace () { - this.renderComponent() - const queryParams = new QueryParams() - const gistHandler = new GistHandler() - const params = queryParams.get() - // get the file from gist - let loadedFromGist = false - if (params.gist) { - await this.processCreateWorkspace('gist-sample') - this.fileProviders.workspace.setWorkspace('gist-sample') - this.initialWorkspace = 'gist-sample' - loadedFromGist = gistHandler.loadFromGist(params, this._deps.fileManager) - } - if (loadedFromGist) return - - if (params.code || params.url) { - try { - await this.processCreateWorkspace('code-sample') - this.fileProviders.workspace.setWorkspace('code-sample') - let path = '' - let content = '' - if (params.code) { - var hash = bufferToHex(keccakFromString(params.code)) - path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' - content = atob(params.code) - await this.fileProviders.workspace.set(path, content) - } - if (params.url) { - const data = await this.call('contentImport', 'resolve', params.url) - path = data.cleanUrl - content = data.content - await this.fileProviders.workspace.set(path, content) - } - this.initialWorkspace = 'code-sample' - await this.fileManager.openFile(path) - } catch (e) { - console.error(e) - } - return - } - - const self = this - this.appManager.on('manager', 'pluginDeactivated', self.removePluginActions.bind(this)) - // insert example contracts if there are no files to show - return new Promise((resolve, reject) => { - this.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { - if (error) return reject(error) - if (Object.keys(filesList).length === 0) { - await this.createWorkspace('default_workspace') - resolve('default_workspace') - } else { - this.fileProviders.browser.resolveDirectory('.workspaces', async (error, filesList) => { - if (error) return reject(error) - if (Object.keys(filesList).length > 0) { - const workspacePath = Object.keys(filesList)[0].split('/').filter(val => val) - const workspaceName = workspacePath[workspacePath.length - 1] - - this.fileProviders.workspace.setWorkspace(workspaceName) - return resolve(workspaceName) - } - return reject(new Error('Can\'t find available workspace.')) - }) - } - }) - }) - } - async createNewFile () { return await this.request.createNewFile() } diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 2ad2ec9503..0f5aee08bd 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -6,13 +6,55 @@ const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-para const examples = require('../../../../../../apps/remix-ide/src/app/editor/examples') let plugin -export const setCurrentWorkspace = (workspace: string) => { +const setCurrentWorkspace = (workspace: string) => { return { type: 'SET_CURRENT_WORKSPACE', payload: workspace } } +const processCreateWorkspace = async (name: string) => { + const workspaceProvider = plugin.fileProviders.workspace + const browserProvider = plugin.fileProviders.browser + const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name + const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath + const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath) + const workspacePathExists = await browserProvider.exists(workspacePath) + + if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath) + if (!workspacePathExists) browserProvider.createDir(workspacePath) + workspaceProvider.setWorkspace(name) +} + +const createWorkspace = async (workspaceName: string, setDefaults = true) => { + if (!workspaceName) throw new Error('workspace name cannot be empty') + if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') + if (await workspaceExists(workspaceName)) throw new Error('workspace already exists') + else { + const workspaceProvider = plugin.fileProviders.workspace + + await processCreateWorkspace(workspaceName) + if (setDefaults) { + // insert example contracts if there are no files to show + for (const file in examples) { + try { + await workspaceProvider.set(examples[file].name, examples[file].content) + } catch (error) { + console.error(error) + } + } + } + } +} + +const workspaceExists = async (name: string) => { + const workspaceProvider = plugin.fileProviders.workspace + const browserProvider = plugin.fileProviders.browser + const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name + + return browserProvider.exists(workspacePath) +} + export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatch) => { plugin = filePanelPlugin const queryParams = new QueryParams() @@ -66,49 +108,24 @@ export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatc } }) }) - return dispatch(setCurrentWorkspace(initialWorkspace)) -} -const processCreateWorkspace = async (name: string) => { - const workspaceProvider = plugin.fileProviders.workspace - const browserProvider = plugin.fileProviders.browser - const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name - const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath - const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath) - const workspacePathExists = await browserProvider.exists(workspacePath) + // plugin.fileProviders.localhost.event.off('disconnected', localhostDisconnect) + // plugin.fileProviders.localhost.event.on('disconnected', localhostDisconnect) + // plugin.fileProviders.localhost.event.on('connected', () => { + // remixdExplorer.show() + // setWorkspace(LOCALHOST) + // }) - if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath) - if (!workspacePathExists) browserProvider.createDir(workspacePath) - plugin.fileProviders.workspace.setWorkspace(name) -} + // plugin.fileProviders.localhost.event.on('disconnected', () => { + // remixdExplorer.hide() + // }) -const createWorkspace = async (workspaceName, setDefaults = true) => { - if (!workspaceName) throw new Error('name cannot be empty') - if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') - if (await workspaceExists(workspaceName)) throw new Error('workspace already exists') - else { - const workspaceProvider = plugin.fileProviders.workspace + // plugin.fileProviders.localhost.event.on('loading', () => { + // remixdExplorer.loading() + // }) - await processCreateWorkspace(workspaceName) - workspaceProvider.setWorkspace(workspaceName) - plugin.workspaceName = workspaceName - if (setDefaults) { - // insert example contracts if there are no files to show - for (const file in examples) { - try { - await workspaceProvider.set(examples[file].name, examples[file].content) - } catch (error) { - console.error(error) - } - } - } - } -} - -const workspaceExists = async (name) => { - const workspaceProvider = plugin.fileProviders.workspace - const browserProvider = plugin.fileProviders.browser - const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name - - return browserProvider.exists(workspacePath) + // plugin.fileProviders.workspace.event.on('createWorkspace', (name) => { + // createNewWorkspace(name) + // }) + return dispatch(setCurrentWorkspace(initialWorkspace)) } diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index bb376b64c5..81a31899a2 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -2,8 +2,17 @@ interface Action { type: string payload: Record | string } +interface State { + browser: { + currentWorkspace: string, + workspaces: string[], + isRequesting: boolean, + isSuccessful: boolean, + error: string + } +} -export const browserInitialState = { +export const browserInitialState: State = { browser: { currentWorkspace: '', workspaces: [], @@ -20,7 +29,8 @@ export const browserReducer = (state = browserInitialState, action: Action) => { ...state, browser: { ...state.browser, - currentWorkspace: typeof action.payload === 'string' ? action.payload : '' + currentWorkspace: typeof action.payload === 'string' ? action.payload : '', + workspaces: typeof action.payload === 'string' ? state.browser.workspaces.includes(action.payload) ? state.browser.workspaces : [...state.browser.workspaces, action.payload] : state.browser.workspaces } } } 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 a18d6426c5..df685d448b 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -32,6 +32,37 @@ var canUpload = window.File || window.FileReader || window.FileList || window.Bl export const Workspace = (props: WorkspaceProps) => { const LOCALHOST = ' - connect to localhost - ' const NO_WORKSPACE = ' - none - ' + const [state, setState] = useState({ + workspaces: [], + reset: false, + currentWorkspace: NO_WORKSPACE, + hideRemixdExplorer: true, + displayNewFile: false, + externalUploads: null, + uploadFileEvent: null, + modal: { + hide: true, + title: '', + message: null, + okLabel: '', + okFn: () => {}, + cancelLabel: '', + cancelFn: () => {}, + handleHide: null + }, + loadingLocalhost: false, + toasterMsg: '' + }) + const [currentWorkspace, setCurrentWorkspace] = useState('') + const [fs, dispatch] = useReducer(browserReducer, browserInitialState) + + useEffect(() => { + initWorkspace(props.plugin)(dispatch) + }, []) + + useEffect(() => { + if (fs.browser.currentWorkspace) setCurrentWorkspace(fs.browser.currentWorkspace) + }, [fs.browser.currentWorkspace]) /* extends the parent 'plugin' with some function needed by the file explorer */ props.plugin.resetFocus = (reset) => { @@ -74,30 +105,6 @@ export const Workspace = (props: WorkspaceProps) => { return { name: state.currentWorkspace, isLocalhost: state.currentWorkspace === LOCALHOST, absolutePath: `${props.workspace.workspacesPath}/${state.currentWorkspace}` } } - useEffect(() => { - let getWorkspaces = async () => { - if (props.workspaces && Array.isArray(props.workspaces)) { - if (props.workspaces.length > 0 && state.currentWorkspace === NO_WORKSPACE) { - const currentWorkspace = props.workspace.getWorkspace() ? props.workspace.getWorkspace() : props.workspaces[0] - await props.workspace.setWorkspace(currentWorkspace) - setState(prevState => { - return { ...prevState, workspaces: props.workspaces, currentWorkspace } - }) - } else { - setState(prevState => { - return { ...prevState, workspaces: props.workspaces } - }) - } - } - } - - getWorkspaces() - - return () => { - getWorkspaces = async () => {} - } - }, [props.workspaces]) - const localhostDisconnect = () => { if (state.currentWorkspace === LOCALHOST) setWorkspace(props.workspaces.length > 0 ? props.workspaces[0] : NO_WORKSPACE) // This should be removed some time after refactoring: https://github.com/ethereum/remix-project/issues/1197 @@ -107,38 +114,6 @@ export const Workspace = (props: WorkspaceProps) => { } } - // useEffect(() => { - // props.localhost.event.off('disconnected', localhostDisconnect) - // props.localhost.event.on('disconnected', localhostDisconnect) - // props.localhost.event.on('connected', () => { - // remixdExplorer.show() - // setWorkspace(LOCALHOST) - // }) - - // props.localhost.event.on('disconnected', () => { - // remixdExplorer.hide() - // }) - - // props.localhost.event.on('loading', () => { - // remixdExplorer.loading() - // }) - - // props.workspace.event.on('createWorkspace', (name) => { - // createNewWorkspace(name) - // }) - - // if (props.initialWorkspace) { - // props.workspace.setWorkspace(props.initialWorkspace) - // setState(prevState => { - // return { ...prevState, currentWorkspace: props.initialWorkspace } - // }) - // } - // }, []) - - useEffect(() => { - initWorkspace(props.plugin)(dispatch) - }, []) - const createNewWorkspace = async (workspaceName) => { try { await props.fileManager.closeAllFiles() @@ -151,30 +126,6 @@ export const Workspace = (props: WorkspaceProps) => { } } - const [state, setState] = useState({ - workspaces: [], - reset: false, - currentWorkspace: NO_WORKSPACE, - hideRemixdExplorer: true, - displayNewFile: false, - externalUploads: null, - uploadFileEvent: null, - modal: { - hide: true, - title: '', - message: null, - okLabel: '', - okFn: () => {}, - cancelLabel: '', - cancelFn: () => {}, - handleHide: null - }, - loadingLocalhost: false, - toasterMsg: '' - }) - - const [fs, dispatch] = useReducer(browserReducer, browserInitialState) - const toast = (message: string) => { setState(prevState => { return { ...prevState, toasterMsg: message } @@ -395,9 +346,9 @@ export const Workspace = (props: WorkspaceProps) => { title='Delete'> - setWorkspace(e.target.value)} className="form-control custom-select"> { - state.workspaces + fs.browser.workspaces .map((folder, index) => { return }) From c0a6a539568bcd387efe34d22d2070f97961ab0b Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Fri, 30 Jul 2021 16:10:36 +0100 Subject: [PATCH 06/87] Seperation of concern --- .../src/app/files/workspaceFileProvider.js | 10 +- .../workspace/src/lib/actions/gist-handler.ts | 73 ++++++++ .../workspace/src/lib/actions/workspace.ts | 157 ++++++++++-------- .../workspace/src/lib/reducers/workspace.ts | 10 ++ 4 files changed, 182 insertions(+), 68 deletions(-) create mode 100644 libs/remix-ui/workspace/src/lib/actions/gist-handler.ts diff --git a/apps/remix-ide/src/app/files/workspaceFileProvider.js b/apps/remix-ide/src/app/files/workspaceFileProvider.js index 4b0ac2af2e..8f6b190668 100644 --- a/apps/remix-ide/src/app/files/workspaceFileProvider.js +++ b/apps/remix-ide/src/app/files/workspaceFileProvider.js @@ -80,8 +80,16 @@ class WorkspaceFileProvider extends FileProvider { return path.replace(this.workspacesPath + '/' + this.workspace + '/', '') } - createWorkspace (name) { + async createWorkspace (name) { if (!name) name = 'default_workspace' + this.setWorkspace(name) + const workspacePath = 'browser/' + this.workspacesPath + '/' + name + const workspaceRootPath = 'browser/' + this.workspacesPath + const workspaceRootPathExists = await super.exists(workspaceRootPath) + const workspacePathExists = await super.exists(workspacePath) + + if (!workspaceRootPathExists) super.createDir(workspaceRootPath) + if (!workspacePathExists) super.createDir(workspacePath) this.event.emit('createWorkspace', name) } } diff --git a/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts b/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts new file mode 100644 index 0000000000..733ffede34 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts @@ -0,0 +1,73 @@ +// var modalDialogCustom = require('../app/ui/modal-dialog-custom') +var request = require('request') + +// Allowing window to be overriden for testing +function GistHandler (_window) { + if (_window !== undefined) { + // modalDialogCustom = _window + } + + this.handleLoad = function (params, cb) { + if (!cb) cb = () => {} + var loadingFromGist = false + var gistId + if (params.gist === '') { + loadingFromGist = true + // modalDialogCustom.prompt('Load a Gist', 'Enter the ID of the Gist or URL you would like to load.', null, (target) => { + if (target !== '') { + gistId = getGistId(target) + if (gistId) { + cb(gistId) + } else { + // modalDialogCustom.alert('Gist load error', 'Error while loading gist. Please provide a valid Gist ID or URL.') + } + } + }) + return loadingFromGist + } else { + gistId = params.gist + loadingFromGist = !!gistId + } + if (loadingFromGist) { + cb(gistId) + } + return loadingFromGist + } + + function getGistId (str) { + var idr = /[0-9A-Fa-f]{8,}/ + var match = idr.exec(str) + return match ? match[0] : null + } + + this.loadFromGist = (params, fileManager) => { + const self = this + return self.handleLoad(params, function (gistId) { + request.get({ + url: `https://api.github.com/gists/${gistId}`, + json: true + }, async (error, response, data = {}) => { + if (error || !data.files) { + // modalDialogCustom.alert('Gist load error', error || data.message) + return + } + const obj = {} + Object.keys(data.files).forEach((element) => { + const path = element.replace(/\.\.\./g, '/') + + obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] + }) + fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { + if (!errorLoadingFile) { + const provider = fileManager.getProvider('workspace') + provider.lastLoadedGistId = gistId + } else { + // modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile) + } + }) + }) + }) + } +} + +module.exports = GistHandler diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 0f5aee08bd..8ede21ac4b 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -1,7 +1,7 @@ import { bufferToHex, keccakFromString } from 'ethereumjs-util' import { checkSpecialChars, checkSlash } from '../../../../../../apps/remix-ide/src/lib/helper' -const GistHandler = require('../../../../../../apps/remix-ide/src/lib/gist-handler') +// const GistHandler = require('../../../../../../apps/remix-ide/src/lib/gist-handler') const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params') const examples = require('../../../../../../apps/remix-ide/src/app/editor/examples') let plugin @@ -13,35 +13,53 @@ const setCurrentWorkspace = (workspace: string) => { } } -const processCreateWorkspace = async (name: string) => { - const workspaceProvider = plugin.fileProviders.workspace - const browserProvider = plugin.fileProviders.browser - const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name - const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath - const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath) - const workspacePathExists = await browserProvider.exists(workspacePath) - - if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath) - if (!workspacePathExists) browserProvider.createDir(workspacePath) - workspaceProvider.setWorkspace(name) +const setWorkspaces = (workspaces: string[]) => { + return { + type: 'SET_WORKSPACES', + payload: workspaces + } } -const createWorkspace = async (workspaceName: string, setDefaults = true) => { +const createWorkspaceTemplate = async (workspaceName: string, setDefaults = true, template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => { if (!workspaceName) throw new Error('workspace name cannot be empty') if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') if (await workspaceExists(workspaceName)) throw new Error('workspace already exists') else { const workspaceProvider = plugin.fileProviders.workspace - await processCreateWorkspace(workspaceName) + await workspaceProvider.createWorkspace(workspaceName) if (setDefaults) { - // insert example contracts if there are no files to show - for (const file in examples) { - try { - await workspaceProvider.set(examples[file].name, examples[file].content) - } catch (error) { - console.error(error) - } + switch (template) { + case 'code-template': + // creates a new workspace code-sample and loads code from url params. + try { + const queryParams = new QueryParams() + const params = queryParams.get() + await plugin.fileProviders.worspace.createWorkspace(workspaceName) + const hash = bufferToHex(keccakFromString(params.code)) + const fileName = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' + const path = fileName + + await plugin.fileProviders.workspace.set(path, atob(params.code)) + await plugin.fileManager.openFile(fileName) + } catch (e) { + console.error(e) + } + break + case 'gist-template': + // creates a new workspace gist-sample and get the file from gist + break + case 'default-template': + // creates a new workspace and populates it with default project template. + // insert example contracts + for (const file in examples) { + try { + await workspaceProvider.set(examples[file].name, examples[file].content) + } catch (error) { + console.error(error) + } + } + break } } } @@ -58,56 +76,39 @@ const workspaceExists = async (name: string) => { export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatch) => { plugin = filePanelPlugin const queryParams = new QueryParams() - const gistHandler = new GistHandler() + // const gistHandler = new GistHandler() const params = queryParams.get() - let initialWorkspace = '' - // get the file from gist - let loadedFromGist = false + // let loadedFromGist = false + const workspaces = await getWorkspaces() || [] + + dispatch(setWorkspaces(workspaces)) + // if (params.gist) { + // initialWorkspace = 'gist-sample' + // await filePanelPlugin.fileProviders.workspace.createWorkspace(initialWorkspace) + // loadedFromGist = gistHandler.loadFromGist(params, plugin.fileManager) + // } + // if (loadedFromGist) { + // dispatch(setWorkspaces(workspaces)) + // dispatch(setCurrentWorkspace(initialWorkspace)) + // return + // } if (params.gist) { - await processCreateWorkspace('gist-sample') - initialWorkspace = 'gist-sample' - loadedFromGist = gistHandler.loadFromGist(params, plugin.fileManager) - } - if (loadedFromGist) return dispatch(setCurrentWorkspace(initialWorkspace)) - - if (params.code) { - try { - await processCreateWorkspace('code-sample') - const hash = bufferToHex(keccakFromString(params.code)) - const fileName = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' - const path = fileName - - await plugin.fileProviders.workspace.set(path, atob(params.code)) - initialWorkspace = 'code-sample' - await plugin.fileManager.openFile(fileName) - } catch (e) { - console.error(e) - } - return dispatch(setCurrentWorkspace(initialWorkspace)) - } - initialWorkspace = await new Promise((resolve, reject) => { - plugin.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { - if (error) return reject(error) - if (Object.keys(filesList).length === 0) { - await createWorkspace('default_workspace') - return resolve('default_workspace') - } else { - plugin.fileProviders.browser.resolveDirectory('.workspaces', async (error, filesList) => { - if (error) return reject(error) - if (Object.keys(filesList).length > 0) { - const workspacePath = Object.keys(filesList)[0].split('/').filter(val => val) - const workspaceName = workspacePath[workspacePath.length - 1] - - plugin.fileProviders.workspace.setWorkspace(workspaceName) - return resolve(workspaceName) - } - return reject(new Error('Can\'t find available workspace.')) - }) + } else if (params.code) { + await createWorkspaceTemplate('code-sample', true, 'code-template') + dispatch(setCurrentWorkspace('code-sample')) + } else { + if (workspaces.length === 0) { + await createWorkspaceTemplate('default_workspace') + dispatch(setCurrentWorkspace('default_workspace')) + } else { + if (workspaces.length > 0) { + plugin.fileProviders.workspace.setWorkspace(workspaces[workspaces.length - 1]) + dispatch(setCurrentWorkspace(workspaces[workspaces.length - 1])) } - }) - }) + } + } // plugin.fileProviders.localhost.event.off('disconnected', localhostDisconnect) // plugin.fileProviders.localhost.event.on('disconnected', localhostDisconnect) @@ -127,5 +128,27 @@ export const initWorkspace = (filePanelPlugin) => async (dispatch: React.Dispatc // plugin.fileProviders.workspace.event.on('createWorkspace', (name) => { // createNewWorkspace(name) // }) - return dispatch(setCurrentWorkspace(initialWorkspace)) +} + +const getWorkspaces = async (): Promise | undefined => { + try { + const workspaces: string[] = await new Promise((resolve, reject) => { + const workspacesPath = plugin.fileProviders.workspace.workspacesPath + + plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => { + if (error) { + console.error(error) + return reject(error) + } + resolve(Object.keys(items) + .filter((item) => items[item].isDirectory) + .map((folder) => folder.replace(workspacesPath + '/', ''))) + }) + }) + + return workspaces + } catch (e) { + // modalDialogCustom.alert('Workspaces have not been created on your system. Please use "Migrate old filesystem to workspace" on the home page to transfer your files or start by creating a new workspace in the File Explorers.') + console.log(e) + } } diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index 81a31899a2..403bde8396 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -34,6 +34,16 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } } } + + case 'SET_WORKSPACES': { + return { + ...state, + browser: { + ...state.browser, + workspaces: Array.isArray(action.payload) ? action.payload : state.browser.workspaces + } + } + } default: throw new Error() } From 9084e342ed7a330db30b88ebf38938ba3dad0eac Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Tue, 3 Aug 2021 15:12:22 +0100 Subject: [PATCH 07/87] Display workspace files --- .../workspace/src/lib/actions/gist-handler.ts | 92 ++++++++----------- .../workspace/src/lib/remix-ui-workspace.tsx | 29 +++--- package.json | 1 + 3 files changed, 52 insertions(+), 70 deletions(-) diff --git a/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts b/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts index 733ffede34..5df4ad76c7 100644 --- a/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts +++ b/libs/remix-ui/workspace/src/lib/actions/gist-handler.ts @@ -1,73 +1,55 @@ // var modalDialogCustom = require('../app/ui/modal-dialog-custom') -var request = require('request') +import * as request from 'request' -// Allowing window to be overriden for testing -function GistHandler (_window) { - if (_window !== undefined) { - // modalDialogCustom = _window - } +export class GistHandler { + handleLoad (params) { + let loadingFromGist = false + let gistId - this.handleLoad = function (params, cb) { - if (!cb) cb = () => {} - var loadingFromGist = false - var gistId - if (params.gist === '') { + if (params.gist) { loadingFromGist = true - // modalDialogCustom.prompt('Load a Gist', 'Enter the ID of the Gist or URL you would like to load.', null, (target) => { - if (target !== '') { - gistId = getGistId(target) - if (gistId) { - cb(gistId) - } else { - // modalDialogCustom.alert('Gist load error', 'Error while loading gist. Please provide a valid Gist ID or URL.') - } - } - }) - return loadingFromGist } else { gistId = params.gist loadingFromGist = !!gistId } - if (loadingFromGist) { - cb(gistId) - } - return loadingFromGist + + return gistId } - function getGistId (str) { - var idr = /[0-9A-Fa-f]{8,}/ - var match = idr.exec(str) + getGistId (str: string) { + const idr = /[0-9A-Fa-f]{8,}/ + const match = idr.exec(str) + return match ? match[0] : null } - this.loadFromGist = (params, fileManager) => { - const self = this - return self.handleLoad(params, function (gistId) { - request.get({ - url: `https://api.github.com/gists/${gistId}`, - json: true - }, async (error, response, data = {}) => { - if (error || !data.files) { - // modalDialogCustom.alert('Gist load error', error || data.message) - return - } - const obj = {} - Object.keys(data.files).forEach((element) => { - const path = element.replace(/\.\.\./g, '/') + loadFromGist (params, fileManager) { + const gistId = this.handleLoad(params) - obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] - }) - fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { - if (!errorLoadingFile) { - const provider = fileManager.getProvider('workspace') - provider.lastLoadedGistId = gistId - } else { - // modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile) - } - }) + request.get({ + url: `https://api.github.com/gists/${gistId}`, + json: true + }, async (error, response, data = {}) => { + if (error || !data.files) { + // modalDialogCustom.alert('Gist load error', error || data.message) + return + } + const obj = {} + + Object.keys(data.files).forEach((element) => { + const path = element.replace(/\.\.\./g, '/') + + obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] + }) + fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { + if (!errorLoadingFile) { + const provider = fileManager.getProvider('workspace') + + provider.lastLoadedGistId = gistId + } else { + // modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile) + } }) }) } } - -module.exports = GistHandler 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 df685d448b..ab26239d8c 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -35,7 +35,6 @@ export const Workspace = (props: WorkspaceProps) => { const [state, setState] = useState({ workspaces: [], reset: false, - currentWorkspace: NO_WORKSPACE, hideRemixdExplorer: true, displayNewFile: false, externalUploads: null, @@ -53,7 +52,7 @@ export const Workspace = (props: WorkspaceProps) => { loadingLocalhost: false, toasterMsg: '' }) - const [currentWorkspace, setCurrentWorkspace] = useState('') + const [currentWorkspace, setCurrentWorkspace] = useState(NO_WORKSPACE) const [fs, dispatch] = useReducer(browserReducer, browserInitialState) useEffect(() => { @@ -102,14 +101,14 @@ export const Workspace = (props: WorkspaceProps) => { } props.request.getCurrentWorkspace = () => { - return { name: state.currentWorkspace, isLocalhost: state.currentWorkspace === LOCALHOST, absolutePath: `${props.workspace.workspacesPath}/${state.currentWorkspace}` } + return { name: currentWorkspace, isLocalhost: currentWorkspace === LOCALHOST, absolutePath: `${props.workspace.workspacesPath}/${currentWorkspace}` } } const localhostDisconnect = () => { - if (state.currentWorkspace === LOCALHOST) setWorkspace(props.workspaces.length > 0 ? props.workspaces[0] : NO_WORKSPACE) + if (currentWorkspace === LOCALHOST) setWorkspace(props.workspaces.length > 0 ? props.workspaces[0] : NO_WORKSPACE) // This should be removed some time after refactoring: https://github.com/ethereum/remix-project/issues/1197 else { - setWorkspace(state.currentWorkspace) // Useful to switch to last selcted workspace when remixd is disconnected + setWorkspace(currentWorkspace) // Useful to switch to last selcted workspace when remixd is disconnected props.fileManager.setMode('browser') } } @@ -161,7 +160,7 @@ export const Workspace = (props: WorkspaceProps) => { const workspaceName = workspaceRenameInput.current.value try { - await props.renameWorkspace(state.currentWorkspace, workspaceName) + await props.renameWorkspace(currentWorkspace, workspaceName) setWorkspace(workspaceName) props.workspaceRenamed({ name: workspaceName }) } catch (e) { @@ -188,8 +187,8 @@ export const Workspace = (props: WorkspaceProps) => { const onFinishDeleteWorkspace = async () => { await props.fileManager.closeAllFiles() const workspacesPath = props.workspace.workspacesPath - props.browser.remove(workspacesPath + '/' + state.currentWorkspace) - const name = state.currentWorkspace + props.browser.remove(workspacesPath + '/' + currentWorkspace) + const name = currentWorkspace setWorkspace(NO_WORKSPACE) props.workspaceDeleted({ name }) } @@ -284,7 +283,7 @@ export const Workspace = (props: WorkspaceProps) => { return ( <> { state.modal.message } - + ) } @@ -324,7 +323,7 @@ export const Workspace = (props: WorkspaceProps) => { title='Create'>