Commit 501d4b01 authored by ioedeveloper's avatar ioedeveloper

Update loading code from url params and fetch gist using axios

parent da9207dd
......@@ -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)
}
......
......@@ -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'
}[]
}
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: ''
}]
export * from './lib/remix-ui-helper';
export * from './lib/remix-ui-helper'
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
}
export * from './lib/remix-ui-workspace'
export * from './lib/providers/FileSystemProvider'
export * from './lib/contexts'
// 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)
}
})
})
}
}
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'))
}
}
......
// 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>
)
......
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>
......
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[]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment