pull/3488/head
filip mertens 2 years ago
commit 3a115d6ef6
  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.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",

@ -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<string, any>) => 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<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)) {
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 => {

@ -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 (
<CustomTooltip
placement="right"
placement={placement as Placement}
tooltipId="uploadFileTooltip"
tooltipClasses="text-nowrap"
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) => {
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) => {
</label>
</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 {
return (
<CustomTooltip

@ -190,6 +190,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
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}
/>
</div>
}

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

@ -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,

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

@ -98,6 +98,7 @@ export interface FileExplorerProps {
dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>,
dispatchDownloadPath: (path: 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>,
dispatchCopyFolder: (src: string, dest: string) => Promise<void>,
dispatchRunScript: (path: string) => Promise<void>,
@ -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 {

Loading…
Cancel
Save