split files to fix circular dependency errors

pull/5370/head
ioedeveloper 3 years ago
parent 29f9b0edcd
commit e872dd338a
  1. 27
      libs/remix-ui/workspace/src/lib/actions/events.ts
  2. 378
      libs/remix-ui/workspace/src/lib/actions/index.ts
  3. 19
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  4. 542
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  5. 3
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  6. 2
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  7. 38
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  8. 10
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx

@ -1,7 +1,7 @@
import { extractParentFromKey } from '@remix-ui/helper'
import React from 'react'
import { action } from '../types'
import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, removeContextMenuItem, rootFolderChangedSuccess, setContextMenuItem } from './payload'
import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, rootFolderChangedSuccess, setContextMenuItem, setMode } from './payload'
import { addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace'
const queuedEvents = []
@ -61,28 +61,25 @@ export const listenOnProviderEvents = (provider) => async (reducerDispatch: Reac
await executeEvent('fileRenamed', oldPath, newPath)
})
// 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 () => {
plugin.fileManager.setMode('browser')
dispatch(setMode('browser'))
dispatch(loadLocalhostError('Remixd disconnected!'))
const workspaceProvider = plugin.fileProviders.workspace
await switchToWorkspace(workspaceProvider.workspace)
})
provider.event.on('connected', async () => {
plugin.fileManager.setMode('localhost')
dispatch(setMode('localhost'))
fetchWorkspaceDirectory('/')
dispatch(loadLocalhostSuccess())
})
provider.event.on('loadingLocalhost', async () => {
await switchToWorkspace(LOCALHOST)
// setState(prevState => {
// return { ...prevState, loadingLocalhost: true }
// })
dispatch(loadLocalhostRequest())
})
provider.event.on('fileExternallyChanged', async (path: string, file: { content: string }) => {

@ -0,0 +1,378 @@
import React from 'react'
import { extractNameFromKey, createNonClashingNameAsync } from '@remix-ui/helper'
import Gists from 'gists'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel/type'
import { displayNotification, displayPopUp, fetchDirectoryError, fetchDirectoryRequest, fetchDirectorySuccess, focusElement, hideNotification, hidePopUp, removeInputFieldSuccess, setCurrentWorkspace, setDeleteWorkspace, setExpandPath, setMode, setWorkspaces } from './payload'
import { listenOnPluginEvents, listenOnProviderEvents } from './events'
import { createWorkspaceTemplate, loadWorkspacePreset, setPlugin } from './workspace'
export * from './events'
export * from './workspace'
const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params')
const queryParams = new QueryParams()
let plugin, dispatch: React.Dispatch<any>
export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => {
if (filePanelPlugin) {
plugin = filePanelPlugin
dispatch = reducerDispatch
setPlugin(plugin, dispatch)
const workspaceProvider = filePanelPlugin.fileProviders.workspace
const localhostProvider = filePanelPlugin.fileProviders.localhost
const params = queryParams.get()
const workspaces = await getWorkspaces() || []
dispatch(setWorkspaces(workspaces))
if (params.gist) {
await createWorkspaceTemplate('gist-sample', 'gist-template')
await loadWorkspacePreset('gist-template')
dispatch(setCurrentWorkspace('gist-sample'))
} else if (params.code || params.url) {
await createWorkspaceTemplate('code-sample', 'code-template')
await loadWorkspacePreset('code-template')
dispatch(setCurrentWorkspace('code-sample'))
} else {
if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'default-template')
await loadWorkspacePreset('default-template')
dispatch(setCurrentWorkspace('default_workspace'))
} else {
if (workspaces.length > 0) {
workspaceProvider.setWorkspace(workspaces[workspaces.length - 1])
dispatch(setCurrentWorkspace(workspaces[workspaces.length - 1]))
}
}
}
listenOnPluginEvents(plugin)
listenOnProviderEvents(workspaceProvider)(dispatch)
listenOnProviderEvents(localhostProvider)(dispatch)
dispatch(setMode('browser'))
}
}
export const fetchDirectory = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
dispatch(fetchDirectoryRequest(promise))
promise.then((fileTree) => {
dispatch(fetchDirectorySuccess(path, fileTree))
}).catch((error) => {
dispatch(fetchDirectoryError({ error }))
})
return promise
}
export const removeInputField = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
promise.then((files) => {
dispatch(removeInputFieldSuccess(path, files))
}).catch((error) => {
console.error(error)
})
return promise
}
export const deleteWorkspace = async (workspaceName: string) => {
await deleteWorkspaceFromProvider(workspaceName)
await dispatch(setDeleteWorkspace(workspaceName))
}
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
try {
const packaged = await packageGistFiles(folder)
// check for token
const config = plugin.registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
if (!accessToken) {
dispatch(displayNotification('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Close', null, () => {}))
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
const gists = new Gists({ token: accessToken })
if (id) {
const originalFileList = await getOriginalFiles(id)
// Telling the GIST API to remove files
const updatedFileList = Object.keys(packaged)
const allItems = Object.keys(originalFileList)
.filter(fileName => updatedFileList.indexOf(fileName) === -1)
.reduce((acc, deleteFileName) => ({
...acc,
[deleteFileName]: null
}), originalFileList)
// adding new files
updatedFileList.forEach((file) => {
const _items = file.split('/')
const _fileName = _items[_items.length - 1]
allItems[_fileName] = packaged[file]
})
dispatch(displayPopUp('Saving gist (' + id + ') ...'))
gists.edit({
description: description,
public: true,
files: allItems,
id: id
}, (error, result) => {
handleGistResponse(error, result)
if (!error) {
for (const key in allItems) {
if (allItems[key] === null) delete allItems[key]
}
}
})
} else {
// id is not existing, need to create a new gist
dispatch(displayPopUp('Creating a new gist ...'))
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
handleGistResponse(error, result)
})
}
}
} catch (error) {
console.log(error)
dispatch(displayNotification('Publish to gist Failed', 'Failed to create gist: ' + error.message, 'Close', null, async () => {}))
}
}
export const clearPopUp = async () => {
dispatch(hidePopUp())
}
export const createNewFile = async (path: string, rootDir: string) => {
const fileManager = plugin.fileManager
const newName = await createNonClashingNameAsync(path, fileManager)
const createFile = await fileManager.writeFile(newName, '')
if (!createFile) {
return dispatch(displayPopUp('Failed to create file ' + newName))
} else {
const path = newName.indexOf(rootDir + '/') === 0 ? newName.replace(rootDir + '/', '') : newName
await fileManager.open(path)
setFocusElement([{ key: path, type: 'file' }])
}
}
export const setFocusElement = async (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => {
dispatch(focusElement(elements))
}
export const createNewFolder = async (path: string, rootDir: string) => {
const fileManager = plugin.fileManager
const dirName = path + '/'
const exists = await fileManager.exists(dirName)
if (exists) {
return dispatch(displayNotification('Rename File Failed', `A file or folder ${extractNameFromKey(path)} already exists at this location. Please choose a different name.`, 'Close', null, () => {}))
}
await fileManager.mkdir(dirName)
path = path.indexOf(rootDir + '/') === 0 ? path.replace(rootDir + '/', '') : path
dispatch(focusElement([{ key: path, type: 'folder' }]))
}
export const deletePath = async (path: string[]) => {
const fileManager = plugin.fileManager
for (const p of path) {
try {
await fileManager.remove(p)
} catch (e) {
const isDir = await fileManager.isDirectory(p)
dispatch(displayPopUp(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`))
}
}
}
export const renamePath = async (oldPath: string, newPath: string) => {
const fileManager = plugin.fileManager
const exists = await fileManager.exists(newPath)
if (exists) {
dispatch(displayNotification('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, 'Close', null, () => {}))
} else {
await fileManager.rename(oldPath, newPath)
}
}
export const copyFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
fileManager.copyFile(src, dest)
} catch (error) {
console.log('Oops! An error ocurred while performing copyFile operation.' + error)
dispatch(displayPopUp('Oops! An error ocurred while performing copyFile operation.' + error))
}
}
export const copyFolder = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
fileManager.copyDir(src, dest)
} catch (error) {
console.log('Oops! An error ocurred while performing copyDir operation.' + error)
dispatch(displayPopUp('Oops! An error ocurred while performing copyDir operation.' + error))
}
}
export const runScript = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
provider.get(path, (error, content: string) => {
if (error) {
dispatch(displayPopUp(error))
return console.log(error)
}
plugin.call('scriptRunner', 'execute', content)
})
}
export const emitContextMenuEvent = async (cmd: customAction) => {
plugin.call(cmd.id, cmd.name, cmd)
}
export const handleClickFile = async (path: string, type: 'file' | 'folder' | 'gist') => {
plugin.fileManager.open(path)
dispatch(focusElement([{ key: path, type }]))
}
export const handleExpandPath = (paths: string[]) => {
dispatch(setExpandPath(paths))
}
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 + '/', '')))
})
})
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)
}
}
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 || []
}

@ -207,3 +207,22 @@ export const setExpandPath = (paths: string[]) => {
payload: paths
}
}
export const loadLocalhostError = (error: any) => {
return {
type: 'LOAD_LOCALHOST_ERROR',
payload: error
}
}
export const loadLocalhostRequest = () => {
return {
type: 'LOAD_LOCALHOST_REQUEST'
}
}
export const loadLocalhostSuccess = () => {
return {
type: 'LOAD_LOCALHOST_SUCCESS'
}
}

@ -1,75 +1,19 @@
import React from 'react'
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import axios, { AxiosResponse } from 'axios'
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, setExpandPath, setMode, setRenameWorkspace, setWorkspaces } from './payload'
import { listenOnPluginEvents, listenOnProviderEvents } from './events'
import { addInputFieldSuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setMode, setRenameWorkspace } from './payload'
import { checkSlash, checkSpecialChars } from '@remix-ui/helper'
const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params')
const examples = require('../../../../../../apps/remix-ide/src/app/editor/examples')
const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params')
const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - '
const queryParams = new QueryParams()
let plugin, dispatch: React.Dispatch<any>
export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => {
if (filePanelPlugin) {
plugin = filePanelPlugin
dispatch = reducerDispatch
const workspaceProvider = filePanelPlugin.fileProviders.workspace
const localhostProvider = filePanelPlugin.fileProviders.localhost
const params = queryParams.get()
const workspaces = await getWorkspaces() || []
dispatch(setWorkspaces(workspaces))
if (params.gist) {
await createWorkspaceTemplate('gist-sample', 'gist-template')
await loadWorkspacePreset('gist-template')
dispatch(setCurrentWorkspace('gist-sample'))
} else if (params.code || params.url) {
await createWorkspaceTemplate('code-sample', 'code-template')
await loadWorkspacePreset('code-template')
dispatch(setCurrentWorkspace('code-sample'))
} else {
if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'default-template')
await loadWorkspacePreset('default-template')
dispatch(setCurrentWorkspace('default_workspace'))
} else {
if (workspaces.length > 0) {
workspaceProvider.setWorkspace(workspaces[workspaces.length - 1])
dispatch(setCurrentWorkspace(workspaces[workspaces.length - 1]))
}
}
}
listenOnPluginEvents(plugin)
listenOnProviderEvents(workspaceProvider)(dispatch)
listenOnProviderEvents(localhostProvider)(dispatch)
dispatch(setMode('browser'))
}
}
export const fetchDirectory = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
dispatch(fetchDirectoryRequest(promise))
promise.then((fileTree) => {
dispatch(fetchDirectorySuccess(path, fileTree))
}).catch((error) => {
dispatch(fetchDirectoryError({ error }))
})
return promise
export const setPlugin = (filePanelPlugin, reducerDispatch) => {
plugin = filePanelPlugin
dispatch = reducerDispatch
}
export const addInputField = async (type: 'file' | 'folder', path: string) => {
@ -90,24 +34,6 @@ export const addInputField = async (type: 'file' | 'folder', path: string) => {
return promise
}
export const removeInputField = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
promise.then((files) => {
dispatch(removeInputFieldSuccess(path, files))
}).catch((error) => {
console.error(error)
})
return promise
}
export const createWorkspace = async (workspaceName: string) => {
const promise = createWorkspaceTemplate(workspaceName, 'default-template')
@ -123,280 +49,7 @@ export const createWorkspace = async (workspaceName: string) => {
return promise
}
export const fetchWorkspaceDirectory = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
dispatch(fetchWorkspaceDirectoryRequest(promise))
promise.then((fileTree) => {
dispatch(fetchWorkspaceDirectorySuccess(path, fileTree))
}).catch((error) => {
dispatch(fetchWorkspaceDirectoryError({ error }))
})
return promise
}
export const switchToWorkspace = async (name: string) => {
await plugin.fileManager.closeAllFiles()
if (name === LOCALHOST) {
plugin.fileManager.setMode('localhost')
const isActive = await plugin.call('manager', 'isActive', 'remixd')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'remixd')
dispatch(setMode('localhost'))
plugin.emit('setWorkspace', { name: LOCALHOST, isLocalhost: true })
} else if (name === NO_WORKSPACE) {
plugin.fileProviders.workspace.clearWorkspace()
dispatch(setCurrentWorkspace(null))
} else {
plugin.fileManager.setMode('browser')
const isActive = await plugin.call('manager', 'isActive', 'remixd')
if (isActive) plugin.call('manager', 'deactivatePlugin', 'remixd')
await plugin.fileProviders.workspace.setWorkspace(name)
dispatch(setMode('browser'))
dispatch(setCurrentWorkspace(name))
plugin.emit('setWorkspace', { name, isLocalhost: false })
}
}
export const renameWorkspace = async (oldName: string, workspaceName: string) => {
await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName))
}
export const deleteWorkspace = async (workspaceName: string) => {
await deleteWorkspaceFromProvider(workspaceName)
await dispatch(setDeleteWorkspace(workspaceName))
}
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
try {
const packaged = await packageGistFiles(folder)
// check for token
const config = plugin.registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
if (!accessToken) {
dispatch(displayNotification('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Close', null, () => {}))
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
const gists = new Gists({ token: accessToken })
if (id) {
const originalFileList = await getOriginalFiles(id)
// Telling the GIST API to remove files
const updatedFileList = Object.keys(packaged)
const allItems = Object.keys(originalFileList)
.filter(fileName => updatedFileList.indexOf(fileName) === -1)
.reduce((acc, deleteFileName) => ({
...acc,
[deleteFileName]: null
}), originalFileList)
// adding new files
updatedFileList.forEach((file) => {
const _items = file.split('/')
const _fileName = _items[_items.length - 1]
allItems[_fileName] = packaged[file]
})
dispatch(displayPopUp('Saving gist (' + id + ') ...'))
gists.edit({
description: description,
public: true,
files: allItems,
id: id
}, (error, result) => {
handleGistResponse(error, result)
if (!error) {
for (const key in allItems) {
if (allItems[key] === null) delete allItems[key]
}
}
})
} else {
// id is not existing, need to create a new gist
dispatch(displayPopUp('Creating a new gist ...'))
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
handleGistResponse(error, result)
})
}
}
} catch (error) {
console.log(error)
dispatch(displayNotification('Publish to gist Failed', 'Failed to create gist: ' + error.message, 'Close', null, async () => {}))
}
}
export const clearPopUp = async () => {
dispatch(hidePopUp())
}
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
// pick that up via the 'fileAdded' event from the files module.
[...target.files].forEach((file) => {
const workspaceProvider = plugin.fileProviders.workspace
const loadFile = (name: string): void => {
const fileReader = new FileReader()
fileReader.onload = async function (event) {
if (checkSpecialChars(file.name)) {
dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => {}))
return
}
const success = await workspaceProvider.set(name, event.target.result)
if (!success) {
return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => {}))
}
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
editor.setText(event.target.result)
}
}
fileReader.readAsText(file)
}
const name = `${targetFolder}/${file.name}`
workspaceProvider.exists(name).then(exist => {
if (!exist) {
loadFile(name)
} else {
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => {
loadFile(name)
}, () => {}))
}
}).catch(error => {
if (error) console.log(error)
})
})
}
export const createNewFile = async (path: string, rootDir: string) => {
const fileManager = plugin.fileManager
const newName = await createNonClashingNameAsync(path, fileManager)
const createFile = await fileManager.writeFile(newName, '')
if (!createFile) {
return dispatch(displayPopUp('Failed to create file ' + newName))
} else {
const path = newName.indexOf(rootDir + '/') === 0 ? newName.replace(rootDir + '/', '') : newName
await fileManager.open(path)
setFocusElement([{ key: path, type: 'file' }])
}
}
export const setFocusElement = async (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => {
dispatch(focusElement(elements))
}
export const createNewFolder = async (path: string, rootDir: string) => {
const fileManager = plugin.fileManager
const dirName = path + '/'
const exists = await fileManager.exists(dirName)
if (exists) {
return dispatch(displayNotification('Rename File Failed', `A file or folder ${extractNameFromKey(path)} already exists at this location. Please choose a different name.`, 'Close', null, () => {}))
}
await fileManager.mkdir(dirName)
path = path.indexOf(rootDir + '/') === 0 ? path.replace(rootDir + '/', '') : path
dispatch(focusElement([{ key: path, type: 'folder' }]))
}
export const deletePath = async (path: string[]) => {
const fileManager = plugin.fileManager
for (const p of path) {
try {
await fileManager.remove(p)
} catch (e) {
const isDir = await fileManager.isDirectory(p)
dispatch(displayPopUp(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`))
}
}
}
export const renamePath = async (oldPath: string, newPath: string) => {
const fileManager = plugin.fileManager
const exists = await fileManager.exists(newPath)
if (exists) {
dispatch(displayNotification('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, 'Close', null, () => {}))
} else {
await fileManager.rename(oldPath, newPath)
}
}
export const copyFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
fileManager.copyFile(src, dest)
} catch (error) {
console.log('Oops! An error ocurred while performing copyFile operation.' + error)
dispatch(displayPopUp('Oops! An error ocurred while performing copyFile operation.' + error))
}
}
export const copyFolder = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
fileManager.copyDir(src, dest)
} catch (error) {
console.log('Oops! An error ocurred while performing copyDir operation.' + error)
dispatch(displayPopUp('Oops! An error ocurred while performing copyDir operation.' + error))
}
}
export const runScript = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
provider.get(path, (error, content: string) => {
if (error) {
dispatch(displayPopUp(error))
return console.log(error)
}
plugin.call('scriptRunner', 'execute', content)
})
}
export const emitContextMenuEvent = async (cmd: customAction) => {
plugin.call(cmd.id, cmd.name, cmd)
}
export const handleClickFile = async (path: string, type: 'file' | 'folder' | 'gist') => {
plugin.fileManager.open(path)
dispatch(focusElement([{ key: path, type }]))
}
export const handleExpandPath = (paths: string[]) => {
dispatch(setExpandPath(paths))
}
const createWorkspaceTemplate = async (workspaceName: string, template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => {
export const createWorkspaceTemplate = async (workspaceName: string, 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')
@ -407,7 +60,7 @@ const createWorkspaceTemplate = async (workspaceName: string, template: 'gist-te
}
}
const loadWorkspacePreset = async (template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => {
export const loadWorkspacePreset = async (template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => {
const workspaceProvider = plugin.fileProviders.workspace
const params = queryParams.get()
@ -482,7 +135,7 @@ const loadWorkspacePreset = async (template: 'gist-template' | 'code-template' |
}
}
const workspaceExists = async (name: string) => {
export const workspaceExists = async (name: string) => {
const workspaceProvider = plugin.fileProviders.workspace
const browserProvider = plugin.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
@ -490,7 +143,31 @@ const workspaceExists = async (name: string) => {
return browserProvider.exists(workspacePath)
}
const renameWorkspaceFromProvider = async (oldName: string, workspaceName: string) => {
export const fetchWorkspaceDirectory = async (path: string) => {
const provider = plugin.fileManager.currentFileProvider()
const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
resolve(fileTree)
})
})
dispatch(fetchWorkspaceDirectoryRequest(promise))
promise.then((fileTree) => {
dispatch(fetchWorkspaceDirectorySuccess(path, fileTree))
}).catch((error) => {
dispatch(fetchWorkspaceDirectoryError({ error }))
})
return promise
}
export const renameWorkspace = async (oldName: string, workspaceName: string) => {
await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName))
}
export 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')
@ -502,110 +179,69 @@ const renameWorkspaceFromProvider = async (oldName: string, workspaceName: strin
plugin.emit('renameWorkspace', { name: workspaceName })
}
const deleteWorkspaceFromProvider = async (workspaceName: string) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
export const switchToWorkspace = async (name: string) => {
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
if (name === LOCALHOST) {
const isActive = await plugin.call('manager', 'isActive', 'remixd')
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 + '/', '')))
})
})
if (!isActive) await plugin.call('manager', 'activatePlugin', 'remixd')
dispatch(setMode('localhost'))
plugin.emit('setWorkspace', { name: LOCALHOST, isLocalhost: true })
} else if (name === NO_WORKSPACE) {
plugin.fileProviders.workspace.clearWorkspace()
dispatch(setCurrentWorkspace(null))
} else {
const isActive = await plugin.call('manager', 'isActive', 'remixd')
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)
if (isActive) plugin.call('manager', 'deactivatePlugin', 'remixd')
await plugin.fileProviders.workspace.setWorkspace(name)
dispatch(setMode('browser'))
dispatch(setCurrentWorkspace(name))
plugin.emit('setWorkspace', { name, isLocalhost: false })
}
}
const packageGistFiles = async (directory) => {
return new Promise((resolve, reject) => {
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
// pick that up via the 'fileAdded' event from the files module.
[...target.files].forEach((file) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = workspaceProvider.isFile(directory)
const ret = {}
const loadFile = (name: string): void => {
const fileReader = new FileReader()
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)
}
}
})
}
fileReader.onload = async function (event) {
if (checkSpecialChars(file.name)) {
dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => {}))
return
}
const success = await workspaceProvider.set(name, event.target.result)
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))
}
}
}
if (!success) {
return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => {}))
}
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
/**
* 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 []
}
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
editor.setText(event.target.result)
}
}
fileReader.readAsText(file)
}
const name = `${targetFolder}/${file.name}`
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
workspaceProvider.exists(name).then(exist => {
if (!exist) {
loadFile(name)
} else {
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => {
loadFile(name)
}, () => {}))
}
}).catch(error => {
if (error) console.log(error)
})
})
}

@ -10,7 +10,6 @@ import '../css/file-explorer.css'
import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath } from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileRender } from './file-render'
import { handleExpandPath } from '../actions/workspace'
export const FileExplorer = (props: FileExplorerProps) => {
const { name, contextMenuItems, removedContextMenuItems, files } = props
@ -404,7 +403,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} else {
expandPath = [...new Set(props.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(props.name)))]
}
handleExpandPath(expandPath)
props.dispatchHandleExpandPath(expandPath)
}
return (

@ -5,7 +5,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, addInputField, removeInputField, createWorkspace, fetchWorkspaceDirectory, switchToWorkspace, renameWorkspace, deleteWorkspace, clearPopUp, publishToGist, uploadFile, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath } from '../actions/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from '../actions'
import { Modal, WorkspaceProps } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace'

@ -533,6 +533,44 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
case 'LOAD_LOCALHOST_REQUEST': {
return {
...state,
localhost: {
...state.localhost,
isRequesting: true,
isSuccessful: false,
error: null
}
}
}
case 'LOAD_LOCALHOST_SUCCESS': {
return {
...state,
localhost: {
...state.localhost,
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'LOAD_LOCALHOST_ERROR': {
const payload = action.payload as string
return {
...state,
localhost: {
...state.localhost,
isRequesting: false,
isSuccessful: false,
error: payload
}
}
}
default:
throw new Error()
}

@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import { FileExplorer } from './components/file-explorer' // eslint-disable-line
import './css/remix-ui-workspace.css'
import { WorkspaceState } from './types'
import { FileSystemContext } from './contexts'
const canUpload = window.File || window.FileReader || window.FileList || window.Blob
@ -9,11 +8,6 @@ const canUpload = window.File || window.FileReader || window.FileList || window.
export function Workspace () {
const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - '
const [state] = useState<WorkspaceState>({
hideRemixdExplorer: true,
displayNewFile: false,
loadingLocalhost: false
})
const [currentWorkspace, setCurrentWorkspace] = useState<string>(NO_WORKSPACE)
const global = useContext(FileSystemContext)
const workspaceRenameInput = useRef()
@ -215,9 +209,9 @@ export function Workspace () {
}
</div>
{
state.loadingLocalhost ? <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>
global.fs.localhost.isRequesting ? <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>
: <div className='pl-2 filesystemexplorer remixui_treeview'>
{ global.fs.mode === 'localhost' &&
{ global.fs.mode === 'localhost' && global.fs.localhost.isSuccessful &&
<FileExplorer
name='localhost'
menuItems={['createNewFile', 'createNewFolder']}

Loading…
Cancel
Save