Skip to content

Commit 0db0e8a

Browse files
authored
Integrate drag for selecting files in grid and list perspectvie (#2242)
1 parent 86cdf93 commit 0db0e8a

File tree

4 files changed

+438
-233
lines changed

4 files changed

+438
-233
lines changed

src/renderer/components/DragItemTypes.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export default {
2020
TAG: 'tag',
2121
TAG_GROUP: 'tag_group',
2222
FILE: 'file_move',
23+
SELECTION: 'selection',
2324
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* TagSpaces - universal file and folder organizer
3+
* Copyright (C) 2025-present TagSpaces GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License (version 3) as
7+
* published by the Free Software Foundation.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*
17+
*/
18+
19+
// **********************************************************************
20+
// Selection Area Component
21+
// **********************************************************************
22+
//
23+
// This component covers the full area where selection is enabled.
24+
// It uses React DnD’s useDrag so that a drag gesture anywhere in the
25+
// container will produce the selection rectangle. When the drag ends,
26+
// it computes the rectangle (in viewport coordinates) and passes it
27+
// to an onSelect callback.
28+
//
29+
import DragItemTypes from '-/components/DragItemTypes';
30+
import React, { useEffect } from 'react';
31+
import { useDrag } from 'react-dnd';
32+
import { getEmptyImage } from 'react-dnd-html5-backend';
33+
34+
interface SelectionAreaProps {
35+
onSelect: (rect: DOMRect) => void;
36+
children: React.ReactNode;
37+
}
38+
39+
export const SelectionArea: React.FC<SelectionAreaProps> = ({
40+
onSelect,
41+
children,
42+
}) => {
43+
// useDrag returns a ref that you attach to the element that should capture
44+
// drag events. Here we use it on the container.
45+
const [, drag, preview] = useDrag({
46+
type: DragItemTypes.SELECTION,
47+
// No payload is needed here
48+
item: {},
49+
end: (_, monitor) => {
50+
const initialOffset = monitor.getInitialClientOffset();
51+
const dropOffset = monitor.getClientOffset();
52+
if (initialOffset && dropOffset) {
53+
// Compute rectangle coordinates based on the drag's start and end points
54+
const x = Math.min(initialOffset.x, dropOffset.x);
55+
const y = Math.min(initialOffset.y, dropOffset.y);
56+
const width = Math.abs(initialOffset.x - dropOffset.x);
57+
const height = Math.abs(initialOffset.y - dropOffset.y);
58+
// Create a DOMRect-like object (DOMRect constructor is available in modern browsers)
59+
const rect = new DOMRect(x, y, width, height);
60+
onSelect(rect);
61+
}
62+
},
63+
});
64+
65+
// Disable the default drag preview by using an empty image.
66+
useEffect(() => {
67+
preview(getEmptyImage(), { captureDraggingState: true });
68+
}, [preview]);
69+
70+
return (
71+
<div ref={drag} style={{ height: '100%' }}>
72+
{children}
73+
</div>
74+
);
75+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* TagSpaces - universal file and folder organizer
3+
* Copyright (C) 2025-present TagSpaces GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License (version 3) as
7+
* published by the Free Software Foundation.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*
17+
*/
18+
19+
// **********************************************************************
20+
// Custom Drag Layer for Rendering the Selection Rectangle
21+
// **********************************************************************
22+
//
23+
// This component uses useDragLayer to tap into the current drag state
24+
// and renders a rectangle that goes from the initial drag point to the
25+
// current pointer location.
26+
//
27+
import DragItemTypes from '-/components/DragItemTypes';
28+
import { alpha, useTheme } from '@mui/material/styles';
29+
import React from 'react';
30+
import { useDragLayer } from 'react-dnd';
31+
32+
const SelectionDragLayer: React.FC = () => {
33+
const theme = useTheme();
34+
const { isDragging, itemType, initialOffset, currentOffset } = useDragLayer(
35+
(monitor) => ({
36+
isDragging: monitor.isDragging(),
37+
itemType: monitor.getItemType(),
38+
initialOffset: monitor.getInitialClientOffset(),
39+
currentOffset: monitor.getClientOffset(),
40+
}),
41+
);
42+
43+
// Only render the selection rectangle when dragging with the selection type.
44+
if (
45+
!isDragging ||
46+
itemType !== DragItemTypes.SELECTION ||
47+
!initialOffset ||
48+
!currentOffset
49+
) {
50+
return null;
51+
}
52+
53+
// Compute rectangle geometry based on the initial and current pointer locations
54+
const x = Math.min(initialOffset.x, currentOffset.x);
55+
const y = Math.min(initialOffset.y, currentOffset.y);
56+
const width = Math.abs(initialOffset.x - currentOffset.x);
57+
const height = Math.abs(initialOffset.y - currentOffset.y);
58+
59+
const layerStyle: React.CSSProperties = {
60+
position: 'fixed',
61+
pointerEvents: 'none',
62+
left: 0,
63+
top: 0,
64+
width: '100%',
65+
height: '100%',
66+
zIndex: 1000,
67+
};
68+
69+
return (
70+
<div style={layerStyle}>
71+
<div
72+
style={{
73+
position: 'absolute',
74+
left: x,
75+
top: y,
76+
width,
77+
height,
78+
border: '1px dashed black',
79+
backgroundColor: alpha(theme.palette.primary.main, 0.1),
80+
}}
81+
/>
82+
</div>
83+
);
84+
};
85+
86+
export default SelectionDragLayer;

0 commit comments

Comments
 (0)