Implement file-panel exposed apis

pull/1575/head
ioedeveloper 3 years ago
parent 59f231967a
commit 7ad686d1e4
  1. 94
      apps/remix-ide/src/app/panels/file-panel.js
  2. 10
      libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx
  3. 211
      libs/remix-ui/workspace/src/lib/actions/events.ts
  4. 186
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  5. 810
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  6. 42
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  7. 11
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx

@ -4,13 +4,11 @@ import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line
import { checkSpecialChars, checkSlash } from '../../lib/helper'
const { RemixdHandle } = require('../files/remixd-handle.js')
const { GitHandle } = require('../files/git-handle.js')
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 modalDialogCustom = require('../ui/modal-dialog-custom')
/*
Overview of APIs:
@ -61,6 +59,7 @@ module.exports = class Filepanel extends ViewPlugin {
this.workspaces = []
this.initialWorkspace = null
this.appManager = appManager
this.workspaceStatus = {}
}
onActivation () {
@ -104,33 +103,18 @@ module.exports = class Filepanel extends ViewPlugin {
this.renderComponent()
}
async getCurrentWorkspace () {
return await this.request.getCurrentWorkspace()
getCurrentWorkspace () {
return this.workspaceStatus
}
async getWorkspaces () {
const result = new Promise((resolve, reject) => {
const workspacesPath = this.fileProviders.workspace.workspacesPath
this.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 + '/', '')))
})
})
try {
this.workspaces = await result
} 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)
}
this.renderComponent()
getWorkspaces () {
return this.workspaces
}
setWorkspaces (workspaces) {
this.worspaces = workspaces
}
async createNewFile () {
const provider = this.fileManager.currentFileProvider()
const dir = provider.workspace || '/'
@ -145,66 +129,18 @@ module.exports = class Filepanel extends ViewPlugin {
return this.emit('uploadFileEvent', dir, target)
}
async processCreateWorkspace (name) {
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)
const workspacePathExists = await browserProvider.exists(workspacePath)
async createWorkspace (workspaceName) {
this.emit('createWorkspace', workspaceName)
}
if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath)
if (!workspacePathExists) browserProvider.createDir(workspacePath)
async renameWorkspace (oldName, workspaceName) {
this.emit('renameWorkspace', oldName, workspaceName)
}
async workspaceExists (name) {
setWorkspace (workspace) {
const workspaceProvider = this.fileProviders.workspace
const browserProvider = this.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
return browserProvider.exists(workspacePath)
}
async createWorkspace (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 this.workspaceExists(workspaceName)) throw new Error('workspace already exists')
else {
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
if (setDefaults) {
for (const file in examples) {
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
}
}
}
}
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.fileProviders.browser
const workspacesPath = this.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, setEvent = true) {
if (workspace.isLocalhost) {
this.call('manager', 'activatePlugin', 'remixd')
} else if (await this.call('manager', 'isActive', 'remixd')) {
this.call('manager', 'deactivatePlugin', 'remixd')
}
if (setEvent) {
this.fileManager.setMode(workspace.isLocalhost ? 'localhost' : 'browser')
this.emit('setWorkspace', workspace)
}
this.workspaceStatus = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
}
workspaceDeleted (workspace) {

@ -110,7 +110,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
<input
className="form-control"
onChange={e => setName(e.target.value)}
value={ name}
value={ name || '' }
id="plugin-name"
data-id="localPluginName"
placeholder="Should be camelCase" />
@ -120,7 +120,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
<input
className="form-control"
onChange={e => setDisplayName(e.target.value)}
value={ displayName }
value={ displayName || '' }
id="plugin-displayname"
data-id="localPluginDisplayName"
placeholder="Name in the header" />
@ -130,7 +130,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
<input
className="form-control"
onChange={e => setMethods(e.target.value)}
value={ methods }
value={ methods || '' }
id="plugin-methods"
data-id="localPluginMethods"
placeholder="Methods" />
@ -140,7 +140,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
<input
className="form-control"
onChange={e => setCanactivate(e.target.value)}
value={ canactivate }
value={ canactivate || '' }
id="plugin-canactivate"
data-id="localPluginCanActivate"
placeholder="Plugin names" />
@ -151,7 +151,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
<input
className="form-control"
onChange={e => setUrl(e.target.value)}
value={ url }
value={ url || '' }
id="plugin-url"
data-id="localPluginUrl"
placeholder="ex: https://localhost:8000" />

@ -0,0 +1,211 @@
import { extractParentFromKey } from '@remix-ui/helper'
import React from 'react'
import { displayNotification, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, rootFolderChangedSuccess } from './payload'
import { addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace'
const queuedEvents = []
const pendingEvents = {}
const LOCALHOST = ' - connect to localhost - '
let plugin, dispatch: React.Dispatch<any>
export const listenOnEvents = (filePanelPlugin, provider) => async (reducerDispatch: React.Dispatch<any>) => {
plugin = filePanelPlugin
dispatch = reducerDispatch
provider.event.on('fileAdded', async (filePath: string) => {
await executeEvent('fileAdded', filePath)
})
provider.event.on('folderAdded', async (folderPath: string) => {
if (folderPath.indexOf('/.workspaces') === 0) return
await executeEvent('folderAdded', folderPath)
})
provider.event.on('fileRemoved', async (removePath: string) => {
await executeEvent('fileRemoved', removePath)
})
provider.event.on('fileRenamed', async (oldPath: string, newPath: string) => {
await executeEvent('fileRenamed', oldPath, newPath)
})
plugin.on('remixd', 'rootFolderChanged', async (path: string) => {
await executeEvent('rootFolderChanged', path)
})
// provider.event.on('disconnected', () => {
// dispatch(setMode('browser'))
// })
provider.event.on('connected', async () => {
fetchWorkspaceDirectory('/')
// setState(prevState => {
// return { ...prevState, hideRemixdExplorer: false, loadingLocalhost: false }
// })
})
provider.event.on('disconnected', async () => {
const workspaceProvider = plugin.fileProviders.workspace
await switchToWorkspace(workspaceProvider.workspace)
})
provider.event.on('loadingLocalhost', async () => {
await switchToWorkspace(LOCALHOST)
// setState(prevState => {
// return { ...prevState, loadingLocalhost: true }
// })
})
provider.event.on('fileExternallyChanged', async (path: string, file: { content: string }) => {
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== file.content) {
if (provider.isReadOnly(path)) return editor.setText(file.content)
dispatch(displayNotification(
path + ' changed',
'This file has been changed outside of Remix IDE.',
'Replace by the new content', 'Keep the content displayed in Remix',
() => {
editor.setText(file.content)
}
))
}
})
provider.event.on('fileRenamedError', async () => {
dispatch(displayNotification('File Renamed Failed', '', 'Ok', 'Cancel'))
})
plugin.on('filePanel', 'displayNewFileInput', (path) => {
addInputField('file', path)
})
plugin.on('filePanel', 'uploadFileEvent', (dir: string, target) => {
uploadFile(target, dir)
})
provider.event.on('createWorkspace', (name: string) => {
createWorkspace(name)
})
plugin.on('filePanel', 'createWorkspace', (name: string) => {
createWorkspace(name)
})
plugin.on('filePanel', 'renameWorkspace', (oldName: string, workspaceName: string) => {
renameWorkspace(oldName, workspaceName)
})
}
const fileAdded = async (filePath: string) => {
await dispatch(fileAddedSuccess(filePath))
if (filePath.includes('_test.sol')) {
plugin.emit('newTestFileCreated', filePath)
}
}
const folderAdded = async (folderPath: string) => {
const provider = plugin.fileManager.currentFileProvider()
const path = extractParentFromKey(folderPath) || provider.workspace || provider.type || ''
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
promise.then((files) => {
dispatch(folderAddedSuccess(path, files))
}).catch((error) => {
console.error(error)
})
return promise
}
const fileRemoved = async (removePath: string) => {
await dispatch(fileRemovedSuccess(removePath))
}
const fileRenamed = async (oldPath: string) => {
const provider = plugin.fileManager.currentFileProvider()
const path = extractParentFromKey(oldPath) || provider.workspace || provider.type || ''
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
promise.then((files) => {
dispatch(fileRenamedSuccess(path, oldPath, files))
}).catch((error) => {
console.error(error)
})
}
const rootFolderChanged = async (path) => {
await dispatch(rootFolderChangedSuccess(path))
}
const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemoved' | 'fileRenamed' | 'rootFolderChanged', ...args) => {
if (Object.keys(pendingEvents).length) {
return queuedEvents.push({ eventName, path: args[0] })
}
pendingEvents[eventName + args[0]] = { eventName, path: args[0] }
switch (eventName) {
case 'fileAdded':
await fileAdded(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
await executeEvent(next.eventName, next.path)
}
break
case 'folderAdded':
await folderAdded(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
await executeEvent(next.eventName, next.path)
}
break
case 'fileRemoved':
await fileRemoved(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
await executeEvent(next.eventName, next.path)
}
break
case 'fileRenamed':
await fileRenamed(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
await executeEvent(next.eventName, next.path)
}
break
case 'rootFolderChanged':
await rootFolderChanged(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
await executeEvent(next.eventName, next.path)
}
break
}
}

@ -0,0 +1,186 @@
export const setCurrentWorkspace = (workspace: string) => {
return {
type: 'SET_CURRENT_WORKSPACE',
payload: workspace
}
}
export const setWorkspaces = (workspaces: string[]) => {
return {
type: 'SET_WORKSPACES',
payload: workspaces
}
}
export const setMode = (mode: 'browser' | 'localhost') => {
return {
type: 'SET_MODE',
payload: mode
}
}
export const fetchDirectoryError = (error: any) => {
return {
type: 'FETCH_DIRECTORY_ERROR',
payload: error
}
}
export const fetchDirectoryRequest = (promise: Promise<any>) => {
return {
type: 'FETCH_DIRECTORY_REQUEST',
payload: promise
}
}
export const fetchDirectorySuccess = (path: string, fileTree) => {
return {
type: 'FETCH_DIRECTORY_SUCCESS',
payload: { path, fileTree }
}
}
export const displayNotification = (title: string, message: string, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => {
return {
type: 'DISPLAY_NOTIFICATION',
payload: { title, message, labelOk, labelCancel, actionOk, actionCancel }
}
}
export const hideNotification = () => {
return {
type: 'HIDE_NOTIFICATION'
}
}
export const fileAddedSuccess = (filePath: string) => {
return {
type: 'FILE_ADDED_SUCCESS',
payload: filePath
}
}
export const folderAddedSuccess = (folderPath: string, fileTree) => {
return {
type: 'FOLDER_ADDED_SUCCESS',
payload: { path: folderPath, fileTree }
}
}
export const fileRemovedSuccess = (removePath: string) => {
return {
type: 'FILE_REMOVED_SUCCESS',
payload: removePath
}
}
export const fileRenamedSuccess = (path: string, oldPath: string, fileTree) => {
return {
type: 'FILE_RENAMED_SUCCESS',
payload: { path, oldPath, fileTree }
}
}
export const rootFolderChangedSuccess = (path: string) => {
return {
type: 'ROOT_FOLDER_CHANGED',
payload: path
}
}
export const addInputFieldSuccess = (path: string, fileTree, type: 'file' | 'folder' | 'gist') => {
return {
type: 'ADD_INPUT_FIELD',
payload: { path, fileTree, type }
}
}
export const removeInputFieldSuccess = (path: string, fileTree) => {
return {
type: 'REMOVE_INPUT_FIELD',
payload: { path, fileTree }
}
}
export const setReadOnlyMode = (mode: boolean) => {
return {
type: 'SET_READ_ONLY_MODE',
payload: mode
}
}
export const createWorkspaceError = (error: any) => {
return {
type: 'CREATE_WORKSPACE_ERROR',
payload: error
}
}
export const createWorkspaceRequest = (promise: Promise<any>) => {
return {
type: 'CREATE_WORKSPACE_REQUEST',
payload: promise
}
}
export const createWorkspaceSuccess = (workspaceName: string) => {
return {
type: 'CREATE_WORKSPACE_SUCCESS',
payload: workspaceName
}
}
export const fetchWorkspaceDirectoryError = (error: any) => {
return {
type: 'FETCH_WORKSPACE_DIRECTORY_ERROR',
payload: error
}
}
export const fetchWorkspaceDirectoryRequest = (promise: Promise<any>) => {
return {
type: 'FETCH_WORKSPACE_DIRECTORY_REQUEST',
payload: promise
}
}
export const fetchWorkspaceDirectorySuccess = (path: string, fileTree) => {
return {
type: 'FETCH_WORKSPACE_DIRECTORY_SUCCESS',
payload: { path, fileTree }
}
}
export const setRenameWorkspace = (oldName: string, workspaceName: string) => {
return {
type: 'RENAME_WORKSPACE',
payload: { oldName, workspaceName }
}
}
export const setDeleteWorkspace = (workspaceName: string) => {
return {
type: 'DELETE_WORKSPACE',
payload: workspaceName
}
}
export const displayPopUp = (message: string) => {
return {
type: 'DISPLAY_POPUP_MESSAGE',
payload: message
}
}
export const hidePopUp = () => {
return {
type: 'HIDE_POPUP_MESSAGE'
}
}
export const focusElement = (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => {
return {
type: 'SET_FOCUS_ELEMENT',
payload: elements
}
}

@ -1,495 +1,20 @@
import React from 'react'
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import axios, { AxiosResponse } from 'axios'
import { checkSpecialChars, checkSlash, extractParentFromKey, extractNameFromKey, createNonClashingNameAsync } from '@remix-ui/helper'
import { checkSpecialChars, checkSlash, extractNameFromKey, createNonClashingNameAsync } from '@remix-ui/helper'
import Gists from 'gists'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel/type'
import { addInputFieldSuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchDirectoryError, fetchDirectoryRequest, fetchDirectorySuccess, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, focusElement, hideNotification, hidePopUp, removeInputFieldSuccess, setCurrentWorkspace, setDeleteWorkspace, setMode, setRenameWorkspace, setWorkspaces } from './payload'
import { listenOnEvents } from './events'
const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params')
const examples = require('../../../../../../apps/remix-ide/src/app/editor/examples')
const queuedEvents = []
const pendingEvents = {}
const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - '
const queryParams = new QueryParams()
let plugin, dispatch: React.Dispatch<any>
const setCurrentWorkspace = (workspace: string) => {
return {
type: 'SET_CURRENT_WORKSPACE',
payload: workspace
}
}
const setWorkspaces = (workspaces: string[]) => {
return {
type: 'SET_WORKSPACES',
payload: workspaces
}
}
const setMode = (mode: 'browser' | 'localhost') => {
return {
type: 'SET_MODE',
payload: mode
}
}
const fetchDirectoryError = (error: any) => {
return {
type: 'FETCH_DIRECTORY_ERROR',
payload: error
}
}
const fetchDirectoryRequest = (promise: Promise<any>) => {
return {
type: 'FETCH_DIRECTORY_REQUEST',
payload: promise
}
}
const fetchDirectorySuccess = (path: string, fileTree) => {
return {
type: 'FETCH_DIRECTORY_SUCCESS',
payload: { path, fileTree }
}
}
const displayNotification = (title: string, message: string, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => {
return {
type: 'DISPLAY_NOTIFICATION',
payload: { title, message, labelOk, labelCancel, actionOk, actionCancel }
}
}
const hideNotification = () => {
return {
type: 'HIDE_NOTIFICATION'
}
}
const fileAddedSuccess = (filePath: string) => {
return {
type: 'FILE_ADDED_SUCCESS',
payload: filePath
}
}
const folderAddedSuccess = (folderPath: string, fileTree) => {
return {
type: 'FOLDER_ADDED_SUCCESS',
payload: { path: folderPath, fileTree }
}
}
const fileRemovedSuccess = (removePath: string) => {
return {
type: 'FILE_REMOVED_SUCCESS',
payload: removePath
}
}
const fileRenamedSuccess = (path: string, oldPath: string, fileTree) => {
return {
type: 'FILE_RENAMED_SUCCESS',
payload: { path, oldPath, fileTree }
}
}
const rootFolderChangedSuccess = (path: string) => {
return {
type: 'ROOT_FOLDER_CHANGED',
payload: path
}
}
const addInputFieldSuccess = (path: string, fileTree, type: 'file' | 'folder' | 'gist') => {
return {
type: 'ADD_INPUT_FIELD',
payload: { path, fileTree, type }
}
}
const removeInputFieldSuccess = (path: string, fileTree) => {
return {
type: 'REMOVE_INPUT_FIELD',
payload: { path, fileTree }
}
}
const setReadOnlyMode = (mode: boolean) => {
return {
type: 'SET_READ_ONLY_MODE',
payload: mode
}
}
const createWorkspaceError = (error: any) => {
return {
type: 'CREATE_WORKSPACE_ERROR',
payload: error
}
}
const createWorkspaceRequest = (promise: Promise<any>) => {
return {
type: 'CREATE_WORKSPACE_REQUEST',
payload: promise
}
}
const createWorkspaceSuccess = (workspaceName: string) => {
return {
type: 'CREATE_WORKSPACE_SUCCESS',
payload: workspaceName
}
}
const fetchWorkspaceDirectoryError = (error: any) => {
return {
type: 'FETCH_WORKSPACE_DIRECTORY_ERROR',
payload: error
}
}
const fetchWorkspaceDirectoryRequest = (promise: Promise<any>) => {
return {
type: 'FETCH_WORKSPACE_DIRECTORY_REQUEST',
payload: promise
}
}
const fetchWorkspaceDirectorySuccess = (path: string, fileTree) => {
return {
type: 'FETCH_WORKSPACE_DIRECTORY_SUCCESS',
payload: { path, fileTree }
}
}
const setRenameWorkspace = (oldName: string, workspaceName: string) => {
return {
type: 'RENAME_WORKSPACE',
payload: { oldName, workspaceName }
}
}
const setDeleteWorkspace = (workspaceName: string) => {
return {
type: 'DELETE_WORKSPACE',
payload: workspaceName
}
}
const displayPopUp = (message: string) => {
return {
type: 'DISPLAY_POPUP_MESSAGE',
payload: message
}
}
const hidePopUp = () => {
return {
type: 'HIDE_POPUP_MESSAGE'
}
}
const focusElement = (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => {
return {
type: 'SET_FOCUS_ELEMENT',
payload: elements
}
}
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) && template === 'default-template') throw new Error('workspace already exists')
else {
const workspaceProvider = plugin.fileProviders.workspace
await workspaceProvider.createWorkspace(workspaceName)
if (setDefaults) {
const params = queryParams.get()
switch (template) {
case 'code-template':
// creates a new workspace code-sample and loads code from url params.
try {
await workspaceProvider.createWorkspace(workspaceName)
let path = ''; let content = ''
if (params.code) {
const hash = bufferToHex(keccakFromString(params.code))
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
await workspaceProvider.set(path, content)
} else if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
await workspaceProvider.set(path, content)
}
await plugin.fileManager.openFile(path)
} catch (e) {
console.error(e)
}
break
case 'gist-template':
// creates a new workspace gist-sample and get the file from gist
try {
const gistId = params.gist
const response: AxiosResponse = await axios.get(`https://api.github.com/gists/${gistId}`)
const data = response.data
if (!data.files) {
return dispatch(displayNotification('Gist load error', 'No files found', 'OK', null, () => { dispatch(hideNotification()) }, null))
}
const obj = {}
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) {
const provider = plugin.fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null))
}
})
} catch (e) {
dispatch(displayNotification('Gist load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null))
console.error(e)
}
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
}
}
}
}
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)
}
const renameWorkspaceFromProvider = async (oldName: string, workspaceName: string) => {
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')
const browserProvider = plugin.fileProviders.browser
const workspaceProvider = plugin.fileProviders.workspace
const workspacesPath = workspaceProvider.workspacesPath
browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
workspaceProvider.setWorkspace(workspaceName)
plugin.emit('renameWorkspace', { name: workspaceName })
}
const deleteWorkspaceFromProvider = async (workspaceName: string) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
await plugin.fileManager.closeAllFiles()
plugin.fileProviders.browser.remove(workspacesPath + '/' + workspaceName)
plugin.emit('deleteWorkspace', { name: workspaceName })
}
const getWorkspaces = async (): Promise<string[]> | 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) {
dispatch(displayNotification('Workspaces', '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.', 'OK', null, () => { dispatch(hideNotification()) }, null))
console.log(e)
}
}
const packageGistFiles = async (directory) => {
return new Promise((resolve, reject) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = workspaceProvider.isFile(directory)
const ret = {}
if (isFile) {
try {
workspaceProvider.get(directory, (error, content) => {
if (error) throw new Error('An error ocurred while getting file content. ' + directory)
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
directory = directory.replace(/\//g, '...')
ret[directory] = { content }
return resolve(ret)
})
} catch (e) {
return reject(e)
}
} else {
try {
(async () => {
await workspaceProvider.copyFolderToJson(directory, ({ path, content }) => {
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
if (path.indexOf('gist-') === 0) {
path = path.split('/')
path.shift()
path = path.join('/')
}
path = path.replace(/\//g, '...')
ret[path] = { content }
})
resolve(ret)
})()
} catch (e) {
return reject(e)
}
}
})
}
const handleGistResponse = (error, data) => {
if (error) {
dispatch(displayNotification('Publish to gist Failed', 'Failed to manage gist: ' + error, 'Close', null))
} else {
if (data.html_url) {
dispatch(displayNotification('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, 'OK', 'Cancel', () => {
window.open(data.html_url, '_blank')
}, () => {}))
} else {
const error = JSON.stringify(data.errors, null, '\t') || ''
const message = data.message === 'Not Found' ? data.message + '. Please make sure the API token has right to create a gist.' : data.message
dispatch(displayNotification('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, 'Close', null))
}
}
}
/**
* This function is to get the original content of given gist
* @params id is the gist id to fetch
*/
const getOriginalFiles = async (id) => {
if (!id) {
return []
}
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
}
const listenOnEvents = (provider) => {
provider.event.on('fileAdded', async (filePath: string) => {
await executeEvent('fileAdded', filePath)
})
provider.event.on('folderAdded', async (folderPath: string) => {
if (folderPath.indexOf('/.workspaces') === 0) return
await executeEvent('folderAdded', folderPath)
})
provider.event.on('fileRemoved', async (removePath: string) => {
await executeEvent('fileRemoved', removePath)
})
provider.event.on('fileRenamed', async (oldPath: string, newPath: string) => {
await executeEvent('fileRenamed', oldPath, newPath)
})
plugin.on('remixd', 'rootFolderChanged', async (path: string) => {
await executeEvent('rootFolderChanged', path)
})
// provider.event.on('disconnected', () => {
// dispatch(setMode('browser'))
// })
provider.event.on('connected', async () => {
fetchWorkspaceDirectory('/')(dispatch)
// setState(prevState => {
// return { ...prevState, hideRemixdExplorer: false, loadingLocalhost: false }
// })
})
provider.event.on('disconnected', async () => {
const workspaceProvider = plugin.fileProviders.workspace
await switchToWorkspace(workspaceProvider.workspace)(dispatch)
})
provider.event.on('loadingLocalhost', async () => {
await switchToWorkspace(LOCALHOST)(dispatch)
// setState(prevState => {
// return { ...prevState, loadingLocalhost: true }
// })
})
provider.event.on('fileExternallyChanged', async (path: string, file: { content: string }) => {
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== file.content) {
if (provider.isReadOnly(path)) return editor.setText(file.content)
dispatch(displayNotification(
path + ' changed',
'This file has been changed outside of Remix IDE.',
'Replace by the new content', 'Keep the content displayed in Remix',
() => {
editor.setText(file.content)
}
))
}
})
provider.event.on('fileRenamedError', async () => {
dispatch(displayNotification('File Renamed Failed', '', 'Ok', 'Cancel'))
})
plugin.on('filePanel', 'displayNewFileInput', (path) => {
addInputField('file', path)(dispatch)
})
plugin.on('filePanel', 'uploadFileEvent', (dir: string, target) => {
uploadFile(target, dir)(dispatch)
})
}
export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => {
if (filePanelPlugin) {
plugin = filePanelPlugin
@ -518,17 +43,14 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
}
}
listenOnEvents(workspaceProvider)
listenOnEvents(localhostProvider)
// provider.event.on('createWorkspace', (name) => {
// createNewWorkspace(name)
// })
listenOnEvents(plugin, workspaceProvider)(dispatch)
listenOnEvents(plugin, localhostProvider)(dispatch)
// dispatch(setWorkspaces(workspaces))
dispatch(setMode('browser'))
}
}
export const fetchDirectory = (path: string) => (dispatch: React.Dispatch<any>) => {
export const fetchDirectory = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
@ -547,7 +69,7 @@ export const fetchDirectory = (path: string) => (dispatch: React.Dispatch<any>)
return promise
}
export const addInputField = (type: 'file' | 'folder', path: string) => (dispatch: React.Dispatch<any>) => {
export const addInputField = async (type: 'file' | 'folder', path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
@ -565,7 +87,7 @@ export const addInputField = (type: 'file' | 'folder', path: string) => (dispatc
return promise
}
export const removeInputField = (path: string) => (dispatch: React.Dispatch<any>) => {
export const removeInputField = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
@ -583,21 +105,21 @@ export const removeInputField = (path: string) => (dispatch: React.Dispatch<any>
return promise
}
export const createWorkspace = (workspaceName: string) => (dispatch: React.Dispatch<any>) => {
export const createWorkspace = async (workspaceName: string) => {
const promise = createWorkspaceTemplate(workspaceName, true, 'default-template')
dispatch(createWorkspaceRequest(promise))
promise.then(async () => {
await plugin.fileManager.closeAllFiles()
dispatch(createWorkspaceSuccess(workspaceName))
switchToWorkspace(workspaceName)(dispatch)
switchToWorkspace(workspaceName)
}).catch((error) => {
dispatch(createWorkspaceError({ error }))
})
return promise
}
export const fetchWorkspaceDirectory = (path: string) => (dispatch: React.Dispatch<any>) => {
export const fetchWorkspaceDirectory = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
@ -616,7 +138,7 @@ export const fetchWorkspaceDirectory = (path: string) => (dispatch: React.Dispat
return promise
}
export const switchToWorkspace = (name: string) => async (dispatch: React.Dispatch<any>) => {
export const switchToWorkspace = async (name: string) => {
await plugin.fileManager.closeAllFiles()
if (name === LOCALHOST) {
plugin.fileManager.setMode('localhost')
@ -641,17 +163,17 @@ export const switchToWorkspace = (name: string) => async (dispatch: React.Dispat
}
}
export const renameWorkspace = (oldName: string, workspaceName: string) => async (dispatch: React.Dispatch<any>) => {
export const renameWorkspace = async (oldName: string, workspaceName: string) => {
await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName))
}
export const deleteWorkspace = (workspaceName: string) => async (dispatch: React.Dispatch<any>) => {
export const deleteWorkspace = async (workspaceName: string) => {
await deleteWorkspaceFromProvider(workspaceName)
await dispatch(setDeleteWorkspace(workspaceName))
}
export const publishToGist = (path?: string, type?: string) => async (dispatch: React.Dispatch<any>) => {
export const publishToGist = async (path?: string, type?: string) => {
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = path || '/'
const id = type === 'gist' ? extractNameFromKey(path).split('-')[1] : null
@ -717,11 +239,11 @@ export const publishToGist = (path?: string, type?: string) => async (dispatch:
}
}
export const clearPopUp = () => async (dispatch: React.Dispatch<any>) => {
export const clearPopUp = async () => {
dispatch(hidePopUp())
}
export const uploadFile = (target, targetFolder: string) => async (dispatch: React.Dispatch<any>) => {
export const uploadFile = async (target, targetFolder: string) => {
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
@ -766,7 +288,7 @@ export const uploadFile = (target, targetFolder: string) => async (dispatch: Rea
})
}
export const createNewFile = (path: string, rootDir: string) => async (dispatch: React.Dispatch<any>) => {
export const createNewFile = async (path: string, rootDir: string) => {
const fileManager = plugin.fileManager
const newName = await createNonClashingNameAsync(path, fileManager)
const createFile = await fileManager.writeFile(newName, '')
@ -777,15 +299,15 @@ export const createNewFile = (path: string, rootDir: string) => async (dispatch:
const path = newName.indexOf(rootDir + '/') === 0 ? newName.replace(rootDir + '/', '') : newName
await fileManager.open(path)
setFocusElement([{ key: path, type: 'file' }])(dispatch)
setFocusElement([{ key: path, type: 'file' }])
}
}
export const setFocusElement = (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => async (dispatch: React.Dispatch<any>) => {
export const setFocusElement = async (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => {
dispatch(focusElement(elements))
}
export const createNewFolder = (path: string, rootDir: string) => async (dispatch: React.Dispatch<any>) => {
export const createNewFolder = async (path: string, rootDir: string) => {
const fileManager = plugin.fileManager
const dirName = path + '/'
const exists = await fileManager.exists(dirName)
@ -798,7 +320,7 @@ export const createNewFolder = (path: string, rootDir: string) => async (dispatc
dispatch(focusElement([{ key: path, type: 'folder' }]))
}
export const deletePath = (path: string[]) => async (dispatch: React.Dispatch<any>) => {
export const deletePath = async (path: string[]) => {
const fileManager = plugin.fileManager
for (const p of path) {
@ -812,7 +334,7 @@ export const deletePath = (path: string[]) => async (dispatch: React.Dispatch<an
}
}
export const renamePath = (oldPath: string, newPath: string) => async (dispatch: React.Dispatch<any>) => {
export const renamePath = async (oldPath: string, newPath: string) => {
const fileManager = plugin.fileManager
const exists = await fileManager.exists(newPath)
@ -823,7 +345,7 @@ export const renamePath = (oldPath: string, newPath: string) => async (dispatch:
}
}
export const copyFile = (src: string, dest: string) => async (dispatch: React.Dispatch<any>) => {
export const copyFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
@ -834,7 +356,7 @@ export const copyFile = (src: string, dest: string) => async (dispatch: React.Di
}
}
export const copyFolder = (src: string, dest: string) => async (dispatch: React.Dispatch<any>) => {
export const copyFolder = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
@ -845,7 +367,7 @@ export const copyFolder = (src: string, dest: string) => async (dispatch: React.
}
}
export const runScript = (path: string) => async (dispatch: React.Dispatch<any>) => {
export const runScript = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
provider.get(path, (error, content: string) => {
@ -857,122 +379,224 @@ export const runScript = (path: string) => async (dispatch: React.Dispatch<any>)
})
}
export const emitContextMenuEvent = (cmd: customAction) => async () => {
export const emitContextMenuEvent = async (cmd: customAction) => {
plugin.call(cmd.id, cmd.name, cmd)
}
export const handleClickFile = (path: string, type: 'file' | 'folder' | 'gist') => async (dispatch: React.Dispatch<any>) => {
export const handleClickFile = async (path: string, type: 'file' | 'folder' | 'gist') => {
plugin.fileManager.open(path)
dispatch(focusElement([{ key: path, type }]))
}
const fileAdded = async (filePath: string) => {
await dispatch(fileAddedSuccess(filePath))
if (filePath.includes('_test.sol')) {
plugin.emit('newTestFileCreated', filePath)
}
}
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) && template === 'default-template') throw new Error('workspace already exists')
else {
const workspaceProvider = plugin.fileProviders.workspace
const folderAdded = async (folderPath: string) => {
const provider = plugin.fileManager.currentFileProvider()
const path = extractParentFromKey(folderPath) || provider.workspace || provider.type || ''
await workspaceProvider.createWorkspace(workspaceName)
if (setDefaults) {
const params = queryParams.get()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
switch (template) {
case 'code-template':
// creates a new workspace code-sample and loads code from url params.
try {
await workspaceProvider.createWorkspace(workspaceName)
let path = ''; let content = ''
resolve(fileTree)
})
})
if (params.code) {
const hash = bufferToHex(keccakFromString(params.code))
promise.then((files) => {
dispatch(folderAddedSuccess(path, files))
}).catch((error) => {
console.error(error)
})
return promise
}
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
await workspaceProvider.set(path, content)
} else if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url)
const fileRemoved = async (removePath: string) => {
await dispatch(fileRemovedSuccess(removePath))
}
path = data.cleanUrl
content = data.content
await workspaceProvider.set(path, content)
}
await plugin.fileManager.openFile(path)
} catch (e) {
console.error(e)
}
break
const fileRenamed = async (oldPath: string) => {
const provider = plugin.fileManager.currentFileProvider()
const path = extractParentFromKey(oldPath) || provider.workspace || provider.type || ''
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
case 'gist-template':
// creates a new workspace gist-sample and get the file from gist
try {
const gistId = params.gist
const response: AxiosResponse = await axios.get(`https://api.github.com/gists/${gistId}`)
const data = response.data
resolve(fileTree)
})
})
if (!data.files) {
return dispatch(displayNotification('Gist load error', 'No files found', 'OK', null, () => { dispatch(hideNotification()) }, null))
}
const obj = {}
promise.then((files) => {
dispatch(fileRenamedSuccess(path, oldPath, files))
}).catch((error) => {
console.error(error)
})
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) {
const provider = plugin.fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null))
}
})
} catch (e) {
dispatch(displayNotification('Gist load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null))
console.error(e)
}
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
}
}
}
}
const rootFolderChanged = async (path) => {
await dispatch(rootFolderChangedSuccess(path))
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)
}
const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemoved' | 'fileRenamed' | 'rootFolderChanged', ...args) => {
if (Object.keys(pendingEvents).length) {
return queuedEvents.push({ eventName, path: args[0] })
}
pendingEvents[eventName + args[0]] = { eventName, path: args[0] }
switch (eventName) {
case 'fileAdded':
await fileAdded(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
await executeEvent(next.eventName, next.path)
}
break
const renameWorkspaceFromProvider = async (oldName: string, workspaceName: string) => {
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')
const browserProvider = plugin.fileProviders.browser
const workspaceProvider = plugin.fileProviders.workspace
const workspacesPath = workspaceProvider.workspacesPath
browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
workspaceProvider.setWorkspace(workspaceName)
plugin.emit('renameWorkspace', { name: workspaceName })
}
case 'folderAdded':
await folderAdded(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
const deleteWorkspaceFromProvider = async (workspaceName: string) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
await executeEvent(next.eventName, next.path)
}
break
await plugin.fileManager.closeAllFiles()
plugin.fileProviders.browser.remove(workspacesPath + '/' + workspaceName)
plugin.emit('deleteWorkspace', { name: workspaceName })
}
case 'fileRemoved':
await fileRemoved(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
const getWorkspaces = async (): Promise<string[]> | undefined => {
try {
const workspaces: string[] = await new Promise((resolve, reject) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
await executeEvent(next.eventName, next.path)
}
break
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 + '/', '')))
})
})
plugin.setWorkspaces(workspaces)
return workspaces
} catch (e) {
dispatch(displayNotification('Workspaces', '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.', 'OK', null, () => { dispatch(hideNotification()) }, null))
console.log(e)
}
}
case 'fileRenamed':
await fileRenamed(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
const packageGistFiles = async (directory) => {
return new Promise((resolve, reject) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = workspaceProvider.isFile(directory)
const ret = {}
await executeEvent(next.eventName, next.path)
if (isFile) {
try {
workspaceProvider.get(directory, (error, content) => {
if (error) throw new Error('An error ocurred while getting file content. ' + directory)
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
directory = directory.replace(/\//g, '...')
ret[directory] = { content }
return resolve(ret)
})
} catch (e) {
return reject(e)
}
} else {
try {
(async () => {
await workspaceProvider.copyFolderToJson(directory, ({ path, content }) => {
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
if (path.indexOf('gist-') === 0) {
path = path.split('/')
path.shift()
path = path.join('/')
}
path = path.replace(/\//g, '...')
ret[path] = { content }
})
resolve(ret)
})()
} catch (e) {
return reject(e)
}
break
}
})
}
case 'rootFolderChanged':
await rootFolderChanged(args[0])
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
const handleGistResponse = (error, data) => {
if (error) {
dispatch(displayNotification('Publish to gist Failed', 'Failed to manage gist: ' + error, 'Close', null))
} else {
if (data.html_url) {
dispatch(displayNotification('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, 'OK', 'Cancel', () => {
window.open(data.html_url, '_blank')
}, () => {}))
} else {
const error = JSON.stringify(data.errors, null, '\t') || ''
const message = data.message === 'Not Found' ? data.message + '. Please make sure the API token has right to create a gist.' : data.message
await executeEvent(next.eventName, next.path)
}
break
dispatch(displayNotification('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, 'Close', null))
}
}
}
/**
* This function is to get the original content of given gist
* @params id is the gist id to fetch
*/
const getOriginalFiles = async (id) => {
if (!id) {
return []
}
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
}

@ -32,83 +32,83 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
}
const dispatchFetchDirectory = async (path: string) => {
await fetchDirectory(path)(fsDispatch)
await fetchDirectory(path)
}
const dispatchAddInputField = async (path: string, type: 'file' | 'folder') => {
await addInputField(type, path)(fsDispatch)
await addInputField(type, path)
}
const dispatchRemoveInputField = async (path: string) => {
await removeInputField(path)(fsDispatch)
await removeInputField(path)
}
const dispatchCreateWorkspace = async (workspaceName: string) => {
await createWorkspace(workspaceName)(fsDispatch)
await createWorkspace(workspaceName)
}
const dispatchFetchWorkspaceDirectory = async (path: string) => {
await fetchWorkspaceDirectory(path)(fsDispatch)
await fetchWorkspaceDirectory(path)
}
const dispatchSwitchToWorkspace = async (name: string) => {
await switchToWorkspace(name)(fsDispatch)
await switchToWorkspace(name)
}
const dispatchRenameWorkspace = async (oldName: string, workspaceName: string) => {
await renameWorkspace(oldName, workspaceName)(fsDispatch)
await renameWorkspace(oldName, workspaceName)
}
const dispatchDeleteWorkspace = async (workspaceName: string) => {
await deleteWorkspace(workspaceName)(fsDispatch)
await deleteWorkspace(workspaceName)
}
const dispatchPublishToGist = async (path?: string, type?: string) => {
await publishToGist(path, type)(fsDispatch)
await publishToGist(path, type)
}
const dispatchUploadFile = async (target?: SyntheticEvent, targetFolder?: string) => {
await uploadFile(target, targetFolder)(fsDispatch)
await uploadFile(target, targetFolder)
}
const dispatchCreateNewFile = async (path: string, rootDir: string) => {
await createNewFile(path, rootDir)(fsDispatch)
await createNewFile(path, rootDir)
}
const dispatchSetFocusElement = async (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => {
await setFocusElement(elements)(fsDispatch)
await setFocusElement(elements)
}
const dispatchCreateNewFolder = async (path: string, rootDir: string) => {
await createNewFolder(path, rootDir)(fsDispatch)
await createNewFolder(path, rootDir)
}
const dispatchDeletePath = async (path: string[]) => {
await deletePath(path)(fsDispatch)
await deletePath(path)
}
const dispatchRenamePath = async (oldPath: string, newPath: string) => {
await renamePath(oldPath, newPath)(fsDispatch)
await renamePath(oldPath, newPath)
}
const dispatchCopyFile = async (src: string, dest: string) => {
await copyFile(src, dest)(fsDispatch)
await copyFile(src, dest)
}
const dispatchCopyFolder = async (src: string, dest: string) => {
await copyFolder(src, dest)(fsDispatch)
await copyFolder(src, dest)
}
const dispatchRunScript = async (path: string) => {
await runScript(path)(fsDispatch)
await runScript(path)
}
const dispatchEmitContextMenuEvent = async (cmd: customAction) => {
await emitContextMenuEvent(cmd)()
await emitContextMenuEvent(cmd)
}
const dispatchHandleClickFile = async (path: string, type: 'file' | 'folder' | 'gist') => {
await handleClickFile(path, type)(fsDispatch)
await handleClickFile(path, type)
}
useEffect(() => {
@ -171,7 +171,7 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
const handleToaster = () => {
setFocusToaster('')
clearPopUp()(fsDispatch)
clearPopUp()
}
const toast = (toasterMsg: string) => {

@ -42,17 +42,6 @@ export function Workspace (props: WorkspaceProps) {
}
}, [global.fs.browser.workspaces])
/* implement an external API, consumed by the parent */
props.plugin.request.createWorkspace = () => {
return createWorkspace()
}
props.plugin.request.getCurrentWorkspace = () => {
return { name: currentWorkspace, isLocalhost: currentWorkspace === LOCALHOST, absolutePath: `${props.plugin.workspace.workspacesPath}/${currentWorkspace}` }
}
/* workspace creation, renaming and deletion */
const renameCurrentWorkspace = () => {
global.modal('Rename Current Workspace', renameModalMessage(), 'OK', onFinishRenameWorkspace, '')
}

Loading…
Cancel
Save