Refactor delete all for multi-select

pull/1233/head
ioedeveloper 4 years ago
parent 16f539627b
commit 4d89a8b744
  1. 41
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  2. 82
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  3. 7
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  4. 4
      package-lock.json

@ -6,6 +6,7 @@ import './css/file-explorer-context-menu.css'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props
const contextMenuRef = useRef(null)
useEffect(() => {
contextMenuRef.current.focus()
}, [])
@ -21,43 +22,24 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
}
}, [pageX, pageY])
const filterItem = (item: action) => {
/**
* if there are multiple elements focused we need to take this and all conditions must be met plus the action must be set to 'multi'
* for example : 'downloadAsZip' with type ['file','folder','multi'] will work on files and folders when multiple are selected
**/
const nonRootFocus = focus.filter((el) => { return !(el.key === '' && el.type === 'folder') })
if (nonRootFocus.length > 1) {
for (const element of nonRootFocus) {
if (!itemMatchesCondition(item, element.type, element.key)) return false
}
return (item.type.includes('multi'))
} else {
return itemMatchesCondition(item, type, path)
}
}
const itemMatchesCondition = (item: action, itemType: string, itemPath: string) => {
if (item.type && Array.isArray(item.type) && (item.type.findIndex(name => name === itemType) !== -1)) return true
else if (item.path && Array.isArray(item.path) && (item.path.findIndex(key => key === itemPath) !== -1)) return true
else if (item.extension && Array.isArray(item.extension) && (item.extension.findIndex(ext => itemPath.endsWith(ext)) !== -1)) return true
else if (item.pattern && Array.isArray(item.pattern) && (item.pattern.filter(value => itemPath.match(new RegExp(value))).length > 0)) return true
const itemMatchesCondition = (item: action) => {
if (item.type && Array.isArray(item.type) && (item.type.findIndex(name => name === type) !== -1)) return true
else if (item.path && Array.isArray(item.path) && (item.path.findIndex(key => key === path) !== -1)) return true
else if (item.extension && Array.isArray(item.extension) && (item.extension.findIndex(ext => path.endsWith(ext)) !== -1)) return true
else if (item.pattern && Array.isArray(item.pattern) && (item.pattern.filter(value => path.match(new RegExp(value))).length > 0)) return true
else return false
}
const getPath = () => {
const nonRootFocus = focus.filter((el) => { return !(el.key === '' && el.type === 'folder') })
if (nonRootFocus.length > 1) {
return nonRootFocus.map((element) => { return element.key })
if (focus.length > 1) {
return focus.map((element) => element.key)
} else {
return path
}
}
const menu = () => {
return actions.filter(item => {
return filterItem(item)
}).map((item, index) => {
return actions.filter(item => itemMatchesCondition(item)).map((item, index) => {
return <li
id={`menuitem${item.name.toLowerCase()}`}
key={index}
@ -75,7 +57,7 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
renamePath(path, type)
break
case 'Delete':
deletePath(getPath())
deletePath(path)
break
case 'Push changes to gist':
pushChangesToGist(path, type)
@ -95,6 +77,9 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
case 'Paste':
paste(path, type)
break
case 'Delete All':
deletePath(getPath())
break
default:
emit && emit(item.id, getPath())
break

@ -6,7 +6,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import Gists from 'gists'
import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line
import { FileExplorerContextMenu } from './file-explorer-context-menu' // eslint-disable-line
import { FileExplorerProps, File } from './types'
import { FileExplorerProps, File, MenuItems } from './types'
import { fileSystemReducer, fileSystemInitialState } from './reducers/fileSystem'
import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
@ -33,63 +33,80 @@ export const FileExplorer = (props: FileExplorerProps) => {
type: ['folder', 'gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'newFolder',
name: 'New Folder',
type: ['folder', 'gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'rename',
name: 'Rename',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'delete',
name: 'Delete',
type: ['file', 'folder', 'gist', 'multi'],
type: ['file', 'folder', 'gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'run',
name: 'Run',
type: [],
path: [],
extension: ['.js'],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: ['gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'publishFolderToGist',
name: 'Publish folder to gist',
type: ['folder'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'publishFileToGist',
name: 'Publish file to gist',
type: ['file'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'copy',
name: 'Copy',
type: ['folder', 'file'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'deleteAll',
name: 'Delete All',
type: ['folder', 'file'],
path: [],
extension: [],
pattern: [],
multiselect: true
}],
focusContext: {
element: null,
@ -108,7 +125,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide: true,
title: '',
message: '',
children: <></>,
okLabel: '',
okFn: () => {},
cancelLabel: '',
@ -212,8 +228,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
okFn: prevState.modals[0].okFn,
cancelLabel: prevState.modals[0].cancelLabel,
cancelFn: prevState.modals[0].cancelFn,
handleHide: prevState.modals[0].handleHide,
children: prevState.modals[0].children
handleHide: prevState.modals[0].handleHide
}
prevState.modals.shift()
@ -259,14 +274,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
type: ['folder', 'file'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}])
} else {
removeMenuItems(['paste'])
}
}, [canPaste])
const addMenuItems = (items: { id: string, name: string, type: string[], path: string[], extension: string[], pattern: string[] }[]) => {
const addMenuItems = (items: MenuItems) => {
setState(prevState => {
// filter duplicate items
const actions = items.filter(({ name }) => prevState.actions.findIndex(action => action.name === name) === -1)
@ -355,13 +371,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
const deletePath = async (path: string | string[]) => {
const filesProvider = fileSystem.provider.provider
if (!Array.isArray(path)) path = [path]
const children: React.ReactFragment = <div><div>Are you sure you want to delete {path.length > 1 ? 'these items' : 'this item'}?</div>{path.map((item, i) => (<li key={i}>{item}</li>))}</div>
for (const p of path) {
if (filesProvider.isReadOnly(p)) {
return toast('cannot delete file. ' + name + ' is a read only explorer')
}
}
modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, '', 'OK', async () => {
modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, deleteMessage(path), 'OK', async () => {
const fileManager = state.fileManager
for (const p of path) {
try {
@ -371,7 +386,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`)
}
}
}, 'Cancel', () => {}, children)
}, 'Cancel', () => {})
}
const renamePath = async (oldPath: string, newPath: string) => {
@ -596,14 +611,13 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const modal = (title: string, message: string, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void, children?:React.ReactNode) => {
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
setState(prevState => {
return {
...prevState,
modals: [...prevState.modals,
{
message,
children,
title,
okLabel,
okFn,
@ -631,11 +645,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
} else {
if (state.focusElement.findIndex(item => item.key === path) !== -1) {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement.filter(item => item.key !== path)] }
return { ...prevState, focusElement: prevState.focusElement.filter(item => item.key !== path) }
})
} else {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement, { key: path, type }] }
const nonRootFocus = prevState.focusElement.filter((el) => { return !(el.key === '' && el.type === 'folder') })
nonRootFocus.push({ key: path, type })
return { ...prevState, focusElement: nonRootFocus }
})
}
}
@ -649,7 +666,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
} else {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement, { key: path, type }] }
const nonRootFocus = prevState.focusElement.filter((el) => { return !(el.key === '' && el.type === 'folder') })
nonRootFocus.push({ key: path, type })
return { ...prevState, focusElement: nonRootFocus }
})
}
} else {
@ -806,6 +826,17 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const deleteMessage = (path: string[]) => {
return (
<div>
<div>Are you sure you want to delete {path.length > 1 ? 'these items' : 'this item'}?</div>
{
path.map((item, i) => (<li key={i}>{item}</li>))
}
</div>
)
}
const label = (file: File) => {
return (
<div
@ -961,7 +992,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
id={ props.name }
title={ state.focusModal.title }
message={ state.focusModal.message }
children={ state.focusModal.children }
hide={ state.focusModal.hide }
okLabel={ state.focusModal.okLabel }
okFn={ state.focusModal.okFn }
@ -973,7 +1003,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
<Toaster message={state.toasterMsg} />
{ state.showContextMenu &&
<FileExplorerContextMenu
actions={state.actions}
actions={state.focusElement.length > 1 ? state.actions.filter(item => item.multiselect) : state.actions.filter(item => !item.multiselect)}
hideContextMenu={hideContextMenu}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}

@ -6,7 +6,7 @@ export interface FileExplorerProps {
menuItems?: string[],
plugin: any,
focusRoot: boolean,
contextMenuItems: { id: string, name: string, type: string[], path: string[], extension: string[], pattern: string[] }[],
contextMenuItems: MenuItems,
displayInput?: boolean,
externalUploads?: EventTarget & HTMLInputElement
}
@ -28,7 +28,10 @@ export interface FileExplorerMenuProps {
publishToGist: (path?: string) => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void
}
export type action = { name: string, type: string[], path: string[], extension: string[], pattern: string[], id: string }
export type action = { name: string, type: string[], path: string[], extension: string[], pattern: string[], id: string, multiselect: boolean }
export type MenuItems = action[]
export interface FileExplorerContextMenuProps {
actions: action[],
createNewFile: (folder?: string) => void,

4
package-lock.json generated

@ -17419,7 +17419,7 @@
"express-ws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-4.0.0.tgz",
"integrity": "sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==",
"integrity": "sha1-2r2NyXRRZBiQKkH+bjDtlJtNNsQ=",
"requires": {
"ws": "^5.2.0"
},
@ -17427,7 +17427,7 @@
"ws": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"integrity": "sha1-3/7xSGa46NyRM1glFNG++vlumA8=",
"requires": {
"async-limiter": "~1.0.0"
}

Loading…
Cancel
Save