pull/5370/head
filip mertens 2 years ago
commit 9a1d2bfe36
  1. 9
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  2. 90
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  3. 51
      libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx
  4. 9
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  5. 1
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  6. 7
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  7. 4
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  8. 2
      libs/remix-ui/workspace/src/lib/types/index.ts

@ -39,10 +39,11 @@
"filePanel.paste": "Paste", "filePanel.paste": "Paste",
"filePanel.compile": "Compile", "filePanel.compile": "Compile",
"filePanel.compileForNahmii": "Compile for Nahmii", "filePanel.compileForNahmii": "Compile for Nahmii",
"filePanel.createNewFile": "Create New File", "filePanel.createNewFile": "Create new file",
"filePanel.createNewFolder": "Create New Folder", "filePanel.createNewFolder": "Create new folder",
"filePanel.publishToGist": "Publish all the current workspace files to a github gist", "filePanel.publishToGist": "Publish all files to GitHub gist",
"filePanel.uploadFile": "Load a local file into current workspace", "filePanel.uploadFile": "Upload files",
"filePanel.uploadFolder": "Upload folder",
"filePanel.updateGist": "Update the current [gist] explorer", "filePanel.updateGist": "Update the current [gist] explorer",
"filePanel.viewAllBranches": "View all branches", "filePanel.viewAllBranches": "View all branches",
"filePanel.createBranch": "Create branch", "filePanel.createBranch": "Create branch",

@ -12,6 +12,7 @@ import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/consta
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB' import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB'
import { getUncommittedFiles } from '../utils/gitStatusFilter' import { getUncommittedFiles } from '../utils/gitStatusFilter'
import { AppModal, ModalTypes } from '@remix-ui/app'
declare global { declare global {
interface Window { remixFileSystemCallback: IndexedDBStorage; } 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<string, any>) => void) => { export const uploadFile = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
// TODO The file explorer is merely a view on the current state of // 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 // 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. // pick that up via the 'fileAdded' event from the files module.
[...target.files].forEach(async (file) => { [...target.files].forEach(async (file) => {
const workspaceProvider = plugin.fileProviders.workspace const workspaceProvider = plugin.fileProviders.workspace
const loadFile = (name: string): void => { const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}`
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
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) { if (!await workspaceProvider.exists(name)) {
editor.setText(name, event.target.result) 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) plugin.call('notification', 'modal', modalContent)
cb && cb(null, true)
} }
const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}` })
}
export const uploadFolder = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => 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)) { if (!await workspaceProvider.exists(name)) {
loadFile(name) loadFile(name, file, workspaceProvider, cb)
} else { } else {
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => { const modalContent: AppModal = {
loadFile(name) 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 => { export const getWorkspaces = async (): Promise<{ name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[]> | undefined => {

@ -10,27 +10,33 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
menuItems: [ menuItems: [
{ {
action: 'createNewFile', action: 'createNewFile',
title: 'Create New File', title: 'Create new file',
icon: 'far fa-file', icon: 'far fa-file',
placement: 'top-start' placement: 'top'
}, },
{ {
action: 'createNewFolder', action: 'createNewFolder',
title: 'Create New Folder', title: 'Create new folder',
icon: 'far fa-folder', icon: 'far fa-folder',
placement: 'top-end' placement: 'top'
}, },
{ {
action: 'publishToGist', action: 'publishToGist',
title: 'Publish all the current workspace files to a github gist', title: 'Publish current workspace to GitHub gist',
icon: 'fab fa-github', icon: 'fab fa-github',
placement: 'bottom-start' placement: 'top'
}, },
{ {
action: 'uploadFile', action: 'uploadFile',
title: 'Load a local file into current workspace', title: 'Upload files into current workspace',
icon: 'fa fa-upload', 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', action: 'updateGist',
@ -41,6 +47,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })), ].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })),
actions: {} actions: {}
}) })
const enableDirUpload = { directory: "", webkitdirectory: "" }
useEffect(() => { useEffect(() => {
const actions = { const actions = {
@ -67,7 +74,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
if (action === 'uploadFile') { if (action === 'uploadFile') {
return ( return (
<CustomTooltip <CustomTooltip
placement="right" placement={placement as Placement}
tooltipId="uploadFileTooltip" tooltipId="uploadFileTooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id={`filePanel.${action}`} defaultMessage={title} />} tooltipText={<FormattedMessage id={`filePanel.${action}`} defaultMessage={title} />}
@ -81,6 +88,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
> >
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onChange={(e) => { <input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onChange={(e) => {
e.stopPropagation() e.stopPropagation()
_paq.push(['trackEvent', 'fileExplorer', 'fileAction', action])
props.uploadFile(e.target) props.uploadFile(e.target)
e.target.value = null e.target.value = null
}} }}
@ -88,6 +96,31 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
</label> </label>
</CustomTooltip> </CustomTooltip>
) )
} else if (action === 'uploadFolder') {
return (
<CustomTooltip
placement={placement as Placement}
tooltipId="uploadFolderTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id={`filePanel.${action}`} defaultMessage={title} />}
key={`index-${action}-${placement}-${icon}`}
>
<label
id={action}
data-id={'fileExplorerUploadFolder' + action }
className={icon + ' mb-0 remixui_newFile'}
key={`index-${action}-${placement}-${icon}`}
>
<input id="folderUpload" data-id="fileExplorerFolderUpload" type="file" onChange={(e) => {
e.stopPropagation()
_paq.push(['trackEvent', 'fileExplorer', 'fileAction', action])
props.uploadFolder(e.target)
e.target.value = null
}}
{...enableDirUpload} multiple />
</label>
</CustomTooltip>
)
} else { } else {
return ( return (
<CustomTooltip <CustomTooltip

@ -190,6 +190,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
props.dispatchUploadFile(target, parentFolder) 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) => { const copyFile = (src: string, dest: string) => {
try { try {
props.dispatchCopyFile(src, dest) props.dispatchCopyFile(src, dest)
@ -459,6 +467,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
createNewFolder={handleNewFolderInput} createNewFolder={handleNewFolderInput}
publishToGist={publishToGist} publishToGist={publishToGist}
uploadFile={uploadFile} uploadFile={uploadFile}
uploadFolder={uploadFolder}
/> />
</div> </div>
} }

@ -18,6 +18,7 @@ export const FileSystemContext = createContext<{
dispatchDeleteAllWorkspaces: () => Promise<void>, dispatchDeleteAllWorkspaces: () => Promise<void>,
dispatchPublishToGist: (path?: string, type?: string) => Promise<void>, dispatchPublishToGist: (path?: string, type?: string) => Promise<void>,
dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>, dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchUploadFolder: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchCreateNewFile: (path: string, rootDir: string) => Promise<void>, dispatchCreateNewFile: (path: string, rootDir: string) => Promise<void>,
dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise<void>, dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise<void>,
dispatchCreateNewFolder: (path: string, rootDir: string) => Promise<void>, dispatchCreateNewFolder: (path: string, rootDir: string) => Promise<void>,

@ -7,7 +7,7 @@ import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace' import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, 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 showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction
} from '../actions' } from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
@ -79,6 +79,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await uploadFile(target, targetFolder) await uploadFile(target, targetFolder)
} }
const dispatchUploadFolder = async (target?: SyntheticEvent, targetFolder?: string) => {
await uploadFolder(target, targetFolder)
}
const dispatchCreateNewFile = async (path: string, rootDir: string) => { const dispatchCreateNewFile = async (path: string, rootDir: string) => {
await createNewFile(path, rootDir) await createNewFile(path, rootDir)
} }
@ -265,6 +269,7 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchDeleteAllWorkspaces, dispatchDeleteAllWorkspaces,
dispatchPublishToGist, dispatchPublishToGist,
dispatchUploadFile, dispatchUploadFile,
dispatchUploadFolder,
dispatchCreateNewFile, dispatchCreateNewFile,
dispatchSetFocusElement, dispatchSetFocusElement,
dispatchCreateNewFolder, dispatchCreateNewFolder,

@ -528,7 +528,7 @@ export function Workspace () {
<div className='h-100 remixui_treeview' data-id='filePanelFileExplorerTree'> <div className='h-100 remixui_treeview' data-id='filePanelFileExplorerTree'>
<FileExplorer <FileExplorer
name={currentWorkspace} name={currentWorkspace}
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '', canUpload ? 'uploadFolder' : '']}
contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems} contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems}
removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems} removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems}
files={global.fs.browser.files} files={global.fs.browser.files}
@ -547,6 +547,7 @@ export function Workspace () {
dispatchRenamePath={global.dispatchRenamePath} dispatchRenamePath={global.dispatchRenamePath}
dispatchDownloadPath={global.dispatchDownloadPath} dispatchDownloadPath={global.dispatchDownloadPath}
dispatchUploadFile={global.dispatchUploadFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchUploadFolder={global.dispatchUploadFolder}
dispatchCopyFile={global.dispatchCopyFile} dispatchCopyFile={global.dispatchCopyFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFolder={global.dispatchCopyFolder}
dispatchPublishToGist={global.dispatchPublishToGist} dispatchPublishToGist={global.dispatchPublishToGist}
@ -587,6 +588,7 @@ export function Workspace () {
dispatchRenamePath={global.dispatchRenamePath} dispatchRenamePath={global.dispatchRenamePath}
dispatchDownloadPath={global.dispatchDownloadPath} dispatchDownloadPath={global.dispatchDownloadPath}
dispatchUploadFile={global.dispatchUploadFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchUploadFolder={global.dispatchUploadFolder}
dispatchCopyFile={global.dispatchCopyFile} dispatchCopyFile={global.dispatchCopyFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFolder={global.dispatchCopyFolder}
dispatchPublishToGist={global.dispatchPublishToGist} dispatchPublishToGist={global.dispatchPublishToGist}

@ -98,6 +98,7 @@ export interface FileExplorerProps {
dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>, dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>,
dispatchDownloadPath: (path: string) => Promise<void>, dispatchDownloadPath: (path: string) => Promise<void>,
dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>, dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchUploadFolder: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchCopyFile: (src: string, dest: string) => Promise<void>, dispatchCopyFile: (src: string, dest: string) => Promise<void>,
dispatchCopyFolder: (src: string, dest: string) => Promise<void>, dispatchCopyFolder: (src: string, dest: string) => Promise<void>,
dispatchRunScript: (path: string) => Promise<void>, dispatchRunScript: (path: string) => Promise<void>,
@ -120,6 +121,7 @@ export interface FileExplorerMenuProps {
createNewFolder: (parentFolder?: string) => void, createNewFolder: (parentFolder?: string) => void,
publishToGist: (path?: string) => void, publishToGist: (path?: string) => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void uploadFile: (target: EventTarget & HTMLInputElement) => void
uploadFolder: (target: EventTarget & HTMLInputElement) => void
tooltipPlacement?: Placement tooltipPlacement?: Placement
} }
export interface FileExplorerContextMenuProps { export interface FileExplorerContextMenuProps {

Loading…
Cancel
Save