Update loading code from url params and fetch gist using axios

pull/1575/head
ioedeveloper 4 years ago
parent da9207ddd7
commit 501d4b01e4
  1. 10
      apps/remix-ide/src/app/panels/file-panel.js
  2. 151
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  3. 48
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  4. 64
      libs/remix-ui/file-explorer/src/lib/utils/index.ts
  5. 2
      libs/remix-ui/helper/src/index.ts
  6. 22
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  7. 2
      libs/remix-ui/workspace/src/index.ts
  8. 55
      libs/remix-ui/workspace/src/lib/actions/gist-handler.ts
  9. 98
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  10. 34
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  11. 61
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  12. 28
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  13. 10
      libs/remix-ui/workspace/src/lib/types/index.ts

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { FileSystemProvider, Workspace } from '@remix-ui/workspace' // eslint-disable-line
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')
@ -63,15 +63,17 @@ module.exports = class Filepanel extends ViewPlugin {
this.appManager = appManager
}
onActivation () {
this.renderComponent()
}
render () {
return this.el
}
renderComponent () {
ReactDOM.render(
<FileSystemProvider plugin={this}>
<Workspace plugin={this} />
</FileSystemProvider>
<FileSystemProvider plugin={this} />
, this.el)
}

@ -1,38 +1,26 @@
import React, { useEffect, useState, useRef, useReducer } from 'react' // eslint-disable-line
import React, { useEffect, useState, useRef, useReducer, useContext } from 'react' // eslint-disable-line
// import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' // eslint-disable-line
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import Gists from 'gists'
import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line
import { FileExplorerContextMenu } from './file-explorer-context-menu' // eslint-disable-line
import { FileExplorerProps, File, MenuItems } from './types'
import { FileExplorerProps, File, MenuItems, FileExplorerState } from './types'
import { fileSystemReducer, fileSystemInitialState } from './reducers/fileSystem'
import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import { init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import { FileSystemContext } from '@remix-ui/workspace'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
import { contextMenuActions } from './utils'
import './css/file-explorer.css'
const queryParams = new QueryParams()
export const FileExplorer = (props: FileExplorerProps) => {
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads, removedContextMenuItems, resetFocus } = props
const [state, setState] = useState<{
focusElement: { key: string, type: 'folder' | 'file' | 'gist' }[],
fileManager: any,
ctrlKey: boolean,
newFileName: string,
actions: { id: string, name: string, type?: Array<'folder' | 'gist' | 'file'>, path?: string[], extension?: string[], pattern?: string[], multiselect: boolean, label: string }[],
focusContext: { element: string, x: string, y: string, type: string },
focusEdit: { element: string, type: string, isNew: boolean, lastEdit: string },
expandPath: string[],
toasterMsg: string,
mouseOverElement: string,
showContextMenu: boolean,
reservedKeywords: string[],
copyElement: string[]
}>({
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads, removedContextMenuItems, resetFocus, files } = props
const [state, setState] = useState<FileExplorerState>({
focusElement: [{
key: '',
type: 'folder'
@ -40,67 +28,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
fileManager: null,
ctrlKey: false,
newFileName: '',
actions: [{
id: 'newFile',
name: 'New File',
type: ['folder', 'gist'],
multiselect: false,
label: ''
}, {
id: 'newFolder',
name: 'New Folder',
type: ['folder', 'gist'],
multiselect: false,
label: ''
}, {
id: 'rename',
name: 'Rename',
type: ['file', 'folder'],
multiselect: false,
label: ''
}, {
id: 'delete',
name: 'Delete',
type: ['file', 'folder', 'gist'],
multiselect: false,
label: ''
}, {
id: 'run',
name: 'Run',
extension: ['.js'],
multiselect: false,
label: ''
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: ['gist'],
multiselect: false,
label: ''
}, {
id: 'publishFolderToGist',
name: 'Publish folder to gist',
type: ['folder'],
multiselect: false,
label: ''
}, {
id: 'publishFileToGist',
name: 'Publish file to gist',
type: ['file'],
multiselect: false,
label: ''
}, {
id: 'copy',
name: 'Copy',
type: ['folder', 'file'],
multiselect: false,
label: ''
}, {
id: 'deleteAll',
name: 'Delete All',
type: ['folder', 'file'],
multiselect: true,
label: ''
}],
actions: contextMenuActions,
focusContext: {
element: null,
x: null,
@ -123,22 +51,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
const [canPaste, setCanPaste] = useState(false)
const [fileSystem, dispatch] = useReducer(fileSystemReducer, fileSystemInitialState)
const editRef = useRef(null)
const global = useContext(FileSystemContext)
useEffect(() => {
init(props.filesProvider, props.plugin, props.registry)(dispatch)
}, [])
useEffect(() => {
const provider = fileSystem.provider.provider
if (provider) {
fetchDirectory(provider, props.name)(dispatch)
}
}, [fileSystem.provider.provider, props.name])
useEffect(() => {
if (fileSystem.notification.message) {
modal(fileSystem.notification.title, fileSystem.notification.message, fileSystem.notification.labelOk, fileSystem.notification.actionOk, fileSystem.notification.labelCancel, fileSystem.notification.actionCancel)
global.modal(fileSystem.notification.title, fileSystem.notification.message, fileSystem.notification.labelOk, fileSystem.notification.actionOk, fileSystem.notification.labelCancel, fileSystem.notification.actionCancel)
}
}, [fileSystem.notification.message])
@ -317,7 +238,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
} catch (error) {
return modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {})
return global.modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
@ -329,14 +250,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager.exists(dirName)
if (exists) {
return modal('Rename File Failed', `A file or folder ${extractNameFromKey(newFolderPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
return global.modal('Rename File Failed', `A file or folder ${extractNameFromKey(newFolderPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
}
await fileManager.mkdir(dirName)
setState(prevState => {
return { ...prevState, focusElement: [{ key: newFolderPath, type: 'folder' }] }
})
} catch (e) {
return modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, 'Close', async () => {})
return global.modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, 'Close', async () => {})
}
}
@ -348,7 +269,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
return toast('cannot delete file. ' + name + ' is a read only explorer')
}
}
modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, deleteMessage(path), 'OK', async () => {
global.modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, deleteMessage(path), 'OK', async () => {
const fileManager = state.fileManager
for (const p of path) {
try {
@ -367,12 +288,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager.exists(newPath)
if (exists) {
modal('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
global.modal('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
} else {
await fileManager.rename(oldPath, newPath)
}
} catch (error) {
modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {})
global.modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
@ -395,13 +316,13 @@ export const FileExplorer = (props: FileExplorerProps) => {
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modal('File Upload Failed', 'Special characters are not allowed', 'Close', async () => {})
global.modal('File Upload Failed', 'Special characters are not allowed', 'Close', async () => {})
return
}
const success = await filesProvider.set(name, event.target.result)
if (!success) {
return modal('File Upload Failed', 'Failed to create file ' + name, 'Close', async () => {})
return global.modal('File Upload Failed', 'Failed to create file ' + name, 'Close', async () => {})
}
const config = registry.get('config').api
const editor = registry.get('editor').api
@ -418,7 +339,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
if (!exist) {
loadFile(name)
} else {
modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', () => {
global.modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', () => {
loadFile(name)
}, 'Cancel', () => {})
}
@ -449,35 +370,35 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const publishToGist = (path?: string, type?: string) => {
modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${name} workspace as a public gist on github.com?`, 'OK', () => toGist(path, type), 'Cancel', () => {})
global.modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${name} workspace as a public gist on github.com?`, 'OK', () => toGist(path, type), 'Cancel', () => {})
}
const pushChangesToGist = (path?: string, type?: string) => {
modal('Create a public gist', 'Are you sure you want to push changes to remote gist file on github.com?', 'OK', () => toGist(path, type), 'Cancel', () => {})
global.modal('Create a public gist', 'Are you sure you want to push changes to remote gist file on github.com?', 'OK', () => toGist(path, type), 'Cancel', () => {})
}
const publishFolderToGist = (path?: string, type?: string) => {
modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${path} folder as a public gist on github.com?`, 'OK', () => toGist(path, type), 'Cancel', () => {})
global.modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${path} folder as a public gist on github.com?`, 'OK', () => toGist(path, type), 'Cancel', () => {})
}
const publishFileToGist = (path?: string, type?: string) => {
modal('Create a public gist', `Are you sure you want to anonymously publish ${path} file as a public gist on github.com?`, 'OK', () => toGist(path, type), 'Cancel', () => {})
global.modal('Create a public gist', `Are you sure you want to anonymously publish ${path} file as a public gist on github.com?`, 'OK', () => toGist(path, type), 'Cancel', () => {})
}
const toGist = (path?: string, type?: string) => {
const filesProvider = fileSystem.provider.provider
const proccedResult = function (error, data) {
if (error) {
modal('Publish to gist Failed', 'Failed to manage gist: ' + error, 'Close', () => {})
global.modal('Publish to gist Failed', 'Failed to manage gist: ' + error, 'Close', () => {})
} else {
if (data.html_url) {
modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, 'OK', () => {
global.modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, 'OK', () => {
window.open(data.html_url, '_blank')
}, 'Cancel', () => {})
} 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
modal('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, 'Close', () => {})
global.modal('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, 'Close', () => {})
}
}
}
@ -504,14 +425,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
packageFiles(filesProvider, folder, async (error, packaged) => {
if (error) {
console.log(error)
modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, 'Close', async () => {})
global.modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, 'Close', async () => {})
} else {
// check for token
const config = registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
if (!accessToken) {
modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Close', () => {})
global.modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Close', () => {})
} 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='
@ -583,7 +504,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const handleClickFile = (path: string, type: string) => {
const handleClickFile = (path: string, type: 'folder' | 'file' | 'gist') => {
path = path.indexOf(props.name + '/') === 0 ? path.replace(props.name + '/', '') : path
if (!state.ctrlKey) {
state.fileManager.open(path)
@ -606,7 +527,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}
const handleClickFolder = async (path: string, type: string) => {
const handleClickFolder = async (path: string, type: 'folder' | 'file' | 'gist') => {
if (state.ctrlKey) {
if (state.focusElement.findIndex(item => item.key === path) !== -1) {
setState(prevState => {
@ -687,12 +608,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
if (helper.checkSpecialChars(content)) {
modal('Validation Error', 'Special characters are not allowed', 'OK', () => {})
global.modal('Validation Error', 'Special characters are not allowed', 'OK', () => {})
} else {
if (state.focusEdit.isNew) {
if (hasReservedKeyword(content)) {
removeInputField(parentFolder)(dispatch)
modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
global.modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
} else {
state.focusEdit.type === 'file' ? createNewFile(joinPath(parentFolder, content)) : createNewFolder(joinPath(parentFolder, content))
removeInputField(parentFolder)(dispatch)
@ -700,7 +621,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} else {
if (hasReservedKeyword(content)) {
editRef.current.textContent = state.focusEdit.lastEdit
modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
global.modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
} else {
const oldPath: string = state.focusEdit.element
const oldName = extractNameFromKey(oldPath)
@ -759,7 +680,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const handleCopyClick = (path: string, type: string) => {
const handleCopyClick = (path: string, type: 'folder' | 'gist' | 'file') => {
setState(prevState => {
return { ...prevState, copyElement: [{ key: path, type }] }
})
@ -927,8 +848,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
<div className='pb-2'>
<TreeView id='treeViewMenu'>
{
fileSystem.files.files[props.name] && Object.keys(fileSystem.files.files[props.name]).map((key, index) => {
return renderFiles(fileSystem.files.files[props.name][key], index)
files[props.name] && Object.keys(files[props.name]).map((key, index) => {
return renderFiles(files[props.name][key], index)
})
}
</TreeView>

@ -13,14 +13,15 @@ export interface FileExplorerProps {
removedContextMenuItems: MenuItems,
displayInput?: boolean,
externalUploads?: EventTarget & HTMLInputElement,
resetFocus?: (value: boolean) => void
resetFocus?: (value: boolean) => void,
files: { [x: string]: Record<string, File> }
}
export interface File {
path: string,
name: string,
isDirectory: boolean,
type: string,
type: 'folder' | 'file' | 'gist',
child?: File[]
}
@ -34,7 +35,7 @@ export interface FileExplorerMenuProps {
uploadFile: (target: EventTarget & HTMLInputElement) => void
}
export type action = { name: string, type: string[], path: string[], extension: string[], pattern: string[], id: string, multiselect: boolean, label: string }
export type action = { name: string, type?: Array<'folder' | 'gist' | 'file'>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string }
export interface FileExplorerContextMenuProps {
actions: action[],
@ -58,3 +59,44 @@ export interface FileExplorerContextMenuProps {
copy?: (path: string, type: string) => void,
paste?: (destination: string, type: string) => void
}
export interface FileExplorerState {
focusElement: {
key: string
type: 'folder' | 'file' | 'gist'
}[]
fileManager: any
ctrlKey: boolean
newFileName: string
actions: {
id: string
name: string
type?: Array<'folder' | 'gist' | 'file'>
path?: string[]
extension?: string[]
pattern?: string[]
multiselect: boolean
label: string
}[]
focusContext: {
element: string
x: number
y: number
type: string
}
focusEdit: {
element: string
type: string
isNew: boolean
lastEdit: string
}
expandPath: string[]
toasterMsg: string
mouseOverElement: string
showContextMenu: boolean
reservedKeywords: string[]
copyElement: {
key: string
type: 'folder' | 'gist' | 'file'
}[]
}

@ -1,3 +1,5 @@
import { MenuItems } from '../types'
export const extractNameFromKey = (key: string): string => {
const keyPath = key.split('/')
@ -11,3 +13,65 @@ export const extractParentFromKey = (key: string):string => {
return keyPath.join('/')
}
export const contextMenuActions: MenuItems = [{
id: 'newFile',
name: 'New File',
type: ['folder', 'gist'],
multiselect: false,
label: ''
}, {
id: 'newFolder',
name: 'New Folder',
type: ['folder', 'gist'],
multiselect: false,
label: ''
}, {
id: 'rename',
name: 'Rename',
type: ['file', 'folder'],
multiselect: false,
label: ''
}, {
id: 'delete',
name: 'Delete',
type: ['file', 'folder', 'gist'],
multiselect: false,
label: ''
}, {
id: 'run',
name: 'Run',
extension: ['.js'],
multiselect: false,
label: ''
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: ['gist'],
multiselect: false,
label: ''
}, {
id: 'publishFolderToGist',
name: 'Publish folder to gist',
type: ['folder'],
multiselect: false,
label: ''
}, {
id: 'publishFileToGist',
name: 'Publish file to gist',
type: ['file'],
multiselect: false,
label: ''
}, {
id: 'copy',
name: 'Copy',
type: ['folder', 'file'],
multiselect: false,
label: ''
}, {
id: 'deleteAll',
name: 'Delete All',
type: ['folder', 'file'],
multiselect: true,
label: ''
}]

@ -1 +1 @@
export * from './lib/remix-ui-helper';
export * from './lib/remix-ui-helper'

@ -1,3 +1,21 @@
export function remixUiHelper(): string {
return 'remix-ui-helper';
export const extractNameFromKey = (key: string): string => {
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
export const extractParentFromKey = (key: string):string => {
if (!key) return
const keyPath = key.split('/')
keyPath.pop()
return keyPath.join('/')
}
export const checkSpecialChars = (name: string) => {
return name.match(/[:*?"<>\\'|]/) != null
}
export const checkSlash = (name: string) => {
return name.match(/\//) != null
}

@ -1,2 +1,2 @@
export * from './lib/remix-ui-workspace'
export * from './lib/providers/FileSystemProvider'
export * from './lib/contexts'

@ -1,55 +0,0 @@
// var modalDialogCustom = require('../app/ui/modal-dialog-custom')
import * as request from 'request'
export class GistHandler {
handleLoad (params) {
let loadingFromGist = false
let gistId
if (params.gist) {
loadingFromGist = true
} else {
gistId = params.gist
loadingFromGist = !!gistId
}
return gistId
}
getGistId (str: string) {
const idr = /[0-9A-Fa-f]{8,}/
const match = idr.exec(str)
return match ? match[0] : null
}
loadFromGist (params, fileManager) {
const gistId = this.handleLoad(params)
request.get({
url: `https://api.github.com/gists/${gistId}`,
json: true
}, async (error, response, data = {}) => {
if (error || !data.files) {
// modalDialogCustom.alert('Gist load error', error || data.message)
return
}
const obj = {}
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) {
const provider = fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
// modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile)
}
})
})
}
}

@ -1,8 +1,8 @@
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import { checkSpecialChars, checkSlash } from '../../../../../../apps/remix-ide/src/lib/helper'
import React from 'react'
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import axios, { AxiosResponse } from 'axios'
import { checkSpecialChars, checkSlash } from '@remix-ui/helper'
// const GistHandler = require('../../../../../../apps/remix-ide/src/lib/gist-handler')
const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params')
const examples = require('../../../../../../apps/remix-ide/src/app/editor/examples')
// const queuedEvents = []
@ -51,6 +51,19 @@ const fetchDirectorySuccess = (path: string, files) => {
}
}
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: 'DISPLAY_NOTIFICATION'
}
}
export const fetchDirectory = (mode: 'browser' | 'localhost', path: string) => (dispatch: React.Dispatch<any>) => {
const provider = mode === 'browser' ? plugin.fileProviders.workspace : plugin.fileProviders.localhost
const promise = new Promise((resolve) => {
@ -73,34 +86,75 @@ export const fetchDirectory = (mode: 'browser' | 'localhost', path: string) => (
const createWorkspaceTemplate = async (workspaceName: string, setDefaults = true, template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => {
if (!workspaceName) throw new Error('workspace name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
if (await workspaceExists(workspaceName)) throw new Error('workspace already exists')
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 queryParams = new QueryParams()
const params = queryParams.get()
switch (template) {
case 'code-template':
// creates a new workspace code-sample and loads code from url params.
try {
const queryParams = new QueryParams()
const params = queryParams.get()
await workspaceProvider.createWorkspace(workspaceName)
let path = ''; let content = ''
if (params.code) {
const hash = bufferToHex(keccakFromString(params.code))
const hash = bufferToHex(keccakFromString(params.code))
const fileName = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
const path = fileName
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)
await workspaceProvider.set(path, atob(params.code))
await plugin.fileManager.openFile(fileName)
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
console.log('data: ', data)
if (!data.files) {
dispatch(displayNotification('Gist load error', 'No files found', 'OK', null, () => {}, null))
return
}
// const obj = {}
// Object.keys(data.files).forEach((element) => {
// const path = element.replace(/\.\.\./g, '/')
// obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
// })
// fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
// if (!errorLoadingFile) {
// const provider = fileManager.getProvider('workspace')
// provider.lastLoadedGistId = gistId
// } else {
// // modalDialogCustom.alert('', errorLoadingFile.message || errorLoadingFile)
// }
// })
} catch (e) {
dispatch(displayNotification('Gist load error', e.message, 'OK', null, () => {}, null))
console.error(e)
}
break
case 'default-template':
// creates a new workspace and populates it with default project template.
// insert example contracts
@ -150,29 +204,17 @@ const getWorkspaces = async (): Promise<string[]> | undefined => {
export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => {
if (filePanelPlugin) {
console.log('filePanelPlugin: ', filePanelPlugin)
plugin = filePanelPlugin
dispatch = reducerDispatch
const provider = filePanelPlugin.fileProviders.workspace
const queryParams = new QueryParams()
// const gistHandler = new GistHandler()
const params = queryParams.get()
// let loadedFromGist = false
const workspaces = await getWorkspaces() || []
// if (params.gist) {
// initialWorkspace = 'gist-sample'
// await provider.createWorkspace(initialWorkspace)
// loadedFromGist = gistHandler.loadFromGist(params, plugin.fileManager)
// }
// if (loadedFromGist) {
// dispatch(setWorkspaces(workspaces))
// dispatch(setCurrentWorkspace(initialWorkspace))
// return
// }
if (params.gist) {
} else if (params.code) {
await createWorkspaceTemplate('gist-sample', true, 'gist-template')
dispatch(setCurrentWorkspace('gist-sample'))
} else if (params.code || params.url) {
await createWorkspaceTemplate('code-sample', true, 'code-template')
dispatch(setCurrentWorkspace('code-sample'))
} else {
@ -225,7 +267,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
// provider.event.on('createWorkspace', (name) => {
// createNewWorkspace(name)
// })
dispatch(setWorkspaces(workspaces))
// dispatch(setWorkspaces(workspaces))
dispatch(setMode('browser'))
}
}

@ -1,13 +1,16 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, { useReducer, useState, useEffect } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, initLocalhost, fetchDirectory } from '../actions/workspace'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Modal } from '../types'
import { Modal, WorkspaceProps } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace'
export const FileSystemProvider = ({ filePanel, children }) => {
export const FileSystemProvider = (props: WorkspaceProps) => {
const { plugin } = props
const [fs, fsDispatch] = useReducer(browserReducer, browserInitialState)
const [focusModal, setFocusModal] = useState<Modal>({
hide: true,
@ -21,11 +24,11 @@ export const FileSystemProvider = ({ filePanel, children }) => {
const [modals, setModals] = useState<Modal[]>([])
const dispatchInitWorkspace = async () => {
await initWorkspace(filePanel)(fsDispatch)
await initWorkspace(plugin)(fsDispatch)
}
const dispatchInitLocalhost = async () => {
await initLocalhost(filePanel)(fsDispatch)
await initLocalhost(plugin)(fsDispatch)
}
const dispatchFetchDirectory = async (path: string) => {
@ -34,7 +37,7 @@ export const FileSystemProvider = ({ filePanel, children }) => {
useEffect(() => {
if (modals.length > 0) {
setModals(modals => {
setFocusModal(() => {
const focusModal = {
hide: false,
title: modals[0].title,
@ -44,14 +47,12 @@ export const FileSystemProvider = ({ filePanel, children }) => {
cancelLabel: modals[0].cancelLabel,
cancelFn: modals[0].cancelFn
}
modals.shift()
return {
...modals,
focusModal,
modals: modals
}
return focusModal
})
const modalList = modals.slice()
modalList.shift()
setModals(modalList)
}
}, [modals])
@ -62,7 +63,10 @@ export const FileSystemProvider = ({ filePanel, children }) => {
}
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
setModals(modals => [...modals, { message, title, okLabel, okFn, cancelLabel, cancelFn }])
setModals(modals => {
modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn })
return [...modals]
})
}
const value = {
@ -74,7 +78,7 @@ export const FileSystemProvider = ({ filePanel, children }) => {
}
return (
<FileSystemContext.Provider value={value}>
{ children }
<Workspace plugin={plugin} />
<ModalDialog id='fileSystem' { ...focusModal } handleHide={ handleHideModal } />
</FileSystemContext.Provider>
)

@ -1,4 +1,4 @@
import { extractNameFromKey } from '@remix-ui/file-explorer'
import { extractNameFromKey, File } from '@remix-ui/file-explorer'
interface Action {
type: string
payload: any
@ -7,36 +7,52 @@ export interface BrowserState {
browser: {
currentWorkspace: string,
workspaces: string[],
files: []
files: { [x: string]: Record<string, File> }
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
localhost: {
files: [],
files: { [x: string]: Record<string, File> },
isRequesting: boolean,
isSuccessful: boolean,
error: string
},
mode: 'browser' | 'localhost'
mode: 'browser' | 'localhost',
notification: {
title: string,
message: string,
actionOk: () => void,
actionCancel: (() => void) | null,
labelOk: string,
labelCancel: string
}
}
export const browserInitialState: BrowserState = {
browser: {
currentWorkspace: '',
workspaces: [],
files: [],
files: {},
isRequesting: false,
isSuccessful: false,
error: null
},
localhost: {
files: [],
files: {},
isRequesting: false,
isSuccessful: false,
error: null
},
mode: 'browser'
mode: 'browser',
notification: {
title: '',
message: '',
actionOk: () => {},
actionCancel: () => {},
labelOk: '',
labelCancel: ''
}
}
export const browserReducer = (state = browserInitialState, action: Action) => {
@ -67,9 +83,11 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
case 'SET_MODE': {
const payload = action.payload as 'browser' | 'localhost'
return {
...state,
mode: action.payload
mode: payload
}
}
@ -84,6 +102,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
}
case 'FETCH_DIRECTORY_SUCCESS': {
const payload = action.payload as { path: string, files }
@ -98,6 +117,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
}
case 'FETCH_DIRECTORY_ERROR': {
return {
...state,
@ -109,6 +129,29 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
}
case 'DISPLAY_NOTIFICATION': {
const payload = action.payload as { title: string, message: string, actionOk: () => void, actionCancel: () => void, labelOk: string, labelCancel: string }
return {
...state,
notification: {
title: payload.title,
message: payload.message,
actionOk: payload.actionOk || browserInitialState.notification.actionOk,
actionCancel: payload.actionCancel || browserInitialState.notification.actionCancel,
labelOk: payload.labelOk,
labelCancel: payload.labelCancel
}
}
}
case 'HIDE_NOTIFICATION': {
return {
...state,
notification: browserInitialState.notification
}
}
default:
throw new Error()
}
@ -120,7 +163,7 @@ const fetchDirectoryContent = (fileTree, folderPath: string) => {
return { [extractNameFromKey(folderPath)]: files }
}
const normalize = (filesList): any => {
const normalize = (filesList): Record<string, File> => {
const folders = {}
const files = {}

@ -11,7 +11,6 @@ export function Workspace (props: WorkspaceProps) {
const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - '
const [state, setState] = useState<WorkspaceState>({
workspaces: [],
reset: false,
hideRemixdExplorer: true,
displayNewFile: false,
@ -28,7 +27,10 @@ export function Workspace (props: WorkspaceProps) {
}, [])
useEffect(() => {
if (global.fs.browser.currentWorkspace) setCurrentWorkspace(global.fs.browser.currentWorkspace)
if (global.fs.browser.currentWorkspace) {
setCurrentWorkspace(global.fs.browser.currentWorkspace)
global.dispatchFetchDirectory(global.fs.browser.currentWorkspace)
}
}, [global.fs.browser.currentWorkspace])
props.plugin.resetNewFile = () => {
@ -46,18 +48,18 @@ export function Workspace (props: WorkspaceProps) {
return setWorkspace(workspaceName)
}
props.plugin.request.createNewFile = async () => {
if (!state.workspaces.length) await createNewWorkspace('default_workspace')
props.plugin.resetNewFile()
}
// props.plugin.request.createNewFile = async () => {
// if (!state.workspaces.length) await createNewWorkspace('default_workspace')
// props.plugin.resetNewFile()
// }
props.plugin.request.uploadFile = async (target: EventTarget & HTMLInputElement) => {
if (!state.workspaces.length) await createNewWorkspace('default_workspace')
// props.plugin.request.uploadFile = async (target: EventTarget & HTMLInputElement) => {
// if (!state.workspaces.length) await createNewWorkspace('default_workspace')
setState(prevState => {
return { ...prevState, uploadFileEvent: target }
})
}
// setState(prevState => {
// return { ...prevState, uploadFileEvent: target }
// })
// }
props.plugin.request.getCurrentWorkspace = () => {
return { name: currentWorkspace, isLocalhost: currentWorkspace === LOCALHOST, absolutePath: `${props.plugin.workspace.workspacesPath}/${currentWorkspace}` }
@ -287,6 +289,7 @@ export function Workspace (props: WorkspaceProps) {
displayInput={state.displayNewFile}
externalUploads={state.uploadFileEvent}
resetFocus={resetFocus}
files={global.fs.browser.files}
/>
}
</div>
@ -304,6 +307,7 @@ export function Workspace (props: WorkspaceProps) {
contextMenuItems={props.plugin.registeredMenuItems}
removedContextMenuItems={props.plugin.removedMenuItems}
resetFocus={resetFocus}
files={global.fs.localhost.files}
/>
}
</div>

@ -1,5 +1,4 @@
import { MenuItems } from '@remix-ui/file-explorer'
export interface WorkspaceProps {
plugin: {
setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void,
@ -29,7 +28,6 @@ export interface WorkspaceProps {
}
}
export interface WorkspaceState {
workspaces: string[]
reset: boolean
hideRemixdExplorer: boolean
displayNewFile: boolean
@ -48,3 +46,11 @@ export interface Modal {
cancelLabel: string
cancelFn: () => void
}
export interface File {
path: string,
name: string,
isDirectory: boolean,
type: 'folder' | 'file' | 'gist',
child?: File[]
}

Loading…
Cancel
Save