allow folder creation everywhere
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { EllipsisVerticalIcon } from 'lucide-react'
|
import { MousePointerIcon, XIcon } from 'lucide-react'
|
||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FilePanel,
|
FilePanel,
|
||||||
@@ -83,13 +83,41 @@ export default function PathSelector({
|
|||||||
|
|
||||||
const renderToolbar = useCallback(
|
const renderToolbar = useCallback(
|
||||||
(buttons: ToolbarButtons) => [
|
(buttons: ToolbarButtons) => [
|
||||||
[buttons.BackButton, buttons.RefreshButton],
|
[
|
||||||
|
buttons.BackButton,
|
||||||
|
buttons.RefreshButton,
|
||||||
|
<Tooltip
|
||||||
|
key="dismiss-tooltip"
|
||||||
|
content="Close this window (Esc)"
|
||||||
|
placement="top"
|
||||||
|
size="lg"
|
||||||
|
color="foreground"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
size="sm"
|
||||||
|
radius="full"
|
||||||
|
isIconOnly={true}
|
||||||
|
onPress={onClose}
|
||||||
|
>
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
buttons.SearchInput,
|
buttons.SearchInput,
|
||||||
|
buttons.NewFolderButton,
|
||||||
...(allowMultiple
|
...(allowMultiple
|
||||||
? [
|
? [
|
||||||
|
<Tooltip
|
||||||
|
key="select-dropdown-tooltip"
|
||||||
|
content="Select items"
|
||||||
|
placement="top"
|
||||||
|
size="lg"
|
||||||
|
color="foreground"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
key="select-dropdown"
|
|
||||||
shadow={platform() === 'windows' ? 'none' : undefined}
|
shadow={platform() === 'windows' ? 'none' : undefined}
|
||||||
>
|
>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
@@ -97,10 +125,9 @@ export default function PathSelector({
|
|||||||
color="primary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
radius="full"
|
radius="full"
|
||||||
startContent={<EllipsisVerticalIcon className="size-4" />}
|
isIconOnly={true}
|
||||||
className="gap-0.5 min-w-fit"
|
|
||||||
>
|
>
|
||||||
SELECT
|
<MousePointerIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownTrigger>
|
</DropdownTrigger>
|
||||||
<DropdownMenu color="primary">
|
<DropdownMenu color="primary">
|
||||||
@@ -140,20 +167,11 @@ export default function PathSelector({
|
|||||||
Deselect All
|
Deselect All
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>,
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</Tooltip>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<Tooltip
|
|
||||||
key="dismiss-tooltip"
|
|
||||||
content="Close this window (Esc)"
|
|
||||||
placement="top"
|
|
||||||
size="lg"
|
|
||||||
color="foreground"
|
|
||||||
>
|
|
||||||
<Button color="danger" size="sm" radius="full" onPress={onClose}>
|
|
||||||
DISMISS
|
|
||||||
</Button>
|
|
||||||
</Tooltip>,
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
allowMultiple ? (
|
allowMultiple ? (
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Divider } from '@heroui/react'
|
import { Button, Divider, Tooltip } from '@heroui/react'
|
||||||
|
import { FolderPlusIcon } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
startTransition,
|
startTransition,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
@@ -15,6 +17,7 @@ import PathBreadcrumb from './PathBreadcrumb'
|
|||||||
import PreviewDrawer from './PreviewDrawer'
|
import PreviewDrawer from './PreviewDrawer'
|
||||||
import RemoteSidebar from './RemoteSidebar'
|
import RemoteSidebar from './RemoteSidebar'
|
||||||
import type { AllowedKey, ContextMenuItem, Entry, FilePanelHandle, SelectItem } from './types'
|
import type { AllowedKey, ContextMenuItem, Entry, FilePanelHandle, SelectItem } from './types'
|
||||||
|
import useCreateFolder from './useCreateFolder'
|
||||||
import useFileNavigation from './useFileNavigation'
|
import useFileNavigation from './useFileNavigation'
|
||||||
import { RE_LEADING_SLASH, dragStateRef, dropTargetsRef, serializeRemotePath } from './utils'
|
import { RE_LEADING_SLASH, dragStateRef, dropTargetsRef, serializeRemotePath } from './utils'
|
||||||
|
|
||||||
@@ -81,6 +84,36 @@ const FilePanel = forwardRef<
|
|||||||
isActive,
|
isActive,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canCreateFolder, createFolder } = useCreateFolder(
|
||||||
|
nav.selectedRemote,
|
||||||
|
nav.cwd,
|
||||||
|
nav.refresh
|
||||||
|
)
|
||||||
|
|
||||||
|
const newFolderButton = useMemo(
|
||||||
|
() =>
|
||||||
|
canCreateFolder ? (
|
||||||
|
<Tooltip
|
||||||
|
key="new-folder-tooltip"
|
||||||
|
content="Create a new folder in this directory"
|
||||||
|
size="lg"
|
||||||
|
color="foreground"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
radius="full"
|
||||||
|
startContent={<FolderPlusIcon className="size-4" />}
|
||||||
|
className="gap-1 min-w-fit"
|
||||||
|
onPress={createFolder}
|
||||||
|
>
|
||||||
|
NEW
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
) : null,
|
||||||
|
[canCreateFolder, createFolder]
|
||||||
|
)
|
||||||
|
|
||||||
const listRef = useRef<HTMLDivElement>(null)
|
const listRef = useRef<HTMLDivElement>(null)
|
||||||
const panelRef = useRef<HTMLDivElement>(null)
|
const panelRef = useRef<HTMLDivElement>(null)
|
||||||
const panelIdRef = useRef(`panel-${Math.random().toString(36).slice(2)}`)
|
const panelIdRef = useRef(`panel-${Math.random().toString(36).slice(2)}`)
|
||||||
@@ -348,6 +381,7 @@ const FilePanel = forwardRef<
|
|||||||
onSearchChange={nav.setSearchTerm}
|
onSearchChange={nav.setSearchTerm}
|
||||||
renderToolbar={renderToolbar}
|
renderToolbar={renderToolbar}
|
||||||
visible={toolbarVisible && nav.selectedRemote !== 'UI_FAVORITES'}
|
visible={toolbarVisible && nav.selectedRemote !== 'UI_FAVORITES'}
|
||||||
|
newFolderButton={newFolderButton}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type ToolbarButtons = {
|
|||||||
BackButton: ReactNode
|
BackButton: ReactNode
|
||||||
RefreshButton: ReactNode
|
RefreshButton: ReactNode
|
||||||
SearchInput: ReactNode
|
SearchInput: ReactNode
|
||||||
|
NewFolderButton: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PanelToolbar({
|
export default function PanelToolbar({
|
||||||
@@ -18,6 +19,7 @@ export default function PanelToolbar({
|
|||||||
onSearchChange,
|
onSearchChange,
|
||||||
renderToolbar,
|
renderToolbar,
|
||||||
visible = true,
|
visible = true,
|
||||||
|
newFolderButton,
|
||||||
}: {
|
}: {
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
onRefresh: () => void
|
onRefresh: () => void
|
||||||
@@ -27,6 +29,7 @@ export default function PanelToolbar({
|
|||||||
onSearchChange: (term: string) => void
|
onSearchChange: (term: string) => void
|
||||||
renderToolbar?: (buttons: ToolbarButtons) => ReactNode[][]
|
renderToolbar?: (buttons: ToolbarButtons) => ReactNode[][]
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
|
newFolderButton?: ReactNode
|
||||||
}) {
|
}) {
|
||||||
const BackButton = (
|
const BackButton = (
|
||||||
<Tooltip content="Go to parent directory" size="lg" color="foreground">
|
<Tooltip content="Go to parent directory" size="lg" color="foreground">
|
||||||
@@ -78,10 +81,11 @@ export default function PanelToolbar({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const buttons: ToolbarButtons = { BackButton, RefreshButton, SearchInput }
|
const NewFolderButton = newFolderButton ?? null
|
||||||
|
const buttons: ToolbarButtons = { BackButton, RefreshButton, SearchInput, NewFolderButton }
|
||||||
const groups = renderToolbar
|
const groups = renderToolbar
|
||||||
? renderToolbar(buttons)
|
? renderToolbar(buttons)
|
||||||
: [[BackButton, RefreshButton], [SearchInput]]
|
: [[BackButton, RefreshButton], [SearchInput, NewFolderButton]]
|
||||||
|
|
||||||
const motionTransition = {
|
const motionTransition = {
|
||||||
enter: {
|
enter: {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export { default as PanelToolbar, type ToolbarButtons } from './PanelToolbar'
|
|||||||
export { default as PathBreadcrumb } from './PathBreadcrumb'
|
export { default as PathBreadcrumb } from './PathBreadcrumb'
|
||||||
export { default as PreviewDrawer } from './PreviewDrawer'
|
export { default as PreviewDrawer } from './PreviewDrawer'
|
||||||
export { default as RemoteSidebar } from './RemoteSidebar'
|
export { default as RemoteSidebar } from './RemoteSidebar'
|
||||||
|
export { default as useCreateFolder } from './useCreateFolder'
|
||||||
export { default as useFileNavigation } from './useFileNavigation'
|
export { default as useFileNavigation } from './useFileNavigation'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import { getFsInfo } from '../../../lib/format'
|
||||||
|
import rclone from '../../../lib/rclone/client'
|
||||||
|
import { supportsPersistentEmptyFolders } from '../../../lib/rclone/constants'
|
||||||
|
import type { RemoteString } from './types'
|
||||||
|
import { RE_TRAILING_SEPARATORS } from './utils'
|
||||||
|
|
||||||
|
export default function useCreateFolder(
|
||||||
|
remote: RemoteString,
|
||||||
|
cwd: string,
|
||||||
|
refresh: () => void
|
||||||
|
) {
|
||||||
|
const remoteConfigQuery = useQuery({
|
||||||
|
queryKey: ['remote', remote, 'config'],
|
||||||
|
queryFn: async () => {
|
||||||
|
return await rclone('/config/get', {
|
||||||
|
params: { query: { name: remote } },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enabled: !!remote && remote !== 'UI_LOCAL_FS' && remote !== 'UI_FAVORITES',
|
||||||
|
})
|
||||||
|
|
||||||
|
const backendType = useMemo(() => {
|
||||||
|
if (!remote || remote === 'UI_FAVORITES') return null
|
||||||
|
if (remote === 'UI_LOCAL_FS') return 'local'
|
||||||
|
return remoteConfigQuery.data?.type ?? null
|
||||||
|
}, [remote, remoteConfigQuery.data])
|
||||||
|
|
||||||
|
const canCreateFolder = useMemo(() => {
|
||||||
|
if (!remote || remote === 'UI_FAVORITES') return false
|
||||||
|
if (remote === 'UI_LOCAL_FS') return true
|
||||||
|
return supportsPersistentEmptyFolders(backendType)
|
||||||
|
}, [remote, backendType])
|
||||||
|
|
||||||
|
const createFolder = useCallback(async () => {
|
||||||
|
if (!remote || remote === 'UI_FAVORITES') return
|
||||||
|
|
||||||
|
if (!canCreateFolder) {
|
||||||
|
await message(
|
||||||
|
'This backend does not support persistent empty folders. Create a folder by uploading a file into it.',
|
||||||
|
{ title: 'Unsupported Backend', kind: 'warning' }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderName = await invoke<string | null>('prompt', {
|
||||||
|
title: 'New Folder',
|
||||||
|
message: 'Enter a name for the new folder',
|
||||||
|
default: 'New Folder',
|
||||||
|
sensitive: false,
|
||||||
|
})
|
||||||
|
const normalizedFolderName = folderName?.trim()
|
||||||
|
if (!normalizedFolderName) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const normalizedPath = cwd.replace(RE_TRAILING_SEPARATORS, '')
|
||||||
|
const fullTargetPath =
|
||||||
|
remote === 'UI_LOCAL_FS'
|
||||||
|
? `${normalizedPath}${normalizedPath ? '/' : ''}${normalizedFolderName}`
|
||||||
|
: `${remote}:/${normalizedPath}${normalizedPath ? '/' : ''}${normalizedFolderName}`
|
||||||
|
const info = getFsInfo(fullTargetPath)
|
||||||
|
|
||||||
|
await rclone('/operations/mkdir' as any, {
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
fs: info.root === ':local:' ? ':local:/' : info.root,
|
||||||
|
remote: info.filePath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
} catch (error) {
|
||||||
|
await message(error instanceof Error ? error.message : 'Create folder failed', {
|
||||||
|
title: 'Error',
|
||||||
|
kind: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [remote, cwd, refresh, canCreateFolder])
|
||||||
|
|
||||||
|
return { canCreateFolder, createFolder }
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ export const RE_BACKSLASH = /\\/g
|
|||||||
export const RE_TRAILING_SLASH = /\/+$/g
|
export const RE_TRAILING_SLASH = /\/+$/g
|
||||||
export const RE_LEADING_SLASH = /^\/+/
|
export const RE_LEADING_SLASH = /^\/+/
|
||||||
export const RE_PATH_SEPARATOR = /[/\\]/
|
export const RE_PATH_SEPARATOR = /[/\\]/
|
||||||
|
export const RE_TRAILING_SEPARATORS = /[\\/]+$/
|
||||||
|
|
||||||
export const VIRTUAL_PADDING_COUNT = 2
|
export const VIRTUAL_PADDING_COUNT = 2
|
||||||
|
|
||||||
|
|||||||
+1
-182
@@ -27,7 +27,6 @@ import {
|
|||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
FolderPlusIcon,
|
|
||||||
LoaderIcon,
|
LoaderIcon,
|
||||||
MoveIcon,
|
MoveIcon,
|
||||||
SearchCheckIcon,
|
SearchCheckIcon,
|
||||||
@@ -40,14 +39,11 @@ import { getFsInfo } from '../../lib/format'
|
|||||||
import { formatBytes } from '../../lib/format.ts'
|
import { formatBytes } from '../../lib/format.ts'
|
||||||
import { startCopy, startMove } from '../../lib/rclone/api'
|
import { startCopy, startMove } from '../../lib/rclone/api'
|
||||||
import rclone from '../../lib/rclone/client'
|
import rclone from '../../lib/rclone/client'
|
||||||
import { supportsPersistentEmptyFolders } from '../../lib/rclone/constants'
|
|
||||||
import { openWindow } from '../../lib/window'
|
import { openWindow } from '../../lib/window'
|
||||||
import { FileIcon } from '../components/navigator'
|
import { FileIcon } from '../components/navigator'
|
||||||
import { FilePanel, type FilePanelHandle, type ToolbarButtons } from '../components/navigator'
|
import { FilePanel, type FilePanelHandle } from '../components/navigator'
|
||||||
import type { Entry, SelectItem } from '../components/navigator/types'
|
import type { Entry, SelectItem } from '../components/navigator/types'
|
||||||
|
|
||||||
const RE_TRAILING_SEPARATORS = /[\\/]+$/
|
|
||||||
|
|
||||||
export default function Browser() {
|
export default function Browser() {
|
||||||
const leftPanelRef = useRef<FilePanelHandle>(null)
|
const leftPanelRef = useRef<FilePanelHandle>(null)
|
||||||
const rightPanelRef = useRef<FilePanelHandle>(null)
|
const rightPanelRef = useRef<FilePanelHandle>(null)
|
||||||
@@ -78,60 +74,6 @@ export default function Browser() {
|
|||||||
|
|
||||||
const remotes = remotesQuery.data ?? []
|
const remotes = remotesQuery.data ?? []
|
||||||
const firstRemote = remotes[0] ?? null
|
const firstRemote = remotes[0] ?? null
|
||||||
const [leftPanelLocation, setLeftPanelLocation] = useState<{
|
|
||||||
remote: string | null
|
|
||||||
path: string
|
|
||||||
}>({ remote: 'UI_LOCAL_FS', path: '' })
|
|
||||||
const [rightPanelLocation, setRightPanelLocation] = useState<{
|
|
||||||
remote: string | null
|
|
||||||
path: string
|
|
||||||
}>({ remote: null, path: '' })
|
|
||||||
|
|
||||||
const remoteTypesQuery = useQuery({
|
|
||||||
queryKey: ['remotes', 'types'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const dump = await rclone('/config/dump')
|
|
||||||
const types: Record<string, string> = {}
|
|
||||||
if (dump && typeof dump === 'object') {
|
|
||||||
for (const [name, config] of Object.entries(dump)) {
|
|
||||||
if (config && typeof config === 'object' && 'type' in config) {
|
|
||||||
types[name] = (config as { type: string }).type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return types
|
|
||||||
},
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
})
|
|
||||||
|
|
||||||
const remoteTypes = remoteTypesQuery.data ?? {}
|
|
||||||
|
|
||||||
const getBackendTypeForRemote = useCallback(
|
|
||||||
(remote: string | null) => {
|
|
||||||
if (!remote || remote === 'UI_FAVORITES') return null
|
|
||||||
if (remote === 'UI_LOCAL_FS') return 'local'
|
|
||||||
return remoteTypes[remote] ?? null
|
|
||||||
},
|
|
||||||
[remoteTypes]
|
|
||||||
)
|
|
||||||
|
|
||||||
const canCreateFolderAtRemote = useCallback(
|
|
||||||
(remote: string | null) => {
|
|
||||||
if (!remote || remote === 'UI_FAVORITES') return false
|
|
||||||
if (remote === 'UI_LOCAL_FS') return true
|
|
||||||
return supportsPersistentEmptyFolders(getBackendTypeForRemote(remote))
|
|
||||||
},
|
|
||||||
[getBackendTypeForRemote]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleLeftNavigate = useCallback((remote: string, path: string) => {
|
|
||||||
setLeftPanelLocation({ remote, path })
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleRightNavigate = useCallback((remote: string, path: string) => {
|
|
||||||
setRightPanelLocation({ remote, path })
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
(items: SelectItem[], destination: string, _sourceSide: 'left' | 'right') => {
|
(items: SelectItem[], destination: string, _sourceSide: 'left' | 'right') => {
|
||||||
setDropOperation({ items, destination })
|
setDropOperation({ items, destination })
|
||||||
@@ -272,125 +214,6 @@ export default function Browser() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleCreateFolder = useCallback(
|
|
||||||
async (panelSide: 'left' | 'right') => {
|
|
||||||
const panelRef = panelSide === 'left' ? leftPanelRef : rightPanelRef
|
|
||||||
const panel = panelRef.current
|
|
||||||
if (!panel) return
|
|
||||||
|
|
||||||
const currentPath = panel.getCurrentPath()
|
|
||||||
if (!currentPath.remote || currentPath.remote === 'UI_FAVORITES') return
|
|
||||||
|
|
||||||
if (!canCreateFolderAtRemote(currentPath.remote)) {
|
|
||||||
await message(
|
|
||||||
'This backend does not support persistent empty folders. Create a folder by uploading a file into it.',
|
|
||||||
{
|
|
||||||
title: 'Unsupported Backend',
|
|
||||||
kind: 'warning',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const folderName = await invoke<string | null>('prompt', {
|
|
||||||
title: 'New Folder',
|
|
||||||
message: 'Enter a name for the new folder',
|
|
||||||
default: 'New Folder',
|
|
||||||
sensitive: false,
|
|
||||||
})
|
|
||||||
const normalizedFolderName = folderName?.trim()
|
|
||||||
if (!normalizedFolderName) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const normalizedPath = currentPath.path.replace(RE_TRAILING_SEPARATORS, '')
|
|
||||||
const fullTargetPath =
|
|
||||||
currentPath.remote === 'UI_LOCAL_FS'
|
|
||||||
? `${normalizedPath}${normalizedPath ? '/' : ''}${normalizedFolderName}`
|
|
||||||
: `${currentPath.remote}:/${normalizedPath}${normalizedPath ? '/' : ''}${normalizedFolderName}`
|
|
||||||
const info = getFsInfo(fullTargetPath)
|
|
||||||
|
|
||||||
await rclone('/operations/mkdir' as any, {
|
|
||||||
params: {
|
|
||||||
query: {
|
|
||||||
fs: info.root === ':local:' ? ':local:/' : info.root,
|
|
||||||
remote: info.filePath,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
panel.refresh()
|
|
||||||
} catch (error) {
|
|
||||||
await message(error instanceof Error ? error.message : 'Create folder failed', {
|
|
||||||
title: 'Error',
|
|
||||||
kind: 'error',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[canCreateFolderAtRemote]
|
|
||||||
)
|
|
||||||
|
|
||||||
const renderLeftToolbar = useCallback(
|
|
||||||
(buttons: ToolbarButtons) => [
|
|
||||||
[buttons.BackButton, buttons.RefreshButton],
|
|
||||||
[
|
|
||||||
buttons.SearchInput,
|
|
||||||
...(canCreateFolderAtRemote(leftPanelLocation.remote)
|
|
||||||
? [
|
|
||||||
<Tooltip
|
|
||||||
key="left-new-folder-tooltip"
|
|
||||||
content="Create a new folder in this directory"
|
|
||||||
size="lg"
|
|
||||||
color="foreground"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
radius="full"
|
|
||||||
startContent={<FolderPlusIcon className="size-4" />}
|
|
||||||
className="gap-1 min-w-fit"
|
|
||||||
onPress={() => handleCreateFolder('left')}
|
|
||||||
>
|
|
||||||
NEW FOLDER
|
|
||||||
</Button>
|
|
||||||
</Tooltip>,
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[canCreateFolderAtRemote, leftPanelLocation.remote, handleCreateFolder]
|
|
||||||
)
|
|
||||||
|
|
||||||
const renderRightToolbar = useCallback(
|
|
||||||
(buttons: ToolbarButtons) => [
|
|
||||||
[buttons.BackButton, buttons.RefreshButton],
|
|
||||||
[
|
|
||||||
buttons.SearchInput,
|
|
||||||
...(canCreateFolderAtRemote(rightPanelLocation.remote)
|
|
||||||
? [
|
|
||||||
<Tooltip
|
|
||||||
key="right-new-folder-tooltip"
|
|
||||||
content="Create a new folder in this directory"
|
|
||||||
size="lg"
|
|
||||||
color="foreground"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
radius="full"
|
|
||||||
startContent={<FolderPlusIcon className="size-4" />}
|
|
||||||
className="gap-1 min-w-fit"
|
|
||||||
onPress={() => handleCreateFolder('right')}
|
|
||||||
>
|
|
||||||
NEW FOLDER
|
|
||||||
</Button>
|
|
||||||
</Tooltip>,
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[canCreateFolderAtRemote, rightPanelLocation.remote, handleCreateFolder]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'r' && (e.metaKey || e.ctrlKey)) {
|
if (e.key === 'r' && (e.metaKey || e.ctrlKey)) {
|
||||||
@@ -428,8 +251,6 @@ export default function Browser() {
|
|||||||
onDownload={handleDownload}
|
onDownload={handleDownload}
|
||||||
onRename={handleRename}
|
onRename={handleRename}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onNavigate={handleLeftNavigate}
|
|
||||||
renderToolbar={renderLeftToolbar}
|
|
||||||
allowedKeys={['REMOTES', 'LOCAL_FS', 'FAVORITES']}
|
allowedKeys={['REMOTES', 'LOCAL_FS', 'FAVORITES']}
|
||||||
isActive={true}
|
isActive={true}
|
||||||
/>
|
/>
|
||||||
@@ -450,8 +271,6 @@ export default function Browser() {
|
|||||||
onDownload={handleDownload}
|
onDownload={handleDownload}
|
||||||
onRename={handleRename}
|
onRename={handleRename}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onNavigate={handleRightNavigate}
|
|
||||||
renderToolbar={renderRightToolbar}
|
|
||||||
allowedKeys={['REMOTES', 'LOCAL_FS', 'FAVORITES']}
|
allowedKeys={['REMOTES', 'LOCAL_FS', 'FAVORITES']}
|
||||||
isActive={true}
|
isActive={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user