@huyooo/file-explorer-bridge-electron 0.3.0 → 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.
@@ -40,8 +40,11 @@ var import_electron = require("electron");
40
40
  var import_node_path = __toESM(require("path"), 1);
41
41
  var import_file_explorer_core = require("@huyooo/file-explorer-core");
42
42
  var import_node_fs = require("fs");
43
+ var import_path = require("@huyooo/file-explorer-preview/path");
43
44
  var CHANNEL_PREFIX = "file-explorer";
45
+ var PREVIEW_CHANNEL_PREFIX = "file-explorer-preview";
44
46
  var channel = (name) => `${CHANNEL_PREFIX}:${name}`;
47
+ var previewChannel = (name) => `${PREVIEW_CHANNEL_PREFIX}:${name}`;
45
48
  var clipboardAdapter = null;
46
49
  function setClipboardAdapter(adapter) {
47
50
  clipboardAdapter = adapter;
@@ -57,7 +60,10 @@ function createElectronAdapter() {
57
60
  };
58
61
  }
59
62
  var electronUrlEncoder = import_file_explorer_core.encodeFileUrl;
60
- function registerFileExplorerHandlers() {
63
+ var explorerOptions = {};
64
+ var mediaPreviewWindows = /* @__PURE__ */ new Map();
65
+ function registerFileExplorerHandlers(options = {}) {
66
+ explorerOptions = options;
61
67
  const adapter = createElectronAdapter();
62
68
  import_electron.ipcMain.handle(channel("readDirectory"), async (_event, dirPath) => {
63
69
  const thumbnailService = (0, import_file_explorer_core.getThumbnailService)();
@@ -221,6 +227,282 @@ function registerFileExplorerHandlers() {
221
227
  }, 100);
222
228
  return { success: true };
223
229
  });
230
+ import_electron.ipcMain.handle(channel("compressFiles"), async (event, sources, options2) => {
231
+ try {
232
+ const result = await (0, import_file_explorer_core.compressFiles)(sources, options2, (progress) => {
233
+ event.sender.send(channel("compressProgress"), progress);
234
+ });
235
+ return result;
236
+ } catch (error) {
237
+ return { success: false, error: String(error) };
238
+ }
239
+ });
240
+ import_electron.ipcMain.handle(channel("extractArchive"), async (event, archivePath, options2) => {
241
+ try {
242
+ const result = await (0, import_file_explorer_core.extractArchive)(archivePath, options2, (progress) => {
243
+ event.sender.send(channel("extractProgress"), progress);
244
+ });
245
+ return result;
246
+ } catch (error) {
247
+ return { success: false, error: String(error) };
248
+ }
249
+ });
250
+ import_electron.ipcMain.handle(channel("isArchiveFile"), async (_event, filePath) => {
251
+ return (0, import_file_explorer_core.isArchiveFile)(filePath);
252
+ });
253
+ const watchUnsubscribers = /* @__PURE__ */ new Map();
254
+ import_electron.ipcMain.handle(channel("watchDirectory"), async (event, dirPath, watchId) => {
255
+ try {
256
+ const existingUnsubscribe = watchUnsubscribers.get(watchId);
257
+ if (existingUnsubscribe) {
258
+ existingUnsubscribe();
259
+ watchUnsubscribers.delete(watchId);
260
+ }
261
+ const watchManager = (0, import_file_explorer_core.getWatchManager)();
262
+ const unsubscribe = watchManager.watch(dirPath, (watchEvent) => {
263
+ event.sender.send(channel("watchEvent"), {
264
+ watchId,
265
+ event: watchEvent
266
+ });
267
+ });
268
+ watchUnsubscribers.set(watchId, unsubscribe);
269
+ return { success: true };
270
+ } catch (error) {
271
+ return { success: false, error: String(error) };
272
+ }
273
+ });
274
+ import_electron.ipcMain.handle(channel("unwatchDirectory"), async (_event, watchId) => {
275
+ const unsubscribe = watchUnsubscribers.get(watchId);
276
+ if (unsubscribe) {
277
+ unsubscribe();
278
+ watchUnsubscribers.delete(watchId);
279
+ }
280
+ return { success: true };
281
+ });
282
+ import_electron.ipcMain.handle(channel("showFileInfo"), async (_event, filePath) => {
283
+ return await (0, import_file_explorer_core.showFileInfo)(filePath);
284
+ });
285
+ import_electron.ipcMain.handle(channel("openInTerminal"), async (_event, dirPath) => {
286
+ return await (0, import_file_explorer_core.openInTerminal)(dirPath);
287
+ });
288
+ import_electron.ipcMain.handle(channel("openInEditor"), async (_event, targetPath) => {
289
+ return await (0, import_file_explorer_core.openInEditor)(targetPath);
290
+ });
291
+ import_electron.ipcMain.handle(channel("openInNewWindow"), async (_event, folderPath) => {
292
+ try {
293
+ if (explorerOptions.onOpenInNewWindow) {
294
+ explorerOptions.onOpenInNewWindow(folderPath);
295
+ } else {
296
+ await import_electron.shell.openPath(folderPath);
297
+ }
298
+ return { success: true };
299
+ } catch (error) {
300
+ return { success: false, error: String(error) };
301
+ }
302
+ });
303
+ import_electron.ipcMain.on(channel("requestWindowFocus"), (event) => {
304
+ const win = import_electron.BrowserWindow.fromWebContents(event.sender);
305
+ if (win && !win.isFocused()) {
306
+ win.focus();
307
+ }
308
+ });
309
+ import_electron.ipcMain.handle(channel("mediaNeedsTranscode"), async (_event, filePath) => {
310
+ const mediaService = (0, import_file_explorer_core.getMediaService)();
311
+ if (!mediaService) {
312
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
313
+ }
314
+ try {
315
+ const info = await mediaService.needsTranscode(filePath);
316
+ return { success: true, data: info };
317
+ } catch (error) {
318
+ return { success: false, error: String(error) };
319
+ }
320
+ });
321
+ import_electron.ipcMain.handle(channel("mediaGetPlayableUrl"), async (event, filePath) => {
322
+ const mediaService = (0, import_file_explorer_core.getMediaService)();
323
+ if (!mediaService) {
324
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
325
+ }
326
+ try {
327
+ const url = await mediaService.getPlayableUrl(filePath, (progress) => {
328
+ event.sender.send(channel("mediaTranscodeProgress"), {
329
+ filePath,
330
+ progress
331
+ });
332
+ });
333
+ return { success: true, url };
334
+ } catch (error) {
335
+ return { success: false, error: String(error) };
336
+ }
337
+ });
338
+ import_electron.ipcMain.handle(channel("mediaGetMetadata"), async (_event, filePath) => {
339
+ const mediaService = (0, import_file_explorer_core.getMediaService)();
340
+ if (!mediaService) {
341
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
342
+ }
343
+ try {
344
+ const metadata = await mediaService.getMetadata(filePath);
345
+ return { success: true, data: metadata };
346
+ } catch (error) {
347
+ return { success: false, error: String(error) };
348
+ }
349
+ });
350
+ import_electron.ipcMain.handle(channel("mediaCleanupFile"), async (_event, filePath) => {
351
+ const mediaService = (0, import_file_explorer_core.getMediaService)();
352
+ if (!mediaService) {
353
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
354
+ }
355
+ try {
356
+ await mediaService.cleanupFile(filePath);
357
+ return { success: true };
358
+ } catch (error) {
359
+ return { success: false, error: String(error) };
360
+ }
361
+ });
362
+ import_electron.ipcMain.handle(channel("openMediaPreviewWindow"), async (event, filePath, mediaType) => {
363
+ try {
364
+ const fileName = import_node_path.default.basename(filePath);
365
+ const mediaService = (0, import_file_explorer_core.getMediaService)();
366
+ let fileUrl;
367
+ if (mediaType === "image") {
368
+ fileUrl = electronUrlEncoder(filePath);
369
+ } else if (mediaService) {
370
+ const playableUrl = await mediaService.getPlayableUrl(filePath, (progress) => {
371
+ event.sender.send(channel("mediaTranscodeProgress"), {
372
+ filePath,
373
+ progress
374
+ });
375
+ });
376
+ fileUrl = playableUrl || electronUrlEncoder(filePath);
377
+ } else {
378
+ fileUrl = electronUrlEncoder(filePath);
379
+ }
380
+ const existingWindow = mediaPreviewWindows.get(filePath);
381
+ if (existingWindow && !existingWindow.isDestroyed()) {
382
+ existingWindow.focus();
383
+ return { success: true };
384
+ }
385
+ const isMac = process.platform === "darwin";
386
+ const isWin = process.platform === "win32";
387
+ const titleBarHeight = isMac ? 38 : 32;
388
+ let contentWidth = 720;
389
+ let contentHeight = 405;
390
+ let minContentWidth = 720;
391
+ let minContentHeight = 405;
392
+ if (mediaType === "audio") {
393
+ contentWidth = 720;
394
+ contentHeight = 405;
395
+ minContentWidth = 720;
396
+ minContentHeight = 405;
397
+ } else if (mediaType === "image") {
398
+ contentWidth = 1e3;
399
+ contentHeight = 700;
400
+ minContentWidth = 600;
401
+ minContentHeight = 400;
402
+ }
403
+ const width = contentWidth;
404
+ const height = contentHeight + titleBarHeight;
405
+ const minWidth = minContentWidth;
406
+ const minHeight = minContentHeight + titleBarHeight;
407
+ const previewPreloadPath = import_node_path.default.join(__dirname, "../preload/preview.cjs");
408
+ const previewWindow = new import_electron.BrowserWindow({
409
+ width,
410
+ height,
411
+ minWidth,
412
+ minHeight,
413
+ title: fileName,
414
+ modal: false,
415
+ show: false,
416
+ backgroundColor: "#1a1a1a",
417
+ // 窗口功能
418
+ resizable: true,
419
+ maximizable: true,
420
+ fullscreenable: true,
421
+ // Windows 和 macOS 都使用无边框样式,自定义标题栏
422
+ frame: isMac ? true : false,
423
+ // macOS 用 hiddenInset,Windows 完全无边框
424
+ titleBarStyle: isMac ? "hiddenInset" : void 0,
425
+ trafficLightPosition: isMac ? { x: 12, y: 12 } : void 0,
426
+ webPreferences: {
427
+ preload: previewPreloadPath,
428
+ nodeIntegration: false,
429
+ contextIsolation: true,
430
+ // 注意:以下设置会触发安全警告,但这是必要的
431
+ // - sandbox: false - 需要加载本地媒体文件(file:// 协议)
432
+ // - webSecurity: false - 允许加载本地文件和 Iconify CDN
433
+ // 这些警告在开发环境中显示,打包后不会显示
434
+ sandbox: false,
435
+ webSecurity: false
436
+ }
437
+ });
438
+ const previewHtmlPath = (0, import_path.getPreviewHtmlPath)();
439
+ const searchParams = new URLSearchParams({
440
+ type: mediaType,
441
+ url: encodeURIComponent(fileUrl),
442
+ name: encodeURIComponent(fileName),
443
+ platform: isMac ? "darwin" : isWin ? "win32" : "linux"
444
+ });
445
+ previewWindow.loadFile(previewHtmlPath, { search: searchParams.toString() });
446
+ previewWindow.once("ready-to-show", () => {
447
+ previewWindow.show();
448
+ });
449
+ previewWindow.on("maximize", () => {
450
+ previewWindow.webContents.send(previewChannel("maximizeChange"), true);
451
+ });
452
+ previewWindow.on("unmaximize", () => {
453
+ previewWindow.webContents.send(previewChannel("maximizeChange"), false);
454
+ });
455
+ previewWindow.on("closed", () => {
456
+ mediaPreviewWindows.delete(filePath);
457
+ });
458
+ mediaPreviewWindows.set(filePath, previewWindow);
459
+ return { success: true };
460
+ } catch (error) {
461
+ return { success: false, error: String(error) };
462
+ }
463
+ });
464
+ import_electron.ipcMain.handle(channel("closeMediaPreviewWindow"), async (_event, filePath) => {
465
+ const window = mediaPreviewWindows.get(filePath);
466
+ if (window && !window.isDestroyed()) {
467
+ window.close();
468
+ mediaPreviewWindows.delete(filePath);
469
+ }
470
+ return { success: true };
471
+ });
472
+ import_electron.ipcMain.on(previewChannel("minimize"), (event) => {
473
+ const win = import_electron.BrowserWindow.fromWebContents(event.sender);
474
+ if (win) {
475
+ win.minimize();
476
+ }
477
+ });
478
+ import_electron.ipcMain.on(previewChannel("toggleMaximize"), (event) => {
479
+ const win = import_electron.BrowserWindow.fromWebContents(event.sender);
480
+ if (win) {
481
+ if (win.isMaximized()) {
482
+ win.unmaximize();
483
+ } else {
484
+ win.maximize();
485
+ }
486
+ }
487
+ });
488
+ import_electron.ipcMain.on(previewChannel("close"), (event) => {
489
+ const win = import_electron.BrowserWindow.fromWebContents(event.sender);
490
+ if (win) {
491
+ win.close();
492
+ }
493
+ });
494
+ import_electron.ipcMain.on(previewChannel("hide"), (event) => {
495
+ const win = import_electron.BrowserWindow.fromWebContents(event.sender);
496
+ if (win) {
497
+ win.hide();
498
+ }
499
+ });
500
+ import_electron.ipcMain.on(previewChannel("show"), (event) => {
501
+ const win = import_electron.BrowserWindow.fromWebContents(event.sender);
502
+ if (win) {
503
+ win.show();
504
+ }
505
+ });
224
506
  console.log("\u2705 File Explorer IPC handlers registered");
225
507
  }
226
508
  // Annotate the CommonJS export names for ESM import in node:
@@ -17,9 +17,15 @@ declare function setClipboardAdapter(adapter: ClipboardAdapter): void;
17
17
  declare function createElectronAdapter(): PlatformAdapter;
18
18
  /** URL 编码器(使用 core 中的协议工具) */
19
19
  declare const electronUrlEncoder: typeof encodeFileUrl;
20
+ /** 文件管理器配置选项 */
21
+ interface FileExplorerOptions {
22
+ /** 在新窗口中打开文件夹的回调 */
23
+ onOpenInNewWindow?: (folderPath: string) => void;
24
+ }
20
25
  /**
21
26
  * 注册文件系统 IPC handlers
27
+ * @param options 可选配置
22
28
  */
23
- declare function registerFileExplorerHandlers(): void;
29
+ declare function registerFileExplorerHandlers(options?: FileExplorerOptions): void;
24
30
 
25
- export { createElectronAdapter, electronUrlEncoder, registerFileExplorerHandlers, setClipboardAdapter };
31
+ export { type FileExplorerOptions, createElectronAdapter, electronUrlEncoder, registerFileExplorerHandlers, setClipboardAdapter };
@@ -17,9 +17,15 @@ declare function setClipboardAdapter(adapter: ClipboardAdapter): void;
17
17
  declare function createElectronAdapter(): PlatformAdapter;
18
18
  /** URL 编码器(使用 core 中的协议工具) */
19
19
  declare const electronUrlEncoder: typeof encodeFileUrl;
20
+ /** 文件管理器配置选项 */
21
+ interface FileExplorerOptions {
22
+ /** 在新窗口中打开文件夹的回调 */
23
+ onOpenInNewWindow?: (folderPath: string) => void;
24
+ }
20
25
  /**
21
26
  * 注册文件系统 IPC handlers
27
+ * @param options 可选配置
22
28
  */
23
- declare function registerFileExplorerHandlers(): void;
29
+ declare function registerFileExplorerHandlers(options?: FileExplorerOptions): void;
24
30
 
25
- export { createElectronAdapter, electronUrlEncoder, registerFileExplorerHandlers, setClipboardAdapter };
31
+ export { type FileExplorerOptions, createElectronAdapter, electronUrlEncoder, registerFileExplorerHandlers, setClipboardAdapter };
@@ -1,5 +1,5 @@
1
1
  // src/main/index.ts
2
- import { ipcMain, shell } from "electron";
2
+ import { ipcMain, shell, BrowserWindow } from "electron";
3
3
  import path from "path";
4
4
  import {
5
5
  readDirectory,
@@ -21,14 +21,25 @@ import {
21
21
  pasteFiles,
22
22
  searchFiles,
23
23
  searchFilesStream,
24
+ compressFiles,
25
+ extractArchive,
26
+ isArchiveFile,
27
+ getWatchManager,
28
+ showFileInfo,
29
+ openInTerminal,
30
+ openInEditor,
24
31
  getFileType,
25
32
  formatFileSize,
26
33
  formatDateTime,
27
- encodeFileUrl
34
+ encodeFileUrl,
35
+ getMediaService
28
36
  } from "@huyooo/file-explorer-core";
29
37
  import { promises as fs } from "fs";
38
+ import { getPreviewHtmlPath } from "@huyooo/file-explorer-preview/path";
30
39
  var CHANNEL_PREFIX = "file-explorer";
40
+ var PREVIEW_CHANNEL_PREFIX = "file-explorer-preview";
31
41
  var channel = (name) => `${CHANNEL_PREFIX}:${name}`;
42
+ var previewChannel = (name) => `${PREVIEW_CHANNEL_PREFIX}:${name}`;
32
43
  var clipboardAdapter = null;
33
44
  function setClipboardAdapter(adapter) {
34
45
  clipboardAdapter = adapter;
@@ -44,7 +55,10 @@ function createElectronAdapter() {
44
55
  };
45
56
  }
46
57
  var electronUrlEncoder = encodeFileUrl;
47
- function registerFileExplorerHandlers() {
58
+ var explorerOptions = {};
59
+ var mediaPreviewWindows = /* @__PURE__ */ new Map();
60
+ function registerFileExplorerHandlers(options = {}) {
61
+ explorerOptions = options;
48
62
  const adapter = createElectronAdapter();
49
63
  ipcMain.handle(channel("readDirectory"), async (_event, dirPath) => {
50
64
  const thumbnailService = getThumbnailService();
@@ -208,6 +222,282 @@ function registerFileExplorerHandlers() {
208
222
  }, 100);
209
223
  return { success: true };
210
224
  });
225
+ ipcMain.handle(channel("compressFiles"), async (event, sources, options2) => {
226
+ try {
227
+ const result = await compressFiles(sources, options2, (progress) => {
228
+ event.sender.send(channel("compressProgress"), progress);
229
+ });
230
+ return result;
231
+ } catch (error) {
232
+ return { success: false, error: String(error) };
233
+ }
234
+ });
235
+ ipcMain.handle(channel("extractArchive"), async (event, archivePath, options2) => {
236
+ try {
237
+ const result = await extractArchive(archivePath, options2, (progress) => {
238
+ event.sender.send(channel("extractProgress"), progress);
239
+ });
240
+ return result;
241
+ } catch (error) {
242
+ return { success: false, error: String(error) };
243
+ }
244
+ });
245
+ ipcMain.handle(channel("isArchiveFile"), async (_event, filePath) => {
246
+ return isArchiveFile(filePath);
247
+ });
248
+ const watchUnsubscribers = /* @__PURE__ */ new Map();
249
+ ipcMain.handle(channel("watchDirectory"), async (event, dirPath, watchId) => {
250
+ try {
251
+ const existingUnsubscribe = watchUnsubscribers.get(watchId);
252
+ if (existingUnsubscribe) {
253
+ existingUnsubscribe();
254
+ watchUnsubscribers.delete(watchId);
255
+ }
256
+ const watchManager = getWatchManager();
257
+ const unsubscribe = watchManager.watch(dirPath, (watchEvent) => {
258
+ event.sender.send(channel("watchEvent"), {
259
+ watchId,
260
+ event: watchEvent
261
+ });
262
+ });
263
+ watchUnsubscribers.set(watchId, unsubscribe);
264
+ return { success: true };
265
+ } catch (error) {
266
+ return { success: false, error: String(error) };
267
+ }
268
+ });
269
+ ipcMain.handle(channel("unwatchDirectory"), async (_event, watchId) => {
270
+ const unsubscribe = watchUnsubscribers.get(watchId);
271
+ if (unsubscribe) {
272
+ unsubscribe();
273
+ watchUnsubscribers.delete(watchId);
274
+ }
275
+ return { success: true };
276
+ });
277
+ ipcMain.handle(channel("showFileInfo"), async (_event, filePath) => {
278
+ return await showFileInfo(filePath);
279
+ });
280
+ ipcMain.handle(channel("openInTerminal"), async (_event, dirPath) => {
281
+ return await openInTerminal(dirPath);
282
+ });
283
+ ipcMain.handle(channel("openInEditor"), async (_event, targetPath) => {
284
+ return await openInEditor(targetPath);
285
+ });
286
+ ipcMain.handle(channel("openInNewWindow"), async (_event, folderPath) => {
287
+ try {
288
+ if (explorerOptions.onOpenInNewWindow) {
289
+ explorerOptions.onOpenInNewWindow(folderPath);
290
+ } else {
291
+ await shell.openPath(folderPath);
292
+ }
293
+ return { success: true };
294
+ } catch (error) {
295
+ return { success: false, error: String(error) };
296
+ }
297
+ });
298
+ ipcMain.on(channel("requestWindowFocus"), (event) => {
299
+ const win = BrowserWindow.fromWebContents(event.sender);
300
+ if (win && !win.isFocused()) {
301
+ win.focus();
302
+ }
303
+ });
304
+ ipcMain.handle(channel("mediaNeedsTranscode"), async (_event, filePath) => {
305
+ const mediaService = getMediaService();
306
+ if (!mediaService) {
307
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
308
+ }
309
+ try {
310
+ const info = await mediaService.needsTranscode(filePath);
311
+ return { success: true, data: info };
312
+ } catch (error) {
313
+ return { success: false, error: String(error) };
314
+ }
315
+ });
316
+ ipcMain.handle(channel("mediaGetPlayableUrl"), async (event, filePath) => {
317
+ const mediaService = getMediaService();
318
+ if (!mediaService) {
319
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
320
+ }
321
+ try {
322
+ const url = await mediaService.getPlayableUrl(filePath, (progress) => {
323
+ event.sender.send(channel("mediaTranscodeProgress"), {
324
+ filePath,
325
+ progress
326
+ });
327
+ });
328
+ return { success: true, url };
329
+ } catch (error) {
330
+ return { success: false, error: String(error) };
331
+ }
332
+ });
333
+ ipcMain.handle(channel("mediaGetMetadata"), async (_event, filePath) => {
334
+ const mediaService = getMediaService();
335
+ if (!mediaService) {
336
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
337
+ }
338
+ try {
339
+ const metadata = await mediaService.getMetadata(filePath);
340
+ return { success: true, data: metadata };
341
+ } catch (error) {
342
+ return { success: false, error: String(error) };
343
+ }
344
+ });
345
+ ipcMain.handle(channel("mediaCleanupFile"), async (_event, filePath) => {
346
+ const mediaService = getMediaService();
347
+ if (!mediaService) {
348
+ return { success: false, error: "\u5A92\u4F53\u670D\u52A1\u672A\u521D\u59CB\u5316" };
349
+ }
350
+ try {
351
+ await mediaService.cleanupFile(filePath);
352
+ return { success: true };
353
+ } catch (error) {
354
+ return { success: false, error: String(error) };
355
+ }
356
+ });
357
+ ipcMain.handle(channel("openMediaPreviewWindow"), async (event, filePath, mediaType) => {
358
+ try {
359
+ const fileName = path.basename(filePath);
360
+ const mediaService = getMediaService();
361
+ let fileUrl;
362
+ if (mediaType === "image") {
363
+ fileUrl = electronUrlEncoder(filePath);
364
+ } else if (mediaService) {
365
+ const playableUrl = await mediaService.getPlayableUrl(filePath, (progress) => {
366
+ event.sender.send(channel("mediaTranscodeProgress"), {
367
+ filePath,
368
+ progress
369
+ });
370
+ });
371
+ fileUrl = playableUrl || electronUrlEncoder(filePath);
372
+ } else {
373
+ fileUrl = electronUrlEncoder(filePath);
374
+ }
375
+ const existingWindow = mediaPreviewWindows.get(filePath);
376
+ if (existingWindow && !existingWindow.isDestroyed()) {
377
+ existingWindow.focus();
378
+ return { success: true };
379
+ }
380
+ const isMac = process.platform === "darwin";
381
+ const isWin = process.platform === "win32";
382
+ const titleBarHeight = isMac ? 38 : 32;
383
+ let contentWidth = 720;
384
+ let contentHeight = 405;
385
+ let minContentWidth = 720;
386
+ let minContentHeight = 405;
387
+ if (mediaType === "audio") {
388
+ contentWidth = 720;
389
+ contentHeight = 405;
390
+ minContentWidth = 720;
391
+ minContentHeight = 405;
392
+ } else if (mediaType === "image") {
393
+ contentWidth = 1e3;
394
+ contentHeight = 700;
395
+ minContentWidth = 600;
396
+ minContentHeight = 400;
397
+ }
398
+ const width = contentWidth;
399
+ const height = contentHeight + titleBarHeight;
400
+ const minWidth = minContentWidth;
401
+ const minHeight = minContentHeight + titleBarHeight;
402
+ const previewPreloadPath = path.join(__dirname, "../preload/preview.cjs");
403
+ const previewWindow = new BrowserWindow({
404
+ width,
405
+ height,
406
+ minWidth,
407
+ minHeight,
408
+ title: fileName,
409
+ modal: false,
410
+ show: false,
411
+ backgroundColor: "#1a1a1a",
412
+ // 窗口功能
413
+ resizable: true,
414
+ maximizable: true,
415
+ fullscreenable: true,
416
+ // Windows 和 macOS 都使用无边框样式,自定义标题栏
417
+ frame: isMac ? true : false,
418
+ // macOS 用 hiddenInset,Windows 完全无边框
419
+ titleBarStyle: isMac ? "hiddenInset" : void 0,
420
+ trafficLightPosition: isMac ? { x: 12, y: 12 } : void 0,
421
+ webPreferences: {
422
+ preload: previewPreloadPath,
423
+ nodeIntegration: false,
424
+ contextIsolation: true,
425
+ // 注意:以下设置会触发安全警告,但这是必要的
426
+ // - sandbox: false - 需要加载本地媒体文件(file:// 协议)
427
+ // - webSecurity: false - 允许加载本地文件和 Iconify CDN
428
+ // 这些警告在开发环境中显示,打包后不会显示
429
+ sandbox: false,
430
+ webSecurity: false
431
+ }
432
+ });
433
+ const previewHtmlPath = getPreviewHtmlPath();
434
+ const searchParams = new URLSearchParams({
435
+ type: mediaType,
436
+ url: encodeURIComponent(fileUrl),
437
+ name: encodeURIComponent(fileName),
438
+ platform: isMac ? "darwin" : isWin ? "win32" : "linux"
439
+ });
440
+ previewWindow.loadFile(previewHtmlPath, { search: searchParams.toString() });
441
+ previewWindow.once("ready-to-show", () => {
442
+ previewWindow.show();
443
+ });
444
+ previewWindow.on("maximize", () => {
445
+ previewWindow.webContents.send(previewChannel("maximizeChange"), true);
446
+ });
447
+ previewWindow.on("unmaximize", () => {
448
+ previewWindow.webContents.send(previewChannel("maximizeChange"), false);
449
+ });
450
+ previewWindow.on("closed", () => {
451
+ mediaPreviewWindows.delete(filePath);
452
+ });
453
+ mediaPreviewWindows.set(filePath, previewWindow);
454
+ return { success: true };
455
+ } catch (error) {
456
+ return { success: false, error: String(error) };
457
+ }
458
+ });
459
+ ipcMain.handle(channel("closeMediaPreviewWindow"), async (_event, filePath) => {
460
+ const window = mediaPreviewWindows.get(filePath);
461
+ if (window && !window.isDestroyed()) {
462
+ window.close();
463
+ mediaPreviewWindows.delete(filePath);
464
+ }
465
+ return { success: true };
466
+ });
467
+ ipcMain.on(previewChannel("minimize"), (event) => {
468
+ const win = BrowserWindow.fromWebContents(event.sender);
469
+ if (win) {
470
+ win.minimize();
471
+ }
472
+ });
473
+ ipcMain.on(previewChannel("toggleMaximize"), (event) => {
474
+ const win = BrowserWindow.fromWebContents(event.sender);
475
+ if (win) {
476
+ if (win.isMaximized()) {
477
+ win.unmaximize();
478
+ } else {
479
+ win.maximize();
480
+ }
481
+ }
482
+ });
483
+ ipcMain.on(previewChannel("close"), (event) => {
484
+ const win = BrowserWindow.fromWebContents(event.sender);
485
+ if (win) {
486
+ win.close();
487
+ }
488
+ });
489
+ ipcMain.on(previewChannel("hide"), (event) => {
490
+ const win = BrowserWindow.fromWebContents(event.sender);
491
+ if (win) {
492
+ win.hide();
493
+ }
494
+ });
495
+ ipcMain.on(previewChannel("show"), (event) => {
496
+ const win = BrowserWindow.fromWebContents(event.sender);
497
+ if (win) {
498
+ win.show();
499
+ }
500
+ });
211
501
  console.log("\u2705 File Explorer IPC handlers registered");
212
502
  }
213
503
  export {