fix(ui): improve drag selection scroll behavior, add right-click to unpin sidebar items, close preview on back navigation, fix shared files alignment
Some checks failed
Automated Container Build / build-and-push (push) Failing after 9s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 9s
This commit is contained in:
parent
fbad71cbf2
commit
59407278d1
2 changed files with 52 additions and 19 deletions
|
|
@ -220,6 +220,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
const handlePopState = (e: PopStateEvent) => {
|
const handlePopState = (e: PopStateEvent) => {
|
||||||
if (e.state && e.state.path) setCurrentPath(e.state.path);
|
if (e.state && e.state.path) setCurrentPath(e.state.path);
|
||||||
else setCurrentPath(new URLSearchParams(window.location.search).get('path') || '.');
|
else setCurrentPath(new URLSearchParams(window.location.search).get('path') || '.');
|
||||||
|
setPreviewFile(null);
|
||||||
};
|
};
|
||||||
window.addEventListener('popstate', handlePopState);
|
window.addEventListener('popstate', handlePopState);
|
||||||
return () => window.removeEventListener('popstate', handlePopState);
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
|
|
@ -273,7 +274,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
const [pinnedOrder, setPinnedOrder] = useState<string[]>([]);
|
const [pinnedOrder, setPinnedOrder] = useState<string[]>([]);
|
||||||
const [dragOverPinnedItem, setDragOverPinnedItem] = useState<string | null>(null);
|
const [dragOverPinnedItem, setDragOverPinnedItem] = useState<string | null>(null);
|
||||||
const [pinnedRefreshCounter, setPinnedRefreshCounter] = useState(0);
|
const [pinnedRefreshCounter, setPinnedRefreshCounter] = useState(0);
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
|
||||||
const [selectionBox, setSelectionBox] = useState<{ startX: number; startY: number; currentX: number; currentY: number } | null>(null);
|
const [selectionBox, setSelectionBox] = useState<{ startX: number; startY: number; currentX: number; currentY: number } | null>(null);
|
||||||
|
const lastMousePos = useRef<{clientX: number, clientY: number} | null>(null);
|
||||||
const [initialSelectedFiles, setInitialSelectedFiles] = useState<Set<string>>(new Set());
|
const [initialSelectedFiles, setInitialSelectedFiles] = useState<Set<string>>(new Set());
|
||||||
const searchTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
const searchTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
const [downloadToken, setDownloadToken] = useState<string>('');
|
const [downloadToken, setDownloadToken] = useState<string>('');
|
||||||
|
|
@ -527,12 +530,18 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
if (target.closest('button') || target.closest('input') || target.closest('[data-file-item]')) {
|
if (target.closest('button') || target.closest('input') || target.closest('[data-file-item]')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const rect = mainContainerRef.current!.getBoundingClientRect();
|
||||||
|
const scrollLeft = mainContainerRef.current!.scrollLeft;
|
||||||
|
const scrollTop = mainContainerRef.current!.scrollTop;
|
||||||
|
|
||||||
setSelectionBox({
|
setSelectionBox({
|
||||||
startX: e.clientX,
|
startX: e.clientX - rect.left + scrollLeft,
|
||||||
startY: e.clientY,
|
startY: e.clientY - rect.top + scrollTop,
|
||||||
currentX: e.clientX,
|
currentX: e.clientX - rect.left + scrollLeft,
|
||||||
currentY: e.clientY,
|
currentY: e.clientY - rect.top + scrollTop,
|
||||||
});
|
});
|
||||||
|
lastMousePos.current = { clientX: e.clientX, clientY: e.clientY };
|
||||||
|
|
||||||
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
||||||
setInitialSelectedFiles(new Set(selectedFiles));
|
setInitialSelectedFiles(new Set(selectedFiles));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -541,26 +550,35 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(e: React.PointerEvent) {
|
function updateSelection(clientX: number, clientY: number) {
|
||||||
if (!selectionBox) return;
|
setSelectionBox(prev => {
|
||||||
setSelectionBox(prev => prev ? { ...prev, currentX: e.clientX, currentY: e.clientY } : null);
|
if (!prev || !mainContainerRef.current) return null;
|
||||||
|
const rect = mainContainerRef.current.getBoundingClientRect();
|
||||||
|
const scrollLeft = mainContainerRef.current.scrollLeft;
|
||||||
|
const scrollTop = mainContainerRef.current.scrollTop;
|
||||||
|
|
||||||
|
const next = { ...prev, currentX: clientX - rect.left + scrollLeft, currentY: clientY - rect.top + scrollTop };
|
||||||
|
|
||||||
if (mainContainerRef.current) {
|
const boxLeft = Math.min(next.startX, next.currentX);
|
||||||
const boxLeft = Math.min(selectionBox.startX, e.clientX);
|
const boxRight = Math.max(next.startX, next.currentX);
|
||||||
const boxRight = Math.max(selectionBox.startX, e.clientX);
|
const boxTop = Math.min(next.startY, next.currentY);
|
||||||
const boxTop = Math.min(selectionBox.startY, e.clientY);
|
const boxBottom = Math.max(next.startY, next.currentY);
|
||||||
const boxBottom = Math.max(selectionBox.startY, e.clientY);
|
|
||||||
|
const viewportBoxLeft = boxLeft + rect.left - scrollLeft;
|
||||||
|
const viewportBoxRight = boxRight + rect.left - scrollLeft;
|
||||||
|
const viewportBoxTop = boxTop + rect.top - scrollTop;
|
||||||
|
const viewportBoxBottom = boxBottom + rect.top - scrollTop;
|
||||||
|
|
||||||
const fileNodes = mainContainerRef.current.querySelectorAll('[data-file-item]');
|
const fileNodes = mainContainerRef.current.querySelectorAll('[data-file-item]');
|
||||||
const newSelected = new Set(initialSelectedFiles);
|
const newSelected = new Set(initialSelectedFiles);
|
||||||
|
|
||||||
fileNodes.forEach(node => {
|
fileNodes.forEach(node => {
|
||||||
const rect = node.getBoundingClientRect();
|
const fileRect = node.getBoundingClientRect();
|
||||||
const intersects = !(
|
const intersects = !(
|
||||||
rect.right < boxLeft ||
|
fileRect.right < viewportBoxLeft ||
|
||||||
rect.left > boxRight ||
|
fileRect.left > viewportBoxRight ||
|
||||||
rect.bottom < boxTop ||
|
fileRect.bottom < viewportBoxTop ||
|
||||||
rect.top > boxBottom
|
fileRect.top > viewportBoxBottom
|
||||||
);
|
);
|
||||||
|
|
||||||
const filePath = node.getAttribute('data-file-path');
|
const filePath = node.getAttribute('data-file-path');
|
||||||
|
|
@ -574,6 +592,19 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
});
|
});
|
||||||
|
|
||||||
setSelectedFiles(newSelected);
|
setSelectedFiles(newSelected);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePointerMove(e: React.PointerEvent) {
|
||||||
|
if (!selectionBox) return;
|
||||||
|
lastMousePos.current = { clientX: e.clientX, clientY: e.clientY };
|
||||||
|
updateSelection(e.clientX, e.clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScroll(e: React.UIEvent) {
|
||||||
|
if (selectionBox && lastMousePos.current) {
|
||||||
|
updateSelection(lastMousePos.current.clientX, lastMousePos.current.clientY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1270,6 +1301,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
borderTop: dragOverPinnedItem === file.path ? '2px solid var(--color-accent)' : '2px solid transparent',
|
borderTop: dragOverPinnedItem === file.path ? '2px solid var(--color-accent)' : '2px solid transparent',
|
||||||
}}
|
}}
|
||||||
onClick={() => { setActiveSection('files'); navigateTo(file.path); }}
|
onClick={() => { setActiveSection('files'); navigateTo(file.path); }}
|
||||||
|
onContextMenu={(e) => handleContextMenu(e, file)}
|
||||||
>
|
>
|
||||||
<div className="w-4 h-4 flex items-center justify-center flex-shrink-0 [&>svg]:w-4 [&>svg]:h-4">
|
<div className="w-4 h-4 flex items-center justify-center flex-shrink-0 [&>svg]:w-4 [&>svg]:h-4">
|
||||||
{getFileIcon(file)}
|
{getFileIcon(file)}
|
||||||
|
|
@ -1490,10 +1522,11 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
onPointerMove={handlePointerMove}
|
onPointerMove={handlePointerMove}
|
||||||
onPointerUp={handlePointerUp}
|
onPointerUp={handlePointerUp}
|
||||||
onPointerLeave={handlePointerUp}
|
onPointerLeave={handlePointerUp}
|
||||||
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
{selectionBox && (
|
{selectionBox && (
|
||||||
<div
|
<div
|
||||||
className="fixed z-50 pointer-events-none"
|
className="absolute z-50 pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
left: Math.min(selectionBox.startX, selectionBox.currentX),
|
left: Math.min(selectionBox.startX, selectionBox.currentX),
|
||||||
top: Math.min(selectionBox.startY, selectionBox.currentY),
|
top: Math.min(selectionBox.startY, selectionBox.currentY),
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export default function SharedFilesPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
<div className="max-w-5xl mx-auto">
|
<div className="w-full">
|
||||||
<h2 className="text-xl font-semibold mb-6" style={{ color: 'var(--color-text-primary)' }}>Shared Files</h2>
|
<h2 className="text-xl font-semibold mb-6" style={{ color: 'var(--color-text-primary)' }}>Shared Files</h2>
|
||||||
|
|
||||||
{shares.length === 0 ? (
|
{shares.length === 0 ? (
|
||||||
|
|
|
||||||
Reference in a new issue