@huyooo/file-explorer-frontend-react 0.4.18 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +0 -1
- package/dist/index.js +1 -3456
- package/package.json +4 -4
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/components/Breadcrumb.css +0 -61
- package/src/components/Breadcrumb.tsx +0 -38
- package/src/components/CompressDialog.css +0 -267
- package/src/components/CompressDialog.tsx +0 -222
- package/src/components/ContextMenu.css +0 -155
- package/src/components/ContextMenu.tsx +0 -375
- package/src/components/FileGrid.css +0 -239
- package/src/components/FileGrid.tsx +0 -278
- package/src/components/FileIcon.css +0 -41
- package/src/components/FileIcon.tsx +0 -86
- package/src/components/FileInfoDialog.css +0 -214
- package/src/components/FileInfoDialog.tsx +0 -202
- package/src/components/FileList.css +0 -169
- package/src/components/FileList.tsx +0 -228
- package/src/components/FileListView.css +0 -36
- package/src/components/FileListView.tsx +0 -355
- package/src/components/FileSidebar.css +0 -94
- package/src/components/FileSidebar.tsx +0 -66
- package/src/components/ProgressDialog.css +0 -211
- package/src/components/ProgressDialog.tsx +0 -183
- package/src/components/SortIndicator.css +0 -7
- package/src/components/SortIndicator.tsx +0 -19
- package/src/components/StatusBar.css +0 -20
- package/src/components/StatusBar.tsx +0 -21
- package/src/components/Toolbar.css +0 -150
- package/src/components/Toolbar.tsx +0 -127
- package/src/components/Window.css +0 -246
- package/src/components/Window.tsx +0 -335
- package/src/hooks/useApplicationIcon.ts +0 -80
- package/src/hooks/useDragAndDrop.ts +0 -104
- package/src/hooks/useMediaPlayer.ts +0 -164
- package/src/hooks/useSelection.ts +0 -112
- package/src/hooks/useWindowDrag.ts +0 -60
- package/src/hooks/useWindowResize.ts +0 -126
- package/src/index.css +0 -184
- package/src/index.ts +0 -37
- package/src/types/index.ts +0 -274
- package/src/utils/fileTypeIcon.ts +0 -309
- package/src/utils/folderTypeIcon.ts +0 -132
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import type { FileItem } from '../types';
|
|
3
|
-
import { FileType } from '../types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 拖拽操作管理
|
|
7
|
-
*/
|
|
8
|
-
export function useDragAndDrop(
|
|
9
|
-
getSelectedIds: () => Set<string>,
|
|
10
|
-
onSelect: (id: string, multi: boolean, range: boolean) => void,
|
|
11
|
-
onMove: (targetFolderId: string, itemIds: Set<string>) => void
|
|
12
|
-
) {
|
|
13
|
-
const [dragOverId, setDragOverId] = useState<string | null>(null);
|
|
14
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 开始拖拽
|
|
18
|
-
*/
|
|
19
|
-
const handleDragStart = useCallback((e: React.DragEvent, itemId: string) => {
|
|
20
|
-
if (!e.dataTransfer) return;
|
|
21
|
-
|
|
22
|
-
const selectedIds = getSelectedIds();
|
|
23
|
-
|
|
24
|
-
// 如果拖拽的项目未被选中,先选中它
|
|
25
|
-
if (!selectedIds.has(itemId)) {
|
|
26
|
-
onSelect(itemId, false, false);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 设置拖拽数据
|
|
30
|
-
const draggedIds = selectedIds.has(itemId) ? selectedIds : new Set([itemId]);
|
|
31
|
-
e.dataTransfer.setData('text/plain', JSON.stringify([...draggedIds]));
|
|
32
|
-
e.dataTransfer.effectAllowed = 'move';
|
|
33
|
-
|
|
34
|
-
setIsDragging(true);
|
|
35
|
-
}, [getSelectedIds, onSelect]);
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 拖拽经过
|
|
39
|
-
*/
|
|
40
|
-
const handleDragOver = useCallback((e: React.DragEvent, item: FileItem) => {
|
|
41
|
-
if (!isDragging) return;
|
|
42
|
-
|
|
43
|
-
// 只有文件夹可以作为放置目标
|
|
44
|
-
if (item.type === FileType.FOLDER) {
|
|
45
|
-
const selectedIds = getSelectedIds();
|
|
46
|
-
// 不能拖拽到自己身上
|
|
47
|
-
if (!selectedIds.has(item.id)) {
|
|
48
|
-
e.preventDefault();
|
|
49
|
-
setDragOverId(item.id);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}, [isDragging, getSelectedIds]);
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 拖拽离开
|
|
56
|
-
*/
|
|
57
|
-
const handleDragLeave = useCallback(() => {
|
|
58
|
-
setDragOverId(null);
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 放置
|
|
63
|
-
*/
|
|
64
|
-
const handleDrop = useCallback((e: React.DragEvent, targetItem: FileItem) => {
|
|
65
|
-
setDragOverId(null);
|
|
66
|
-
setIsDragging(false);
|
|
67
|
-
|
|
68
|
-
if (!e.dataTransfer || targetItem.type !== FileType.FOLDER) return;
|
|
69
|
-
|
|
70
|
-
const data = e.dataTransfer.getData('text/plain');
|
|
71
|
-
if (!data) return;
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const draggedIds: string[] = JSON.parse(data);
|
|
75
|
-
const itemIds = new Set(draggedIds);
|
|
76
|
-
|
|
77
|
-
// 不能移动到自己
|
|
78
|
-
if (itemIds.has(targetItem.id)) return;
|
|
79
|
-
|
|
80
|
-
onMove(targetItem.id, itemIds);
|
|
81
|
-
} catch {
|
|
82
|
-
// 忽略解析错误
|
|
83
|
-
}
|
|
84
|
-
}, [onMove]);
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 拖拽结束
|
|
88
|
-
*/
|
|
89
|
-
const handleDragEnd = useCallback(() => {
|
|
90
|
-
setDragOverId(null);
|
|
91
|
-
setIsDragging(false);
|
|
92
|
-
}, []);
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
dragOverId,
|
|
96
|
-
isDragging,
|
|
97
|
-
handleDragStart,
|
|
98
|
-
handleDragOver,
|
|
99
|
-
handleDragLeave,
|
|
100
|
-
handleDrop,
|
|
101
|
-
handleDragEnd
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
|
-
import { FileType } from '../types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 媒体播放器功能管理
|
|
6
|
-
*/
|
|
7
|
-
export function useMediaPlayer(
|
|
8
|
-
mediaType: FileType,
|
|
9
|
-
mediaRef: React.RefObject<HTMLVideoElement | HTMLAudioElement>
|
|
10
|
-
) {
|
|
11
|
-
const [isPlaying, setIsPlaying] = useState(false);
|
|
12
|
-
const [progress, setProgress] = useState(0);
|
|
13
|
-
const [currentTime, setCurrentTime] = useState(0);
|
|
14
|
-
const [duration, setDuration] = useState(0);
|
|
15
|
-
const [isMuted, setIsMuted] = useState(false);
|
|
16
|
-
const [volume, setVolume] = useState(1);
|
|
17
|
-
const [lastVolume, setLastVolume] = useState(1);
|
|
18
|
-
const [showControls, setShowControls] = useState(false);
|
|
19
|
-
|
|
20
|
-
const isAudio = mediaType === FileType.MUSIC;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 更新播放进度
|
|
24
|
-
*/
|
|
25
|
-
const updateProgress = useCallback(() => {
|
|
26
|
-
const media = mediaRef.current;
|
|
27
|
-
if (media) {
|
|
28
|
-
const curr = media.currentTime;
|
|
29
|
-
const dur = media.duration;
|
|
30
|
-
setCurrentTime(curr);
|
|
31
|
-
if (dur && !isNaN(dur)) {
|
|
32
|
-
setDuration(dur);
|
|
33
|
-
setProgress((curr / dur) * 100);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}, [mediaRef]);
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 切换播放/暂停
|
|
40
|
-
*/
|
|
41
|
-
const togglePlay = useCallback(() => {
|
|
42
|
-
const media = mediaRef.current;
|
|
43
|
-
if (media) {
|
|
44
|
-
if (isPlaying) {
|
|
45
|
-
media.pause();
|
|
46
|
-
setIsPlaying(false);
|
|
47
|
-
} else {
|
|
48
|
-
media.play();
|
|
49
|
-
setIsPlaying(true);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}, [isPlaying, mediaRef]);
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 切换静音
|
|
56
|
-
*/
|
|
57
|
-
const toggleMute = useCallback(() => {
|
|
58
|
-
const media = mediaRef.current;
|
|
59
|
-
if (media) {
|
|
60
|
-
if (isMuted) {
|
|
61
|
-
const volToRestore = lastVolume || 1;
|
|
62
|
-
media.volume = volToRestore;
|
|
63
|
-
media.muted = false;
|
|
64
|
-
setVolume(volToRestore);
|
|
65
|
-
setIsMuted(false);
|
|
66
|
-
} else {
|
|
67
|
-
setLastVolume(volume);
|
|
68
|
-
media.volume = 0;
|
|
69
|
-
media.muted = true;
|
|
70
|
-
setVolume(0);
|
|
71
|
-
setIsMuted(true);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}, [isMuted, lastVolume, volume, mediaRef]);
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 音量变化
|
|
78
|
-
*/
|
|
79
|
-
const handleVolumeChange = useCallback((val: number) => {
|
|
80
|
-
setVolume(val);
|
|
81
|
-
const media = mediaRef.current;
|
|
82
|
-
if (media) {
|
|
83
|
-
media.volume = val;
|
|
84
|
-
if (val === 0) {
|
|
85
|
-
setIsMuted(true);
|
|
86
|
-
media.muted = true;
|
|
87
|
-
} else {
|
|
88
|
-
setIsMuted(false);
|
|
89
|
-
media.muted = false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}, [mediaRef]);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 跳转到指定时间
|
|
96
|
-
*/
|
|
97
|
-
const seekTo = useCallback((time: number) => {
|
|
98
|
-
const media = mediaRef.current;
|
|
99
|
-
if (media && duration) {
|
|
100
|
-
media.currentTime = time;
|
|
101
|
-
setCurrentTime(time);
|
|
102
|
-
setProgress((time / duration) * 100);
|
|
103
|
-
}
|
|
104
|
-
}, [duration, mediaRef]);
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 格式化时间显示
|
|
108
|
-
*/
|
|
109
|
-
const formatTime = useCallback((time: number) => {
|
|
110
|
-
if (isNaN(time)) return '0:00';
|
|
111
|
-
const minutes = Math.floor(time / 60);
|
|
112
|
-
const seconds = Math.floor(time % 60);
|
|
113
|
-
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
114
|
-
}, []);
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 自动播放
|
|
118
|
-
*/
|
|
119
|
-
const autoPlay = useCallback(() => {
|
|
120
|
-
const media = mediaRef.current;
|
|
121
|
-
if ((mediaType === FileType.VIDEO || isAudio) && media) {
|
|
122
|
-
media.volume = volume;
|
|
123
|
-
media.play().catch(() => {});
|
|
124
|
-
setIsPlaying(true);
|
|
125
|
-
}
|
|
126
|
-
}, [mediaType, isAudio, volume, mediaRef]);
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 监听媒体事件
|
|
130
|
-
*/
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
const media = mediaRef.current;
|
|
133
|
-
if (media) {
|
|
134
|
-
media.addEventListener('timeupdate', updateProgress);
|
|
135
|
-
media.addEventListener('loadedmetadata', updateProgress);
|
|
136
|
-
media.addEventListener('ended', () => {
|
|
137
|
-
setIsPlaying(false);
|
|
138
|
-
});
|
|
139
|
-
return () => {
|
|
140
|
-
media.removeEventListener('timeupdate', updateProgress);
|
|
141
|
-
media.removeEventListener('loadedmetadata', updateProgress);
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
}, [mediaRef, updateProgress]);
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
isPlaying,
|
|
148
|
-
progress,
|
|
149
|
-
currentTime,
|
|
150
|
-
duration,
|
|
151
|
-
isMuted,
|
|
152
|
-
volume,
|
|
153
|
-
showControls,
|
|
154
|
-
isAudio,
|
|
155
|
-
togglePlay,
|
|
156
|
-
toggleMute,
|
|
157
|
-
handleVolumeChange,
|
|
158
|
-
seekTo,
|
|
159
|
-
formatTime,
|
|
160
|
-
autoPlay,
|
|
161
|
-
updateProgress
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import type { FileItem } from '../types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 文件选择状态管理
|
|
6
|
-
*/
|
|
7
|
-
export function useSelection() {
|
|
8
|
-
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
9
|
-
const [lastSelectedId, setLastSelectedId] = useState<string | null>(null);
|
|
10
|
-
const [editingId, setEditingId] = useState<string | null>(null);
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 清除选择
|
|
14
|
-
*/
|
|
15
|
-
const clearSelection = useCallback(() => {
|
|
16
|
-
setSelectedIds(new Set());
|
|
17
|
-
setLastSelectedId(null);
|
|
18
|
-
}, []);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 选择项目
|
|
22
|
-
* @param id 项目 ID(null 表示清除选择)
|
|
23
|
-
* @param items 当前显示的项目列表
|
|
24
|
-
* @param multi 是否多选(Cmd/Ctrl)
|
|
25
|
-
* @param range 是否范围选择(Shift)
|
|
26
|
-
*/
|
|
27
|
-
const selectItem = useCallback((
|
|
28
|
-
id: string | null,
|
|
29
|
-
items: FileItem[],
|
|
30
|
-
multi: boolean = false,
|
|
31
|
-
range: boolean = false
|
|
32
|
-
) => {
|
|
33
|
-
if (!id) {
|
|
34
|
-
clearSelection();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
setSelectedIds(prev => {
|
|
39
|
-
setLastSelectedId(prevLast => {
|
|
40
|
-
// 范围选择
|
|
41
|
-
if (range && prevLast) {
|
|
42
|
-
const indexA = items.findIndex(i => i.id === prevLast);
|
|
43
|
-
const indexB = items.findIndex(i => i.id === id);
|
|
44
|
-
|
|
45
|
-
if (indexA !== -1 && indexB !== -1) {
|
|
46
|
-
const start = Math.min(indexA, indexB);
|
|
47
|
-
const end = Math.max(indexA, indexB);
|
|
48
|
-
const newSet = new Set(multi ? prev : []);
|
|
49
|
-
for (let i = start; i <= end; i++) {
|
|
50
|
-
const item = items[i];
|
|
51
|
-
if (item) {
|
|
52
|
-
newSet.add(item.id);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
setSelectedIds(newSet);
|
|
56
|
-
return id;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 多选切换
|
|
61
|
-
if (multi) {
|
|
62
|
-
const newSet = new Set(prev);
|
|
63
|
-
if (newSet.has(id)) {
|
|
64
|
-
newSet.delete(id);
|
|
65
|
-
} else {
|
|
66
|
-
newSet.add(id);
|
|
67
|
-
}
|
|
68
|
-
setSelectedIds(newSet);
|
|
69
|
-
return id;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 单选
|
|
73
|
-
setSelectedIds(new Set([id]));
|
|
74
|
-
return id;
|
|
75
|
-
});
|
|
76
|
-
return prev;
|
|
77
|
-
});
|
|
78
|
-
}, [clearSelection]);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 全选
|
|
82
|
-
*/
|
|
83
|
-
const selectAll = useCallback((items: FileItem[]) => {
|
|
84
|
-
setSelectedIds(new Set(items.map(i => i.id)));
|
|
85
|
-
}, []);
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 设置编辑状态
|
|
89
|
-
*/
|
|
90
|
-
const setEditing = useCallback((id: string | null) => {
|
|
91
|
-
setEditingId(id);
|
|
92
|
-
}, []);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 检查是否选中
|
|
96
|
-
*/
|
|
97
|
-
const isSelected = useCallback((id: string): boolean => {
|
|
98
|
-
return selectedIds.has(id);
|
|
99
|
-
}, [selectedIds]);
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
selectedIds,
|
|
103
|
-
lastSelectedId,
|
|
104
|
-
editingId,
|
|
105
|
-
clearSelection,
|
|
106
|
-
selectItem,
|
|
107
|
-
selectAll,
|
|
108
|
-
setEditing,
|
|
109
|
-
isSelected
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 窗口拖拽功能管理
|
|
5
|
-
*/
|
|
6
|
-
export function useWindowDrag() {
|
|
7
|
-
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
8
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 鼠标移动处理
|
|
12
|
-
*/
|
|
13
|
-
const handleMouseMove = useCallback((e: MouseEvent) => {
|
|
14
|
-
if (!isDragging) return;
|
|
15
|
-
setPosition(prev => ({
|
|
16
|
-
x: prev.x + e.movementX,
|
|
17
|
-
y: prev.y + e.movementY
|
|
18
|
-
}));
|
|
19
|
-
}, [isDragging]);
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 鼠标释放处理
|
|
23
|
-
*/
|
|
24
|
-
const handleMouseUp = useCallback(() => {
|
|
25
|
-
setIsDragging(false);
|
|
26
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 开始拖拽
|
|
30
|
-
*/
|
|
31
|
-
const startDrag = useCallback((e: React.MouseEvent) => {
|
|
32
|
-
const target = e.target as HTMLElement;
|
|
33
|
-
// 检查是否点击在可拖拽区域,但排除按钮
|
|
34
|
-
if (target.closest('.draggable-area') && !target.closest('button')) {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
setIsDragging(true);
|
|
37
|
-
}
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 监听拖拽状态变化
|
|
42
|
-
*/
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
if (isDragging) {
|
|
45
|
-
window.addEventListener('mousemove', handleMouseMove);
|
|
46
|
-
window.addEventListener('mouseup', handleMouseUp);
|
|
47
|
-
return () => {
|
|
48
|
-
window.removeEventListener('mousemove', handleMouseMove);
|
|
49
|
-
window.removeEventListener('mouseup', handleMouseUp);
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}, [isDragging, handleMouseMove, handleMouseUp]);
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
position,
|
|
56
|
-
isDragging,
|
|
57
|
-
startDrag
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 窗口调整大小功能管理
|
|
7
|
-
*/
|
|
8
|
-
export function useWindowResize(
|
|
9
|
-
initialWidth: number,
|
|
10
|
-
initialHeight: number,
|
|
11
|
-
minWidth: number,
|
|
12
|
-
minHeight: number,
|
|
13
|
-
maxWidth: number,
|
|
14
|
-
maxHeight: number
|
|
15
|
-
) {
|
|
16
|
-
const [width, setWidth] = useState(initialWidth);
|
|
17
|
-
const [height, setHeight] = useState(initialHeight);
|
|
18
|
-
const [isResizing, setIsResizing] = useState(false);
|
|
19
|
-
const [resizeDirection, setResizeDirection] = useState<ResizeDirection | null>(null);
|
|
20
|
-
const [startX, setStartX] = useState(0);
|
|
21
|
-
const [startY, setStartY] = useState(0);
|
|
22
|
-
const [startWidth, setStartWidth] = useState(0);
|
|
23
|
-
const [startHeight, setStartHeight] = useState(0);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 鼠标移动处理
|
|
27
|
-
*/
|
|
28
|
-
const handleMouseMove = useCallback((e: MouseEvent) => {
|
|
29
|
-
if (!isResizing || !resizeDirection) return;
|
|
30
|
-
|
|
31
|
-
const deltaX = e.clientX - startX;
|
|
32
|
-
const deltaY = e.clientY - startY;
|
|
33
|
-
|
|
34
|
-
let newWidth = startWidth;
|
|
35
|
-
let newHeight = startHeight;
|
|
36
|
-
|
|
37
|
-
const direction = resizeDirection;
|
|
38
|
-
|
|
39
|
-
// 处理水平方向
|
|
40
|
-
if (direction.includes('e')) {
|
|
41
|
-
newWidth = Math.min(Math.max(startWidth + deltaX, minWidth), maxWidth);
|
|
42
|
-
} else if (direction.includes('w')) {
|
|
43
|
-
newWidth = Math.min(Math.max(startWidth - deltaX, minWidth), maxWidth);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 处理垂直方向
|
|
47
|
-
if (direction.includes('s')) {
|
|
48
|
-
newHeight = Math.min(Math.max(startHeight + deltaY, minHeight), maxHeight);
|
|
49
|
-
} else if (direction.includes('n')) {
|
|
50
|
-
newHeight = Math.min(Math.max(startHeight - deltaY, minHeight), maxHeight);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
setWidth(newWidth);
|
|
54
|
-
setHeight(newHeight);
|
|
55
|
-
}, [isResizing, resizeDirection, startX, startY, startWidth, startHeight, minWidth, minHeight, maxWidth, maxHeight]);
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 鼠标释放处理
|
|
59
|
-
*/
|
|
60
|
-
const handleMouseUp = useCallback(() => {
|
|
61
|
-
setIsResizing(false);
|
|
62
|
-
setResizeDirection(null);
|
|
63
|
-
}, []);
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 开始调整大小
|
|
67
|
-
*/
|
|
68
|
-
const startResize = useCallback((
|
|
69
|
-
e: React.MouseEvent,
|
|
70
|
-
direction: ResizeDirection,
|
|
71
|
-
currentWidth: number,
|
|
72
|
-
currentHeight: number
|
|
73
|
-
) => {
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
e.stopPropagation();
|
|
76
|
-
setIsResizing(true);
|
|
77
|
-
setResizeDirection(direction);
|
|
78
|
-
setStartX(e.clientX);
|
|
79
|
-
setStartY(e.clientY);
|
|
80
|
-
setStartWidth(currentWidth);
|
|
81
|
-
setStartHeight(currentHeight);
|
|
82
|
-
}, []);
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 获取对应方向的鼠标样式
|
|
86
|
-
*/
|
|
87
|
-
const getCursorForDirection = useCallback((direction: ResizeDirection): string => {
|
|
88
|
-
const cursors: Record<ResizeDirection, string> = {
|
|
89
|
-
n: 'n-resize',
|
|
90
|
-
s: 's-resize',
|
|
91
|
-
e: 'e-resize',
|
|
92
|
-
w: 'w-resize',
|
|
93
|
-
ne: 'ne-resize',
|
|
94
|
-
nw: 'nw-resize',
|
|
95
|
-
se: 'se-resize',
|
|
96
|
-
sw: 'sw-resize'
|
|
97
|
-
};
|
|
98
|
-
return cursors[direction];
|
|
99
|
-
}, []);
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 监听调整大小状态变化
|
|
103
|
-
*/
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
if (isResizing) {
|
|
106
|
-
window.addEventListener('mousemove', handleMouseMove);
|
|
107
|
-
window.addEventListener('mouseup', handleMouseUp);
|
|
108
|
-
document.body.style.cursor = getCursorForDirection(resizeDirection || 'se');
|
|
109
|
-
document.body.style.userSelect = 'none';
|
|
110
|
-
return () => {
|
|
111
|
-
window.removeEventListener('mousemove', handleMouseMove);
|
|
112
|
-
window.removeEventListener('mouseup', handleMouseUp);
|
|
113
|
-
document.body.style.cursor = '';
|
|
114
|
-
document.body.style.userSelect = '';
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}, [isResizing, resizeDirection, handleMouseMove, handleMouseUp, getCursorForDirection]);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
width,
|
|
121
|
-
height,
|
|
122
|
-
isResizing,
|
|
123
|
-
startResize
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|