diff --git a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json index b398cb5e0e..1991d29657 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json +++ b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json @@ -39,10 +39,11 @@ "filePanel.paste": "Paste", "filePanel.compile": "Compile", "filePanel.compileForNahmii": "Compile for Nahmii", - "filePanel.createNewFile": "Create New File", - "filePanel.createNewFolder": "Create New Folder", - "filePanel.publishToGist": "Publish all the current workspace files to a github gist", - "filePanel.uploadFile": "Load a local file into current workspace", + "filePanel.createNewFile": "Create new file", + "filePanel.createNewFolder": "Create new folder", + "filePanel.publishToGist": "Publish all files to GitHub gist", + "filePanel.uploadFile": "Upload files", + "filePanel.uploadFolder": "Upload folder", "filePanel.updateGist": "Update the current [gist] explorer", "filePanel.viewAllBranches": "View all branches", "filePanel.createBranch": "Create branch", diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index a37759044e..3e9120d6a3 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -12,6 +12,7 @@ import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/consta // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB' import { getUncommittedFiles } from '../utils/gitStatusFilter' +import { AppModal, ModalTypes } from '@remix-ui/app' declare global { interface Window { remixFileSystemCallback: IndexedDBStorage; } @@ -358,6 +359,30 @@ export const switchToWorkspace = async (name: string) => { } } +const loadFile = (name, file, provider, cb?): void => { + const fileReader = new FileReader() + + fileReader.onload = async function (event) { + if (checkSpecialChars(file.name)) { + return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => { })) + } + try { + await provider.set(name, event.target.result) + } catch (error) { + return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => { })) + } + + const config = plugin.registry.get('config').api + const editor = plugin.registry.get('editor').api + + if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) { + editor.setText(name, event.target.result) + } + } + fileReader.readAsText(file) + cb && cb(null, true) +} + export const uploadFile = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record) => void) => { // TODO The file explorer is merely a view on the current state of // the files module. Please ask the user here if they want to overwrite @@ -365,39 +390,52 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error, // pick that up via the 'fileAdded' event from the files module. [...target.files].forEach(async (file) => { const workspaceProvider = plugin.fileProviders.workspace - const loadFile = (name: string): void => { - const fileReader = new FileReader() - - fileReader.onload = async function (event) { - if (checkSpecialChars(file.name)) { - return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => { })) - } - try { - await workspaceProvider.set(name, event.target.result) - } catch (error) { - return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => { })) - } - - const config = plugin.registry.get('config').api - const editor = plugin.registry.get('editor').api + const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}` - if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) { - editor.setText(name, event.target.result) - } + if (!await workspaceProvider.exists(name)) { + loadFile(name, file, workspaceProvider, cb) + } else { + const modalContent: AppModal = { + id: 'overwriteUploadFile', + title: 'Confirm overwrite', + message: `The file "${name}" already exists! Would you like to overwrite it?`, + modalType: ModalTypes.confirm, + okLabel: 'OK', + cancelLabel: 'Cancel', + okFn: () => { + loadFile(name, file, workspaceProvider, cb) + }, + cancelFn: () => {}, + hideFn: () => {} } - fileReader.readAsText(file) - cb && cb(null, true) + plugin.call('notification', 'modal', modalContent) } - const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}` + }) +} +export const uploadFolder = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record) => void) => { + for(const file of [...target.files]) { + const workspaceProvider = plugin.fileProviders.workspace + const name = targetFolder === '/' ? file.webkitRelativePath : `${targetFolder}/${file.webkitRelativePath}` if (!await workspaceProvider.exists(name)) { - loadFile(name) + loadFile(name, file, workspaceProvider, cb) } else { - dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => { - loadFile(name) - }, () => { })) + const modalContent: AppModal = { + id: 'overwriteUploadFolderFile', + title: 'Confirm overwrite', + message: `The file "${name}" already exists! Would you like to overwrite it?`, + modalType: ModalTypes.confirm, + okLabel: 'OK', + cancelLabel: 'Cancel', + okFn: () => { + loadFile(name, file, workspaceProvider, cb) + }, + cancelFn: () => {}, + hideFn: () => {} + } + plugin.call('notification', 'modal', modalContent) } - }) + } } export const getWorkspaces = async (): Promise<{ name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[]> | undefined => { diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx index dd01680536..b187b152b9 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx @@ -10,27 +10,33 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => { menuItems: [ { action: 'createNewFile', - title: 'Create New File', + title: 'Create new file', icon: 'far fa-file', - placement: 'top-start' + placement: 'top' }, { action: 'createNewFolder', - title: 'Create New Folder', + title: 'Create new folder', icon: 'far fa-folder', - placement: 'top-end' + placement: 'top' }, { action: 'publishToGist', - title: 'Publish all the current workspace files to a github gist', + title: 'Publish current workspace to GitHub gist', icon: 'fab fa-github', - placement: 'bottom-start' + placement: 'top' }, { action: 'uploadFile', - title: 'Load a local file into current workspace', + title: 'Upload files into current workspace', icon: 'fa fa-upload', - placement: 'right' + placement: 'top' + }, + { + action: 'uploadFolder', + title: 'Upload folder into current workspace', + icon: 'fas fa-folder-upload', + placement: 'top' }, { action: 'updateGist', @@ -41,6 +47,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => { ].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })), actions: {} }) + const enableDirUpload = { directory: "", webkitdirectory: "" } useEffect(() => { const actions = { @@ -67,7 +74,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => { if (action === 'uploadFile') { return ( } @@ -81,6 +88,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => { > { e.stopPropagation() + _paq.push(['trackEvent', 'fileExplorer', 'fileAction', action]) props.uploadFile(e.target) e.target.value = null }} @@ -88,6 +96,31 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => { ) + } else if (action === 'uploadFolder') { + return ( + } + key={`index-${action}-${placement}-${icon}`} + > + + + ) } else { return ( { props.dispatchUploadFile(target, parentFolder) } + const uploadFolder = (target) => { + const parentFolder = getFocusedFolder() + const expandPath = [...new Set([...props.expandPath, parentFolder])] + + props.dispatchHandleExpandPath(expandPath) + props.dispatchUploadFolder(target, parentFolder) + } + const copyFile = (src: string, dest: string) => { try { props.dispatchCopyFile(src, dest) @@ -459,6 +467,7 @@ export const FileExplorer = (props: FileExplorerProps) => { createNewFolder={handleNewFolderInput} publishToGist={publishToGist} uploadFile={uploadFile} + uploadFolder={uploadFolder} /> } diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index 2afd2b706a..c4b342d799 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -18,6 +18,7 @@ export const FileSystemContext = createContext<{ dispatchDeleteAllWorkspaces: () => Promise, dispatchPublishToGist: (path?: string, type?: string) => Promise, dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise, + dispatchUploadFolder: (target?: SyntheticEvent, targetFolder?: string) => Promise, dispatchCreateNewFile: (path: string, rootDir: string) => Promise, dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise, dispatchCreateNewFolder: (path: string, rootDir: string) => Promise, diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index 887a0f1afd..e4bd835c23 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -7,7 +7,7 @@ import { FileSystemContext } from '../contexts' import { browserReducer, browserInitialState } from '../reducers/workspace' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, - fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, + fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction } from '../actions' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' @@ -79,6 +79,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => { await uploadFile(target, targetFolder) } + const dispatchUploadFolder = async (target?: SyntheticEvent, targetFolder?: string) => { + await uploadFolder(target, targetFolder) + } + const dispatchCreateNewFile = async (path: string, rootDir: string) => { await createNewFile(path, rootDir) } @@ -265,6 +269,7 @@ export const FileSystemProvider = (props: WorkspaceProps) => { dispatchDeleteAllWorkspaces, dispatchPublishToGist, dispatchUploadFile, + dispatchUploadFolder, dispatchCreateNewFile, dispatchSetFocusElement, dispatchCreateNewFolder, diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 0bcf4d3ec9..55e625b4e3 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -528,7 +528,7 @@ export function Workspace () {
Promise, dispatchDownloadPath: (path: string) => Promise, dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise, + dispatchUploadFolder: (target?: React.SyntheticEvent, targetFolder?: string) => Promise, dispatchCopyFile: (src: string, dest: string) => Promise, dispatchCopyFolder: (src: string, dest: string) => Promise, dispatchRunScript: (path: string) => Promise, @@ -120,6 +121,7 @@ export interface FileExplorerMenuProps { createNewFolder: (parentFolder?: string) => void, publishToGist: (path?: string) => void, uploadFile: (target: EventTarget & HTMLInputElement) => void + uploadFolder: (target: EventTarget & HTMLInputElement) => void tooltipPlacement?: Placement } export interface FileExplorerContextMenuProps {