|
4 | 4 | import PaneResizer from './PaneResizer.svelte' |
5 | 5 | import LoadingIcon from '../LoadingIcon.svelte' |
6 | 6 | import CopyDialog from '../write-operations/CopyDialog.svelte' |
| 7 | + import CopyProgressDialog from '../write-operations/CopyProgressDialog.svelte' |
| 8 | + import { toBackendIndices, toBackendCursorIndex } from '../write-operations/copy-dialog-utils' |
| 9 | + import { formatBytes } from '$lib/tauri-commands' |
7 | 10 | import { |
8 | 11 | loadAppStatus, |
9 | 12 | saveAppStatus, |
|
85 | 88 | sourceFolderPath: string |
86 | 89 | } | null>(null) |
87 | 90 |
|
| 91 | + // Copy progress dialog state |
| 92 | + let showCopyProgressDialog = $state(false) |
| 93 | + let copyProgressProps = $state<{ |
| 94 | + sourcePaths: string[] |
| 95 | + sourceFolderPath: string |
| 96 | + destinationPath: string |
| 97 | + direction: 'left' | 'right' |
| 98 | + } | null>(null) |
| 99 | +
|
88 | 100 | // Navigation history for each pane (per-pane, session-only) |
89 | 101 | // Initialize with default volume - will be updated on mount with actual state |
90 | 102 | let leftHistory = $state<NavigationHistory>(createHistory(DEFAULT_VOLUME_ID, '~')) |
|
786 | 798 | } |
787 | 799 |
|
788 | 800 | /** Gets file paths for the given indices from a listing. */ |
789 | | - async function getSelectedFilePaths(listingId: string, indices: number[]): Promise<string[]> { |
| 801 | + async function getSelectedFilePaths(listingId: string, backendIndices: number[]): Promise<string[]> { |
790 | 802 | const paths: string[] = [] |
791 | | - for (const index of indices) { |
| 803 | + for (const index of backendIndices) { |
792 | 804 | const file = await getFileAt(listingId, index, showHiddenFiles) |
793 | 805 | if (file && file.name !== '..') { |
794 | 806 | paths.push(file.path) |
|
806 | 818 | const listingId = sourcePaneRef?.getListingId?.() as string | undefined |
807 | 819 | if (!listingId) return |
808 | 820 |
|
| 821 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-call |
| 822 | + const hasParent = sourcePaneRef?.hasParentEntry?.() as boolean | undefined |
809 | 823 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call |
810 | 824 | const selectedIndices = sourcePaneRef?.getSelectedIndices?.() as number[] | undefined |
811 | 825 | const hasSelection = selectedIndices && selectedIndices.length > 0 |
812 | 826 |
|
813 | 827 | const props = hasSelection |
814 | | - ? await buildCopyPropsFromSelection(listingId, selectedIndices, isLeft) |
815 | | - : await buildCopyPropsFromCursor(listingId, sourcePaneRef, isLeft) |
| 828 | + ? await buildCopyPropsFromSelection(listingId, selectedIndices, hasParent ?? false, isLeft) |
| 829 | + : await buildCopyPropsFromCursor(listingId, sourcePaneRef, hasParent ?? false, isLeft) |
816 | 830 |
|
817 | 831 | if (props) { |
818 | 832 | copyDialogProps = props |
|
834 | 848 | async function buildCopyPropsFromSelection( |
835 | 849 | listingId: string, |
836 | 850 | selectedIndices: number[], |
| 851 | + hasParent: boolean, |
837 | 852 | isLeft: boolean, |
838 | 853 | ): Promise<CopyDialogPropsData | null> { |
839 | | - const stats = await getListingStats(listingId, showHiddenFiles, selectedIndices) |
840 | | - const sourcePaths = await getSelectedFilePaths(listingId, selectedIndices) |
| 854 | + // Convert frontend indices to backend indices (adjust for ".." entry) |
| 855 | + const backendIndices = toBackendIndices(selectedIndices, hasParent) |
| 856 | + if (backendIndices.length === 0) return null |
| 857 | +
|
| 858 | + const stats = await getListingStats(listingId, showHiddenFiles, backendIndices) |
| 859 | + const sourcePaths = await getSelectedFilePaths(listingId, backendIndices) |
841 | 860 | if (sourcePaths.length === 0) return null |
842 | 861 |
|
843 | 862 | return { |
|
855 | 874 | async function buildCopyPropsFromCursor( |
856 | 875 | listingId: string, |
857 | 876 | paneRef: FilePane | undefined, |
| 877 | + hasParent: boolean, |
858 | 878 | isLeft: boolean, |
859 | 879 | ): Promise<CopyDialogPropsData | null> { |
860 | 880 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call |
861 | 881 | const cursorIndex = paneRef?.getCursorIndex?.() as number | undefined |
862 | | - if (cursorIndex === undefined || cursorIndex < 0) return null |
| 882 | + const backendIndex = toBackendCursorIndex(cursorIndex ?? -1, hasParent) |
| 883 | + if (backendIndex === null) return null |
863 | 884 |
|
864 | | - const file = await getFileAt(listingId, cursorIndex, showHiddenFiles) |
| 885 | + const file = await getFileAt(listingId, backendIndex, showHiddenFiles) |
865 | 886 | if (!file || file.name === '..') return null |
866 | 887 |
|
867 | 888 | return { |
|
875 | 896 | } |
876 | 897 | } |
877 | 898 |
|
878 | | - function handleCopyConfirm(destination: string, volumeId: string) { |
879 | | - // TODO: Implement actual copy operation using copyFiles() from tauri-commands |
880 | | - const itemCount = copyDialogProps?.sourcePaths.length ?? 0 |
881 | | - log.info(`Copy confirmed: ${String(itemCount)} items to ${destination} (volume: ${volumeId})`) |
| 899 | + function handleCopyConfirm(destination: string) { |
| 900 | + if (!copyDialogProps) return |
| 901 | +
|
| 902 | + // Store the props needed for the progress dialog |
| 903 | + copyProgressProps = { |
| 904 | + sourcePaths: copyDialogProps.sourcePaths, |
| 905 | + sourceFolderPath: copyDialogProps.sourceFolderPath, |
| 906 | + destinationPath: destination, |
| 907 | + direction: copyDialogProps.direction, |
| 908 | + } |
| 909 | +
|
| 910 | + // Close copy dialog and open progress dialog |
882 | 911 | showCopyDialog = false |
883 | 912 | copyDialogProps = null |
884 | | - containerElement?.focus() |
| 913 | + showCopyProgressDialog = true |
885 | 914 | } |
886 | 915 |
|
887 | 916 | function handleCopyCancel() { |
|
890 | 919 | containerElement?.focus() |
891 | 920 | } |
892 | 921 |
|
| 922 | + function handleCopyComplete(filesProcessed: number, bytesProcessed: number) { |
| 923 | + log.info(`Copy complete: ${String(filesProcessed)} files (${formatBytes(bytesProcessed)})`) |
| 924 | +
|
| 925 | + // Refresh the destination pane to show the new files |
| 926 | + const destPaneRef = copyProgressProps?.direction === 'right' ? rightPaneRef : leftPaneRef |
| 927 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-call |
| 928 | + destPaneRef?.refreshView?.() |
| 929 | +
|
| 930 | + showCopyProgressDialog = false |
| 931 | + copyProgressProps = null |
| 932 | + containerElement?.focus() |
| 933 | + } |
| 934 | +
|
| 935 | + function handleCopyCancelled(filesProcessed: number) { |
| 936 | + log.info(`Copy cancelled after ${String(filesProcessed)} files`) |
| 937 | +
|
| 938 | + // Refresh the destination pane to show any files that were copied |
| 939 | + const destPaneRef = copyProgressProps?.direction === 'right' ? rightPaneRef : leftPaneRef |
| 940 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-call |
| 941 | + destPaneRef?.refreshView?.() |
| 942 | +
|
| 943 | + showCopyProgressDialog = false |
| 944 | + copyProgressProps = null |
| 945 | + containerElement?.focus() |
| 946 | + } |
| 947 | +
|
| 948 | + function handleCopyError(error: string) { |
| 949 | + log.error(`Copy failed: ${error}`) |
| 950 | + showCopyProgressDialog = false |
| 951 | + copyProgressProps = null |
| 952 | + // TODO: Show error notification/toast |
| 953 | + containerElement?.focus() |
| 954 | + } |
| 955 | +
|
893 | 956 | // Focus the container after initialization so keyboard events work |
894 | 957 | $effect(() => { |
895 | 958 | if (initialized) { |
|
1180 | 1243 | /> |
1181 | 1244 | {/if} |
1182 | 1245 |
|
| 1246 | +{#if showCopyProgressDialog && copyProgressProps} |
| 1247 | + <CopyProgressDialog |
| 1248 | + sourcePaths={copyProgressProps.sourcePaths} |
| 1249 | + sourceFolderPath={copyProgressProps.sourceFolderPath} |
| 1250 | + destinationPath={copyProgressProps.destinationPath} |
| 1251 | + direction={copyProgressProps.direction} |
| 1252 | + onComplete={handleCopyComplete} |
| 1253 | + onCancelled={handleCopyCancelled} |
| 1254 | + onError={handleCopyError} |
| 1255 | + /> |
| 1256 | +{/if} |
| 1257 | + |
1183 | 1258 | <style> |
1184 | 1259 | .dual-pane-explorer { |
1185 | 1260 | display: flex; |
|
0 commit comments