|
|
|
@ -19,10 +19,7 @@ const queryParams = new QueryParams() |
|
|
|
|
export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads } = props |
|
|
|
|
const [state, setState] = useState({ |
|
|
|
|
focusElement: [{ |
|
|
|
|
key: '', |
|
|
|
|
type: 'folder' |
|
|
|
|
}], |
|
|
|
|
focusElement: [{ key: '', type: 'folder' }], |
|
|
|
|
focusPath: null, |
|
|
|
|
files: [], |
|
|
|
|
fileManager: null, |
|
|
|
@ -52,7 +49,7 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
}, { |
|
|
|
|
id: 'delete', |
|
|
|
|
name: 'Delete', |
|
|
|
|
type: ['file', 'folder'], |
|
|
|
|
type: ['file', 'folder', 'multi'], |
|
|
|
|
path: [], |
|
|
|
|
extension: [], |
|
|
|
|
pattern: [] |
|
|
|
@ -70,6 +67,13 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
path: [], |
|
|
|
|
extension: ['.js'], |
|
|
|
|
pattern: [] |
|
|
|
|
}, { |
|
|
|
|
id: 'test', |
|
|
|
|
name: 'test', |
|
|
|
|
type: ['file', 'folder', 'multi'], |
|
|
|
|
path: [], |
|
|
|
|
extension: [], |
|
|
|
|
pattern: [] |
|
|
|
|
}], |
|
|
|
|
focusContext: { |
|
|
|
|
element: null, |
|
|
|
@ -88,6 +92,7 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
hide: true, |
|
|
|
|
title: '', |
|
|
|
|
message: '', |
|
|
|
|
children: <></>, |
|
|
|
|
ok: { |
|
|
|
|
label: '', |
|
|
|
|
fn: () => {} |
|
|
|
@ -203,7 +208,8 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
message: prevState.modals[0].message, |
|
|
|
|
ok: prevState.modals[0].ok, |
|
|
|
|
cancel: prevState.modals[0].cancel, |
|
|
|
|
handleHide: prevState.modals[0].handleHide |
|
|
|
|
handleHide: prevState.modals[0].handleHide, |
|
|
|
|
children: prevState.modals[0].children |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
prevState.modals.shift() |
|
|
|
@ -216,6 +222,31 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
} |
|
|
|
|
}, [state.modals]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
const keyPressHandler = (e: KeyboardEvent) => { |
|
|
|
|
if (e.key === 'Shift') { |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { ...prevState, ctrlKey: true } |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const keyUpHandler = (e: KeyboardEvent) => { |
|
|
|
|
if (e.key === 'Shift') { |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { ...prevState, ctrlKey: false } |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', keyPressHandler) |
|
|
|
|
document.addEventListener('keyup', keyUpHandler) |
|
|
|
|
return () => { |
|
|
|
|
document.removeEventListener('keydown', keyPressHandler) |
|
|
|
|
document.removeEventListener('keyup', keyUpHandler) |
|
|
|
|
} |
|
|
|
|
}, []) |
|
|
|
|
|
|
|
|
|
const extractNameFromKey = (key: string):string => { |
|
|
|
|
const keyPath = key.split('/') |
|
|
|
|
|
|
|
|
@ -280,29 +311,32 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const deletePath = async (path: string) => { |
|
|
|
|
const deletePath = async (path: string | string[]) => { |
|
|
|
|
const filesProvider = fileSystem.provider.provider |
|
|
|
|
|
|
|
|
|
if (filesProvider.isReadOnly(path)) { |
|
|
|
|
return toast('cannot delete file. ' + name + ' is a read only explorer') |
|
|
|
|
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') |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
const isDir = state.fileManager.isDirectory(path) |
|
|
|
|
|
|
|
|
|
modal(`Delete ${isDir ? 'folder' : 'file'}`, `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, { |
|
|
|
|
modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, '', { |
|
|
|
|
label: 'OK', |
|
|
|
|
fn: async () => { |
|
|
|
|
try { |
|
|
|
|
const fileManager = state.fileManager |
|
|
|
|
|
|
|
|
|
await fileManager.remove(path) |
|
|
|
|
} catch (e) { |
|
|
|
|
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${path}.`) |
|
|
|
|
const fileManager = state.fileManager |
|
|
|
|
for (const p of path) { |
|
|
|
|
try { |
|
|
|
|
await fileManager.remove(p) |
|
|
|
|
} catch (e) { |
|
|
|
|
const isDir = state.fileManager.isDirectory(p) |
|
|
|
|
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, { |
|
|
|
|
label: 'Cancel', |
|
|
|
|
fn: () => {} |
|
|
|
|
}) |
|
|
|
|
}, children) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const renamePath = async (oldPath: string, newPath: string) => { |
|
|
|
@ -526,7 +560,7 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const emitContextMenuEvent = (id: string, path: string) => { |
|
|
|
|
const emitContextMenuEvent = (id: string, path: string | string[]) => { |
|
|
|
|
plugin.emit(id, path) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -536,13 +570,14 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const modal = (title: string, message: string, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => { |
|
|
|
|
const modal = (title: string, message: string, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }, children?:React.ReactNode) => { |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { |
|
|
|
|
...prevState, |
|
|
|
|
modals: [...prevState.modals, |
|
|
|
|
{ |
|
|
|
|
message, |
|
|
|
|
children, |
|
|
|
|
title, |
|
|
|
|
ok, |
|
|
|
|
cancel, |
|
|
|
@ -560,10 +595,22 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
|
|
|
|
|
const handleClickFile = (path: string) => { |
|
|
|
|
path = path.indexOf(props.name + '/') === 0 ? path.replace(props.name + '/', '') : path |
|
|
|
|
state.fileManager.open(path) |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { ...prevState, focusElement: [{ key: path, type: 'file' }] } |
|
|
|
|
}) |
|
|
|
|
if (!state.ctrlKey) { |
|
|
|
|
state.fileManager.open(path) |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { ...prevState, focusElement: [{ key: path, type: 'file' }] } |
|
|
|
|
}) |
|
|
|
|
} else { |
|
|
|
|
if (state.focusElement.findIndex(item => item.key === path) !== -1) { |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { ...prevState, focusElement: [...prevState.focusElement.filter(item => item.key !== path)] } |
|
|
|
|
}) |
|
|
|
|
} else { |
|
|
|
|
setState(prevState => { |
|
|
|
|
return { ...prevState, focusElement: [...prevState.focusElement, { key: path, type: 'file' }] } |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleClickFolder = async (path: string) => { |
|
|
|
@ -864,6 +911,7 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
id={ props.name } |
|
|
|
|
title={ state.focusModal.title } |
|
|
|
|
message={ state.focusModal.message } |
|
|
|
|
children={ state.focusModal.children } |
|
|
|
|
hide={ state.focusModal.hide } |
|
|
|
|
ok={ state.focusModal.ok } |
|
|
|
|
cancel={ state.focusModal.cancel } |
|
|
|
@ -885,6 +933,7 @@ export const FileExplorer = (props: FileExplorerProps) => { |
|
|
|
|
pageY={state.focusContext.y} |
|
|
|
|
path={state.focusContext.element} |
|
|
|
|
type={state.focusContext.type} |
|
|
|
|
focus={state.focusElement} |
|
|
|
|
onMouseOver={(e) => { |
|
|
|
|
e.stopPropagation() |
|
|
|
|
handleMouseOver(state.focusContext.element) |
|
|
|
|