@huyooo/file-explorer-frontend-react 0.4.2

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 (46) hide show
  1. package/dist/index.css +1740 -0
  2. package/dist/index.css.map +1 -0
  3. package/dist/index.d.ts +562 -0
  4. package/dist/index.js +3453 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/style.css +3 -0
  7. package/package.json +58 -0
  8. package/src/components/Breadcrumb.css +61 -0
  9. package/src/components/Breadcrumb.tsx +38 -0
  10. package/src/components/CompressDialog.css +264 -0
  11. package/src/components/CompressDialog.tsx +222 -0
  12. package/src/components/ContextMenu.css +155 -0
  13. package/src/components/ContextMenu.tsx +375 -0
  14. package/src/components/FileGrid.css +267 -0
  15. package/src/components/FileGrid.tsx +277 -0
  16. package/src/components/FileIcon.css +41 -0
  17. package/src/components/FileIcon.tsx +86 -0
  18. package/src/components/FileInfoDialog.css +252 -0
  19. package/src/components/FileInfoDialog.tsx +202 -0
  20. package/src/components/FileList.css +226 -0
  21. package/src/components/FileList.tsx +228 -0
  22. package/src/components/FileListView.css +36 -0
  23. package/src/components/FileListView.tsx +355 -0
  24. package/src/components/FileSidebar.css +94 -0
  25. package/src/components/FileSidebar.tsx +66 -0
  26. package/src/components/ProgressDialog.css +211 -0
  27. package/src/components/ProgressDialog.tsx +183 -0
  28. package/src/components/SortIndicator.css +7 -0
  29. package/src/components/SortIndicator.tsx +19 -0
  30. package/src/components/StatusBar.css +20 -0
  31. package/src/components/StatusBar.tsx +21 -0
  32. package/src/components/Toolbar.css +150 -0
  33. package/src/components/Toolbar.tsx +127 -0
  34. package/src/components/Window.css +246 -0
  35. package/src/components/Window.tsx +335 -0
  36. package/src/hooks/useApplicationIcon.ts +80 -0
  37. package/src/hooks/useDragAndDrop.ts +104 -0
  38. package/src/hooks/useMediaPlayer.ts +164 -0
  39. package/src/hooks/useSelection.ts +112 -0
  40. package/src/hooks/useWindowDrag.ts +60 -0
  41. package/src/hooks/useWindowResize.ts +126 -0
  42. package/src/index.css +3 -0
  43. package/src/index.ts +34 -0
  44. package/src/types/index.ts +274 -0
  45. package/src/utils/fileTypeIcon.ts +309 -0
  46. package/src/utils/folderTypeIcon.ts +132 -0
package/dist/index.js ADDED
@@ -0,0 +1,3453 @@
1
+ // src/types/index.ts
2
+ var FileType = {
3
+ FOLDER: "folder",
4
+ FILE: "file",
5
+ IMAGE: "image",
6
+ VIDEO: "video",
7
+ MUSIC: "music",
8
+ DOCUMENT: "document",
9
+ CODE: "code",
10
+ TEXT: "text",
11
+ PDF: "pdf",
12
+ ARCHIVE: "archive",
13
+ APPLICATION: "application",
14
+ UNKNOWN: "unknown"
15
+ };
16
+
17
+ // src/components/FileListView.tsx
18
+ import { useState, useCallback, useImperativeHandle, forwardRef } from "react";
19
+ import { Icon as Icon3 } from "@iconify/react";
20
+
21
+ // src/components/FileGrid.tsx
22
+ import { useRef, useMemo as useMemo2 } from "react";
23
+
24
+ // src/components/FileIcon.tsx
25
+ import { useMemo } from "react";
26
+ import { Icon } from "@iconify/react";
27
+
28
+ // src/utils/fileTypeIcon.ts
29
+ var KNOWN_TYPES = /* @__PURE__ */ new Set([
30
+ "3d",
31
+ "actionscript",
32
+ "ada",
33
+ "adobe-illustrator",
34
+ "adobe-photoshop",
35
+ "android",
36
+ "angular",
37
+ "applescript",
38
+ "arduino",
39
+ "asciidoc",
40
+ "assembly",
41
+ "astro",
42
+ "audio",
43
+ "aurelia",
44
+ "babel",
45
+ "ballerina",
46
+ "bazel",
47
+ "biome",
48
+ "blender",
49
+ "bower",
50
+ "bun",
51
+ "c",
52
+ "cabal",
53
+ "caddy",
54
+ "cake",
55
+ "certificate",
56
+ "changelog",
57
+ "circleci",
58
+ "claude",
59
+ "clojure",
60
+ "cmake",
61
+ "cobol",
62
+ "coffee",
63
+ "command",
64
+ "conduct",
65
+ "contributing",
66
+ "controller",
67
+ "copilot",
68
+ "cpp",
69
+ "crystal",
70
+ "csharp",
71
+ "css",
72
+ "css-map",
73
+ "cucumber",
74
+ "cuda",
75
+ "cursor",
76
+ "cypress",
77
+ "d",
78
+ "dart",
79
+ "database",
80
+ "deno",
81
+ "dependabot",
82
+ "diff",
83
+ "django",
84
+ "docker",
85
+ "document",
86
+ "drawio",
87
+ "drizzle",
88
+ "editorconfig",
89
+ "ejs",
90
+ "elixir",
91
+ "elm",
92
+ "email",
93
+ "ember",
94
+ "epub",
95
+ "erlang",
96
+ "esbuild",
97
+ "eslint",
98
+ "excalidraw",
99
+ "exe",
100
+ "favicon",
101
+ "figma",
102
+ "firebase",
103
+ "flash",
104
+ "flow",
105
+ "font",
106
+ "forth",
107
+ "fortran",
108
+ "freemarker",
109
+ "fsharp",
110
+ "gamemaker",
111
+ "gatsby",
112
+ "gcp",
113
+ "gemfile",
114
+ "gemini",
115
+ "git",
116
+ "gitlab",
117
+ "gleam",
118
+ "go",
119
+ "go-mod",
120
+ "godot",
121
+ "gradle",
122
+ "graphql",
123
+ "groovy",
124
+ "grunt",
125
+ "gulp",
126
+ "h",
127
+ "haml",
128
+ "handlebars",
129
+ "hardhat",
130
+ "haskell",
131
+ "haxe",
132
+ "hcl",
133
+ "helm",
134
+ "hjson",
135
+ "hosts",
136
+ "hpp",
137
+ "html",
138
+ "http",
139
+ "husky",
140
+ "i18n",
141
+ "image",
142
+ "imba",
143
+ "ionic",
144
+ "jar",
145
+ "java",
146
+ "javaclass",
147
+ "javascript",
148
+ "javascript-map",
149
+ "jenkins",
150
+ "jest",
151
+ "jinja",
152
+ "jsconfig",
153
+ "json",
154
+ "julia",
155
+ "jupyter",
156
+ "just",
157
+ "karma",
158
+ "key",
159
+ "kotlin",
160
+ "kubernetes",
161
+ "laravel",
162
+ "lean",
163
+ "lefthook",
164
+ "lerna",
165
+ "less",
166
+ "lib",
167
+ "license",
168
+ "lighthouse",
169
+ "liquid",
170
+ "lisp",
171
+ "livescript",
172
+ "lock",
173
+ "log",
174
+ "lua",
175
+ "luau",
176
+ "makefile",
177
+ "markdown",
178
+ "markdownlint",
179
+ "matlab",
180
+ "maven",
181
+ "mdx",
182
+ "mercurial",
183
+ "mermaid",
184
+ "meson",
185
+ "minecraft",
186
+ "mjml",
187
+ "mocha",
188
+ "mojo",
189
+ "nest",
190
+ "netlify",
191
+ "next",
192
+ "nginx",
193
+ "nim",
194
+ "nix",
195
+ "nodejs",
196
+ "nodemon",
197
+ "npm",
198
+ "nuget",
199
+ "nunjucks",
200
+ "nuxt",
201
+ "nx",
202
+ "objective-c",
203
+ "objective-cpp",
204
+ "ocaml",
205
+ "odin",
206
+ "openapi",
207
+ "opentofu",
208
+ "pascal",
209
+ "pdf",
210
+ "perl",
211
+ "php",
212
+ "phpstan",
213
+ "phpunit",
214
+ "pipeline",
215
+ "playwright",
216
+ "pnpm",
217
+ "postcss",
218
+ "powerpoint",
219
+ "powershell",
220
+ "prettier",
221
+ "prisma",
222
+ "processing",
223
+ "prolog",
224
+ "proto",
225
+ "pug",
226
+ "puppet",
227
+ "purescript",
228
+ "python",
229
+ "pytorch",
230
+ "quasar",
231
+ "r",
232
+ "racket",
233
+ "razor",
234
+ "react",
235
+ "react-ts",
236
+ "readme",
237
+ "reason",
238
+ "remark",
239
+ "remix",
240
+ "renovate",
241
+ "replit",
242
+ "rescript",
243
+ "riot",
244
+ "roadmap",
245
+ "robot",
246
+ "robots",
247
+ "rollup",
248
+ "routing",
249
+ "rspec",
250
+ "rubocop",
251
+ "ruby",
252
+ "ruff",
253
+ "rust",
254
+ "salt",
255
+ "san",
256
+ "sas",
257
+ "sass",
258
+ "sbt",
259
+ "scala",
260
+ "scheme",
261
+ "search",
262
+ "sentry",
263
+ "sequelize",
264
+ "serverless",
265
+ "settings",
266
+ "shader",
267
+ "shellcheck",
268
+ "sketch",
269
+ "slim",
270
+ "slint",
271
+ "smarty",
272
+ "snakemake",
273
+ "snapcraft",
274
+ "snyk",
275
+ "solidity",
276
+ "spwn",
277
+ "stackblitz",
278
+ "stan",
279
+ "stencil",
280
+ "storybook",
281
+ "stryker",
282
+ "stylelint",
283
+ "stylus",
284
+ "sublime",
285
+ "subtitles",
286
+ "supabase",
287
+ "svelte",
288
+ "svg",
289
+ "svgo",
290
+ "swagger",
291
+ "swc",
292
+ "swift",
293
+ "tailwindcss",
294
+ "taskfile",
295
+ "tauri",
296
+ "tcl",
297
+ "teal",
298
+ "templ",
299
+ "template",
300
+ "terraform",
301
+ "test-js",
302
+ "test-jsx",
303
+ "test-ts",
304
+ "tex",
305
+ "todo",
306
+ "toml",
307
+ "travis",
308
+ "tree",
309
+ "tsconfig",
310
+ "tsdoc",
311
+ "turborepo",
312
+ "twig",
313
+ "typescript",
314
+ "typescript-def",
315
+ "unity",
316
+ "unocss",
317
+ "vagrant",
318
+ "vanilla-extract",
319
+ "vercel",
320
+ "verilog",
321
+ "video",
322
+ "vim",
323
+ "vite",
324
+ "vitest",
325
+ "vlang",
326
+ "vscode",
327
+ "vue",
328
+ "vue-config",
329
+ "wakatime",
330
+ "wallaby",
331
+ "webassembly",
332
+ "webhint",
333
+ "webpack",
334
+ "windicss",
335
+ "word",
336
+ "wrangler",
337
+ "wxt",
338
+ "xaml",
339
+ "xmake",
340
+ "xml",
341
+ "yaml",
342
+ "yarn",
343
+ "zig",
344
+ "zip"
345
+ ]);
346
+ var EXT_MAP = {
347
+ // JavaScript/TypeScript
348
+ "js": "javascript",
349
+ "mjs": "javascript",
350
+ "cjs": "javascript",
351
+ "jsx": "react",
352
+ "ts": "typescript",
353
+ "mts": "typescript",
354
+ "cts": "typescript",
355
+ "tsx": "react-ts",
356
+ // 前端框架
357
+ "vue": "vue",
358
+ "svelte": "svelte",
359
+ "astro": "astro",
360
+ // 后端语言
361
+ "py": "python",
362
+ "pyw": "python",
363
+ "pyi": "python",
364
+ "pyc": "python",
365
+ "java": "java",
366
+ "class": "javaclass",
367
+ "jar": "jar",
368
+ "c": "c",
369
+ "h": "h",
370
+ "cpp": "cpp",
371
+ "cc": "cpp",
372
+ "cxx": "cpp",
373
+ "hpp": "hpp",
374
+ "hh": "hpp",
375
+ "hxx": "hpp",
376
+ "cs": "csharp",
377
+ "csx": "csharp",
378
+ "go": "go",
379
+ "rs": "rust",
380
+ "php": "php",
381
+ "phtml": "php",
382
+ "rb": "ruby",
383
+ "rake": "ruby",
384
+ "swift": "swift",
385
+ "kt": "kotlin",
386
+ "kts": "kotlin",
387
+ "scala": "scala",
388
+ "dart": "dart",
389
+ "lua": "lua",
390
+ "luau": "luau",
391
+ "r": "r",
392
+ "rdata": "r",
393
+ "rds": "r",
394
+ "pl": "perl",
395
+ "pm": "perl",
396
+ "sh": "command",
397
+ "bash": "command",
398
+ "zsh": "command",
399
+ "fish": "command",
400
+ "ps1": "powershell",
401
+ "psm1": "powershell",
402
+ "psd1": "powershell",
403
+ "bat": "command",
404
+ "cmd": "command",
405
+ // 样式
406
+ "css": "css",
407
+ "scss": "sass",
408
+ "sass": "sass",
409
+ "less": "less",
410
+ "styl": "stylus",
411
+ // 标记/配置
412
+ "html": "html",
413
+ "htm": "html",
414
+ "xhtml": "html",
415
+ "xml": "xml",
416
+ "xsl": "xml",
417
+ "xslt": "xml",
418
+ "json": "json",
419
+ "jsonc": "json",
420
+ "json5": "json",
421
+ "yaml": "yaml",
422
+ "yml": "yaml",
423
+ "toml": "toml",
424
+ "ini": "settings",
425
+ "conf": "settings",
426
+ "config": "settings",
427
+ // 文档
428
+ "md": "markdown",
429
+ "markdown": "markdown",
430
+ "mdx": "mdx",
431
+ "txt": "document",
432
+ "pdf": "pdf",
433
+ "doc": "word",
434
+ "docx": "word",
435
+ "dot": "word",
436
+ "dotx": "word",
437
+ "odt": "word",
438
+ "xls": "table",
439
+ "xlsx": "table",
440
+ "xlsm": "table",
441
+ "ods": "table",
442
+ "csv": "table",
443
+ "ppt": "powerpoint",
444
+ "pptx": "powerpoint",
445
+ "odp": "powerpoint",
446
+ // 图片
447
+ "jpg": "image",
448
+ "jpeg": "image",
449
+ "png": "image",
450
+ "gif": "image",
451
+ "webp": "image",
452
+ "ico": "image",
453
+ "bmp": "image",
454
+ "tiff": "image",
455
+ "tif": "image",
456
+ "heic": "image",
457
+ "heif": "image",
458
+ "svg": "svg",
459
+ "psd": "adobe-photoshop",
460
+ "ai": "adobe-illustrator",
461
+ "sketch": "sketch",
462
+ "fig": "figma",
463
+ "figma": "figma",
464
+ // 视频/音频
465
+ "mp4": "video",
466
+ "mov": "video",
467
+ "avi": "video",
468
+ "mkv": "video",
469
+ "webm": "video",
470
+ "flv": "video",
471
+ "wmv": "video",
472
+ "m4v": "video",
473
+ "3gp": "video",
474
+ "mpeg": "video",
475
+ "mpg": "video",
476
+ "mp3": "audio",
477
+ "wav": "audio",
478
+ "flac": "audio",
479
+ "aac": "audio",
480
+ "ogg": "audio",
481
+ "wma": "audio",
482
+ "m4a": "audio",
483
+ "aiff": "audio",
484
+ // 压缩
485
+ "zip": "zip",
486
+ "rar": "zip",
487
+ "7z": "zip",
488
+ "tar": "zip",
489
+ "gz": "zip",
490
+ "bz2": "zip",
491
+ "xz": "zip",
492
+ "tgz": "zip",
493
+ "tbz2": "zip",
494
+ // 数据库
495
+ "sql": "database",
496
+ "db": "database",
497
+ "sqlite": "database",
498
+ "sqlite3": "database",
499
+ "prisma": "prisma",
500
+ // 其他
501
+ "log": "log",
502
+ "lock": "lock",
503
+ "env": "settings",
504
+ "graphql": "graphql",
505
+ "gql": "graphql",
506
+ "proto": "proto",
507
+ "wasm": "webassembly",
508
+ "zig": "zig",
509
+ "nim": "nim",
510
+ "nix": "nix",
511
+ "hcl": "hcl",
512
+ "tf": "terraform",
513
+ "sol": "solidity",
514
+ "ex": "elixir",
515
+ "exs": "elixir",
516
+ "erl": "erlang",
517
+ "hrl": "erlang",
518
+ "hs": "haskell",
519
+ "lhs": "haskell",
520
+ "ml": "ocaml",
521
+ "mli": "ocaml",
522
+ "clj": "clojure",
523
+ "cljs": "clojure",
524
+ "cljc": "clojure",
525
+ "lisp": "lisp",
526
+ "lsp": "lisp",
527
+ "el": "lisp",
528
+ "vim": "vim",
529
+ "dockerfile": "docker"
530
+ };
531
+ var SPECIAL_FILES = {
532
+ // Git
533
+ ".gitignore": "git",
534
+ ".gitattributes": "git",
535
+ ".gitmodules": "git",
536
+ ".gitkeep": "git",
537
+ // 环境
538
+ ".env": "settings",
539
+ ".env.local": "settings",
540
+ ".env.development": "settings",
541
+ ".env.production": "settings",
542
+ ".env.test": "settings",
543
+ ".env.example": "settings",
544
+ // Node/包管理
545
+ "package.json": "nodejs",
546
+ "package-lock.json": "npm",
547
+ "yarn.lock": "yarn",
548
+ ".yarnrc": "yarn",
549
+ ".yarnrc.yml": "yarn",
550
+ "pnpm-lock.yaml": "pnpm",
551
+ ".pnpmfile.cjs": "pnpm",
552
+ "bun.lockb": "bun",
553
+ "bunfig.toml": "bun",
554
+ // Python
555
+ "requirements.txt": "python",
556
+ "pipfile": "python",
557
+ "pipfile.lock": "python",
558
+ "pyproject.toml": "python",
559
+ "poetry.lock": "python",
560
+ "setup.py": "python",
561
+ // Rust
562
+ "cargo.toml": "rust",
563
+ "cargo.lock": "rust",
564
+ // Go
565
+ "go.mod": "go-mod",
566
+ "go.sum": "go-mod",
567
+ "go.work": "go-mod",
568
+ // PHP
569
+ "composer.json": "php",
570
+ "composer.lock": "php",
571
+ // Ruby
572
+ "gemfile": "gemfile",
573
+ "gemfile.lock": "gemfile",
574
+ "rakefile": "ruby",
575
+ // Docker
576
+ "dockerfile": "docker",
577
+ "docker-compose.yml": "docker",
578
+ "docker-compose.yaml": "docker",
579
+ ".dockerignore": "docker",
580
+ // 构建工具
581
+ "makefile": "makefile",
582
+ "gnumakefile": "makefile",
583
+ "cmakelists.txt": "cmake",
584
+ "build.gradle": "gradle",
585
+ "build.gradle.kts": "gradle",
586
+ "settings.gradle": "gradle",
587
+ "pom.xml": "maven",
588
+ // 配置
589
+ "tsconfig.json": "tsconfig",
590
+ "jsconfig.json": "jsconfig",
591
+ ".prettierrc": "prettier",
592
+ ".prettierrc.json": "prettier",
593
+ ".prettierrc.js": "prettier",
594
+ ".prettierignore": "prettier",
595
+ "prettier.config.js": "prettier",
596
+ ".eslintrc": "eslint",
597
+ ".eslintrc.json": "eslint",
598
+ ".eslintrc.js": "eslint",
599
+ ".eslintignore": "eslint",
600
+ "eslint.config.js": "eslint",
601
+ "eslint.config.mjs": "eslint",
602
+ ".editorconfig": "editorconfig",
603
+ "vite.config.ts": "vite",
604
+ "vite.config.js": "vite",
605
+ "webpack.config.js": "webpack",
606
+ "webpack.config.ts": "webpack",
607
+ "rollup.config.js": "rollup",
608
+ "rollup.config.ts": "rollup",
609
+ "esbuild.config.js": "esbuild",
610
+ "tailwind.config.js": "tailwindcss",
611
+ "tailwind.config.ts": "tailwindcss",
612
+ "postcss.config.js": "postcss",
613
+ "postcss.config.cjs": "postcss",
614
+ "babel.config.js": "babel",
615
+ ".babelrc": "babel",
616
+ "jest.config.js": "jest",
617
+ "jest.config.ts": "jest",
618
+ "vitest.config.ts": "vitest",
619
+ "vitest.config.js": "vitest",
620
+ "playwright.config.ts": "playwright",
621
+ "playwright.config.js": "playwright",
622
+ "cypress.config.ts": "cypress",
623
+ "cypress.config.js": "cypress",
624
+ ".swcrc": "swc",
625
+ "swc.config.js": "swc",
626
+ "turbo.json": "turborepo",
627
+ "nx.json": "nx",
628
+ "biome.json": "biome",
629
+ ".nvmrc": "nodejs",
630
+ ".node-version": "nodejs",
631
+ // 框架配置
632
+ "nuxt.config.ts": "nuxt",
633
+ "nuxt.config.js": "nuxt",
634
+ "next.config.js": "next",
635
+ "next.config.mjs": "next",
636
+ "next.config.ts": "next",
637
+ "svelte.config.js": "svelte",
638
+ "astro.config.mjs": "astro",
639
+ "astro.config.ts": "astro",
640
+ "vue.config.js": "vue-config",
641
+ "angular.json": "angular",
642
+ "nest-cli.json": "nest",
643
+ "tauri.conf.json": "tauri",
644
+ // CI/CD
645
+ ".travis.yml": "travis",
646
+ ".gitlab-ci.yml": "gitlab",
647
+ "vercel.json": "vercel",
648
+ "netlify.toml": "netlify",
649
+ // 其他
650
+ "license": "license",
651
+ "license.md": "license",
652
+ "license.txt": "license",
653
+ "readme": "readme",
654
+ "readme.md": "readme",
655
+ "readme.txt": "readme",
656
+ "changelog": "changelog",
657
+ "changelog.md": "changelog",
658
+ ".npmrc": "npm",
659
+ ".npmignore": "npm",
660
+ "robots.txt": "robots",
661
+ ".htaccess": "nginx",
662
+ "vagrantfile": "vagrant",
663
+ ".stylelintrc": "stylelint",
664
+ ".stylelintrc.json": "stylelint",
665
+ "nodemon.json": "nodemon",
666
+ ".huskyrc": "husky",
667
+ "renovate.json": "renovate",
668
+ ".snyk": "snyk",
669
+ "deno.json": "deno",
670
+ "deno.jsonc": "deno"
671
+ };
672
+ function getFileTypeIcon(fileName, fallbackType) {
673
+ const lowerName = fileName.toLowerCase();
674
+ if (SPECIAL_FILES[lowerName]) {
675
+ const type = SPECIAL_FILES[lowerName];
676
+ if (KNOWN_TYPES.has(type)) {
677
+ return `material-icon-theme:${type}`;
678
+ }
679
+ }
680
+ if (lowerName === "dockerfile" || lowerName.startsWith("dockerfile.")) {
681
+ return "material-icon-theme:docker";
682
+ }
683
+ if (lowerName === ".env" || lowerName.startsWith(".env.")) {
684
+ return "material-icon-theme:settings";
685
+ }
686
+ const lastDotIndex = fileName.lastIndexOf(".");
687
+ const ext = lastDotIndex > 0 ? fileName.substring(lastDotIndex + 1).toLowerCase() : "";
688
+ if (ext === "ts" || ext === "js") {
689
+ const baseName = fileName.substring(0, lastDotIndex).toLowerCase();
690
+ if (baseName.endsWith(".d")) {
691
+ return "material-icon-theme:typescript-def";
692
+ }
693
+ if (baseName.endsWith(".test") || baseName.endsWith(".spec")) {
694
+ return ext === "ts" ? "material-icon-theme:test-ts" : "material-icon-theme:test-js";
695
+ }
696
+ }
697
+ if (ext === "jsx" || ext === "tsx") {
698
+ const baseName = fileName.substring(0, lastDotIndex).toLowerCase();
699
+ if (baseName.endsWith(".test") || baseName.endsWith(".spec")) {
700
+ return ext === "tsx" ? "material-icon-theme:test-ts" : "material-icon-theme:test-jsx";
701
+ }
702
+ }
703
+ if (ext && EXT_MAP[ext]) {
704
+ const type = EXT_MAP[ext];
705
+ if (KNOWN_TYPES.has(type)) {
706
+ return `material-icon-theme:${type}`;
707
+ }
708
+ }
709
+ if (fallbackType) {
710
+ return getFallbackIcon(fallbackType);
711
+ }
712
+ return "material-icon-theme:document";
713
+ }
714
+ function getFallbackIcon(type) {
715
+ switch (type) {
716
+ case FileType.IMAGE:
717
+ return "material-icon-theme:image";
718
+ case FileType.VIDEO:
719
+ return "material-icon-theme:video";
720
+ case FileType.MUSIC:
721
+ return "material-icon-theme:audio";
722
+ case FileType.CODE:
723
+ return "material-icon-theme:javascript";
724
+ case FileType.TEXT:
725
+ return "material-icon-theme:document";
726
+ case FileType.DOCUMENT:
727
+ return "material-icon-theme:word";
728
+ case FileType.PDF:
729
+ return "material-icon-theme:pdf";
730
+ case FileType.ARCHIVE:
731
+ return "material-icon-theme:zip";
732
+ case FileType.APPLICATION:
733
+ return "material-icon-theme:exe";
734
+ default:
735
+ return "material-icon-theme:document";
736
+ }
737
+ }
738
+
739
+ // src/utils/folderTypeIcon.ts
740
+ var KNOWN_TYPES2 = /* @__PURE__ */ new Set([
741
+ "admin",
742
+ "android",
743
+ "angular",
744
+ "animation",
745
+ "ansible",
746
+ "api",
747
+ "apollo",
748
+ "app",
749
+ "archive",
750
+ "astro",
751
+ "atom",
752
+ "attachment",
753
+ "audio",
754
+ "aurelia",
755
+ "aws",
756
+ "azure-pipelines",
757
+ "backup",
758
+ "base",
759
+ "batch",
760
+ "benchmark",
761
+ "bibliography",
762
+ "bicep",
763
+ "blender",
764
+ "bloc",
765
+ "bower",
766
+ "buildkite",
767
+ "cart",
768
+ "changesets",
769
+ "ci",
770
+ "circleci",
771
+ "class",
772
+ "claude",
773
+ "client",
774
+ "cline",
775
+ "cloud-functions",
776
+ "cloudflare",
777
+ "cluster",
778
+ "cobol",
779
+ "command",
780
+ "components",
781
+ "config",
782
+ "connection",
783
+ "console",
784
+ "constant",
785
+ "container",
786
+ "content",
787
+ "context",
788
+ "contract",
789
+ "controller",
790
+ "core",
791
+ "coverage",
792
+ "css",
793
+ "cue",
794
+ "cursor",
795
+ "custom",
796
+ "cypress",
797
+ "dal",
798
+ "dart",
799
+ "database",
800
+ "debug",
801
+ "decorators",
802
+ "delta",
803
+ "desktop",
804
+ "directive",
805
+ "dist",
806
+ "docker",
807
+ "docs",
808
+ "download",
809
+ "drizzle",
810
+ "dump",
811
+ "element",
812
+ "enum",
813
+ "environment",
814
+ "error",
815
+ "eslint",
816
+ "event",
817
+ "examples",
818
+ "expo",
819
+ "export",
820
+ "fastlane",
821
+ "favicon",
822
+ "features",
823
+ "filter",
824
+ "firebase",
825
+ "firestore",
826
+ "flow",
827
+ "flutter",
828
+ "font",
829
+ "forgejo",
830
+ "functions",
831
+ "gamemaker",
832
+ "generator",
833
+ "gh-workflows",
834
+ "git",
835
+ "gitea",
836
+ "github",
837
+ "gitlab",
838
+ "global",
839
+ "godot",
840
+ "gradle",
841
+ "graphql",
842
+ "guard",
843
+ "gulp",
844
+ "helm",
845
+ "helper",
846
+ "home",
847
+ "hook",
848
+ "husky",
849
+ "i18n",
850
+ "images",
851
+ "import",
852
+ "include",
853
+ "input",
854
+ "intellij",
855
+ "interceptor",
856
+ "interface",
857
+ "ios",
858
+ "java",
859
+ "javascript",
860
+ "jinja",
861
+ "job",
862
+ "json",
863
+ "jupyter",
864
+ "keys",
865
+ "kubernetes",
866
+ "kusto",
867
+ "layout",
868
+ "lefthook",
869
+ "less",
870
+ "lib",
871
+ "license",
872
+ "link",
873
+ "linux",
874
+ "liquibase",
875
+ "log",
876
+ "lottie",
877
+ "lua",
878
+ "luau",
879
+ "macos",
880
+ "mail",
881
+ "mappings",
882
+ "markdown",
883
+ "mercurial",
884
+ "messages",
885
+ "meta",
886
+ "metro",
887
+ "middleware",
888
+ "migrations",
889
+ "mjml",
890
+ "mobile",
891
+ "mock",
892
+ "mojo",
893
+ "molecule",
894
+ "moon",
895
+ "netlify",
896
+ "next",
897
+ "ngrx-store",
898
+ "node",
899
+ "nuxt",
900
+ "obsidian",
901
+ "organism",
902
+ "other",
903
+ "packages",
904
+ "pdf",
905
+ "pdm",
906
+ "php",
907
+ "phpmailer",
908
+ "pipe",
909
+ "plastic",
910
+ "plugin",
911
+ "policy",
912
+ "powershell",
913
+ "prisma",
914
+ "private",
915
+ "project",
916
+ "prompts",
917
+ "proto",
918
+ "public",
919
+ "python",
920
+ "pytorch",
921
+ "quasar",
922
+ "queue",
923
+ "react-components",
924
+ "redux-reducer",
925
+ "repository",
926
+ "resolver",
927
+ "resource",
928
+ "review",
929
+ "robot",
930
+ "routes",
931
+ "rules",
932
+ "rust",
933
+ "salt",
934
+ "sandbox",
935
+ "sass",
936
+ "scala",
937
+ "scons",
938
+ "scripts",
939
+ "secure",
940
+ "seeders",
941
+ "server",
942
+ "serverless",
943
+ "shader",
944
+ "shared",
945
+ "simulations",
946
+ "snapcraft",
947
+ "snippet",
948
+ "src",
949
+ "src-tauri",
950
+ "stack",
951
+ "stencil",
952
+ "store",
953
+ "storybook",
954
+ "stylus",
955
+ "sublime",
956
+ "supabase",
957
+ "svelte",
958
+ "svg",
959
+ "syntax",
960
+ "target",
961
+ "taskfile",
962
+ "tasks",
963
+ "television",
964
+ "temp",
965
+ "template",
966
+ "terraform",
967
+ "test",
968
+ "theme",
969
+ "toc",
970
+ "tools",
971
+ "trash",
972
+ "trigger",
973
+ "turborepo",
974
+ "typescript",
975
+ "ui",
976
+ "unity",
977
+ "update",
978
+ "upload",
979
+ "utils",
980
+ "vercel",
981
+ "verdaccio",
982
+ "video",
983
+ "views",
984
+ "vm",
985
+ "vscode",
986
+ "vue",
987
+ "vue-directives",
988
+ "vuepress",
989
+ "vuex-store",
990
+ "wakatime",
991
+ "webpack",
992
+ "windows",
993
+ "wordpress",
994
+ "yarn",
995
+ "zeabur"
996
+ ]);
997
+ var ALIASES = {
998
+ // 复数/变体
999
+ "tests": "test",
1000
+ "__tests__": "test",
1001
+ "__test__": "test",
1002
+ "spec": "test",
1003
+ "specs": "test",
1004
+ "script": "scripts",
1005
+ "configs": "config",
1006
+ "configuration": "config",
1007
+ "configurations": "config",
1008
+ "route": "routes",
1009
+ "routing": "routes",
1010
+ "stories": "storybook",
1011
+ "story": "storybook",
1012
+ "templates": "template",
1013
+ "themes": "theme",
1014
+ "videos": "video",
1015
+ "imgs": "images",
1016
+ "img": "images",
1017
+ "image": "images",
1018
+ "fonts": "font",
1019
+ "styles": "css",
1020
+ "style": "css",
1021
+ "styling": "css",
1022
+ "stylesheets": "css",
1023
+ "view": "views",
1024
+ "pages": "views",
1025
+ "page": "views",
1026
+ "layouts": "layout",
1027
+ "models": "database",
1028
+ "model": "database",
1029
+ "modules": "lib",
1030
+ "module": "lib",
1031
+ "mods": "lib",
1032
+ "plugins": "plugin",
1033
+ "addons": "plugin",
1034
+ "extensions": "plugin",
1035
+ "middlewares": "middleware",
1036
+ "helpers": "helper",
1037
+ "utilities": "utils",
1038
+ "util": "utils",
1039
+ "tool": "tools",
1040
+ "tooling": "tools",
1041
+ "resources": "resource",
1042
+ "res": "resource",
1043
+ "assets": "resource",
1044
+ "asset": "resource",
1045
+ "hooks": "hook",
1046
+ "composables": "hook",
1047
+ "mocks": "mock",
1048
+ "__mocks__": "mock",
1049
+ "fixtures": "mock",
1050
+ "__fixtures__": "mock",
1051
+ "logs": "log",
1052
+ "uploads": "upload",
1053
+ "function": "functions",
1054
+ "func": "functions",
1055
+ "fns": "functions",
1056
+ "services": "server",
1057
+ "service": "server",
1058
+ "component": "components",
1059
+ "comp": "components",
1060
+ "comps": "components",
1061
+ "controllers": "controller",
1062
+ // 常见变体
1063
+ "source": "src",
1064
+ "sources": "src",
1065
+ "distribution": "dist",
1066
+ "build": "dist",
1067
+ "builds": "dist",
1068
+ "out": "dist",
1069
+ "output": "dist",
1070
+ "documentation": "docs",
1071
+ "doc": "docs",
1072
+ "document": "docs",
1073
+ "documents": "docs",
1074
+ "static": "public",
1075
+ "statics": "public",
1076
+ "publics": "public",
1077
+ "libs": "lib",
1078
+ "library": "lib",
1079
+ "vendor": "lib",
1080
+ "vendors": "lib",
1081
+ "bin": "scripts",
1082
+ "binaries": "scripts",
1083
+ "tmp": "temp",
1084
+ "temporary": "temp",
1085
+ "cache": "temp",
1086
+ "caches": "temp",
1087
+ ".cache": "temp",
1088
+ ".turbo": "temp",
1089
+ "types": "typescript",
1090
+ "@types": "typescript",
1091
+ "typings": "typescript",
1092
+ "dts": "typescript",
1093
+ "locales": "i18n",
1094
+ "locale": "i18n",
1095
+ "lang": "i18n",
1096
+ "languages": "i18n",
1097
+ "translations": "i18n",
1098
+ "db": "database",
1099
+ "databases": "database",
1100
+ "sql": "database",
1101
+ "queries": "database",
1102
+ "migration": "migrations",
1103
+ "seeds": "seeders",
1104
+ "seed": "seeders",
1105
+ // 技术栈
1106
+ ".vscode": "vscode",
1107
+ ".github": "github",
1108
+ ".gitlab": "gitlab",
1109
+ ".circleci": "circleci",
1110
+ ".husky": "husky",
1111
+ ".docker": "docker",
1112
+ "node_modules": "node",
1113
+ "scss": "sass",
1114
+ "renderer": "client",
1115
+ "frontend": "client",
1116
+ "web": "client",
1117
+ "webapp": "client",
1118
+ "website": "client",
1119
+ "backend": "server",
1120
+ "main": "server",
1121
+ "preload": "scripts",
1122
+ ".idea": "intellij",
1123
+ ".git": "git",
1124
+ "k8s": "kubernetes",
1125
+ "kube": "kubernetes",
1126
+ "mongo": "database",
1127
+ "mongodb": "database",
1128
+ "mysql": "database",
1129
+ "postgres": "database",
1130
+ "api": "api",
1131
+ "apis": "api",
1132
+ "interfaces": "interface",
1133
+ "notebook": "jupyter",
1134
+ "notebooks": "jupyter",
1135
+ "ipynb": "jupyter",
1136
+ "notification": "messages",
1137
+ "notifications": "messages",
1138
+ "env": "environment",
1139
+ "envs": "environment",
1140
+ "redux": "redux-reducer",
1141
+ "store": "store",
1142
+ "stores": "store",
1143
+ "electron": "desktop",
1144
+ "tauri": "src-tauri"
1145
+ };
1146
+ function getFolderTypeIcon(folderName) {
1147
+ const name = (folderName || "").trim();
1148
+ if (!name) return void 0;
1149
+ const lower = name.replace(/[\\/]+$/, "").toLowerCase();
1150
+ const standardName = ALIASES[lower] ?? lower;
1151
+ if (KNOWN_TYPES2.has(standardName)) {
1152
+ return `material-icon-theme:folder-${standardName}`;
1153
+ }
1154
+ return void 0;
1155
+ }
1156
+
1157
+ // src/components/FileIcon.tsx
1158
+ import { jsx } from "react/jsx-runtime";
1159
+ function FileIcon({ type, name, className = "", size = 24 }) {
1160
+ const iconName = useMemo(() => {
1161
+ if (type === FileType.FOLDER) {
1162
+ const folderIcon = name ? getFolderTypeIcon(name) : void 0;
1163
+ return folderIcon ?? "flat-color-icons:folder";
1164
+ }
1165
+ if (name) {
1166
+ return getFileTypeIcon(name, type);
1167
+ }
1168
+ switch (type) {
1169
+ case FileType.IMAGE:
1170
+ return "material-icon-theme:image";
1171
+ case FileType.TEXT:
1172
+ return "material-icon-theme:document";
1173
+ case FileType.CODE:
1174
+ return "material-icon-theme:javascript";
1175
+ case FileType.MUSIC:
1176
+ return "material-icon-theme:audio";
1177
+ case FileType.VIDEO:
1178
+ return "material-icon-theme:video";
1179
+ case FileType.PDF:
1180
+ return "material-icon-theme:pdf";
1181
+ case FileType.DOCUMENT:
1182
+ return "material-icon-theme:word";
1183
+ case FileType.APPLICATION:
1184
+ return "material-icon-theme:exe";
1185
+ case FileType.ARCHIVE:
1186
+ return "material-icon-theme:zip";
1187
+ default:
1188
+ return "material-icon-theme:document";
1189
+ }
1190
+ }, [type, name]);
1191
+ const iconClass = useMemo(() => {
1192
+ const base = "file-icon";
1193
+ switch (type) {
1194
+ case FileType.FOLDER:
1195
+ return `${base} file-icon--folder`;
1196
+ case FileType.IMAGE:
1197
+ return `${base} file-icon--image`;
1198
+ case FileType.TEXT:
1199
+ return `${base} file-icon--text`;
1200
+ case FileType.CODE:
1201
+ return `${base} file-icon--code`;
1202
+ case FileType.MUSIC:
1203
+ return `${base} file-icon--music`;
1204
+ case FileType.VIDEO:
1205
+ return `${base} file-icon--video`;
1206
+ case FileType.PDF:
1207
+ case FileType.DOCUMENT:
1208
+ return `${base} file-icon--pdf`;
1209
+ case FileType.APPLICATION:
1210
+ return `${base} file-icon--application`;
1211
+ case FileType.ARCHIVE:
1212
+ return `${base} file-icon--archive`;
1213
+ default:
1214
+ return `${base} file-icon--default`;
1215
+ }
1216
+ }, [type]);
1217
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(Icon, { icon: iconName, width: size, height: size, className: iconClass }) });
1218
+ }
1219
+
1220
+ // src/components/FileGrid.tsx
1221
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
1222
+ function splitFileName(name, isFolder) {
1223
+ if (isFolder) {
1224
+ return { baseName: name, ext: "" };
1225
+ }
1226
+ const lastDot = name.lastIndexOf(".");
1227
+ if (lastDot <= 0) {
1228
+ return { baseName: name, ext: "" };
1229
+ }
1230
+ return {
1231
+ baseName: name.substring(0, lastDot),
1232
+ ext: name.substring(lastDot)
1233
+ // 包含点号
1234
+ };
1235
+ }
1236
+ function FileName({
1237
+ name,
1238
+ isFolder,
1239
+ isSelected,
1240
+ onClick
1241
+ }) {
1242
+ const { baseName, ext } = useMemo2(() => splitFileName(name, isFolder), [name, isFolder]);
1243
+ return /* @__PURE__ */ jsx2(
1244
+ "span",
1245
+ {
1246
+ onClick,
1247
+ className: `file-grid-item-name ${isSelected ? "file-grid-item-name--selected" : ""}`,
1248
+ title: name,
1249
+ children: ext ? /* @__PURE__ */ jsxs(Fragment, { children: [
1250
+ /* @__PURE__ */ jsx2("span", { className: "file-grid-item-name-base", children: baseName }),
1251
+ /* @__PURE__ */ jsx2("span", { className: "file-grid-item-name-ext", children: ext })
1252
+ ] }) : name
1253
+ }
1254
+ );
1255
+ }
1256
+ function FileGrid({
1257
+ items,
1258
+ selectedIds,
1259
+ editingId,
1260
+ dragOverId,
1261
+ getAppIconUrl,
1262
+ onSelect,
1263
+ onOpen,
1264
+ onContextMenu,
1265
+ onContextMenuEmpty,
1266
+ onNameClick,
1267
+ onRename,
1268
+ onRenameCancel,
1269
+ onDragStart,
1270
+ onDragOver,
1271
+ onDragLeave,
1272
+ onDrop,
1273
+ onThumbnailError
1274
+ }) {
1275
+ const renameInputRef = useRef(null);
1276
+ const handleEmptyContextMenu = (e) => {
1277
+ const target = e.target;
1278
+ if (!target.closest(".file-grid-item")) {
1279
+ onContextMenuEmpty?.(e);
1280
+ }
1281
+ };
1282
+ const hasThumbnail = (item) => {
1283
+ if (item.type === FileType.APPLICATION && getAppIconUrl?.(item)) {
1284
+ return true;
1285
+ }
1286
+ if (item.type === FileType.IMAGE) {
1287
+ return !!item.thumbnailUrl;
1288
+ }
1289
+ if (item.type === FileType.VIDEO) {
1290
+ return !!item.thumbnailUrl;
1291
+ }
1292
+ return false;
1293
+ };
1294
+ const handleVideoHover = (e, isHover) => {
1295
+ const video = e.currentTarget;
1296
+ if (isHover) {
1297
+ video.play().catch(() => {
1298
+ });
1299
+ } else {
1300
+ video.pause();
1301
+ video.currentTime = 0;
1302
+ }
1303
+ };
1304
+ const handleRename = (item, e) => {
1305
+ const input = e.target;
1306
+ const newName = input.value.trim();
1307
+ if (newName && newName !== item.name) {
1308
+ onRename?.(item, newName);
1309
+ } else {
1310
+ onRenameCancel?.(item);
1311
+ }
1312
+ };
1313
+ const handleEnterKey = (e) => {
1314
+ e.currentTarget.blur();
1315
+ };
1316
+ const handleEscapeKey = (e) => {
1317
+ const input = e.currentTarget;
1318
+ const item = items.find((i) => i.id === editingId);
1319
+ if (item) {
1320
+ input.value = item.name;
1321
+ }
1322
+ input.blur();
1323
+ };
1324
+ return /* @__PURE__ */ jsx2("div", { className: "file-grid", onContextMenu: (e) => {
1325
+ e.preventDefault();
1326
+ handleEmptyContextMenu(e);
1327
+ }, children: items.map((item) => {
1328
+ const isSelected = selectedIds.has(item.id);
1329
+ const isEditing = editingId === item.id;
1330
+ const isDragOver = dragOverId === item.id;
1331
+ const hasThumb = hasThumbnail(item);
1332
+ const appIconUrl = item.type === FileType.APPLICATION ? getAppIconUrl?.(item) : void 0;
1333
+ let itemClassName = "file-grid-item";
1334
+ if (isSelected && !isEditing) {
1335
+ itemClassName += " file-grid-item--selected";
1336
+ } else if (isDragOver) {
1337
+ itemClassName += " file-grid-item--drag-over";
1338
+ } else {
1339
+ itemClassName += " file-grid-item--normal";
1340
+ }
1341
+ return /* @__PURE__ */ jsxs(
1342
+ "div",
1343
+ {
1344
+ draggable: !isEditing,
1345
+ onDragStart: (e) => onDragStart?.(e, item),
1346
+ onDragOver: (e) => {
1347
+ e.preventDefault();
1348
+ onDragOver?.(e, item);
1349
+ },
1350
+ onDragLeave: (e) => onDragLeave?.(e),
1351
+ onDrop: (e) => {
1352
+ e.preventDefault();
1353
+ onDrop?.(e, item);
1354
+ },
1355
+ onClick: (e) => {
1356
+ e.stopPropagation();
1357
+ onSelect?.(item, e);
1358
+ },
1359
+ onDoubleClick: (e) => {
1360
+ e.stopPropagation();
1361
+ onOpen?.(item);
1362
+ },
1363
+ onContextMenu: (e) => {
1364
+ e.preventDefault();
1365
+ e.stopPropagation();
1366
+ onContextMenu?.(item, e);
1367
+ },
1368
+ className: itemClassName,
1369
+ children: [
1370
+ /* @__PURE__ */ jsxs("div", { className: "file-grid-item-icon", children: [
1371
+ appIconUrl && /* @__PURE__ */ jsx2(
1372
+ "img",
1373
+ {
1374
+ src: appIconUrl,
1375
+ alt: item.name,
1376
+ className: `file-grid-item-thumbnail file-grid-item-thumbnail--application ${isSelected && !isEditing ? "file-grid-item-thumbnail--selected" : ""}`
1377
+ }
1378
+ ),
1379
+ !appIconUrl && item.type === FileType.VIDEO && item.thumbnailUrl && hasThumb && /* @__PURE__ */ jsxs("div", { className: "file-grid-item-thumbnail file-grid-item-thumbnail--video", children: [
1380
+ /* @__PURE__ */ jsx2(
1381
+ "img",
1382
+ {
1383
+ src: item.thumbnailUrl,
1384
+ alt: item.name,
1385
+ className: "file-grid-item-thumbnail",
1386
+ onError: (e) => onThumbnailError?.(item, e)
1387
+ }
1388
+ ),
1389
+ /* @__PURE__ */ jsx2("div", { className: "file-grid-item-video-play", children: /* @__PURE__ */ jsx2("div", { className: "file-grid-item-video-play-icon" }) })
1390
+ ] }),
1391
+ !appIconUrl && item.type === FileType.IMAGE && item.thumbnailUrl && hasThumb && /* @__PURE__ */ jsx2(
1392
+ "img",
1393
+ {
1394
+ src: item.thumbnailUrl,
1395
+ alt: item.name,
1396
+ className: "file-grid-item-thumbnail",
1397
+ onError: (e) => onThumbnailError?.(item, e)
1398
+ }
1399
+ ),
1400
+ !appIconUrl && !hasThumb && /* @__PURE__ */ jsx2(FileIcon, { type: item.type, name: item.name, size: 48 })
1401
+ ] }),
1402
+ /* @__PURE__ */ jsx2("div", { className: "file-grid-item-name-wrapper", children: isEditing ? /* @__PURE__ */ jsx2(
1403
+ "input",
1404
+ {
1405
+ ref: renameInputRef,
1406
+ type: "text",
1407
+ className: "file-grid-item-rename-input",
1408
+ defaultValue: item.name,
1409
+ onBlur: (e) => handleRename(item, e),
1410
+ onKeyDown: (e) => {
1411
+ if (e.key === "Enter") {
1412
+ handleEnterKey(e);
1413
+ } else if (e.key === "Escape") {
1414
+ handleEscapeKey(e);
1415
+ }
1416
+ },
1417
+ autoFocus: true
1418
+ }
1419
+ ) : /* @__PURE__ */ jsx2(
1420
+ FileName,
1421
+ {
1422
+ name: item.name,
1423
+ isFolder: item.type === FileType.FOLDER,
1424
+ isSelected,
1425
+ onClick: (e) => {
1426
+ e.stopPropagation();
1427
+ onNameClick?.(item, e);
1428
+ }
1429
+ }
1430
+ ) })
1431
+ ]
1432
+ },
1433
+ item.id
1434
+ );
1435
+ }) });
1436
+ }
1437
+
1438
+ // src/components/FileList.tsx
1439
+ import { useRef as useRef2 } from "react";
1440
+
1441
+ // src/components/SortIndicator.tsx
1442
+ import { Icon as Icon2 } from "@iconify/react";
1443
+ import { jsx as jsx3 } from "react/jsx-runtime";
1444
+ function SortIndicator({ direction }) {
1445
+ return /* @__PURE__ */ jsx3("span", { className: "sort-indicator", children: /* @__PURE__ */ jsx3(
1446
+ Icon2,
1447
+ {
1448
+ icon: direction === "asc" ? "lucide:chevron-up" : "lucide:chevron-down",
1449
+ width: 12,
1450
+ height: 12
1451
+ }
1452
+ ) });
1453
+ }
1454
+
1455
+ // src/components/FileList.tsx
1456
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1457
+ function getTypeLabel(type) {
1458
+ const labels = {
1459
+ [FileType.FOLDER]: "\u6587\u4EF6\u5939",
1460
+ [FileType.FILE]: "\u6587\u4EF6",
1461
+ [FileType.IMAGE]: "\u56FE\u7247",
1462
+ [FileType.VIDEO]: "\u89C6\u9891",
1463
+ [FileType.MUSIC]: "\u97F3\u9891",
1464
+ [FileType.DOCUMENT]: "\u6587\u6863",
1465
+ [FileType.CODE]: "\u4EE3\u7801",
1466
+ [FileType.TEXT]: "\u6587\u672C",
1467
+ [FileType.PDF]: "PDF",
1468
+ [FileType.ARCHIVE]: "\u538B\u7F29\u5305",
1469
+ [FileType.APPLICATION]: "\u5E94\u7528\u7A0B\u5E8F",
1470
+ [FileType.UNKNOWN]: "\u672A\u77E5"
1471
+ };
1472
+ return labels[type] || type;
1473
+ }
1474
+ function FileList({
1475
+ items,
1476
+ selectedIds,
1477
+ sortConfig,
1478
+ editingId,
1479
+ dragOverId,
1480
+ onSelect,
1481
+ onOpen,
1482
+ onContextMenu,
1483
+ onContextMenuEmpty,
1484
+ onNameClick,
1485
+ onRename,
1486
+ onRenameCancel,
1487
+ onSort,
1488
+ onDragStart,
1489
+ onDragOver,
1490
+ onDragLeave,
1491
+ onDrop
1492
+ }) {
1493
+ const renameInputRef = useRef2(null);
1494
+ const handleEmptyContextMenu = (e) => {
1495
+ const target = e.target;
1496
+ if (!target.closest("tr")) {
1497
+ onContextMenuEmpty?.(e);
1498
+ }
1499
+ };
1500
+ const handleRename = (item, e) => {
1501
+ const input = e.target;
1502
+ const newName = input.value.trim();
1503
+ if (newName && newName !== item.name) {
1504
+ onRename?.(item, newName);
1505
+ } else {
1506
+ onRenameCancel?.(item);
1507
+ }
1508
+ };
1509
+ const handleEnterKey = (e) => {
1510
+ e.currentTarget.blur();
1511
+ };
1512
+ const handleEscapeKey = (e) => {
1513
+ const input = e.currentTarget;
1514
+ const item = items.find((i) => i.id === editingId);
1515
+ if (item) {
1516
+ input.value = item.name;
1517
+ }
1518
+ input.blur();
1519
+ };
1520
+ return /* @__PURE__ */ jsx4("div", { className: "file-list", onContextMenu: (e) => {
1521
+ e.preventDefault();
1522
+ handleEmptyContextMenu(e);
1523
+ }, children: /* @__PURE__ */ jsxs2("table", { className: "file-list-table", children: [
1524
+ /* @__PURE__ */ jsx4("thead", { className: "file-list-header", children: /* @__PURE__ */ jsxs2("tr", { children: [
1525
+ /* @__PURE__ */ jsxs2(
1526
+ "th",
1527
+ {
1528
+ className: "file-list-header-cell file-list-header-cell--name",
1529
+ onClick: () => onSort?.("name"),
1530
+ children: [
1531
+ "\u540D\u79F0",
1532
+ sortConfig?.field === "name" && /* @__PURE__ */ jsx4(SortIndicator, { direction: sortConfig.direction })
1533
+ ]
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ jsxs2(
1537
+ "th",
1538
+ {
1539
+ className: "file-list-header-cell",
1540
+ onClick: () => onSort?.("dateModified"),
1541
+ children: [
1542
+ "\u4FEE\u6539\u65E5\u671F",
1543
+ sortConfig?.field === "dateModified" && /* @__PURE__ */ jsx4(SortIndicator, { direction: sortConfig.direction })
1544
+ ]
1545
+ }
1546
+ ),
1547
+ /* @__PURE__ */ jsxs2(
1548
+ "th",
1549
+ {
1550
+ className: "file-list-header-cell",
1551
+ onClick: () => onSort?.("size"),
1552
+ children: [
1553
+ "\u5927\u5C0F",
1554
+ sortConfig?.field === "size" && /* @__PURE__ */ jsx4(SortIndicator, { direction: sortConfig.direction })
1555
+ ]
1556
+ }
1557
+ ),
1558
+ /* @__PURE__ */ jsxs2(
1559
+ "th",
1560
+ {
1561
+ className: "file-list-header-cell",
1562
+ onClick: () => onSort?.("type"),
1563
+ children: [
1564
+ "\u7C7B\u578B",
1565
+ sortConfig?.field === "type" && /* @__PURE__ */ jsx4(SortIndicator, { direction: sortConfig.direction })
1566
+ ]
1567
+ }
1568
+ )
1569
+ ] }) }),
1570
+ /* @__PURE__ */ jsx4("tbody", { className: "file-list-body", children: items.map((item, index) => {
1571
+ const isSelected = selectedIds.has(item.id);
1572
+ const isEditing = editingId === item.id;
1573
+ const isDragOver = dragOverId === item.id;
1574
+ let rowClassName = "file-list-row";
1575
+ if (isSelected) {
1576
+ rowClassName += " file-list-row--selected";
1577
+ } else if (isDragOver) {
1578
+ rowClassName += " file-list-row--drag-over";
1579
+ } else if (index % 2 === 0) {
1580
+ rowClassName += " file-list-row--even";
1581
+ } else {
1582
+ rowClassName += " file-list-row--odd";
1583
+ }
1584
+ return /* @__PURE__ */ jsxs2(
1585
+ "tr",
1586
+ {
1587
+ draggable: !isEditing,
1588
+ onDragStart: (e) => onDragStart?.(e, item),
1589
+ onDragOver: (e) => {
1590
+ e.preventDefault();
1591
+ onDragOver?.(e, item);
1592
+ },
1593
+ onDragLeave: (e) => onDragLeave?.(e),
1594
+ onDrop: (e) => {
1595
+ e.preventDefault();
1596
+ onDrop?.(e, item);
1597
+ },
1598
+ onClick: (e) => {
1599
+ e.stopPropagation();
1600
+ onSelect?.(item, e);
1601
+ },
1602
+ onDoubleClick: (e) => {
1603
+ e.stopPropagation();
1604
+ onOpen?.(item);
1605
+ },
1606
+ onContextMenu: (e) => {
1607
+ e.preventDefault();
1608
+ e.stopPropagation();
1609
+ onContextMenu?.(item, e);
1610
+ },
1611
+ className: rowClassName,
1612
+ children: [
1613
+ /* @__PURE__ */ jsxs2("td", { className: `file-list-cell file-list-cell--name ${isSelected ? "file-list-cell--selected" : ""}`, children: [
1614
+ /* @__PURE__ */ jsx4(FileIcon, { type: item.type, name: item.name, size: 16 }),
1615
+ isEditing ? /* @__PURE__ */ jsx4(
1616
+ "input",
1617
+ {
1618
+ ref: renameInputRef,
1619
+ type: "text",
1620
+ className: "file-list-rename-input",
1621
+ defaultValue: item.name,
1622
+ onBlur: (e) => handleRename(item, e),
1623
+ onKeyDown: (e) => {
1624
+ if (e.key === "Enter") {
1625
+ handleEnterKey(e);
1626
+ } else if (e.key === "Escape") {
1627
+ handleEscapeKey(e);
1628
+ }
1629
+ },
1630
+ autoFocus: true
1631
+ }
1632
+ ) : /* @__PURE__ */ jsx4(
1633
+ "span",
1634
+ {
1635
+ onClick: (e) => {
1636
+ e.stopPropagation();
1637
+ onNameClick?.(item, e);
1638
+ },
1639
+ className: `file-list-name ${isSelected ? "file-list-name--selected" : ""}`,
1640
+ children: item.name
1641
+ }
1642
+ )
1643
+ ] }),
1644
+ /* @__PURE__ */ jsx4("td", { className: `file-list-cell ${isSelected ? "file-list-cell--selected" : ""}`, children: item.dateModified || "--" }),
1645
+ /* @__PURE__ */ jsx4("td", { className: `file-list-cell file-list-cell--size ${isSelected ? "file-list-cell--selected" : ""}`, children: item.size || "--" }),
1646
+ /* @__PURE__ */ jsx4("td", { className: `file-list-cell ${isSelected ? "file-list-cell--selected" : ""}`, children: getTypeLabel(item.type) })
1647
+ ]
1648
+ },
1649
+ item.id
1650
+ );
1651
+ }) })
1652
+ ] }) });
1653
+ }
1654
+
1655
+ // src/components/FileListView.tsx
1656
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1657
+ var FileListView = forwardRef(
1658
+ ({
1659
+ items,
1660
+ viewMode = "grid",
1661
+ loading = false,
1662
+ adapter,
1663
+ currentPath,
1664
+ getAppIconUrl,
1665
+ onOpen,
1666
+ onSelectionChange,
1667
+ onContextMenu,
1668
+ onContextMenuEmpty,
1669
+ onRename,
1670
+ onSortChange,
1671
+ onMove
1672
+ }, ref) => {
1673
+ const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
1674
+ const [editingId, setEditingId] = useState(null);
1675
+ const [dragOverId, setDragOverId] = useState(null);
1676
+ const [sortConfig, setSortConfig] = useState({
1677
+ field: "name",
1678
+ direction: "asc"
1679
+ });
1680
+ const selectedItems = items.filter((item) => selectedIds.has(item.id));
1681
+ const handleSelect = useCallback(
1682
+ (item, e) => {
1683
+ if (e.metaKey || e.ctrlKey) {
1684
+ setSelectedIds((prev) => {
1685
+ const newSet = new Set(prev);
1686
+ if (newSet.has(item.id)) {
1687
+ newSet.delete(item.id);
1688
+ } else {
1689
+ newSet.add(item.id);
1690
+ }
1691
+ const newItems = items.filter((i) => newSet.has(i.id));
1692
+ onSelectionChange?.(newSet, newItems);
1693
+ return newSet;
1694
+ });
1695
+ } else if (e.shiftKey && selectedIds.size > 0) {
1696
+ const lastId = Array.from(selectedIds).pop();
1697
+ const lastIndex = items.findIndex((i) => i.id === lastId);
1698
+ const currentIndex = items.findIndex((i) => i.id === item.id);
1699
+ const start = Math.min(lastIndex, currentIndex);
1700
+ const end = Math.max(lastIndex, currentIndex);
1701
+ const newSet = /* @__PURE__ */ new Set();
1702
+ for (let i = start; i <= end; i++) {
1703
+ newSet.add(items[i].id);
1704
+ }
1705
+ setSelectedIds(newSet);
1706
+ const newItems = items.filter((i) => newSet.has(i.id));
1707
+ onSelectionChange?.(newSet, newItems);
1708
+ } else {
1709
+ const newSet = /* @__PURE__ */ new Set([item.id]);
1710
+ setSelectedIds(newSet);
1711
+ onSelectionChange?.(newSet, [item]);
1712
+ }
1713
+ },
1714
+ [items, selectedIds, onSelectionChange]
1715
+ );
1716
+ const handleEmptyClick = useCallback(
1717
+ (e) => {
1718
+ if (e.target === e.currentTarget) {
1719
+ clearSelection();
1720
+ }
1721
+ },
1722
+ []
1723
+ );
1724
+ const clearSelection = useCallback(() => {
1725
+ setSelectedIds(/* @__PURE__ */ new Set());
1726
+ onSelectionChange?.(/* @__PURE__ */ new Set(), []);
1727
+ }, [onSelectionChange]);
1728
+ const handleOpen = useCallback(
1729
+ (item) => {
1730
+ onOpen?.(item);
1731
+ },
1732
+ [onOpen]
1733
+ );
1734
+ const handleContextMenu = useCallback(
1735
+ (item, e) => {
1736
+ if (!selectedIds.has(item.id)) {
1737
+ const newSet = /* @__PURE__ */ new Set([item.id]);
1738
+ setSelectedIds(newSet);
1739
+ onSelectionChange?.(newSet, [item]);
1740
+ }
1741
+ onContextMenu?.(e, item);
1742
+ },
1743
+ [selectedIds, onSelectionChange, onContextMenu]
1744
+ );
1745
+ const handleEmptyContextMenu = useCallback(
1746
+ (e) => {
1747
+ const target = e.target;
1748
+ if (!target.closest(".file-grid-item") && !target.closest(".file-list-row")) {
1749
+ clearSelection();
1750
+ onContextMenuEmpty?.(e);
1751
+ }
1752
+ },
1753
+ [clearSelection, onContextMenuEmpty]
1754
+ );
1755
+ const handleEmptyContextMenuFromChild = useCallback(
1756
+ (e) => {
1757
+ clearSelection();
1758
+ onContextMenuEmpty?.(e);
1759
+ },
1760
+ [clearSelection, onContextMenuEmpty]
1761
+ );
1762
+ const handleNameClick = useCallback(
1763
+ (item, e) => {
1764
+ if (selectedIds.has(item.id) && selectedIds.size === 1) {
1765
+ setTimeout(() => {
1766
+ if (selectedIds.has(item.id)) {
1767
+ setEditingId(item.id);
1768
+ }
1769
+ }, 500);
1770
+ }
1771
+ },
1772
+ [selectedIds]
1773
+ );
1774
+ const handleRename = useCallback(
1775
+ (item, newName) => {
1776
+ if (newName && newName !== item.name) {
1777
+ onRename?.(item, newName);
1778
+ }
1779
+ setEditingId(null);
1780
+ },
1781
+ [onRename]
1782
+ );
1783
+ const handleRenameCancel = useCallback(() => {
1784
+ setEditingId(null);
1785
+ }, []);
1786
+ const handleSort = useCallback(
1787
+ (field) => {
1788
+ setSortConfig((prev) => {
1789
+ const newConfig = prev.field === field ? { ...prev, direction: prev.direction === "asc" ? "desc" : "asc" } : { field, direction: "asc" };
1790
+ onSortChange?.(newConfig);
1791
+ return newConfig;
1792
+ });
1793
+ },
1794
+ [onSortChange]
1795
+ );
1796
+ const handleDragStart = useCallback(
1797
+ (e, item) => {
1798
+ if (!selectedIds.has(item.id)) {
1799
+ const newSet = /* @__PURE__ */ new Set([item.id]);
1800
+ setSelectedIds(newSet);
1801
+ onSelectionChange?.(newSet, [item]);
1802
+ }
1803
+ e.dataTransfer.setData(
1804
+ "text/plain",
1805
+ JSON.stringify([...selectedIds])
1806
+ );
1807
+ },
1808
+ [selectedIds, onSelectionChange]
1809
+ );
1810
+ const handleDragOver = useCallback(
1811
+ (e, item) => {
1812
+ if (item.type === FileType.FOLDER && !selectedIds.has(item.id)) {
1813
+ setDragOverId(item.id);
1814
+ }
1815
+ },
1816
+ [selectedIds]
1817
+ );
1818
+ const handleDragLeave = useCallback(() => {
1819
+ setDragOverId(null);
1820
+ }, []);
1821
+ const handleDrop = useCallback(
1822
+ (e, targetItem) => {
1823
+ setDragOverId(null);
1824
+ if (targetItem.type !== FileType.FOLDER) return;
1825
+ const data = e.dataTransfer.getData("text/plain");
1826
+ if (!data) return;
1827
+ try {
1828
+ const draggedIds = JSON.parse(data);
1829
+ if (draggedIds.includes(targetItem.id)) return;
1830
+ onMove?.(draggedIds, targetItem.id);
1831
+ clearSelection();
1832
+ } catch (error) {
1833
+ console.error("\u62D6\u62FD\u89E3\u6790\u5931\u8D25:", error);
1834
+ }
1835
+ },
1836
+ [onMove, clearSelection]
1837
+ );
1838
+ const startRename = useCallback((id) => {
1839
+ setEditingId(id);
1840
+ }, []);
1841
+ const selectAll = useCallback(() => {
1842
+ const newSet = new Set(items.map((i) => i.id));
1843
+ setSelectedIds(newSet);
1844
+ onSelectionChange?.(newSet, items);
1845
+ }, [items, onSelectionChange]);
1846
+ useImperativeHandle(
1847
+ ref,
1848
+ () => ({
1849
+ clearSelection,
1850
+ startRename,
1851
+ selectAll,
1852
+ selectedIds,
1853
+ selectedItems
1854
+ }),
1855
+ [selectedIds, selectedItems, clearSelection, startRename, selectAll]
1856
+ );
1857
+ return /* @__PURE__ */ jsxs3(
1858
+ "div",
1859
+ {
1860
+ className: "file-list-view",
1861
+ onClick: handleEmptyClick,
1862
+ onContextMenu: (e) => {
1863
+ e.preventDefault();
1864
+ handleEmptyContextMenu(e);
1865
+ },
1866
+ children: [
1867
+ loading && /* @__PURE__ */ jsxs3("div", { className: "file-list-view-loading", children: [
1868
+ /* @__PURE__ */ jsx5("div", { className: "file-list-view-spinner" }),
1869
+ /* @__PURE__ */ jsx5("p", { children: "\u52A0\u8F7D\u4E2D..." })
1870
+ ] }),
1871
+ !loading && items.length === 0 && /* @__PURE__ */ jsxs3("div", { className: "file-list-view-empty", children: [
1872
+ /* @__PURE__ */ jsx5(Icon3, { icon: "lucide:folder-open", width: 64, height: 64, className: "file-list-view-empty-icon" }),
1873
+ /* @__PURE__ */ jsx5("p", { children: "\u6587\u4EF6\u5939\u4E3A\u7A7A" })
1874
+ ] }),
1875
+ !loading && items.length > 0 && viewMode === "grid" && /* @__PURE__ */ jsx5(
1876
+ FileGrid,
1877
+ {
1878
+ items,
1879
+ selectedIds,
1880
+ editingId,
1881
+ dragOverId,
1882
+ getAppIconUrl,
1883
+ onSelect: handleSelect,
1884
+ onOpen: handleOpen,
1885
+ onContextMenu: handleContextMenu,
1886
+ onContextMenuEmpty: handleEmptyContextMenuFromChild,
1887
+ onNameClick: handleNameClick,
1888
+ onRename: handleRename,
1889
+ onRenameCancel: handleRenameCancel,
1890
+ onDragStart: handleDragStart,
1891
+ onDragOver: handleDragOver,
1892
+ onDragLeave: handleDragLeave,
1893
+ onDrop: handleDrop
1894
+ }
1895
+ ),
1896
+ !loading && items.length > 0 && viewMode === "list" && /* @__PURE__ */ jsx5(
1897
+ FileList,
1898
+ {
1899
+ items,
1900
+ selectedIds,
1901
+ editingId,
1902
+ dragOverId,
1903
+ sortConfig,
1904
+ onSelect: handleSelect,
1905
+ onOpen: handleOpen,
1906
+ onContextMenu: handleContextMenu,
1907
+ onContextMenuEmpty: handleEmptyContextMenuFromChild,
1908
+ onNameClick: handleNameClick,
1909
+ onRename: handleRename,
1910
+ onRenameCancel: handleRenameCancel,
1911
+ onSort: handleSort,
1912
+ onDragStart: handleDragStart,
1913
+ onDragOver: handleDragOver,
1914
+ onDragLeave: handleDragLeave,
1915
+ onDrop: handleDrop
1916
+ }
1917
+ )
1918
+ ]
1919
+ }
1920
+ );
1921
+ }
1922
+ );
1923
+ FileListView.displayName = "FileListView";
1924
+
1925
+ // src/components/FileSidebar.tsx
1926
+ import { Icon as Icon4 } from "@iconify/react";
1927
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1928
+ function FileSidebar({ sections, activeId, onNavigate }) {
1929
+ const handleNavigate = (item) => {
1930
+ onNavigate?.(item);
1931
+ };
1932
+ const getIconName = (iconName) => {
1933
+ if (!iconName) return "mdi:folder";
1934
+ if (iconName.includes(":")) return iconName;
1935
+ return `lucide:${iconName.toLowerCase()}`;
1936
+ };
1937
+ return /* @__PURE__ */ jsx6("div", { className: "file-sidebar", children: sections.map((section) => /* @__PURE__ */ jsxs4("div", { className: "file-sidebar-section", children: [
1938
+ /* @__PURE__ */ jsx6("div", { className: "file-sidebar-section-title", children: section.title }),
1939
+ /* @__PURE__ */ jsx6("ul", { className: "file-sidebar-list", children: section.items.map((item) => {
1940
+ const isActive = activeId === item.id;
1941
+ return /* @__PURE__ */ jsxs4(
1942
+ "li",
1943
+ {
1944
+ onClick: () => handleNavigate(item),
1945
+ className: `file-sidebar-item ${isActive ? "file-sidebar-item--active" : ""}`,
1946
+ children: [
1947
+ /* @__PURE__ */ jsx6(
1948
+ Icon4,
1949
+ {
1950
+ icon: getIconName(item.icon),
1951
+ width: 18,
1952
+ height: 18,
1953
+ className: isActive ? "file-sidebar-item-icon--active" : "file-sidebar-item-icon"
1954
+ }
1955
+ ),
1956
+ /* @__PURE__ */ jsx6("span", { children: item.label })
1957
+ ]
1958
+ },
1959
+ item.id
1960
+ );
1961
+ }) })
1962
+ ] }, section.id)) });
1963
+ }
1964
+
1965
+ // src/components/Toolbar.tsx
1966
+ import { Icon as Icon6 } from "@iconify/react";
1967
+
1968
+ // src/components/Breadcrumb.tsx
1969
+ import { Icon as Icon5 } from "@iconify/react";
1970
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1971
+ function Breadcrumb({ items, onNavigate }) {
1972
+ const handleClick = (item, index) => {
1973
+ if (index < items.length - 1) {
1974
+ onNavigate?.(item, index);
1975
+ }
1976
+ };
1977
+ return /* @__PURE__ */ jsx7("div", { className: "file-breadcrumb", children: items.map((item, index) => /* @__PURE__ */ jsxs5("span", { className: "file-breadcrumb-item", children: [
1978
+ /* @__PURE__ */ jsx7(
1979
+ "span",
1980
+ {
1981
+ onClick: () => handleClick(item, index),
1982
+ className: `file-breadcrumb-link ${index === items.length - 1 ? "file-breadcrumb-link--current" : ""}`,
1983
+ children: item.name
1984
+ }
1985
+ ),
1986
+ index < items.length - 1 && /* @__PURE__ */ jsx7(Icon5, { icon: "lucide:chevron-right", width: 14, height: 14, className: "file-breadcrumb-separator" })
1987
+ ] }, item.id)) });
1988
+ }
1989
+
1990
+ // src/components/Toolbar.tsx
1991
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1992
+ function Toolbar({
1993
+ canGoBack = false,
1994
+ canGoForward = false,
1995
+ breadcrumbs = [],
1996
+ viewMode = "grid",
1997
+ searchQuery = "",
1998
+ showSearch = false,
1999
+ showViewToggle = true,
2000
+ draggable = false,
2001
+ onBack,
2002
+ onForward,
2003
+ onBreadcrumbNavigate,
2004
+ onViewModeChange,
2005
+ onSearchQueryChange,
2006
+ children,
2007
+ breadcrumbSlot,
2008
+ actionsSlot
2009
+ }) {
2010
+ return /* @__PURE__ */ jsxs6(
2011
+ "div",
2012
+ {
2013
+ className: `file-toolbar ${draggable ? "file-toolbar--draggable" : ""}`,
2014
+ children: [
2015
+ /* @__PURE__ */ jsxs6("div", { className: "file-toolbar-nav", children: [
2016
+ /* @__PURE__ */ jsx8(
2017
+ "button",
2018
+ {
2019
+ className: "file-toolbar-button",
2020
+ onClick: onBack,
2021
+ disabled: !canGoBack,
2022
+ title: "\u540E\u9000",
2023
+ children: /* @__PURE__ */ jsx8(Icon6, { icon: "lucide:chevron-left", width: 18, height: 18 })
2024
+ }
2025
+ ),
2026
+ /* @__PURE__ */ jsx8(
2027
+ "button",
2028
+ {
2029
+ className: "file-toolbar-button",
2030
+ onClick: onForward,
2031
+ disabled: !canGoForward,
2032
+ title: "\u524D\u8FDB",
2033
+ children: /* @__PURE__ */ jsx8(Icon6, { icon: "lucide:chevron-right", width: 18, height: 18 })
2034
+ }
2035
+ )
2036
+ ] }),
2037
+ /* @__PURE__ */ jsx8("div", { className: "file-toolbar-breadcrumb", children: breadcrumbSlot || breadcrumbs.length > 0 && /* @__PURE__ */ jsx8(
2038
+ Breadcrumb,
2039
+ {
2040
+ items: breadcrumbs,
2041
+ onNavigate: onBreadcrumbNavigate
2042
+ }
2043
+ ) }),
2044
+ children && /* @__PURE__ */ jsx8("div", { className: "file-toolbar-custom", children }),
2045
+ /* @__PURE__ */ jsxs6("div", { className: "file-toolbar-actions", children: [
2046
+ showSearch && /* @__PURE__ */ jsxs6("div", { className: "file-toolbar-search", children: [
2047
+ /* @__PURE__ */ jsx8(Icon6, { icon: "lucide:search", width: 16, height: 16, className: "file-toolbar-search-icon" }),
2048
+ /* @__PURE__ */ jsx8(
2049
+ "input",
2050
+ {
2051
+ type: "text",
2052
+ value: searchQuery,
2053
+ onChange: (e) => onSearchQueryChange?.(e.target.value),
2054
+ placeholder: "\u641C\u7D22",
2055
+ className: "file-toolbar-search-input"
2056
+ }
2057
+ )
2058
+ ] }),
2059
+ showViewToggle && /* @__PURE__ */ jsxs6("div", { className: "file-toolbar-view-toggle", children: [
2060
+ /* @__PURE__ */ jsx8(
2061
+ "button",
2062
+ {
2063
+ onClick: () => onViewModeChange?.("grid"),
2064
+ className: `file-toolbar-button ${viewMode === "grid" ? "file-toolbar-button--active" : ""}`,
2065
+ title: "\u7F51\u683C\u89C6\u56FE",
2066
+ children: /* @__PURE__ */ jsx8(Icon6, { icon: "lucide:layout-grid", width: 18, height: 18 })
2067
+ }
2068
+ ),
2069
+ /* @__PURE__ */ jsx8(
2070
+ "button",
2071
+ {
2072
+ onClick: () => onViewModeChange?.("list"),
2073
+ className: `file-toolbar-button ${viewMode === "list" ? "file-toolbar-button--active" : ""}`,
2074
+ title: "\u5217\u8868\u89C6\u56FE",
2075
+ children: /* @__PURE__ */ jsx8(Icon6, { icon: "lucide:list", width: 18, height: 18 })
2076
+ }
2077
+ )
2078
+ ] }),
2079
+ actionsSlot
2080
+ ] })
2081
+ ]
2082
+ }
2083
+ );
2084
+ }
2085
+
2086
+ // src/components/StatusBar.tsx
2087
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2088
+ function StatusBar({ itemCount = 0, selectedCount = 0, children }) {
2089
+ return /* @__PURE__ */ jsx9("div", { className: "file-status-bar", children: children || /* @__PURE__ */ jsxs7(Fragment2, { children: [
2090
+ /* @__PURE__ */ jsxs7("span", { children: [
2091
+ itemCount,
2092
+ " \u4E2A\u9879\u76EE"
2093
+ ] }),
2094
+ selectedCount > 0 && /* @__PURE__ */ jsxs7("span", { children: [
2095
+ " \u2022 \u5DF2\u9009\u62E9 ",
2096
+ selectedCount,
2097
+ " \u4E2A"
2098
+ ] })
2099
+ ] }) });
2100
+ }
2101
+
2102
+ // src/components/ContextMenu.tsx
2103
+ import { createPortal } from "react-dom";
2104
+ import { useMemo as useMemo3, useEffect, useRef as useRef3, useCallback as useCallback2, useState as useState2 } from "react";
2105
+ import { Icon as Icon7 } from "@iconify/react";
2106
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2107
+ var MARGIN = 8;
2108
+ var MENU_WIDTH = 220;
2109
+ var MENU_ITEM_HEIGHT = 32;
2110
+ var SEPARATOR_HEIGHT = 9;
2111
+ var MENU_PADDING = 8;
2112
+ var SUBMENU_GAP = 0;
2113
+ function estimateMenuHeight(items) {
2114
+ let height = MENU_PADDING;
2115
+ for (const item of items) {
2116
+ height += item.separator ? SEPARATOR_HEIGHT : MENU_ITEM_HEIGHT;
2117
+ }
2118
+ return height;
2119
+ }
2120
+ function calculateMenuPosition(clickX, clickY, menuHeight) {
2121
+ const viewportWidth = window.innerWidth;
2122
+ const viewportHeight = window.innerHeight;
2123
+ let adjustedX = clickX;
2124
+ let adjustedY = clickY;
2125
+ const spaceRight = viewportWidth - clickX;
2126
+ const spaceLeft = clickX;
2127
+ if (spaceRight < MENU_WIDTH + MARGIN) {
2128
+ if (spaceLeft >= MENU_WIDTH + MARGIN) {
2129
+ adjustedX = clickX - MENU_WIDTH;
2130
+ } else {
2131
+ if (spaceRight > spaceLeft) {
2132
+ adjustedX = viewportWidth - MENU_WIDTH - MARGIN;
2133
+ } else {
2134
+ adjustedX = MARGIN;
2135
+ }
2136
+ }
2137
+ }
2138
+ const spaceBottom = viewportHeight - clickY;
2139
+ const spaceTop = clickY;
2140
+ if (spaceBottom < menuHeight + MARGIN) {
2141
+ if (spaceTop >= menuHeight + MARGIN) {
2142
+ adjustedY = clickY - menuHeight;
2143
+ } else {
2144
+ if (spaceBottom > spaceTop) {
2145
+ adjustedY = viewportHeight - menuHeight - MARGIN;
2146
+ } else {
2147
+ adjustedY = MARGIN;
2148
+ }
2149
+ }
2150
+ }
2151
+ adjustedX = Math.max(MARGIN, Math.min(adjustedX, viewportWidth - MENU_WIDTH - MARGIN));
2152
+ adjustedY = Math.max(MARGIN, Math.min(adjustedY, viewportHeight - menuHeight - MARGIN));
2153
+ return { x: adjustedX, y: adjustedY };
2154
+ }
2155
+ function calculateSubmenuPosition(parentRect, submenuHeight) {
2156
+ const viewportWidth = window.innerWidth;
2157
+ const viewportHeight = window.innerHeight;
2158
+ const spaceRight = viewportWidth - parentRect.right;
2159
+ const spaceLeft = parentRect.left;
2160
+ let submenuX;
2161
+ let submenuY;
2162
+ if (spaceRight >= MENU_WIDTH + SUBMENU_GAP + MARGIN) {
2163
+ submenuX = parentRect.right + SUBMENU_GAP;
2164
+ } else if (spaceLeft >= MENU_WIDTH + SUBMENU_GAP + MARGIN) {
2165
+ submenuX = parentRect.left - MENU_WIDTH - SUBMENU_GAP;
2166
+ } else {
2167
+ if (spaceRight > spaceLeft) {
2168
+ submenuX = viewportWidth - MENU_WIDTH - MARGIN;
2169
+ } else {
2170
+ submenuX = MARGIN;
2171
+ }
2172
+ }
2173
+ submenuY = parentRect.top;
2174
+ if (submenuY + submenuHeight > viewportHeight - MARGIN) {
2175
+ submenuY = viewportHeight - submenuHeight - MARGIN;
2176
+ }
2177
+ if (submenuY < MARGIN) {
2178
+ submenuY = MARGIN;
2179
+ }
2180
+ return { x: submenuX, y: submenuY };
2181
+ }
2182
+ function ContextMenu({
2183
+ visible,
2184
+ x,
2185
+ y,
2186
+ options,
2187
+ onClose,
2188
+ onSelect
2189
+ }) {
2190
+ const menuRef = useRef3(null);
2191
+ const [hoveredItemId, setHoveredItemId] = useState2(null);
2192
+ const [submenuPosition, setSubmenuPosition] = useState2(null);
2193
+ const itemRefs = useRef3(/* @__PURE__ */ new Map());
2194
+ const position = useMemo3(() => {
2195
+ const estimatedHeight = estimateMenuHeight(options);
2196
+ return calculateMenuPosition(x, y, estimatedHeight);
2197
+ }, [x, y, options]);
2198
+ const menuStyle = useMemo3(
2199
+ () => ({
2200
+ left: `${position.x}px`,
2201
+ top: `${position.y}px`
2202
+ }),
2203
+ [position]
2204
+ );
2205
+ const hoveredItem = useMemo3(() => {
2206
+ if (!hoveredItemId) return null;
2207
+ return options.find((opt) => opt.id === hoveredItemId && opt.children && opt.children.length > 0);
2208
+ }, [hoveredItemId, options]);
2209
+ useEffect(() => {
2210
+ if (!hoveredItemId || !hoveredItem) {
2211
+ setSubmenuPosition(null);
2212
+ return;
2213
+ }
2214
+ const itemEl = itemRefs.current.get(hoveredItemId);
2215
+ if (!itemEl) {
2216
+ setSubmenuPosition(null);
2217
+ return;
2218
+ }
2219
+ const rect = itemEl.getBoundingClientRect();
2220
+ const submenuHeight = estimateMenuHeight(hoveredItem.children || []);
2221
+ const pos = calculateSubmenuPosition(rect, submenuHeight);
2222
+ setSubmenuPosition(pos);
2223
+ }, [hoveredItemId, hoveredItem]);
2224
+ useEffect(() => {
2225
+ if (!visible) return;
2226
+ const handleClickOutside = (e) => {
2227
+ const target = e.target;
2228
+ const menuContainer = document.querySelector(".context-menu-container");
2229
+ if (menuContainer && menuContainer.contains(target)) {
2230
+ return;
2231
+ }
2232
+ onClose?.();
2233
+ };
2234
+ const handleEscape = (e) => {
2235
+ if (e.key === "Escape") {
2236
+ onClose?.();
2237
+ }
2238
+ };
2239
+ document.addEventListener("mousedown", handleClickOutside);
2240
+ document.addEventListener("keydown", handleEscape);
2241
+ return () => {
2242
+ document.removeEventListener("mousedown", handleClickOutside);
2243
+ document.removeEventListener("keydown", handleEscape);
2244
+ };
2245
+ }, [visible, onClose]);
2246
+ useEffect(() => {
2247
+ if (!visible) {
2248
+ setHoveredItemId(null);
2249
+ setSubmenuPosition(null);
2250
+ }
2251
+ }, [visible]);
2252
+ const renderIcon = (iconName) => {
2253
+ if (!iconName) return null;
2254
+ return /* @__PURE__ */ jsx10(Icon7, { icon: iconName, width: 16, height: 16, className: "context-menu-item-icon" });
2255
+ };
2256
+ const handleOptionClick = useCallback2(
2257
+ (option) => {
2258
+ if (option.disabled) return;
2259
+ if (option.children && option.children.length > 0) {
2260
+ return;
2261
+ }
2262
+ if (option.action) {
2263
+ option.action();
2264
+ }
2265
+ onSelect?.(option);
2266
+ onClose?.();
2267
+ },
2268
+ [onClose, onSelect]
2269
+ );
2270
+ const handleItemMouseEnter = useCallback2((option) => {
2271
+ if (option.children && option.children.length > 0) {
2272
+ setHoveredItemId(option.id);
2273
+ } else {
2274
+ setHoveredItemId(null);
2275
+ }
2276
+ }, []);
2277
+ if (!visible) return null;
2278
+ return createPortal(
2279
+ /* @__PURE__ */ jsxs8("div", { className: "context-menu-container", children: [
2280
+ /* @__PURE__ */ jsx10("div", { ref: menuRef, className: "context-menu", style: menuStyle, children: options.map((option, index) => {
2281
+ if (option.separator) {
2282
+ return /* @__PURE__ */ jsx10("div", { className: "context-menu-separator" }, index);
2283
+ }
2284
+ const hasChildren = option.children && option.children.length > 0;
2285
+ const isHovered = hoveredItemId === option.id;
2286
+ return /* @__PURE__ */ jsxs8(
2287
+ "div",
2288
+ {
2289
+ ref: (el) => {
2290
+ if (el) {
2291
+ itemRefs.current.set(option.id, el);
2292
+ } else {
2293
+ itemRefs.current.delete(option.id);
2294
+ }
2295
+ },
2296
+ className: `context-menu-item ${option.disabled ? "context-menu-item--disabled" : ""} ${option.danger ? "context-menu-item--danger" : ""} ${hasChildren ? "context-menu-item--has-children" : ""} ${isHovered ? "context-menu-item--active" : ""}`,
2297
+ onClick: () => handleOptionClick(option),
2298
+ onMouseEnter: () => handleItemMouseEnter(option),
2299
+ children: [
2300
+ option.icon && renderIcon(option.icon),
2301
+ /* @__PURE__ */ jsx10("span", { className: "context-menu-item-label", children: option.label }),
2302
+ option.checked && /* @__PURE__ */ jsx10(Icon7, { icon: "lucide:check", width: 14, height: 14, className: "context-menu-item-check" }),
2303
+ option.shortcut && !hasChildren && /* @__PURE__ */ jsx10("span", { className: "context-menu-item-shortcut", children: option.shortcut }),
2304
+ hasChildren && /* @__PURE__ */ jsx10(Icon7, { icon: "lucide:chevron-right", width: 14, height: 14, className: "context-menu-item-arrow" })
2305
+ ]
2306
+ },
2307
+ option.id || index
2308
+ );
2309
+ }) }),
2310
+ hoveredItem && submenuPosition && /* @__PURE__ */ jsx10(
2311
+ "div",
2312
+ {
2313
+ className: "context-menu context-menu-submenu",
2314
+ style: {
2315
+ left: `${submenuPosition.x}px`,
2316
+ top: `${submenuPosition.y}px`
2317
+ },
2318
+ onMouseEnter: () => setHoveredItemId(hoveredItem.id),
2319
+ onMouseLeave: () => setHoveredItemId(null),
2320
+ children: hoveredItem.children.map((child, childIndex) => {
2321
+ if (child.separator) {
2322
+ return /* @__PURE__ */ jsx10("div", { className: "context-menu-separator" }, `child-sep-${childIndex}`);
2323
+ }
2324
+ return /* @__PURE__ */ jsxs8(
2325
+ "div",
2326
+ {
2327
+ className: `context-menu-item ${child.disabled ? "context-menu-item--disabled" : ""} ${child.danger ? "context-menu-item--danger" : ""}`,
2328
+ onClick: () => handleOptionClick(child),
2329
+ children: [
2330
+ child.icon && renderIcon(child.icon),
2331
+ /* @__PURE__ */ jsx10("span", { className: "context-menu-item-label", children: child.label }),
2332
+ child.checked && /* @__PURE__ */ jsx10(Icon7, { icon: "lucide:check", width: 14, height: 14, className: "context-menu-item-check" }),
2333
+ child.shortcut && /* @__PURE__ */ jsx10("span", { className: "context-menu-item-shortcut", children: child.shortcut })
2334
+ ]
2335
+ },
2336
+ child.id || childIndex
2337
+ );
2338
+ })
2339
+ }
2340
+ )
2341
+ ] }),
2342
+ document.body
2343
+ );
2344
+ }
2345
+
2346
+ // src/components/Window.tsx
2347
+ import { createPortal as createPortal2 } from "react-dom";
2348
+ import { useMemo as useMemo4, useRef as useRef4, useEffect as useEffect4 } from "react";
2349
+ import { Icon as Icon8 } from "@iconify/react";
2350
+
2351
+ // src/hooks/useWindowDrag.ts
2352
+ import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2 } from "react";
2353
+ function useWindowDrag() {
2354
+ const [position, setPosition] = useState3({ x: 0, y: 0 });
2355
+ const [isDragging, setIsDragging] = useState3(false);
2356
+ const handleMouseMove = useCallback3((e) => {
2357
+ if (!isDragging) return;
2358
+ setPosition((prev) => ({
2359
+ x: prev.x + e.movementX,
2360
+ y: prev.y + e.movementY
2361
+ }));
2362
+ }, [isDragging]);
2363
+ const handleMouseUp = useCallback3(() => {
2364
+ setIsDragging(false);
2365
+ }, []);
2366
+ const startDrag = useCallback3((e) => {
2367
+ const target = e.target;
2368
+ if (target.closest(".draggable-area") && !target.closest("button")) {
2369
+ e.preventDefault();
2370
+ setIsDragging(true);
2371
+ }
2372
+ }, []);
2373
+ useEffect2(() => {
2374
+ if (isDragging) {
2375
+ window.addEventListener("mousemove", handleMouseMove);
2376
+ window.addEventListener("mouseup", handleMouseUp);
2377
+ return () => {
2378
+ window.removeEventListener("mousemove", handleMouseMove);
2379
+ window.removeEventListener("mouseup", handleMouseUp);
2380
+ };
2381
+ }
2382
+ }, [isDragging, handleMouseMove, handleMouseUp]);
2383
+ return {
2384
+ position,
2385
+ isDragging,
2386
+ startDrag
2387
+ };
2388
+ }
2389
+
2390
+ // src/hooks/useWindowResize.ts
2391
+ import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3 } from "react";
2392
+ function useWindowResize(initialWidth, initialHeight, minWidth, minHeight, maxWidth, maxHeight) {
2393
+ const [width, setWidth] = useState4(initialWidth);
2394
+ const [height, setHeight] = useState4(initialHeight);
2395
+ const [isResizing, setIsResizing] = useState4(false);
2396
+ const [resizeDirection, setResizeDirection] = useState4(null);
2397
+ const [startX, setStartX] = useState4(0);
2398
+ const [startY, setStartY] = useState4(0);
2399
+ const [startWidth, setStartWidth] = useState4(0);
2400
+ const [startHeight, setStartHeight] = useState4(0);
2401
+ const handleMouseMove = useCallback4((e) => {
2402
+ if (!isResizing || !resizeDirection) return;
2403
+ const deltaX = e.clientX - startX;
2404
+ const deltaY = e.clientY - startY;
2405
+ let newWidth = startWidth;
2406
+ let newHeight = startHeight;
2407
+ const direction = resizeDirection;
2408
+ if (direction.includes("e")) {
2409
+ newWidth = Math.min(Math.max(startWidth + deltaX, minWidth), maxWidth);
2410
+ } else if (direction.includes("w")) {
2411
+ newWidth = Math.min(Math.max(startWidth - deltaX, minWidth), maxWidth);
2412
+ }
2413
+ if (direction.includes("s")) {
2414
+ newHeight = Math.min(Math.max(startHeight + deltaY, minHeight), maxHeight);
2415
+ } else if (direction.includes("n")) {
2416
+ newHeight = Math.min(Math.max(startHeight - deltaY, minHeight), maxHeight);
2417
+ }
2418
+ setWidth(newWidth);
2419
+ setHeight(newHeight);
2420
+ }, [isResizing, resizeDirection, startX, startY, startWidth, startHeight, minWidth, minHeight, maxWidth, maxHeight]);
2421
+ const handleMouseUp = useCallback4(() => {
2422
+ setIsResizing(false);
2423
+ setResizeDirection(null);
2424
+ }, []);
2425
+ const startResize = useCallback4((e, direction, currentWidth, currentHeight) => {
2426
+ e.preventDefault();
2427
+ e.stopPropagation();
2428
+ setIsResizing(true);
2429
+ setResizeDirection(direction);
2430
+ setStartX(e.clientX);
2431
+ setStartY(e.clientY);
2432
+ setStartWidth(currentWidth);
2433
+ setStartHeight(currentHeight);
2434
+ }, []);
2435
+ const getCursorForDirection = useCallback4((direction) => {
2436
+ const cursors = {
2437
+ n: "n-resize",
2438
+ s: "s-resize",
2439
+ e: "e-resize",
2440
+ w: "w-resize",
2441
+ ne: "ne-resize",
2442
+ nw: "nw-resize",
2443
+ se: "se-resize",
2444
+ sw: "sw-resize"
2445
+ };
2446
+ return cursors[direction];
2447
+ }, []);
2448
+ useEffect3(() => {
2449
+ if (isResizing) {
2450
+ window.addEventListener("mousemove", handleMouseMove);
2451
+ window.addEventListener("mouseup", handleMouseUp);
2452
+ document.body.style.cursor = getCursorForDirection(resizeDirection || "se");
2453
+ document.body.style.userSelect = "none";
2454
+ return () => {
2455
+ window.removeEventListener("mousemove", handleMouseMove);
2456
+ window.removeEventListener("mouseup", handleMouseUp);
2457
+ document.body.style.cursor = "";
2458
+ document.body.style.userSelect = "";
2459
+ };
2460
+ }
2461
+ }, [isResizing, resizeDirection, handleMouseMove, handleMouseUp, getCursorForDirection]);
2462
+ return {
2463
+ width,
2464
+ height,
2465
+ isResizing,
2466
+ startResize
2467
+ };
2468
+ }
2469
+
2470
+ // src/components/Window.tsx
2471
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2472
+ function Window({
2473
+ title,
2474
+ showTitleBar = true,
2475
+ showMinimize = false,
2476
+ showMaximize = false,
2477
+ draggable = true,
2478
+ resizable = true,
2479
+ closeOnBackdrop = true,
2480
+ width = "auto",
2481
+ height = "auto",
2482
+ minWidth = 300,
2483
+ minHeight = 200,
2484
+ maxWidth = "80vw",
2485
+ maxHeight = "80vh",
2486
+ fitContent = false,
2487
+ onClose,
2488
+ onMinimize,
2489
+ onMaximize,
2490
+ children,
2491
+ titleSlot,
2492
+ actionsSlot
2493
+ }) {
2494
+ const windowContainerRef = useRef4(null);
2495
+ const windowDrag = draggable ? useWindowDrag() : null;
2496
+ const isAutoSize = useMemo4(() => {
2497
+ return fitContent || width === "auto" || width === "fit-content" || height === "auto" || height === "fit-content";
2498
+ }, [fitContent, width, height]);
2499
+ const getInitialSize = () => {
2500
+ if (isAutoSize) {
2501
+ return { initialWidth: 0, initialHeight: 0 };
2502
+ }
2503
+ const defaultWidth = 600;
2504
+ const defaultHeight = 500;
2505
+ let initialWidth2 = defaultWidth;
2506
+ let initialHeight2 = defaultHeight;
2507
+ if (width !== "auto" && width !== "fit-content") {
2508
+ initialWidth2 = typeof width === "number" ? width : parseInt(width);
2509
+ }
2510
+ if (height !== "auto" && height !== "fit-content") {
2511
+ initialHeight2 = typeof height === "number" ? height : parseInt(height);
2512
+ }
2513
+ return { initialWidth: initialWidth2, initialHeight: initialHeight2 };
2514
+ };
2515
+ const parseSize = (size, defaultPx) => {
2516
+ if (typeof size === "number") return size;
2517
+ if (typeof size === "string") {
2518
+ if (size.endsWith("px")) return parseInt(size);
2519
+ if (size.endsWith("vw"))
2520
+ return parseInt(size) / 100 * window.innerWidth;
2521
+ if (size.endsWith("vh"))
2522
+ return parseInt(size) / 100 * window.innerHeight;
2523
+ }
2524
+ return defaultPx;
2525
+ };
2526
+ const { initialWidth, initialHeight } = getInitialSize();
2527
+ const minW = parseSize(minWidth, 500);
2528
+ const minH = parseSize(minHeight, 350);
2529
+ const maxW = parseSize(maxWidth, window.innerWidth * 0.8);
2530
+ const maxH = parseSize(maxHeight, window.innerHeight * 0.8);
2531
+ const windowResize = resizable ? useWindowResize(initialWidth, initialHeight, minW, minH, maxW, maxH) : null;
2532
+ const windowStyle = useMemo4(() => {
2533
+ const baseStyle = {
2534
+ left: "50%",
2535
+ top: "50%"
2536
+ };
2537
+ let translateX = "-50%";
2538
+ let translateY = "-50%";
2539
+ if (draggable && windowDrag) {
2540
+ translateX = `calc(-50% + ${windowDrag.position.x}px)`;
2541
+ translateY = `calc(-50% + ${windowDrag.position.y}px)`;
2542
+ }
2543
+ baseStyle.transform = `translate(${translateX}, ${translateY})`;
2544
+ baseStyle.transformOrigin = "center center";
2545
+ if (isAutoSize) {
2546
+ if (resizable && windowResize && windowResize.width > 0) {
2547
+ baseStyle.width = `${windowResize.width}px`;
2548
+ baseStyle.height = `${windowResize.height}px`;
2549
+ }
2550
+ } else {
2551
+ if (resizable && windowResize) {
2552
+ baseStyle.width = `${windowResize.width}px`;
2553
+ baseStyle.height = `${windowResize.height}px`;
2554
+ } else {
2555
+ if (width !== "auto" && width !== "fit-content") {
2556
+ baseStyle.width = typeof width === "number" ? `${width}px` : width;
2557
+ }
2558
+ if (height !== "auto" && height !== "fit-content") {
2559
+ baseStyle.height = typeof height === "number" ? `${height}px` : height;
2560
+ }
2561
+ }
2562
+ }
2563
+ if (minWidth) {
2564
+ baseStyle.minWidth = typeof minWidth === "number" ? `${minWidth}px` : minWidth;
2565
+ }
2566
+ if (minHeight) {
2567
+ baseStyle.minHeight = typeof minHeight === "number" ? `${minHeight}px` : minHeight;
2568
+ }
2569
+ if (maxWidth) {
2570
+ baseStyle.maxWidth = typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth;
2571
+ }
2572
+ if (maxHeight) {
2573
+ baseStyle.maxHeight = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
2574
+ }
2575
+ return baseStyle;
2576
+ }, [
2577
+ draggable,
2578
+ windowDrag,
2579
+ isAutoSize,
2580
+ resizable,
2581
+ windowResize,
2582
+ width,
2583
+ height,
2584
+ minWidth,
2585
+ minHeight,
2586
+ maxWidth,
2587
+ maxHeight
2588
+ ]);
2589
+ const handleBackdropClick = (e) => {
2590
+ if (closeOnBackdrop && e.target === e.currentTarget) {
2591
+ onClose?.();
2592
+ }
2593
+ };
2594
+ const handleDragStart = (e) => {
2595
+ if (draggable && windowDrag) {
2596
+ windowDrag.startDrag(e);
2597
+ }
2598
+ };
2599
+ const handleResizeStart = (e, direction) => {
2600
+ if (!resizable || !windowResize || !windowContainerRef.current) return;
2601
+ const rect = windowContainerRef.current.getBoundingClientRect();
2602
+ const currentWidth = rect.width;
2603
+ const currentHeight = rect.height;
2604
+ windowResize.startResize(e, direction, currentWidth, currentHeight);
2605
+ };
2606
+ useEffect4(() => {
2607
+ const handleEsc = (e) => {
2608
+ if (e.key === "Escape") {
2609
+ e.preventDefault();
2610
+ onClose?.();
2611
+ }
2612
+ };
2613
+ window.addEventListener("keydown", handleEsc);
2614
+ return () => {
2615
+ window.removeEventListener("keydown", handleEsc);
2616
+ };
2617
+ }, [onClose]);
2618
+ return createPortal2(
2619
+ /* @__PURE__ */ jsx11("div", { className: "window-overlay", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs9(
2620
+ "div",
2621
+ {
2622
+ ref: windowContainerRef,
2623
+ className: "window-container",
2624
+ style: windowStyle,
2625
+ onClick: (e) => e.stopPropagation(),
2626
+ children: [
2627
+ showTitleBar && /* @__PURE__ */ jsxs9(
2628
+ "div",
2629
+ {
2630
+ className: "window-title-bar draggable-area",
2631
+ onMouseDown: handleDragStart,
2632
+ children: [
2633
+ /* @__PURE__ */ jsxs9("div", { className: "window-controls", children: [
2634
+ /* @__PURE__ */ jsx11(
2635
+ "button",
2636
+ {
2637
+ onClick: (e) => {
2638
+ e.stopPropagation();
2639
+ onClose?.();
2640
+ },
2641
+ className: "window-control-button window-control-button--close",
2642
+ children: /* @__PURE__ */ jsx11(Icon8, { icon: "lucide:x", width: 7, height: 7, className: "window-control-icon" })
2643
+ }
2644
+ ),
2645
+ showMinimize && /* @__PURE__ */ jsx11(
2646
+ "button",
2647
+ {
2648
+ onClick: (e) => {
2649
+ e.stopPropagation();
2650
+ onMinimize?.();
2651
+ },
2652
+ className: "window-control-button window-control-button--minimize",
2653
+ children: /* @__PURE__ */ jsx11(Icon8, { icon: "lucide:minus", width: 7, height: 7, className: "window-control-icon" })
2654
+ }
2655
+ ),
2656
+ showMaximize && /* @__PURE__ */ jsx11(
2657
+ "button",
2658
+ {
2659
+ onClick: (e) => {
2660
+ e.stopPropagation();
2661
+ onMaximize?.();
2662
+ },
2663
+ className: "window-control-button window-control-button--maximize",
2664
+ children: /* @__PURE__ */ jsx11(Icon8, { icon: "lucide:maximize-2", width: 7, height: 7, className: "window-control-icon" })
2665
+ }
2666
+ )
2667
+ ] }),
2668
+ /* @__PURE__ */ jsx11("div", { className: "window-title-info", children: titleSlot || /* @__PURE__ */ jsx11("span", { className: "window-title-text", children: title }) }),
2669
+ /* @__PURE__ */ jsx11("div", { className: "window-title-actions", children: actionsSlot })
2670
+ ]
2671
+ }
2672
+ ),
2673
+ /* @__PURE__ */ jsx11("div", { className: "window-content", children }),
2674
+ resizable && /* @__PURE__ */ jsxs9(Fragment3, { children: [
2675
+ /* @__PURE__ */ jsx11(
2676
+ "div",
2677
+ {
2678
+ className: "window-resize-handle window-resize-handle--n",
2679
+ onMouseDown: (e) => handleResizeStart(e, "n")
2680
+ }
2681
+ ),
2682
+ /* @__PURE__ */ jsx11(
2683
+ "div",
2684
+ {
2685
+ className: "window-resize-handle window-resize-handle--s",
2686
+ onMouseDown: (e) => handleResizeStart(e, "s")
2687
+ }
2688
+ ),
2689
+ /* @__PURE__ */ jsx11(
2690
+ "div",
2691
+ {
2692
+ className: "window-resize-handle window-resize-handle--e",
2693
+ onMouseDown: (e) => handleResizeStart(e, "e")
2694
+ }
2695
+ ),
2696
+ /* @__PURE__ */ jsx11(
2697
+ "div",
2698
+ {
2699
+ className: "window-resize-handle window-resize-handle--w",
2700
+ onMouseDown: (e) => handleResizeStart(e, "w")
2701
+ }
2702
+ ),
2703
+ /* @__PURE__ */ jsx11(
2704
+ "div",
2705
+ {
2706
+ className: "window-resize-handle window-resize-handle--ne",
2707
+ onMouseDown: (e) => handleResizeStart(e, "ne")
2708
+ }
2709
+ ),
2710
+ /* @__PURE__ */ jsx11(
2711
+ "div",
2712
+ {
2713
+ className: "window-resize-handle window-resize-handle--nw",
2714
+ onMouseDown: (e) => handleResizeStart(e, "nw")
2715
+ }
2716
+ ),
2717
+ /* @__PURE__ */ jsx11(
2718
+ "div",
2719
+ {
2720
+ className: "window-resize-handle window-resize-handle--se",
2721
+ onMouseDown: (e) => handleResizeStart(e, "se")
2722
+ }
2723
+ ),
2724
+ /* @__PURE__ */ jsx11(
2725
+ "div",
2726
+ {
2727
+ className: "window-resize-handle window-resize-handle--sw",
2728
+ onMouseDown: (e) => handleResizeStart(e, "sw")
2729
+ }
2730
+ )
2731
+ ] })
2732
+ ]
2733
+ }
2734
+ ) }),
2735
+ document.body
2736
+ );
2737
+ }
2738
+
2739
+ // src/components/CompressDialog.tsx
2740
+ import { useState as useState6, useMemo as useMemo5, useEffect as useEffect5 } from "react";
2741
+ import { createPortal as createPortal3 } from "react-dom";
2742
+ import { Icon as Icon9 } from "@iconify/react";
2743
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2744
+ var FORMAT_OPTIONS = [
2745
+ { value: "zip", label: "ZIP", ext: ".zip" },
2746
+ { value: "tgz", label: "TAR.GZ (gzip)", ext: ".tar.gz" },
2747
+ { value: "tarbz2", label: "TAR.BZ2 (bzip2)", ext: ".tar.bz2" },
2748
+ { value: "tar", label: "TAR (\u65E0\u538B\u7F29)", ext: ".tar" }
2749
+ ];
2750
+ var LEVEL_OPTIONS = [
2751
+ { value: "fast", label: "\u5FEB\u901F", desc: "\u538B\u7F29\u901F\u5EA6\u5FEB\uFF0C\u6587\u4EF6\u8F83\u5927" },
2752
+ { value: "normal", label: "\u6807\u51C6", desc: "\u5E73\u8861\u901F\u5EA6\u548C\u5927\u5C0F" },
2753
+ { value: "best", label: "\u6700\u4F73", desc: "\u6587\u4EF6\u6700\u5C0F\uFF0C\u901F\u5EA6\u8F83\u6162" }
2754
+ ];
2755
+ function CompressDialog({
2756
+ visible,
2757
+ filePaths,
2758
+ outputDir,
2759
+ onConfirm,
2760
+ onCancel
2761
+ }) {
2762
+ const [format, setFormat] = useState6("zip");
2763
+ const [level, setLevel] = useState6("normal");
2764
+ const [outputName, setOutputName] = useState6("");
2765
+ const [deleteSource, setDeleteSource] = useState6(false);
2766
+ const [password, setPassword] = useState6("");
2767
+ const [showPassword, setShowPassword] = useState6(false);
2768
+ const defaultOutputName = useMemo5(() => {
2769
+ if (filePaths.length === 0) return "archive";
2770
+ if (filePaths.length === 1) {
2771
+ const name = filePaths[0].split("/").pop() || "archive";
2772
+ return name.replace(/\.[^.]+$/, "");
2773
+ }
2774
+ return "\u538B\u7F29\u6587\u4EF6";
2775
+ }, [filePaths]);
2776
+ useEffect5(() => {
2777
+ if (visible) {
2778
+ setOutputName(defaultOutputName);
2779
+ setFormat("zip");
2780
+ setLevel("normal");
2781
+ setDeleteSource(false);
2782
+ setPassword("");
2783
+ }
2784
+ }, [visible, defaultOutputName]);
2785
+ const currentExt = FORMAT_OPTIONS.find((f) => f.value === format)?.ext || ".zip";
2786
+ const fullOutputPath = `${outputDir}/${outputName}${currentExt}`;
2787
+ const handleConfirm = () => {
2788
+ onConfirm({
2789
+ format,
2790
+ level,
2791
+ outputName: outputName + currentExt,
2792
+ deleteSource
2793
+ });
2794
+ };
2795
+ const supportsPassword = false;
2796
+ if (!visible) return null;
2797
+ return createPortal3(
2798
+ /* @__PURE__ */ jsx12("div", { className: "compress-dialog-overlay", onClick: onCancel, children: /* @__PURE__ */ jsxs10("div", { className: "compress-dialog", onClick: (e) => e.stopPropagation(), children: [
2799
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-header", children: [
2800
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-title", children: [
2801
+ /* @__PURE__ */ jsx12(Icon9, { icon: "lucide:archive", width: 20, height: 20 }),
2802
+ /* @__PURE__ */ jsx12("span", { children: "\u538B\u7F29\u6587\u4EF6" })
2803
+ ] }),
2804
+ /* @__PURE__ */ jsx12("button", { className: "compress-dialog-close", onClick: onCancel, children: /* @__PURE__ */ jsx12(Icon9, { icon: "lucide:x", width: 18, height: 18 }) })
2805
+ ] }),
2806
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-content", children: [
2807
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-info", children: [
2808
+ /* @__PURE__ */ jsx12(Icon9, { icon: "lucide:file-archive", width: 16, height: 16 }),
2809
+ /* @__PURE__ */ jsx12("span", { children: filePaths.length === 1 ? filePaths[0].split("/").pop() : `${filePaths.length} \u4E2A\u9879\u76EE` })
2810
+ ] }),
2811
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-field", children: [
2812
+ /* @__PURE__ */ jsx12("label", { children: "\u6587\u4EF6\u540D" }),
2813
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-input-group", children: [
2814
+ /* @__PURE__ */ jsx12(
2815
+ "input",
2816
+ {
2817
+ type: "text",
2818
+ value: outputName,
2819
+ onChange: (e) => setOutputName(e.target.value),
2820
+ placeholder: "\u8F93\u5165\u6587\u4EF6\u540D"
2821
+ }
2822
+ ),
2823
+ /* @__PURE__ */ jsx12("span", { className: "compress-dialog-ext", children: currentExt })
2824
+ ] })
2825
+ ] }),
2826
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-field", children: [
2827
+ /* @__PURE__ */ jsx12("label", { children: "\u538B\u7F29\u683C\u5F0F" }),
2828
+ /* @__PURE__ */ jsx12("select", { value: format, onChange: (e) => setFormat(e.target.value), children: FORMAT_OPTIONS.map((opt) => /* @__PURE__ */ jsx12("option", { value: opt.value, children: opt.label }, opt.value)) })
2829
+ ] }),
2830
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-field", children: [
2831
+ /* @__PURE__ */ jsx12("label", { children: "\u538B\u7F29\u7EA7\u522B" }),
2832
+ /* @__PURE__ */ jsx12("div", { className: "compress-dialog-levels", children: LEVEL_OPTIONS.map((opt) => /* @__PURE__ */ jsxs10("label", { className: "compress-dialog-level", children: [
2833
+ /* @__PURE__ */ jsx12(
2834
+ "input",
2835
+ {
2836
+ type: "radio",
2837
+ name: "level",
2838
+ value: opt.value,
2839
+ checked: level === opt.value,
2840
+ onChange: () => setLevel(opt.value)
2841
+ }
2842
+ ),
2843
+ /* @__PURE__ */ jsx12("span", { className: "compress-dialog-level-label", children: opt.label }),
2844
+ /* @__PURE__ */ jsx12("span", { className: "compress-dialog-level-desc", children: opt.desc })
2845
+ ] }, opt.value)) })
2846
+ ] }),
2847
+ supportsPassword && /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-field", children: [
2848
+ /* @__PURE__ */ jsx12("label", { children: "\u5BC6\u7801\u4FDD\u62A4\uFF08\u53EF\u9009\uFF09" }),
2849
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-input-group", children: [
2850
+ /* @__PURE__ */ jsx12(
2851
+ "input",
2852
+ {
2853
+ type: showPassword ? "text" : "password",
2854
+ value: password,
2855
+ onChange: (e) => setPassword(e.target.value),
2856
+ placeholder: "\u8BBE\u7F6E\u5BC6\u7801"
2857
+ }
2858
+ ),
2859
+ /* @__PURE__ */ jsx12(
2860
+ "button",
2861
+ {
2862
+ type: "button",
2863
+ className: "compress-dialog-toggle-password",
2864
+ onClick: () => setShowPassword(!showPassword),
2865
+ children: showPassword ? "\u9690\u85CF" : "\u663E\u793A"
2866
+ }
2867
+ )
2868
+ ] })
2869
+ ] }),
2870
+ /* @__PURE__ */ jsx12("div", { className: "compress-dialog-field compress-dialog-checkbox", children: /* @__PURE__ */ jsxs10("label", { children: [
2871
+ /* @__PURE__ */ jsx12(
2872
+ "input",
2873
+ {
2874
+ type: "checkbox",
2875
+ checked: deleteSource,
2876
+ onChange: (e) => setDeleteSource(e.target.checked)
2877
+ }
2878
+ ),
2879
+ /* @__PURE__ */ jsx12("span", { children: "\u538B\u7F29\u540E\u5220\u9664\u6E90\u6587\u4EF6" })
2880
+ ] }) }),
2881
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-preview", children: [
2882
+ /* @__PURE__ */ jsx12("span", { className: "compress-dialog-preview-label", children: "\u8F93\u51FA\u4F4D\u7F6E:" }),
2883
+ /* @__PURE__ */ jsx12("span", { className: "compress-dialog-preview-path", children: fullOutputPath })
2884
+ ] })
2885
+ ] }),
2886
+ /* @__PURE__ */ jsxs10("div", { className: "compress-dialog-footer", children: [
2887
+ /* @__PURE__ */ jsx12("button", { className: "compress-dialog-btn compress-dialog-btn-cancel", onClick: onCancel, children: "\u53D6\u6D88" }),
2888
+ /* @__PURE__ */ jsx12(
2889
+ "button",
2890
+ {
2891
+ className: "compress-dialog-btn compress-dialog-btn-confirm",
2892
+ onClick: handleConfirm,
2893
+ disabled: !outputName.trim(),
2894
+ children: "\u538B\u7F29"
2895
+ }
2896
+ )
2897
+ ] })
2898
+ ] }) }),
2899
+ document.body
2900
+ );
2901
+ }
2902
+
2903
+ // src/components/ProgressDialog.tsx
2904
+ import { createPortal as createPortal4 } from "react-dom";
2905
+ import { Icon as Icon10 } from "@iconify/react";
2906
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2907
+ function ProgressDialog({
2908
+ visible,
2909
+ progress,
2910
+ onCancel,
2911
+ onClose,
2912
+ onOpenFolder
2913
+ }) {
2914
+ const { type, status, percent, currentFile, processedCount, totalCount, error, outputPath } = progress;
2915
+ const title = type === "compress" ? "\u538B\u7F29\u6587\u4EF6" : "\u89E3\u538B\u6587\u4EF6";
2916
+ const isCompleted = status === "success" || status === "error";
2917
+ const StatusIcon = () => {
2918
+ switch (status) {
2919
+ case "processing":
2920
+ return /* @__PURE__ */ jsx13(Icon10, { icon: "lucide:loader-2", width: 24, height: 24, className: "progress-dialog-icon-spin" });
2921
+ case "success":
2922
+ return /* @__PURE__ */ jsx13(Icon10, { icon: "lucide:check-circle", width: 24, height: 24, className: "progress-dialog-icon-success" });
2923
+ case "error":
2924
+ return /* @__PURE__ */ jsx13(Icon10, { icon: "lucide:x-circle", width: 24, height: 24, className: "progress-dialog-icon-error" });
2925
+ default:
2926
+ return /* @__PURE__ */ jsx13(Icon10, { icon: "lucide:archive", width: 24, height: 24 });
2927
+ }
2928
+ };
2929
+ const getStatusText = () => {
2930
+ switch (status) {
2931
+ case "pending":
2932
+ return "\u51C6\u5907\u4E2D...";
2933
+ case "processing":
2934
+ return type === "compress" ? "\u6B63\u5728\u538B\u7F29..." : "\u6B63\u5728\u89E3\u538B...";
2935
+ case "success":
2936
+ return type === "compress" ? "\u538B\u7F29\u5B8C\u6210" : "\u89E3\u538B\u5B8C\u6210";
2937
+ case "error":
2938
+ return "\u64CD\u4F5C\u5931\u8D25";
2939
+ default:
2940
+ return "";
2941
+ }
2942
+ };
2943
+ const handleClose = () => {
2944
+ if (isCompleted) {
2945
+ onClose?.();
2946
+ } else {
2947
+ onCancel?.();
2948
+ }
2949
+ };
2950
+ if (!visible) return null;
2951
+ return createPortal4(
2952
+ /* @__PURE__ */ jsx13("div", { className: "progress-dialog-overlay", children: /* @__PURE__ */ jsxs11("div", { className: "progress-dialog", children: [
2953
+ /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-header", children: [
2954
+ /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-title", children: [
2955
+ /* @__PURE__ */ jsx13(StatusIcon, {}),
2956
+ /* @__PURE__ */ jsx13("span", { children: title })
2957
+ ] }),
2958
+ isCompleted && /* @__PURE__ */ jsx13("button", { className: "progress-dialog-close", onClick: handleClose, children: /* @__PURE__ */ jsx13(Icon10, { icon: "lucide:x", width: 18, height: 18 }) })
2959
+ ] }),
2960
+ /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-content", children: [
2961
+ /* @__PURE__ */ jsx13("div", { className: "progress-dialog-status", children: getStatusText() }),
2962
+ status === "processing" && /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-bar-container", children: [
2963
+ /* @__PURE__ */ jsx13("div", { className: "progress-dialog-bar", children: /* @__PURE__ */ jsx13(
2964
+ "div",
2965
+ {
2966
+ className: "progress-dialog-bar-fill",
2967
+ style: { width: `${percent}%` }
2968
+ }
2969
+ ) }),
2970
+ /* @__PURE__ */ jsxs11("span", { className: "progress-dialog-percent", children: [
2971
+ percent,
2972
+ "%"
2973
+ ] })
2974
+ ] }),
2975
+ currentFile && status === "processing" && /* @__PURE__ */ jsx13("div", { className: "progress-dialog-current-file", children: currentFile }),
2976
+ totalCount && totalCount > 0 && status === "processing" && /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-count", children: [
2977
+ processedCount || 0,
2978
+ " / ",
2979
+ totalCount,
2980
+ " \u4E2A\u6587\u4EF6"
2981
+ ] }),
2982
+ error && /* @__PURE__ */ jsx13("div", { className: "progress-dialog-error", children: error }),
2983
+ status === "success" && outputPath && /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-output", children: [
2984
+ /* @__PURE__ */ jsx13("span", { className: "progress-dialog-output-label", children: "\u8F93\u51FA\u4F4D\u7F6E:" }),
2985
+ /* @__PURE__ */ jsx13("span", { className: "progress-dialog-output-path", children: outputPath })
2986
+ ] })
2987
+ ] }),
2988
+ /* @__PURE__ */ jsxs11("div", { className: "progress-dialog-footer", children: [
2989
+ status === "processing" && onCancel && /* @__PURE__ */ jsx13("button", { className: "progress-dialog-btn progress-dialog-btn-cancel", onClick: onCancel, children: "\u53D6\u6D88" }),
2990
+ status === "success" && outputPath && onOpenFolder && /* @__PURE__ */ jsxs11(
2991
+ "button",
2992
+ {
2993
+ className: "progress-dialog-btn progress-dialog-btn-folder",
2994
+ onClick: () => onOpenFolder(outputPath),
2995
+ children: [
2996
+ /* @__PURE__ */ jsx13(Icon10, { icon: "lucide:folder-open", width: 16, height: 16 }),
2997
+ "\u6253\u5F00\u6587\u4EF6\u5939"
2998
+ ]
2999
+ }
3000
+ ),
3001
+ isCompleted && /* @__PURE__ */ jsx13("button", { className: "progress-dialog-btn progress-dialog-btn-close", onClick: handleClose, children: "\u5173\u95ED" })
3002
+ ] })
3003
+ ] }) }),
3004
+ document.body
3005
+ );
3006
+ }
3007
+
3008
+ // src/components/FileInfoDialog.tsx
3009
+ import React from "react";
3010
+ import { Icon as Icon11 } from "@iconify/react";
3011
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3012
+ function getFileIcon(type, name) {
3013
+ if (type === FileType.FOLDER) {
3014
+ return /* @__PURE__ */ jsx14(Icon11, { icon: "flat-color-icons:folder", width: 48, height: 48, className: "file-info-icon" });
3015
+ }
3016
+ if (name) {
3017
+ const iconName2 = getFileTypeIcon(name, type);
3018
+ return /* @__PURE__ */ jsx14(Icon11, { icon: iconName2, width: 48, height: 48, className: "file-info-icon" });
3019
+ }
3020
+ const iconMap = {
3021
+ [FileType.IMAGE]: "material-icon-theme:image",
3022
+ [FileType.VIDEO]: "material-icon-theme:video",
3023
+ [FileType.MUSIC]: "material-icon-theme:audio",
3024
+ [FileType.DOCUMENT]: "material-icon-theme:word",
3025
+ [FileType.CODE]: "material-icon-theme:javascript",
3026
+ [FileType.ARCHIVE]: "material-icon-theme:zip",
3027
+ [FileType.PDF]: "material-icon-theme:pdf",
3028
+ [FileType.TEXT]: "material-icon-theme:document",
3029
+ [FileType.APPLICATION]: "material-icon-theme:exe"
3030
+ };
3031
+ const iconName = iconMap[type] || "material-icon-theme:document";
3032
+ return /* @__PURE__ */ jsx14(Icon11, { icon: iconName, width: 48, height: 48, className: "file-info-icon" });
3033
+ }
3034
+ function getFileTypeName(type, extension) {
3035
+ switch (type) {
3036
+ case FileType.FOLDER:
3037
+ return "\u6587\u4EF6\u5939";
3038
+ case FileType.IMAGE:
3039
+ return `\u56FE\u7247${extension ? ` (${extension.toUpperCase()})` : ""}`;
3040
+ case FileType.VIDEO:
3041
+ return `\u89C6\u9891${extension ? ` (${extension.toUpperCase()})` : ""}`;
3042
+ case FileType.MUSIC:
3043
+ return `\u97F3\u9891${extension ? ` (${extension.toUpperCase()})` : ""}`;
3044
+ case FileType.DOCUMENT:
3045
+ return `\u6587\u6863${extension ? ` (${extension.toUpperCase()})` : ""}`;
3046
+ case FileType.CODE:
3047
+ return `\u4EE3\u7801\u6587\u4EF6${extension ? ` (${extension.toUpperCase()})` : ""}`;
3048
+ case FileType.ARCHIVE:
3049
+ return `\u538B\u7F29\u5305${extension ? ` (${extension.toUpperCase()})` : ""}`;
3050
+ default:
3051
+ return extension ? `${extension.toUpperCase()} \u6587\u4EF6` : "\u6587\u4EF6";
3052
+ }
3053
+ }
3054
+ function getExtension(filename) {
3055
+ const lastDot = filename.lastIndexOf(".");
3056
+ if (lastDot === -1 || lastDot === 0) return void 0;
3057
+ return filename.substring(lastDot + 1).toLowerCase();
3058
+ }
3059
+ function getDirectory(path) {
3060
+ const lastSlash = path.lastIndexOf("/");
3061
+ if (lastSlash === -1) return path;
3062
+ return path.substring(0, lastSlash) || "/";
3063
+ }
3064
+ function FileInfoDialog({ visible, item, onClose }) {
3065
+ if (!visible || !item) return null;
3066
+ const extension = getExtension(item.name);
3067
+ const directory = getDirectory(item.id);
3068
+ const handleBackdropClick = (e) => {
3069
+ if (e.target === e.currentTarget) {
3070
+ onClose();
3071
+ }
3072
+ };
3073
+ React.useEffect(() => {
3074
+ const handleKeyDown = (e) => {
3075
+ if (e.key === "Escape") {
3076
+ onClose();
3077
+ }
3078
+ };
3079
+ if (visible) {
3080
+ document.addEventListener("keydown", handleKeyDown);
3081
+ }
3082
+ return () => document.removeEventListener("keydown", handleKeyDown);
3083
+ }, [visible, onClose]);
3084
+ return /* @__PURE__ */ jsx14("div", { className: "file-info-dialog-overlay", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs12("div", { className: "file-info-dialog", children: [
3085
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-dialog-header", children: [
3086
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-dialog-title", children: [
3087
+ getFileIcon(item.type, item.name),
3088
+ /* @__PURE__ */ jsx14("span", { className: "file-info-dialog-name", title: item.name, children: item.name })
3089
+ ] }),
3090
+ /* @__PURE__ */ jsx14("button", { className: "file-info-dialog-close", onClick: onClose, children: /* @__PURE__ */ jsx14(Icon11, { icon: "lucide:x", width: 18, height: 18 }) })
3091
+ ] }),
3092
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-dialog-content", children: [
3093
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-row", children: [
3094
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-label", children: [
3095
+ /* @__PURE__ */ jsx14(Icon11, { icon: "lucide:file", width: 14, height: 14 }),
3096
+ /* @__PURE__ */ jsx14("span", { children: "\u7C7B\u578B" })
3097
+ ] }),
3098
+ /* @__PURE__ */ jsx14("div", { className: "file-info-value", children: getFileTypeName(item.type, extension) })
3099
+ ] }),
3100
+ item.type !== FileType.FOLDER && item.size && /* @__PURE__ */ jsxs12("div", { className: "file-info-row", children: [
3101
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-label", children: [
3102
+ /* @__PURE__ */ jsx14(Icon11, { icon: "lucide:hard-drive", width: 14, height: 14 }),
3103
+ /* @__PURE__ */ jsx14("span", { children: "\u5927\u5C0F" })
3104
+ ] }),
3105
+ /* @__PURE__ */ jsx14("div", { className: "file-info-value", children: item.size })
3106
+ ] }),
3107
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-row", children: [
3108
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-label", children: [
3109
+ /* @__PURE__ */ jsx14(Icon11, { icon: "lucide:map-pin", width: 14, height: 14 }),
3110
+ /* @__PURE__ */ jsx14("span", { children: "\u4F4D\u7F6E" })
3111
+ ] }),
3112
+ /* @__PURE__ */ jsx14("div", { className: "file-info-value file-info-value--path", title: directory, children: directory })
3113
+ ] }),
3114
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-row", children: [
3115
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-label", children: [
3116
+ /* @__PURE__ */ jsx14(Icon11, { icon: "lucide:map-pin", width: 14, height: 14 }),
3117
+ /* @__PURE__ */ jsx14("span", { children: "\u5B8C\u6574\u8DEF\u5F84" })
3118
+ ] }),
3119
+ /* @__PURE__ */ jsx14("div", { className: "file-info-value file-info-value--path", title: item.id, children: item.id })
3120
+ ] }),
3121
+ item.dateModified && /* @__PURE__ */ jsxs12("div", { className: "file-info-row", children: [
3122
+ /* @__PURE__ */ jsxs12("div", { className: "file-info-label", children: [
3123
+ /* @__PURE__ */ jsx14(Icon11, { icon: "lucide:clock", width: 14, height: 14 }),
3124
+ /* @__PURE__ */ jsx14("span", { children: "\u4FEE\u6539\u65F6\u95F4" })
3125
+ ] }),
3126
+ /* @__PURE__ */ jsx14("div", { className: "file-info-value", children: item.dateModified })
3127
+ ] })
3128
+ ] }),
3129
+ /* @__PURE__ */ jsx14("div", { className: "file-info-dialog-footer", children: /* @__PURE__ */ jsx14("button", { className: "file-info-dialog-btn", onClick: onClose, children: "\u5173\u95ED" }) })
3130
+ ] }) });
3131
+ }
3132
+
3133
+ // src/hooks/useSelection.ts
3134
+ import { useState as useState7, useCallback as useCallback5 } from "react";
3135
+ function useSelection() {
3136
+ const [selectedIds, setSelectedIds] = useState7(/* @__PURE__ */ new Set());
3137
+ const [lastSelectedId, setLastSelectedId] = useState7(null);
3138
+ const [editingId, setEditingId] = useState7(null);
3139
+ const clearSelection = useCallback5(() => {
3140
+ setSelectedIds(/* @__PURE__ */ new Set());
3141
+ setLastSelectedId(null);
3142
+ }, []);
3143
+ const selectItem = useCallback5((id, items, multi = false, range = false) => {
3144
+ if (!id) {
3145
+ clearSelection();
3146
+ return;
3147
+ }
3148
+ setSelectedIds((prev) => {
3149
+ setLastSelectedId((prevLast) => {
3150
+ if (range && prevLast) {
3151
+ const indexA = items.findIndex((i) => i.id === prevLast);
3152
+ const indexB = items.findIndex((i) => i.id === id);
3153
+ if (indexA !== -1 && indexB !== -1) {
3154
+ const start = Math.min(indexA, indexB);
3155
+ const end = Math.max(indexA, indexB);
3156
+ const newSet = new Set(multi ? prev : []);
3157
+ for (let i = start; i <= end; i++) {
3158
+ const item = items[i];
3159
+ if (item) {
3160
+ newSet.add(item.id);
3161
+ }
3162
+ }
3163
+ setSelectedIds(newSet);
3164
+ return id;
3165
+ }
3166
+ }
3167
+ if (multi) {
3168
+ const newSet = new Set(prev);
3169
+ if (newSet.has(id)) {
3170
+ newSet.delete(id);
3171
+ } else {
3172
+ newSet.add(id);
3173
+ }
3174
+ setSelectedIds(newSet);
3175
+ return id;
3176
+ }
3177
+ setSelectedIds(/* @__PURE__ */ new Set([id]));
3178
+ return id;
3179
+ });
3180
+ return prev;
3181
+ });
3182
+ }, [clearSelection]);
3183
+ const selectAll = useCallback5((items) => {
3184
+ setSelectedIds(new Set(items.map((i) => i.id)));
3185
+ }, []);
3186
+ const setEditing = useCallback5((id) => {
3187
+ setEditingId(id);
3188
+ }, []);
3189
+ const isSelected = useCallback5((id) => {
3190
+ return selectedIds.has(id);
3191
+ }, [selectedIds]);
3192
+ return {
3193
+ selectedIds,
3194
+ lastSelectedId,
3195
+ editingId,
3196
+ clearSelection,
3197
+ selectItem,
3198
+ selectAll,
3199
+ setEditing,
3200
+ isSelected
3201
+ };
3202
+ }
3203
+
3204
+ // src/hooks/useDragAndDrop.ts
3205
+ import { useState as useState8, useCallback as useCallback6 } from "react";
3206
+ function useDragAndDrop(getSelectedIds, onSelect, onMove) {
3207
+ const [dragOverId, setDragOverId] = useState8(null);
3208
+ const [isDragging, setIsDragging] = useState8(false);
3209
+ const handleDragStart = useCallback6((e, itemId) => {
3210
+ if (!e.dataTransfer) return;
3211
+ const selectedIds = getSelectedIds();
3212
+ if (!selectedIds.has(itemId)) {
3213
+ onSelect(itemId, false, false);
3214
+ }
3215
+ const draggedIds = selectedIds.has(itemId) ? selectedIds : /* @__PURE__ */ new Set([itemId]);
3216
+ e.dataTransfer.setData("text/plain", JSON.stringify([...draggedIds]));
3217
+ e.dataTransfer.effectAllowed = "move";
3218
+ setIsDragging(true);
3219
+ }, [getSelectedIds, onSelect]);
3220
+ const handleDragOver = useCallback6((e, item) => {
3221
+ if (!isDragging) return;
3222
+ if (item.type === FileType.FOLDER) {
3223
+ const selectedIds = getSelectedIds();
3224
+ if (!selectedIds.has(item.id)) {
3225
+ e.preventDefault();
3226
+ setDragOverId(item.id);
3227
+ }
3228
+ }
3229
+ }, [isDragging, getSelectedIds]);
3230
+ const handleDragLeave = useCallback6(() => {
3231
+ setDragOverId(null);
3232
+ }, []);
3233
+ const handleDrop = useCallback6((e, targetItem) => {
3234
+ setDragOverId(null);
3235
+ setIsDragging(false);
3236
+ if (!e.dataTransfer || targetItem.type !== FileType.FOLDER) return;
3237
+ const data = e.dataTransfer.getData("text/plain");
3238
+ if (!data) return;
3239
+ try {
3240
+ const draggedIds = JSON.parse(data);
3241
+ const itemIds = new Set(draggedIds);
3242
+ if (itemIds.has(targetItem.id)) return;
3243
+ onMove(targetItem.id, itemIds);
3244
+ } catch {
3245
+ }
3246
+ }, [onMove]);
3247
+ const handleDragEnd = useCallback6(() => {
3248
+ setDragOverId(null);
3249
+ setIsDragging(false);
3250
+ }, []);
3251
+ return {
3252
+ dragOverId,
3253
+ isDragging,
3254
+ handleDragStart,
3255
+ handleDragOver,
3256
+ handleDragLeave,
3257
+ handleDrop,
3258
+ handleDragEnd
3259
+ };
3260
+ }
3261
+
3262
+ // src/hooks/useMediaPlayer.ts
3263
+ import { useState as useState9, useCallback as useCallback7, useEffect as useEffect6 } from "react";
3264
+ function useMediaPlayer(mediaType, mediaRef) {
3265
+ const [isPlaying, setIsPlaying] = useState9(false);
3266
+ const [progress, setProgress] = useState9(0);
3267
+ const [currentTime, setCurrentTime] = useState9(0);
3268
+ const [duration, setDuration] = useState9(0);
3269
+ const [isMuted, setIsMuted] = useState9(false);
3270
+ const [volume, setVolume] = useState9(1);
3271
+ const [lastVolume, setLastVolume] = useState9(1);
3272
+ const [showControls, setShowControls] = useState9(false);
3273
+ const isAudio = mediaType === FileType.MUSIC;
3274
+ const updateProgress = useCallback7(() => {
3275
+ const media = mediaRef.current;
3276
+ if (media) {
3277
+ const curr = media.currentTime;
3278
+ const dur = media.duration;
3279
+ setCurrentTime(curr);
3280
+ if (dur && !isNaN(dur)) {
3281
+ setDuration(dur);
3282
+ setProgress(curr / dur * 100);
3283
+ }
3284
+ }
3285
+ }, [mediaRef]);
3286
+ const togglePlay = useCallback7(() => {
3287
+ const media = mediaRef.current;
3288
+ if (media) {
3289
+ if (isPlaying) {
3290
+ media.pause();
3291
+ setIsPlaying(false);
3292
+ } else {
3293
+ media.play();
3294
+ setIsPlaying(true);
3295
+ }
3296
+ }
3297
+ }, [isPlaying, mediaRef]);
3298
+ const toggleMute = useCallback7(() => {
3299
+ const media = mediaRef.current;
3300
+ if (media) {
3301
+ if (isMuted) {
3302
+ const volToRestore = lastVolume || 1;
3303
+ media.volume = volToRestore;
3304
+ media.muted = false;
3305
+ setVolume(volToRestore);
3306
+ setIsMuted(false);
3307
+ } else {
3308
+ setLastVolume(volume);
3309
+ media.volume = 0;
3310
+ media.muted = true;
3311
+ setVolume(0);
3312
+ setIsMuted(true);
3313
+ }
3314
+ }
3315
+ }, [isMuted, lastVolume, volume, mediaRef]);
3316
+ const handleVolumeChange = useCallback7((val) => {
3317
+ setVolume(val);
3318
+ const media = mediaRef.current;
3319
+ if (media) {
3320
+ media.volume = val;
3321
+ if (val === 0) {
3322
+ setIsMuted(true);
3323
+ media.muted = true;
3324
+ } else {
3325
+ setIsMuted(false);
3326
+ media.muted = false;
3327
+ }
3328
+ }
3329
+ }, [mediaRef]);
3330
+ const seekTo = useCallback7((time) => {
3331
+ const media = mediaRef.current;
3332
+ if (media && duration) {
3333
+ media.currentTime = time;
3334
+ setCurrentTime(time);
3335
+ setProgress(time / duration * 100);
3336
+ }
3337
+ }, [duration, mediaRef]);
3338
+ const formatTime = useCallback7((time) => {
3339
+ if (isNaN(time)) return "0:00";
3340
+ const minutes = Math.floor(time / 60);
3341
+ const seconds = Math.floor(time % 60);
3342
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
3343
+ }, []);
3344
+ const autoPlay = useCallback7(() => {
3345
+ const media = mediaRef.current;
3346
+ if ((mediaType === FileType.VIDEO || isAudio) && media) {
3347
+ media.volume = volume;
3348
+ media.play().catch(() => {
3349
+ });
3350
+ setIsPlaying(true);
3351
+ }
3352
+ }, [mediaType, isAudio, volume, mediaRef]);
3353
+ useEffect6(() => {
3354
+ const media = mediaRef.current;
3355
+ if (media) {
3356
+ media.addEventListener("timeupdate", updateProgress);
3357
+ media.addEventListener("loadedmetadata", updateProgress);
3358
+ media.addEventListener("ended", () => {
3359
+ setIsPlaying(false);
3360
+ });
3361
+ return () => {
3362
+ media.removeEventListener("timeupdate", updateProgress);
3363
+ media.removeEventListener("loadedmetadata", updateProgress);
3364
+ };
3365
+ }
3366
+ }, [mediaRef, updateProgress]);
3367
+ return {
3368
+ isPlaying,
3369
+ progress,
3370
+ currentTime,
3371
+ duration,
3372
+ isMuted,
3373
+ volume,
3374
+ showControls,
3375
+ isAudio,
3376
+ togglePlay,
3377
+ toggleMute,
3378
+ handleVolumeChange,
3379
+ seekTo,
3380
+ formatTime,
3381
+ autoPlay,
3382
+ updateProgress
3383
+ };
3384
+ }
3385
+
3386
+ // src/hooks/useApplicationIcon.ts
3387
+ import { useState as useState10, useCallback as useCallback8, useEffect as useEffect7 } from "react";
3388
+ function useApplicationIcon(items) {
3389
+ const appIconCache = /* @__PURE__ */ new Map();
3390
+ const [appIconUrls, setAppIconUrls] = useState10(/* @__PURE__ */ new Map());
3391
+ const loadApplicationIcon = useCallback8(async (item) => {
3392
+ if (item.type !== FileType.APPLICATION || !item.id) return;
3393
+ if (appIconCache.has(item.id)) {
3394
+ const cachedUrl = appIconCache.get(item.id);
3395
+ if (cachedUrl) {
3396
+ setAppIconUrls((prev) => new Map(prev).set(item.id, cachedUrl));
3397
+ }
3398
+ return;
3399
+ }
3400
+ if (appIconUrls.has(item.id)) {
3401
+ return;
3402
+ }
3403
+ if (typeof window.fileExplorerAPI !== "undefined" && window.fileExplorerAPI.getApplicationIcon) {
3404
+ try {
3405
+ const iconUrl = await window.fileExplorerAPI.getApplicationIcon(item.id);
3406
+ if (iconUrl) {
3407
+ appIconCache.set(item.id, iconUrl);
3408
+ setAppIconUrls((prev) => new Map(prev).set(item.id, iconUrl));
3409
+ }
3410
+ } catch (error) {
3411
+ console.error(`Failed to load application icon for ${item.name}:`, error);
3412
+ }
3413
+ }
3414
+ }, [appIconUrls]);
3415
+ useEffect7(() => {
3416
+ items.forEach((item) => {
3417
+ if (item.type === FileType.APPLICATION && !appIconUrls.has(item.id)) {
3418
+ loadApplicationIcon(item);
3419
+ }
3420
+ });
3421
+ }, [items, appIconUrls, loadApplicationIcon]);
3422
+ const getAppIconUrl = useCallback8((item) => {
3423
+ return appIconUrls.get(item.id);
3424
+ }, [appIconUrls]);
3425
+ return {
3426
+ getAppIconUrl,
3427
+ loadApplicationIcon
3428
+ };
3429
+ }
3430
+ export {
3431
+ Breadcrumb,
3432
+ CompressDialog,
3433
+ ContextMenu,
3434
+ FileGrid,
3435
+ FileIcon,
3436
+ FileInfoDialog,
3437
+ FileList,
3438
+ FileListView,
3439
+ FileSidebar,
3440
+ FileType,
3441
+ ProgressDialog,
3442
+ SortIndicator,
3443
+ StatusBar,
3444
+ Toolbar,
3445
+ Window,
3446
+ useApplicationIcon,
3447
+ useDragAndDrop,
3448
+ useMediaPlayer,
3449
+ useSelection,
3450
+ useWindowDrag,
3451
+ useWindowResize
3452
+ };
3453
+ //# sourceMappingURL=index.js.map