@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.
Files changed (44) hide show
  1. package/dist/index.css +0 -1
  2. package/dist/index.js +1 -3456
  3. package/package.json +4 -4
  4. package/dist/index.css.map +0 -1
  5. package/dist/index.js.map +0 -1
  6. package/src/components/Breadcrumb.css +0 -61
  7. package/src/components/Breadcrumb.tsx +0 -38
  8. package/src/components/CompressDialog.css +0 -267
  9. package/src/components/CompressDialog.tsx +0 -222
  10. package/src/components/ContextMenu.css +0 -155
  11. package/src/components/ContextMenu.tsx +0 -375
  12. package/src/components/FileGrid.css +0 -239
  13. package/src/components/FileGrid.tsx +0 -278
  14. package/src/components/FileIcon.css +0 -41
  15. package/src/components/FileIcon.tsx +0 -86
  16. package/src/components/FileInfoDialog.css +0 -214
  17. package/src/components/FileInfoDialog.tsx +0 -202
  18. package/src/components/FileList.css +0 -169
  19. package/src/components/FileList.tsx +0 -228
  20. package/src/components/FileListView.css +0 -36
  21. package/src/components/FileListView.tsx +0 -355
  22. package/src/components/FileSidebar.css +0 -94
  23. package/src/components/FileSidebar.tsx +0 -66
  24. package/src/components/ProgressDialog.css +0 -211
  25. package/src/components/ProgressDialog.tsx +0 -183
  26. package/src/components/SortIndicator.css +0 -7
  27. package/src/components/SortIndicator.tsx +0 -19
  28. package/src/components/StatusBar.css +0 -20
  29. package/src/components/StatusBar.tsx +0 -21
  30. package/src/components/Toolbar.css +0 -150
  31. package/src/components/Toolbar.tsx +0 -127
  32. package/src/components/Window.css +0 -246
  33. package/src/components/Window.tsx +0 -335
  34. package/src/hooks/useApplicationIcon.ts +0 -80
  35. package/src/hooks/useDragAndDrop.ts +0 -104
  36. package/src/hooks/useMediaPlayer.ts +0 -164
  37. package/src/hooks/useSelection.ts +0 -112
  38. package/src/hooks/useWindowDrag.ts +0 -60
  39. package/src/hooks/useWindowResize.ts +0 -126
  40. package/src/index.css +0 -184
  41. package/src/index.ts +0 -37
  42. package/src/types/index.ts +0 -274
  43. package/src/utils/fileTypeIcon.ts +0 -309
  44. package/src/utils/folderTypeIcon.ts +0 -132
@@ -1,246 +0,0 @@
1
- .window-overlay {
2
- position: fixed;
3
- inset: 0;
4
- z-index: 100;
5
- background: var(--huyooo-overlay);
6
- animation: fade-in 200ms ease-out;
7
- display: flex;
8
- align-items: center;
9
- justify-content: center;
10
- }
11
-
12
- .window-container {
13
- position: absolute;
14
- display: flex;
15
- flex-direction: column;
16
- background: var(--huyooo-panel-bg);
17
- backdrop-filter: blur(24px);
18
- border: 1px solid var(--huyooo-border);
19
- box-shadow: var(--huyooo-shadow-lg);
20
- border-radius: 0.5rem;
21
- overflow: hidden;
22
- animation: window-fade-in 200ms ease-out;
23
- }
24
-
25
- .window-title-bar {
26
- height: 2.5rem;
27
- display: flex;
28
- align-items: center;
29
- justify-content: space-between;
30
- padding: 0 0.75rem;
31
- border-bottom: 1px solid var(--huyooo-border);
32
- flex-shrink: 0;
33
- user-select: none;
34
- background: var(--huyooo-surface);
35
- z-index: 20;
36
- cursor: grab;
37
- }
38
-
39
- .window-title-bar:active {
40
- cursor: grabbing;
41
- }
42
-
43
- .window-controls {
44
- display: flex;
45
- align-items: center;
46
- gap: 0.5rem;
47
- }
48
-
49
- .window-control-button {
50
- width: 0.75rem;
51
- height: 0.75rem;
52
- border-radius: 50%;
53
- display: flex;
54
- align-items: center;
55
- justify-content: center;
56
- border: 1px solid;
57
- box-shadow: var(--huyooo-shadow-sm);
58
- outline: none;
59
- cursor: pointer;
60
- padding: 0;
61
- background: transparent;
62
- flex-shrink: 0;
63
- position: relative;
64
- }
65
-
66
- .window-control-button svg {
67
- width: 7px !important;
68
- height: 7px !important;
69
- display: block;
70
- flex-shrink: 0;
71
- }
72
-
73
- .window-control-button .window-control-icon {
74
- width: 7px !important;
75
- height: 7px !important;
76
- min-width: 7px;
77
- min-height: 7px;
78
- }
79
-
80
- .window-control-button--close {
81
- background-color: var(--huyooo-window-close-bg);
82
- border-color: var(--huyooo-window-close-border);
83
- }
84
-
85
- .window-control-button--minimize {
86
- background-color: var(--huyooo-window-minimize-bg);
87
- border-color: var(--huyooo-window-minimize-border);
88
- }
89
-
90
- .window-control-button--maximize {
91
- background-color: var(--huyooo-window-maximize-bg);
92
- border-color: var(--huyooo-window-maximize-border);
93
- }
94
-
95
- .window-control-icon {
96
- color: var(--huyooo-window-control-icon);
97
- opacity: 0;
98
- transition: opacity 200ms;
99
- width: 7px;
100
- height: 7px;
101
- flex-shrink: 0;
102
- }
103
-
104
- .window-controls:hover .window-control-icon {
105
- opacity: 1;
106
- }
107
-
108
- .window-control-button--close:hover {
109
- background-color: color-mix(in srgb, var(--huyooo-window-close-bg) 80%, transparent);
110
- }
111
-
112
- .window-control-button--minimize:hover {
113
- background-color: color-mix(in srgb, var(--huyooo-window-minimize-bg) 80%, transparent);
114
- }
115
-
116
- .window-control-button--maximize:hover {
117
- background-color: color-mix(in srgb, var(--huyooo-window-maximize-bg) 80%, transparent);
118
- }
119
-
120
- .window-title-info {
121
- display: flex;
122
- flex-direction: column;
123
- align-items: center;
124
- justify-content: center;
125
- flex: 1;
126
- margin: 0 1rem;
127
- overflow: hidden;
128
- pointer-events: none;
129
- }
130
-
131
- .window-title-text {
132
- font-weight: 500;
133
- font-size: 0.75rem;
134
- color: var(--huyooo-text-muted);
135
- overflow: hidden;
136
- text-overflow: ellipsis;
137
- white-space: nowrap;
138
- width: 100%;
139
- text-align: center;
140
- }
141
-
142
- .window-title-actions {
143
- display: flex;
144
- align-items: center;
145
- justify-content: flex-end;
146
- min-width: 4rem;
147
- flex-shrink: 0;
148
- }
149
-
150
- .window-content {
151
- flex: 1;
152
- display: flex;
153
- flex-direction: column;
154
- overflow: hidden;
155
- position: relative;
156
- width: 100%;
157
- height: 100%;
158
- }
159
-
160
- /* Resize Handles */
161
- .window-resize-handle {
162
- position: absolute;
163
- background: transparent;
164
- z-index: 30;
165
- }
166
-
167
- .window-resize-handle--n {
168
- top: 0;
169
- left: 0;
170
- right: 0;
171
- height: 4px;
172
- cursor: n-resize;
173
- }
174
-
175
- .window-resize-handle--s {
176
- bottom: 0;
177
- left: 0;
178
- right: 0;
179
- height: 4px;
180
- cursor: s-resize;
181
- }
182
-
183
- .window-resize-handle--e {
184
- top: 0;
185
- right: 0;
186
- bottom: 0;
187
- width: 4px;
188
- cursor: e-resize;
189
- }
190
-
191
- .window-resize-handle--w {
192
- top: 0;
193
- left: 0;
194
- bottom: 0;
195
- width: 4px;
196
- cursor: w-resize;
197
- }
198
-
199
- .window-resize-handle--ne {
200
- top: 0;
201
- right: 0;
202
- width: 8px;
203
- height: 8px;
204
- cursor: ne-resize;
205
- }
206
-
207
- .window-resize-handle--nw {
208
- top: 0;
209
- left: 0;
210
- width: 8px;
211
- height: 8px;
212
- cursor: nw-resize;
213
- }
214
-
215
- .window-resize-handle--se {
216
- bottom: 0;
217
- right: 0;
218
- width: 8px;
219
- height: 8px;
220
- cursor: se-resize;
221
- }
222
-
223
- .window-resize-handle--sw {
224
- bottom: 0;
225
- left: 0;
226
- width: 8px;
227
- height: 8px;
228
- cursor: sw-resize;
229
- }
230
-
231
- @keyframes fade-in {
232
- from { opacity: 0; }
233
- to { opacity: 1; }
234
- }
235
-
236
- @keyframes window-fade-in {
237
- from {
238
- opacity: 0;
239
- transform: translate(-50%, -50%) scale(0.95);
240
- }
241
- to {
242
- opacity: 1;
243
- transform: translate(-50%, -50%) scale(1);
244
- }
245
- }
246
-
@@ -1,335 +0,0 @@
1
- import { createPortal } from 'react-dom';
2
- import { useMemo, useRef, useEffect, useState } from 'react';
3
- import { Icon } from '@iconify/react';
4
- import { useWindowDrag } from '../hooks/useWindowDrag';
5
- import { useWindowResize } from '../hooks/useWindowResize';
6
- import './Window.css';
7
-
8
- interface WindowProps {
9
- title?: string;
10
- showTitleBar?: boolean;
11
- showMinimize?: boolean;
12
- showMaximize?: boolean;
13
- draggable?: boolean;
14
- resizable?: boolean;
15
- width?: string | number;
16
- height?: string | number;
17
- minWidth?: string | number;
18
- minHeight?: string | number;
19
- maxWidth?: string | number;
20
- maxHeight?: string | number;
21
- closeOnBackdrop?: boolean;
22
- fitContent?: boolean;
23
- onClose?: () => void;
24
- onMinimize?: () => void;
25
- onMaximize?: () => void;
26
- children?: React.ReactNode;
27
- titleSlot?: React.ReactNode;
28
- actionsSlot?: React.ReactNode;
29
- }
30
-
31
- export function Window({
32
- title,
33
- showTitleBar = true,
34
- showMinimize = false,
35
- showMaximize = false,
36
- draggable = true,
37
- resizable = true,
38
- closeOnBackdrop = true,
39
- width = 'auto',
40
- height = 'auto',
41
- minWidth = 300,
42
- minHeight = 200,
43
- maxWidth = '80vw',
44
- maxHeight = '80vh',
45
- fitContent = false,
46
- onClose,
47
- onMinimize,
48
- onMaximize,
49
- children,
50
- titleSlot,
51
- actionsSlot,
52
- }: WindowProps) {
53
- const windowContainerRef = useRef<HTMLDivElement>(null);
54
- const windowDrag = draggable ? useWindowDrag() : null;
55
-
56
- /**
57
- * 是否使用自适应内容模式
58
- */
59
- const isAutoSize = useMemo(() => {
60
- return (
61
- fitContent ||
62
- width === 'auto' ||
63
- width === 'fit-content' ||
64
- height === 'auto' ||
65
- height === 'fit-content'
66
- );
67
- }, [fitContent, width, height]);
68
-
69
- /**
70
- * 计算初始尺寸
71
- */
72
- const getInitialSize = () => {
73
- if (isAutoSize) {
74
- return { initialWidth: 0, initialHeight: 0 };
75
- }
76
-
77
- const defaultWidth = 600;
78
- const defaultHeight = 500;
79
-
80
- let initialWidth = defaultWidth;
81
- let initialHeight = defaultHeight;
82
-
83
- if (width !== 'auto' && width !== 'fit-content') {
84
- initialWidth =
85
- typeof width === 'number' ? width : parseInt(width as string);
86
- }
87
- if (height !== 'auto' && height !== 'fit-content') {
88
- initialHeight =
89
- typeof height === 'number' ? height : parseInt(height as string);
90
- }
91
-
92
- return { initialWidth, initialHeight };
93
- };
94
-
95
- /**
96
- * 解析尺寸限制
97
- */
98
- const parseSize = (size: string | number, defaultPx: number): number => {
99
- if (typeof size === 'number') return size;
100
- if (typeof size === 'string') {
101
- if (size.endsWith('px')) return parseInt(size);
102
- if (size.endsWith('vw'))
103
- return (parseInt(size) / 100) * window.innerWidth;
104
- if (size.endsWith('vh'))
105
- return (parseInt(size) / 100) * window.innerHeight;
106
- }
107
- return defaultPx;
108
- };
109
-
110
- const { initialWidth, initialHeight } = getInitialSize();
111
- const minW = parseSize(minWidth, 500);
112
- const minH = parseSize(minHeight, 350);
113
- const maxW = parseSize(maxWidth, window.innerWidth * 0.8);
114
- const maxH = parseSize(maxHeight, window.innerHeight * 0.8);
115
-
116
- const windowResize = resizable
117
- ? useWindowResize(initialWidth, initialHeight, minW, minH, maxW, maxH)
118
- : null;
119
-
120
- const windowStyle = useMemo(() => {
121
- const baseStyle: React.CSSProperties = {
122
- left: '50%',
123
- top: '50%',
124
- };
125
-
126
- let translateX = '-50%';
127
- let translateY = '-50%';
128
-
129
- if (draggable && windowDrag) {
130
- translateX = `calc(-50% + ${windowDrag.position.x}px)`;
131
- translateY = `calc(-50% + ${windowDrag.position.y}px)`;
132
- }
133
-
134
- baseStyle.transform = `translate(${translateX}, ${translateY})`;
135
- baseStyle.transformOrigin = 'center center';
136
-
137
- if (isAutoSize) {
138
- if (resizable && windowResize && windowResize.width > 0) {
139
- baseStyle.width = `${windowResize.width}px`;
140
- baseStyle.height = `${windowResize.height}px`;
141
- }
142
- } else {
143
- if (resizable && windowResize) {
144
- baseStyle.width = `${windowResize.width}px`;
145
- baseStyle.height = `${windowResize.height}px`;
146
- } else {
147
- if (width !== 'auto' && width !== 'fit-content') {
148
- baseStyle.width =
149
- typeof width === 'number' ? `${width}px` : (width as string);
150
- }
151
- if (height !== 'auto' && height !== 'fit-content') {
152
- baseStyle.height =
153
- typeof height === 'number' ? `${height}px` : (height as string);
154
- }
155
- }
156
- }
157
-
158
- if (minWidth) {
159
- baseStyle.minWidth =
160
- typeof minWidth === 'number' ? `${minWidth}px` : (minWidth as string);
161
- }
162
- if (minHeight) {
163
- baseStyle.minHeight =
164
- typeof minHeight === 'number'
165
- ? `${minHeight}px`
166
- : (minHeight as string);
167
- }
168
- if (maxWidth) {
169
- baseStyle.maxWidth =
170
- typeof maxWidth === 'number' ? `${maxWidth}px` : (maxWidth as string);
171
- }
172
- if (maxHeight) {
173
- baseStyle.maxHeight =
174
- typeof maxHeight === 'number'
175
- ? `${maxHeight}px`
176
- : (maxHeight as string);
177
- }
178
-
179
- return baseStyle;
180
- }, [
181
- draggable,
182
- windowDrag,
183
- isAutoSize,
184
- resizable,
185
- windowResize,
186
- width,
187
- height,
188
- minWidth,
189
- minHeight,
190
- maxWidth,
191
- maxHeight,
192
- ]);
193
-
194
- const handleBackdropClick = (e: React.MouseEvent) => {
195
- if (closeOnBackdrop && e.target === e.currentTarget) {
196
- onClose?.();
197
- }
198
- };
199
-
200
- const handleDragStart = (e: React.MouseEvent) => {
201
- if (draggable && windowDrag) {
202
- windowDrag.startDrag(e);
203
- }
204
- };
205
-
206
- const handleResizeStart = (
207
- e: React.MouseEvent,
208
- direction: 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'
209
- ) => {
210
- if (!resizable || !windowResize || !windowContainerRef.current) return;
211
-
212
- const rect = windowContainerRef.current.getBoundingClientRect();
213
- const currentWidth = rect.width;
214
- const currentHeight = rect.height;
215
-
216
- windowResize.startResize(e, direction, currentWidth, currentHeight);
217
- };
218
-
219
- /**
220
- * ESC 键关闭
221
- */
222
- useEffect(() => {
223
- const handleEsc = (e: KeyboardEvent) => {
224
- if (e.key === 'Escape') {
225
- e.preventDefault();
226
- onClose?.();
227
- }
228
- };
229
- window.addEventListener('keydown', handleEsc);
230
- return () => {
231
- window.removeEventListener('keydown', handleEsc);
232
- };
233
- }, [onClose]);
234
-
235
- return createPortal(
236
- <div className="window-overlay" onClick={handleBackdropClick}>
237
- <div
238
- ref={windowContainerRef}
239
- className="window-container"
240
- style={windowStyle}
241
- onClick={(e) => e.stopPropagation()}
242
- >
243
- {/* Title Bar */}
244
- {showTitleBar && (
245
- <div
246
- className="window-title-bar draggable-area"
247
- onMouseDown={handleDragStart}
248
- >
249
- <div className="window-controls">
250
- <button
251
- onClick={(e) => {
252
- e.stopPropagation();
253
- onClose?.();
254
- }}
255
- className="window-control-button window-control-button--close"
256
- >
257
- <Icon icon="lucide:x" width={7} height={7} className="window-control-icon" />
258
- </button>
259
- {showMinimize && (
260
- <button
261
- onClick={(e) => {
262
- e.stopPropagation();
263
- onMinimize?.();
264
- }}
265
- className="window-control-button window-control-button--minimize"
266
- >
267
- <Icon icon="lucide:minus" width={7} height={7} className="window-control-icon" />
268
- </button>
269
- )}
270
- {showMaximize && (
271
- <button
272
- onClick={(e) => {
273
- e.stopPropagation();
274
- onMaximize?.();
275
- }}
276
- className="window-control-button window-control-button--maximize"
277
- >
278
- <Icon icon="lucide:maximize-2" width={7} height={7} className="window-control-icon" />
279
- </button>
280
- )}
281
- </div>
282
-
283
- <div className="window-title-info">
284
- {titleSlot || <span className="window-title-text">{title}</span>}
285
- </div>
286
-
287
- <div className="window-title-actions">{actionsSlot}</div>
288
- </div>
289
- )}
290
-
291
- {/* Content */}
292
- <div className="window-content">{children}</div>
293
-
294
- {/* Resize Handles */}
295
- {resizable && (
296
- <>
297
- <div
298
- className="window-resize-handle window-resize-handle--n"
299
- onMouseDown={(e) => handleResizeStart(e, 'n')}
300
- ></div>
301
- <div
302
- className="window-resize-handle window-resize-handle--s"
303
- onMouseDown={(e) => handleResizeStart(e, 's')}
304
- ></div>
305
- <div
306
- className="window-resize-handle window-resize-handle--e"
307
- onMouseDown={(e) => handleResizeStart(e, 'e')}
308
- ></div>
309
- <div
310
- className="window-resize-handle window-resize-handle--w"
311
- onMouseDown={(e) => handleResizeStart(e, 'w')}
312
- ></div>
313
- <div
314
- className="window-resize-handle window-resize-handle--ne"
315
- onMouseDown={(e) => handleResizeStart(e, 'ne')}
316
- ></div>
317
- <div
318
- className="window-resize-handle window-resize-handle--nw"
319
- onMouseDown={(e) => handleResizeStart(e, 'nw')}
320
- ></div>
321
- <div
322
- className="window-resize-handle window-resize-handle--se"
323
- onMouseDown={(e) => handleResizeStart(e, 'se')}
324
- ></div>
325
- <div
326
- className="window-resize-handle window-resize-handle--sw"
327
- onMouseDown={(e) => handleResizeStart(e, 'sw')}
328
- ></div>
329
- </>
330
- )}
331
- </div>
332
- </div>,
333
- document.body
334
- );
335
- }
@@ -1,80 +0,0 @@
1
- import { useState, useCallback, useEffect } from 'react';
2
- import type { FileItem } from '../types';
3
- import { FileType } from '../types';
4
-
5
- // 扩展 Window 类型
6
- declare global {
7
- interface Window {
8
- fileExplorerAPI?: {
9
- getApplicationIcon?: (appPath: string) => Promise<string | null>;
10
- };
11
- }
12
- }
13
-
14
- /**
15
- * 应用程序图标管理 hook
16
- */
17
- export function useApplicationIcon(items: FileItem[]) {
18
- // 应用程序图标缓存(避免重复请求)
19
- const appIconCache = new Map<string, string>();
20
-
21
- // 使用响应式的图标 URL 映射
22
- const [appIconUrls, setAppIconUrls] = useState<Map<string, string>>(new Map());
23
-
24
- /**
25
- * 为应用程序获取图标
26
- */
27
- const loadApplicationIcon = useCallback(async (item: FileItem) => {
28
- if (item.type !== FileType.APPLICATION || !item.id) return;
29
-
30
- // 检查缓存
31
- if (appIconCache.has(item.id)) {
32
- const cachedUrl = appIconCache.get(item.id);
33
- if (cachedUrl) {
34
- setAppIconUrls(prev => new Map(prev).set(item.id, cachedUrl));
35
- }
36
- return;
37
- }
38
-
39
- // 检查响应式映射
40
- if (appIconUrls.has(item.id)) {
41
- return;
42
- }
43
-
44
- if (typeof window.fileExplorerAPI !== 'undefined' && window.fileExplorerAPI.getApplicationIcon) {
45
- try {
46
- const iconUrl = await window.fileExplorerAPI.getApplicationIcon(item.id);
47
- if (iconUrl) {
48
- appIconCache.set(item.id, iconUrl);
49
- setAppIconUrls(prev => new Map(prev).set(item.id, iconUrl));
50
- }
51
- } catch (error) {
52
- console.error(`Failed to load application icon for ${item.name}:`, error);
53
- }
54
- }
55
- }, [appIconUrls]);
56
-
57
- /**
58
- * 监听 items 变化,为应用程序加载图标
59
- */
60
- useEffect(() => {
61
- items.forEach(item => {
62
- if (item.type === FileType.APPLICATION && !appIconUrls.has(item.id)) {
63
- loadApplicationIcon(item);
64
- }
65
- });
66
- }, [items, appIconUrls, loadApplicationIcon]);
67
-
68
- /**
69
- * 获取应用程序图标的响应式 URL
70
- */
71
- const getAppIconUrl = useCallback((item: FileItem): string | undefined => {
72
- return appIconUrls.get(item.id);
73
- }, [appIconUrls]);
74
-
75
- return {
76
- getAppIconUrl,
77
- loadApplicationIcon
78
- };
79
- }
80
-