Fixed expand path bug

pull/1575/head
ioedeveloper 3 years ago
parent 783e92d060
commit 7c5ad3934e
  1. 22
      libs/remix-ui/workspace/src/lib/actions/events.ts
  2. 7
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  3. 164
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  4. 48
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  5. 1
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  6. 9
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  7. 16
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  8. 3
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  9. 4
      libs/remix-ui/workspace/src/lib/types/index.ts

@ -119,7 +119,6 @@ const removePluginActions = (plugin) => {
}
const fileAdded = async (filePath: string) => {
console.log('fileAdded: ', filePath)
await dispatch(fileAddedSuccess(filePath))
if (filePath.includes('_test.sol')) {
plugin.emit('newTestFileCreated', filePath)
@ -127,7 +126,6 @@ const fileAdded = async (filePath: string) => {
}
const folderAdded = async (folderPath: string) => {
console.log('folderAdded: ', folderPath)
const provider = plugin.fileManager.currentFileProvider()
const path = extractParentFromKey(folderPath) || provider.workspace || provider.type || ''
@ -180,7 +178,9 @@ const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemove
pendingEvents[eventName + args[0]] = { eventName, path: args[0] }
switch (eventName) {
case 'fileAdded':
await fileAdded(args[0])
setTimeout(() => {
fileAdded(args[0])
}, 0)
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
@ -190,7 +190,9 @@ const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemove
break
case 'folderAdded':
await folderAdded(args[0])
setTimeout(() => {
folderAdded(args[0])
}, 0)
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
@ -200,7 +202,9 @@ const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemove
break
case 'fileRemoved':
await fileRemoved(args[0])
setTimeout(() => {
fileRemoved(args[0])
}, 0)
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
@ -210,7 +214,9 @@ const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemove
break
case 'fileRenamed':
await fileRenamed(args[0])
setTimeout(() => {
fileRenamed(args[0])
}, 0)
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()
@ -220,7 +226,9 @@ const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemove
break
case 'rootFolderChanged':
await rootFolderChanged(args[0])
setTimeout(() => {
rootFolderChanged(args[0])
}, 0)
delete pendingEvents[eventName + args[0]]
if (queuedEvents.length) {
const next = queuedEvents.pop()

@ -200,3 +200,10 @@ export const removeContextMenuItem = (plugin) => {
payload: plugin
}
}
export const setExpandPath = (paths: string[]) => {
return {
type: 'SET_EXPAND_PATH',
payload: paths
}
}

@ -4,7 +4,7 @@ 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, setMode, setRenameWorkspace, setWorkspaces } from './payload'
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'
const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params')
@ -26,14 +26,17 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
dispatch(setWorkspaces(workspaces))
if (params.gist) {
await createWorkspaceTemplate('gist-sample', true, 'gist-template')
await createWorkspaceTemplate('gist-sample', 'gist-template')
await loadWorkspacePreset('gist-template')
dispatch(setCurrentWorkspace('gist-sample'))
} else if (params.code || params.url) {
await createWorkspaceTemplate('code-sample', true, 'code-template')
await createWorkspaceTemplate('code-sample', 'code-template')
await loadWorkspacePreset('code-template')
dispatch(setCurrentWorkspace('code-sample'))
} else {
if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace')
await createWorkspaceTemplate('default_workspace', 'default-template')
await loadWorkspacePreset('default-template')
dispatch(setCurrentWorkspace('default_workspace'))
} else {
if (workspaces.length > 0) {
@ -106,14 +109,14 @@ export const removeInputField = async (path: string) => {
}
export const createWorkspace = async (workspaceName: string) => {
console.log('workspaceName: ', workspaceName)
const promise = createWorkspaceTemplate(workspaceName, true, 'default-template')
const promise = createWorkspaceTemplate(workspaceName, 'default-template')
dispatch(createWorkspaceRequest(promise))
promise.then(async () => {
await plugin.fileManager.closeAllFiles()
dispatch(createWorkspaceSuccess(workspaceName))
switchToWorkspace(workspaceName)
await loadWorkspacePreset('default-template')
// await switchToWorkspace(workspaceName)
}).catch((error) => {
dispatch(createWorkspaceError({ error }))
})
@ -389,7 +392,11 @@ export const handleClickFile = async (path: string, type: 'file' | 'folder' | 'g
dispatch(focusElement([{ key: path, type }]))
}
const createWorkspaceTemplate = async (workspaceName: string, setDefaults = true, template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => {
export const handleExpandPath = (paths: string[]) => {
dispatch(setExpandPath(paths))
}
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')
@ -397,80 +404,81 @@ const createWorkspaceTemplate = async (workspaceName: string, setDefaults = true
const workspaceProvider = plugin.fileProviders.workspace
await workspaceProvider.createWorkspace(workspaceName)
if (setDefaults) {
const params = queryParams.get()
switch (template) {
case 'code-template':
// creates a new workspace code-sample and loads code from url params.
try {
await workspaceProvider.createWorkspace(workspaceName)
let path = ''; let content = ''
if (params.code) {
const hash = bufferToHex(keccakFromString(params.code))
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
await workspaceProvider.set(path, content)
} else if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
await workspaceProvider.set(path, content)
}
await plugin.fileManager.openFile(path)
} catch (e) {
console.error(e)
}
break
}
}
case 'gist-template':
// creates a new workspace gist-sample and get the file from gist
try {
const gistId = params.gist
const response: AxiosResponse = await axios.get(`https://api.github.com/gists/${gistId}`)
const data = response.data
const loadWorkspacePreset = async (template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => {
const workspaceProvider = plugin.fileProviders.workspace
const params = queryParams.get()
if (!data.files) {
return dispatch(displayNotification('Gist load error', 'No files found', 'OK', null, () => { dispatch(hideNotification()) }, null))
}
const obj = {}
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) {
const provider = plugin.fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null))
}
})
} catch (e) {
dispatch(displayNotification('Gist load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null))
console.error(e)
}
break
case 'default-template':
// creates a new workspace and populates it with default project template.
// insert example contracts
for (const file in examples) {
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
switch (template) {
case 'code-template':
// creates a new workspace code-sample and loads code from url params.
try {
let path = ''; let content = ''
if (params.code) {
const hash = bufferToHex(keccakFromString(params.code))
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
await workspaceProvider.set(path, content)
} else if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
await workspaceProvider.set(path, content)
}
await plugin.fileManager.openFile(path)
} catch (e) {
console.error(e)
}
break
case 'gist-template':
// creates a new workspace gist-sample and get the file from gist
try {
const gistId = params.gist
const response: AxiosResponse = await axios.get(`https://api.github.com/gists/${gistId}`)
const data = response.data
if (!data.files) {
return dispatch(displayNotification('Gist load error', 'No files found', 'OK', null, () => { dispatch(hideNotification()) }, null))
}
const obj = {}
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) {
const provider = plugin.fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null))
}
break
})
} catch (e) {
dispatch(displayNotification('Gist load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null))
console.error(e)
}
}
break
case 'default-template':
// creates a new workspace and populates it with default project template.
// insert example contracts
for (const file in examples) {
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
}
break
}
}

@ -10,6 +10,7 @@ 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
@ -29,7 +30,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
isNew: false,
lastEdit: ''
},
expandPath: [name],
mouseOverElement: null,
showContextMenu: false,
reservedKeywords: [name, 'gist-'],
@ -37,12 +37,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
const [canPaste, setCanPaste] = useState(false)
useEffect(() => {
setState(prevState => {
return { ...prevState, expandPath: [...new Set([...prevState.expandPath, ...props.expandPath])] }
})
}, [props.expandPath])
useEffect(() => {
if (contextMenuItems) {
addMenuItems(contextMenuItems)
@ -177,11 +171,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
const uploadFile = (target) => {
const parentFolder = getFocusedFolder()
const expandPath = [...new Set([...state.expandPath, parentFolder])]
const expandPath = [...new Set([...props.expandPath, parentFolder])]
setState(prevState => {
return { ...prevState, expandPath }
})
props.dispatchHandleExpandPath(expandPath)
props.dispatchUploadFile(target, parentFolder)
}
@ -270,17 +262,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
} else {
let expandPath = []
if (!state.expandPath.includes(path)) {
expandPath = [...new Set([...state.expandPath, path])]
if (!props.expandPath.includes(path)) {
expandPath = [...new Set([...props.expandPath, path])]
props.dispatchFetchDirectory(path)
} else {
expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(path)))]
expandPath = [...new Set(props.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(path)))]
}
props.dispatchSetFocusElement([{ key: path, type }])
setState(prevState => {
return { ...prevState, expandPath }
})
props.dispatchHandleExpandPath(expandPath)
}
}
@ -360,24 +350,20 @@ export const FileExplorer = (props: FileExplorerProps) => {
const handleNewFileInput = async (parentFolder?: string) => {
if (!parentFolder) parentFolder = getFocusedFolder()
const expandPath = [...new Set([...state.expandPath, parentFolder])]
const expandPath = [...new Set([...props.expandPath, parentFolder])]
await props.dispatchAddInputField(parentFolder, 'file')
setState(prevState => {
return { ...prevState, expandPath }
})
props.dispatchHandleExpandPath(expandPath)
editModeOn(parentFolder + '/blank', 'file', true)
}
const handleNewFolderInput = async (parentFolder?: string) => {
if (!parentFolder) parentFolder = getFocusedFolder()
else if ((parentFolder.indexOf('.sol') !== -1) || (parentFolder.indexOf('.js') !== -1)) parentFolder = extractParentFromKey(parentFolder)
const expandPath = [...new Set([...state.expandPath, parentFolder])]
const expandPath = [...new Set([...props.expandPath, parentFolder])]
await props.dispatchAddInputField(parentFolder, 'folder')
setState(prevState => {
return { ...prevState, expandPath }
})
props.dispatchHandleExpandPath(expandPath)
editModeOn(parentFolder + '/blank', 'folder', true)
}
@ -413,14 +399,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
if (e && (e.target as any).getAttribute('data-id') === 'fileExplorerFileUpload') return // we don't want to let propagate the input of type file
let expandPath = []
if (!state.expandPath.includes(props.name)) {
expandPath = [props.name, ...new Set([...state.expandPath])]
if (!props.expandPath.includes(props.name)) {
expandPath = [props.name, ...new Set([...props.expandPath])]
} else {
expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(props.name)))]
expandPath = [...new Set(props.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(props.name)))]
}
setState(prevState => {
return { ...prevState, expandPath }
})
handleExpandPath(expandPath)
}
return (
@ -451,7 +435,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
focusEdit={state.focusEdit}
focusElement={props.focusElement}
ctrlKey={state.ctrlKey}
expandPath={state.expandPath}
expandPath={props.expandPath}
editModeOff={editModeOff}
handleClickFile={handleClickFile}
handleClickFolder={handleClickFolder}

@ -27,4 +27,5 @@ export const FileSystemContext = createContext<{
dispatchRunScript: (path: string) => Promise<void>,
dispatchEmitContextMenuEvent: (cmd: customAction) => Promise<void>,
dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise<void>
dispatchHandleExpandPath: (paths: string[]) => Promise<void>
}>(null)

@ -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 } from '../actions/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 { Modal, WorkspaceProps } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace'
@ -111,6 +111,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await handleClickFile(path, type)
}
const dispatchHandleExpandPath = async (paths: string[]) => {
await handleExpandPath(paths)
}
useEffect(() => {
if (modals.length > 0) {
setFocusModal(() => {
@ -205,7 +209,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchCopyFolder,
dispatchRunScript,
dispatchEmitContextMenuEvent,
dispatchHandleClickFile
dispatchHandleClickFile,
dispatchHandleExpandPath
}
return (
<FileSystemContext.Provider value={value}>

@ -517,6 +517,22 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
case 'SET_EXPAND_PATH': {
const payload = action.payload as string[]
return {
...state,
browser: {
...state.browser,
expandPath: state.mode === 'browser' ? payload : state.browser.expandPath
},
localhost: {
...state.localhost,
expandPath: state.mode === 'localhost' ? payload : state.localhost.expandPath
}
}
}
default:
throw new Error()
}

@ -99,6 +99,7 @@ export function Workspace () {
const switchWorkspace = async (name: string) => {
try {
await global.dispatchSwitchToWorkspace(name)
global.dispatchHandleExpandPath([])
} catch (e) {
global.modal('Switch To Workspace', e.message, 'OK', () => {}, '')
console.error(e)
@ -208,6 +209,7 @@ export function Workspace () {
dispatchFetchDirectory={global.dispatchFetchDirectory}
dispatchRemoveInputField={global.dispatchRemoveInputField}
dispatchAddInputField={global.dispatchAddInputField}
dispatchHandleExpandPath={global.dispatchHandleExpandPath}
/>
}
</div>
@ -242,6 +244,7 @@ export function Workspace () {
dispatchFetchDirectory={global.dispatchFetchDirectory}
dispatchRemoveInputField={global.dispatchRemoveInputField}
dispatchAddInputField={global.dispatchAddInputField}
dispatchHandleExpandPath={global.dispatchHandleExpandPath}
/>
}
</div>

@ -78,7 +78,8 @@ export interface FileExplorerProps {
dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise<void>,
dispatchFetchDirectory:(path: string) => Promise<void>,
dispatchRemoveInputField:(path: string) => Promise<void>,
dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise<void>
dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise<void>,
dispatchHandleExpandPath: (paths: string[]) => Promise<void>
}
export interface FileExplorerMenuProps {
@ -141,7 +142,6 @@ export interface FileExplorerState {
isNew: boolean
lastEdit: string
}
expandPath: string[]
mouseOverElement: string
showContextMenu: boolean
reservedKeywords: string[]

Loading…
Cancel
Save