diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index 11c4f1863c..45c9e2fa98 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -1,10 +1,8 @@ -import React, { useEffect, useState, useRef, useContext } from 'react' // eslint-disable-line -// import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' // eslint-disable-line +import React, { useEffect, useState, useContext, SyntheticEvent } from 'react' // eslint-disable-line import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line import { FileExplorerContextMenu } from './file-explorer-context-menu' // eslint-disable-line import { FileExplorerProps, MenuItems, FileExplorerState } from '../types' -import { FileSystemContext } from '../contexts' import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel' import { contextMenuActions } from '../utils' @@ -14,7 +12,7 @@ import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath } import { FileRender } from './file-render' export const FileExplorer = (props: FileExplorerProps) => { - const { name, contextMenuItems, externalUploads, removedContextMenuItems, files } = props + const { name, contextMenuItems, removedContextMenuItems, files } = props const [state, setState] = useState({ ctrlKey: false, newFileName: '', @@ -38,7 +36,6 @@ export const FileExplorer = (props: FileExplorerProps) => { copyElement: [] }) const [canPaste, setCanPaste] = useState(false) - const global = useContext(FileSystemContext) useEffect(() => { setState(prevState => { @@ -66,12 +63,6 @@ export const FileExplorer = (props: FileExplorerProps) => { } }, [props.focusEdit]) - useEffect(() => { - if (externalUploads) { - uploadFile(externalUploads) - } - }, [externalUploads]) - useEffect(() => { const keyPressHandler = (e: KeyboardEvent) => { if (e.shiftKey) { @@ -155,32 +146,32 @@ export const FileExplorer = (props: FileExplorerProps) => { const createNewFile = async (newFilePath: string) => { try { - global.dispatchCreateNewFile(newFilePath, props.name) + props.dispatchCreateNewFile(newFilePath, props.name) } catch (error) { - return global.modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {}) + return props.modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {}) } } const createNewFolder = async (newFolderPath: string) => { try { - global.dispatchCreateNewFolder(newFolderPath, props.name) + props.dispatchCreateNewFolder(newFolderPath, props.name) } catch (e) { - return global.modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, 'Close', async () => {}) + return props.modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, 'Close', async () => {}) } } const deletePath = async (path: string[]) => { - if (global.fs.readonly) return global.toast('cannot delete file. ' + name + ' is a read only explorer') + if (props.readonly) return props.toast('cannot delete file. ' + name + ' is a read only explorer') if (!Array.isArray(path)) path = [path] - global.modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, deleteMessage(path), 'OK', () => { global.dispatchDeletePath(path) }, 'Cancel', () => {}) + props.modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, deleteMessage(path), 'OK', () => { props.dispatchDeletePath(path) }, 'Cancel', () => {}) } const renamePath = async (oldPath: string, newPath: string) => { try { - global.dispatchRenamePath(oldPath, newPath) + props.dispatchRenamePath(oldPath, newPath) } catch (error) { - global.modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {}) + props.modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {}) } } @@ -191,75 +182,75 @@ export const FileExplorer = (props: FileExplorerProps) => { setState(prevState => { return { ...prevState, expandPath } }) - global.dispatchUploadFile(target, parentFolder) + props.dispatchUploadFile(target, parentFolder) } const copyFile = (src: string, dest: string) => { try { - global.dispatchCopyFile(src, dest) + props.dispatchCopyFile(src, dest) } catch (error) { - global.modal('Copy File Failed', 'Unexpected error while copying file: ' + src, 'Close', async () => {}) + props.modal('Copy File Failed', 'Unexpected error while copying file: ' + src, 'Close', async () => {}) } } const copyFolder = (src: string, dest: string) => { try { - global.dispatchCopyFolder(src, dest) + props.dispatchCopyFolder(src, dest) } catch (error) { - global.modal('Copy Folder Failed', 'Unexpected error while copying folder: ' + src, 'Close', async () => {}) + props.modal('Copy Folder Failed', 'Unexpected error while copying folder: ' + src, 'Close', async () => {}) } } const publishToGist = (path?: string, type?: string) => { - 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', () => {}) + props.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) => { - 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', () => {}) + props.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) => { - 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', () => {}) + props.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) => { - 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', () => {}) + props.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) => { - global.dispatchPublishToGist(path, type) + props.dispatchPublishToGist(path, type) } const runScript = async (path: string) => { try { - global.dispatchRunScript(path) + props.dispatchRunScript(path) } catch (error) { - global.toast('Run script failed') + props.toast('Run script failed') } } const emitContextMenuEvent = (cmd: customAction) => { try { - global.dispatchEmitContextMenuEvent(cmd) + props.dispatchEmitContextMenuEvent(cmd) } catch (error) { - global.toast(error) + props.toast(error) } } const handleClickFile = (path: string, type: 'folder' | 'file' | 'gist') => { path = path.indexOf(props.name + '/') === 0 ? path.replace(props.name + '/', '') : path if (!state.ctrlKey) { - global.dispatchHandleClickFile(path, type) + props.dispatchHandleClickFile(path, type) } else { if (props.focusElement.findIndex(item => item.key === path) !== -1) { const focusElement = props.focusElement.filter(item => item.key !== path) - global.dispatchSetFocusElement(focusElement) + props.dispatchSetFocusElement(focusElement) } else { const nonRootFocus = props.focusElement.filter((el) => { return !(el.key === '' && el.type === 'folder') }) nonRootFocus.push({ key: path, type }) - global.dispatchSetFocusElement(nonRootFocus) + props.dispatchSetFocusElement(nonRootFocus) } } } @@ -269,24 +260,24 @@ export const FileExplorer = (props: FileExplorerProps) => { if (props.focusElement.findIndex(item => item.key === path) !== -1) { const focusElement = props.focusElement.filter(item => item.key !== path) - global.dispatchSetFocusElement(focusElement) + props.dispatchSetFocusElement(focusElement) } else { const nonRootFocus = props.focusElement.filter((el) => { return !(el.key === '' && el.type === 'folder') }) nonRootFocus.push({ key: path, type }) - global.dispatchSetFocusElement(nonRootFocus) + props.dispatchSetFocusElement(nonRootFocus) } } else { let expandPath = [] if (!state.expandPath.includes(path)) { expandPath = [...new Set([...state.expandPath, path])] - global.dispatchFetchDirectory(path) + props.dispatchFetchDirectory(path) } else { expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(path)))] } - global.dispatchSetFocusElement([{ key: path, type }]) + props.dispatchSetFocusElement([{ key: path, type }]) setState(prevState => { return { ...prevState, expandPath } }) @@ -307,7 +298,7 @@ export const FileExplorer = (props: FileExplorerProps) => { } const editModeOn = (path: string, type: string, isNew: boolean = false) => { - if (global.fs.readonly) return + if (props.readonly) return setState(prevState => { return { ...prevState, focusEdit: { ...prevState.focusEdit, element: path, isNew, type } } }) @@ -319,7 +310,7 @@ export const FileExplorer = (props: FileExplorerProps) => { if (!content || (content.trim() === '')) { if (state.focusEdit.isNew) { - global.dispatchRemoveInputField(parentFolder) + props.dispatchRemoveInputField(parentFolder) setState(prevState => { return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } } }) @@ -337,20 +328,20 @@ export const FileExplorer = (props: FileExplorerProps) => { }) } if (checkSpecialChars(content)) { - global.modal('Validation Error', 'Special characters are not allowed', 'OK', () => {}) + props.modal('Validation Error', 'Special characters are not allowed', 'OK', () => {}) } else { if (state.focusEdit.isNew) { if (hasReservedKeyword(content)) { - global.dispatchRemoveInputField(parentFolder) - global.modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {}) + props.dispatchRemoveInputField(parentFolder) + props.modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {}) } else { state.focusEdit.type === 'file' ? createNewFile(joinPath(parentFolder, content)) : createNewFolder(joinPath(parentFolder, content)) - global.dispatchRemoveInputField(parentFolder) + props.dispatchRemoveInputField(parentFolder) } } else { if (hasReservedKeyword(content)) { // editRef.current.textContent = state.focusEdit.lastEdit - global.modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {}) + props.modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {}) } else { const oldPath: string = state.focusEdit.element const oldName = extractNameFromKey(oldPath) @@ -371,7 +362,7 @@ export const FileExplorer = (props: FileExplorerProps) => { if (!parentFolder) parentFolder = getFocusedFolder() const expandPath = [...new Set([...state.expandPath, parentFolder])] - await global.dispatchAddInputField(parentFolder, 'file') + await props.dispatchAddInputField(parentFolder, 'file') setState(prevState => { return { ...prevState, expandPath } }) @@ -383,7 +374,7 @@ export const FileExplorer = (props: FileExplorerProps) => { else if ((parentFolder.indexOf('.sol') !== -1) || (parentFolder.indexOf('.js') !== -1)) parentFolder = extractParentFromKey(parentFolder) const expandPath = [...new Set([...state.expandPath, parentFolder])] - await global.dispatchAddInputField(parentFolder, 'folder') + await props.dispatchAddInputField(parentFolder, 'folder') setState(prevState => { return { ...prevState, expandPath } }) @@ -395,7 +386,7 @@ export const FileExplorer = (props: FileExplorerProps) => { return { ...prevState, copyElement: [{ key: path, type }] } }) setCanPaste(true) - global.toast(`Copied to clipboard ${path}`) + props.toast(`Copied to clipboard ${path}`) } const handlePasteClick = (dest: string, destType: string) => { @@ -416,27 +407,29 @@ export const FileExplorer = (props: FileExplorerProps) => { ) } + const handleFileExplorerMenuClick = (e: SyntheticEvent) => { + e.stopPropagation() + if (e && (e.target as any).getAttribute('data-id') === 'fileExplorerUploadFileuploadFile') return // we don't want to let propagate the input of type file + 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])] + } else { + expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(props.name)))] + } + setState(prevState => { + return { ...prevState, expandPath } + }) + } + return (
{ - e.stopPropagation() - if (e && (e.target as any).getAttribute('data-id') === 'fileExplorerUploadFileuploadFile') return // we don't want to let propagate the input of type file - 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])] - } else { - expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(props.name)))] - } - setState(prevState => { - return { ...prevState, expandPath } - }) - }}> +
}
@@ -218,6 +236,24 @@ export function Workspace (props: WorkspaceProps) { expandPath={global.fs.localhost.expandPath} focusEdit={global.fs.focusEdit} focusElement={global.fs.focusElement} + dispatchCreateNewFile={global.dispatchCreateNewFile} + modal={global.modal} + dispatchCreateNewFolder={global.dispatchCreateNewFolder} + readonly={global.fs.readonly} + toast={global.toast} + dispatchDeletePath={global.dispatchDeletePath} + dispatchRenamePath={global.dispatchRenamePath} + dispatchUploadFile={global.dispatchUploadFile} + dispatchCopyFile={global.dispatchCopyFile} + dispatchCopyFolder={global.dispatchCopyFolder} + dispatchPublishToGist={global.dispatchPublishToGist} + dispatchRunScript={global.dispatchRunScript} + dispatchEmitContextMenuEvent={global.dispatchEmitContextMenuEvent} + dispatchHandleClickFile={global.dispatchHandleClickFile} + dispatchSetFocusElement={global.dispatchSetFocusElement} + dispatchFetchDirectory={global.dispatchFetchDirectory} + dispatchRemoveInputField={global.dispatchRemoveInputField} + dispatchAddInputField={global.dispatchAddInputField} /> }
diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index 5547feffdb..392094ffff 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -57,12 +57,28 @@ export interface FileExplorerProps { menuItems?: string[], contextMenuItems: MenuItems, removedContextMenuItems: MenuItems, - displayInput?: boolean, - externalUploads?: EventTarget & HTMLInputElement, files: { [x: string]: Record }, expandPath: string[], focusEdit: string, - focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[] + focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[], + dispatchCreateNewFile: (path: string, rootDir: string) => Promise, + modal:(title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, + dispatchCreateNewFolder: (path: string, rootDir: string) => Promise, + readonly: boolean, + toast: (toasterMsg: string) => void, + dispatchDeletePath: (path: string[]) => Promise, + dispatchRenamePath: (oldPath: string, newPath: string) => Promise, + dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise, + dispatchCopyFile: (src: string, dest: string) => Promise, + dispatchCopyFolder: (src: string, dest: string) => Promise, + dispatchRunScript: (path: string) => Promise, + dispatchPublishToGist: (path?: string, type?: string) => Promise, + dispatchEmitContextMenuEvent: (cmd: customAction) => Promise, + dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise, + dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise, + dispatchFetchDirectory:(path: string) => Promise, + dispatchRemoveInputField:(path: string) => Promise, + dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise } export interface FileExplorerMenuProps {