Merge pull request 'Fix #3,#4,#5,#6,#7,#8,#9: Security hardening - Electron context isolation, XSS fixes, command injection fix' (#13) from fix/security-all into main
Reviewed-on: https://git.example.com/jarianc/MovieMapper/pulls/13
This commit is contained in:
commit
f6e9d5457a
23
index.html
23
index.html
@ -637,6 +637,29 @@
|
||||
<span id="tagged-count">0</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Module system polyfill for renderer (contextIsolation enabled, nodeIntegration disabled)
|
||||
window.require = window.createModuleRequire();
|
||||
var module = { exports: {} };
|
||||
</script>
|
||||
<script src="utils/renderer/AppState.js"></script>
|
||||
<script>window.AppState = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/FileListManager.js"></script>
|
||||
<script>window.FileListManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/FileManager.js"></script>
|
||||
<script>window.FileManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/TagManager.js"></script>
|
||||
<script>window.TagManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/EpisodeManager.js"></script>
|
||||
<script>window.EpisodeManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/SearchManager.js"></script>
|
||||
<script>window.SearchManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/ModalManager.js"></script>
|
||||
<script>window.ModalManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/ProgressManager.js"></script>
|
||||
<script>window.ProgressManager = module.exports; module={exports:{};};</script>
|
||||
<script src="utils/renderer/UIManager.js"></script>
|
||||
<script>window.UIManager = module.exports; module={exports:{};};</script>
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
51
main.js
51
main.js
@ -31,9 +31,10 @@ function createWindow() {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -844,24 +845,28 @@ ipcMain.handle('move-to-extras', async (event, filePath) => {
|
||||
});
|
||||
|
||||
// IPC handler for opening file in default player
|
||||
// FIXED: Use execFile instead of exec to prevent command injection
|
||||
ipcMain.handle('open-file-in-player', async (event, filePath) => {
|
||||
try {
|
||||
const { exec } = require('child_process');
|
||||
const { execFile } = require('child_process');
|
||||
|
||||
// Open file in default player based on OS
|
||||
let command;
|
||||
// Open file in default player based on OS using execFile (safe against injection)
|
||||
let command, args;
|
||||
if (process.platform === 'darwin') {
|
||||
// macOS
|
||||
command = `open "${filePath}"`;
|
||||
command = 'open';
|
||||
args = [filePath];
|
||||
} else if (process.platform === 'win32') {
|
||||
// Windows
|
||||
command = `start "" "${filePath}"`;
|
||||
command = 'cmd.exe';
|
||||
args = ['/c', 'start', '""', filePath];
|
||||
} else {
|
||||
// Linux
|
||||
command = `xdg-open "${filePath}"`;
|
||||
command = 'xdg-open';
|
||||
args = [filePath];
|
||||
}
|
||||
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
execFile(command, args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error('Error opening file:', error);
|
||||
} else {
|
||||
@ -876,6 +881,32 @@ ipcMain.handle('open-file-in-player', async (event, filePath) => {
|
||||
}
|
||||
});
|
||||
|
||||
// IPC handler for validating folder path (renderer can't access fs directly)
|
||||
ipcMain.handle('validate-folder', async (event, folderPath) => {
|
||||
try {
|
||||
const stats = fs.statSync(folderPath);
|
||||
return { success: true, exists: true, isDirectory: stats.isDirectory() };
|
||||
} catch (error) {
|
||||
return { success: false, exists: false, isDirectory: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// IPC handler for getting file stats (renderer can't access fs directly)
|
||||
ipcMain.handle('get-file-stats', async (event, filePath) => {
|
||||
try {
|
||||
const stats = fs.statSync(filePath);
|
||||
return {
|
||||
success: true,
|
||||
size: stats.size,
|
||||
mtime: stats.mtime,
|
||||
isFile: stats.isFile(),
|
||||
isDirectory: stats.isDirectory()
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// IPC handler for logging audit events
|
||||
ipcMain.handle('log-audit-event', async (event, { directoryPath, action, details }) => {
|
||||
try {
|
||||
|
||||
53
preload.js
Normal file
53
preload.js
Normal file
@ -0,0 +1,53 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
const fakeIpcRenderer = {
|
||||
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
|
||||
on: (channel, callback) => {
|
||||
const listener = (_event, ...args) => callback(...args);
|
||||
ipcRenderer.on(channel, listener);
|
||||
return () => ipcRenderer.removeListener(channel, listener);
|
||||
},
|
||||
};
|
||||
|
||||
const fakeElectronModule = {
|
||||
ipcRenderer: fakeIpcRenderer,
|
||||
app: null,
|
||||
BrowserWindow: null,
|
||||
dialog: null,
|
||||
ipcMain: null,
|
||||
};
|
||||
|
||||
const fakePathModule = {
|
||||
join: (...args) => path.join(...args),
|
||||
dirname: (p) => path.dirname(p),
|
||||
basename: (p) => path.basename(p),
|
||||
extname: (p) => path.extname(p),
|
||||
sep: path.sep,
|
||||
resolve: (...args) => path.resolve(...args),
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
ipcRenderer: fakeIpcRenderer,
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('createModuleRequire', () => {
|
||||
return function requirePolyfill(moduleName) {
|
||||
if (moduleName === 'electron') {
|
||||
return fakeElectronModule;
|
||||
}
|
||||
if (moduleName === 'path') {
|
||||
return fakePathModule;
|
||||
}
|
||||
if (moduleName === 'fs') {
|
||||
throw new Error('Direct fs access disabled. Use IPC.');
|
||||
}
|
||||
if (moduleName.startsWith('./')) {
|
||||
const baseName = moduleName.replace('./', '').replace('.js', '');
|
||||
const mod = window[baseName];
|
||||
if (mod) return mod;
|
||||
throw new Error(`Module not found: ${moduleName}`);
|
||||
}
|
||||
throw new Error(`Module not allowed: ${moduleName}`);
|
||||
};
|
||||
});
|
||||
11
renderer.js
11
renderer.js
@ -1,4 +1,13 @@
|
||||
const UIManager = require('./utils/renderer/UIManager');
|
||||
// UIManager is loaded via window.UIManager from index.html
|
||||
const UIManager = window.UIManager;
|
||||
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
window.escapeHtml = escapeHtml;
|
||||
|
||||
// Initialize the application when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
@ -60,24 +60,37 @@ class FileListManager {
|
||||
_createFolderElement(file) {
|
||||
const fileItem = document.createElement('div');
|
||||
fileItem.className = 'file-item folder-item';
|
||||
fileItem.innerHTML = `
|
||||
<div style="font-size: 18px; margin-right: 10px;">📁</div>
|
||||
<div class="file-name folder-name" data-file-path="${file.path}">${file.name}</div>
|
||||
<div class="file-duration"></div>
|
||||
<div class="file-quality"></div>
|
||||
<div class="file-fps"></div>
|
||||
<div class="file-tags"></div>
|
||||
`;
|
||||
|
||||
// Add click handler to navigate into folder
|
||||
const icon = document.createElement('div');
|
||||
icon.style.cssText = 'font-size: 18px; margin-right: 10px;';
|
||||
icon.textContent = '\uD83D\uDCC1';
|
||||
|
||||
const nameEl = document.createElement('div');
|
||||
nameEl.className = 'file-name folder-name';
|
||||
nameEl.textContent = file.name;
|
||||
|
||||
const durationEl = document.createElement('div');
|
||||
durationEl.className = 'file-duration';
|
||||
|
||||
const qualityEl = document.createElement('div');
|
||||
qualityEl.className = 'file-quality';
|
||||
|
||||
const fpsEl = document.createElement('div');
|
||||
fpsEl.className = 'file-fps';
|
||||
|
||||
const tagsEl = document.createElement('div');
|
||||
tagsEl.className = 'file-tags';
|
||||
|
||||
fileItem.append(icon, nameEl, durationEl, qualityEl, fpsEl, tagsEl);
|
||||
|
||||
const filePath = file.path;
|
||||
const fileName = file.name;
|
||||
fileItem.addEventListener('click', (e) => {
|
||||
// Don't trigger if clicking on any child elements
|
||||
if (e.target !== fileItem && e.target.className !== 'file-name folder-name') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.onFolderClick) {
|
||||
this.onFolderClick(file.path, file.name);
|
||||
this.onFolderClick(filePath, fileName);
|
||||
}
|
||||
});
|
||||
|
||||
@ -97,35 +110,91 @@ class FileListManager {
|
||||
fileItem.dataset.index = index;
|
||||
fileItem.dataset.filePath = file.path;
|
||||
|
||||
// Use actual duration from file metadata
|
||||
const duration = file.duration || '00:00';
|
||||
const quality = file.quality || 'unknown';
|
||||
const fps = file.fps || 'unknown';
|
||||
|
||||
// Add label for problematic files
|
||||
const problemLabel = file.isProblematic ? '<span class="problematic-label">⚠️</span> ' : '';
|
||||
const dragHandle = document.createElement('div');
|
||||
dragHandle.className = 'drag-handle';
|
||||
dragHandle.textContent = '\u22EE\u22EE';
|
||||
|
||||
fileItem.innerHTML = `
|
||||
<div class="drag-handle">⋮⋮</div>
|
||||
<div class="episode-number-container">
|
||||
<button class="episode-arrow episode-arrow-left" data-file-path="${file.path}" title="Decrease episode range">◀</button>
|
||||
<div class="episode-number" data-episode-start="${episodeNumber}" data-episode-end="${episodeNumber}">${episodeNumber}</div>
|
||||
<button class="episode-arrow episode-arrow-right" data-file-path="${file.path}" title="Increase episode range">▶</button>
|
||||
</div>
|
||||
<div class="file-name" data-file-path="${file.path}">${file.name}</div>
|
||||
<div class="file-duration">${duration}</div>
|
||||
<div class="file-quality">${quality}</div>
|
||||
<div class="file-fps">${fps}</div>
|
||||
<div class="file-tags">
|
||||
<span class="tag-icon extra-tag" data-file-path="${file.path}" title="Mark as Extra">🏷️</span>
|
||||
<span class="tag-icon behind-the-scenes-tag" data-file-path="${file.path}" title="Add Behind the Scenes">🎥</span>
|
||||
<span class="tag-icon delete-tag" data-file-path="${file.path}" title="Mark for Deletion">🗑️</span>
|
||||
<span class="video-preview-btn" data-file-path="${file.path}" title="Preview Video">🎬</span>
|
||||
<button class="play-button" style="opacity: 0.3; cursor: default; flex-shrink: 0;" data-file-path="${file.path}" disabled>▶️</button>
|
||||
</div>
|
||||
`;
|
||||
const episodeContainer = document.createElement('div');
|
||||
episodeContainer.className = 'episode-number-container';
|
||||
|
||||
const leftArrow = document.createElement('button');
|
||||
leftArrow.className = 'episode-arrow episode-arrow-left';
|
||||
leftArrow.title = 'Decrease episode range';
|
||||
leftArrow.textContent = '\u25C0';
|
||||
|
||||
const episodeNum = document.createElement('div');
|
||||
episodeNum.className = 'episode-number';
|
||||
episodeNum.dataset.episodeStart = episodeNumber;
|
||||
episodeNum.dataset.episodeEnd = episodeNumber;
|
||||
episodeNum.textContent = episodeNumber;
|
||||
|
||||
const rightArrow = document.createElement('button');
|
||||
rightArrow.className = 'episode-arrow episode-arrow-right';
|
||||
rightArrow.title = 'Increase episode range';
|
||||
rightArrow.textContent = '\u25B6';
|
||||
|
||||
episodeContainer.append(leftArrow, episodeNum, rightArrow);
|
||||
|
||||
const nameEl = document.createElement('div');
|
||||
nameEl.className = 'file-name';
|
||||
nameEl.textContent = file.name;
|
||||
|
||||
if (file.isProblematic) {
|
||||
const warn = document.createElement('span');
|
||||
warn.className = 'problematic-label';
|
||||
warn.textContent = '\u26A0\ufe0f';
|
||||
nameEl.prepend(warn, ' ');
|
||||
}
|
||||
|
||||
const durEl = document.createElement('div');
|
||||
durEl.className = 'file-duration';
|
||||
durEl.textContent = duration;
|
||||
|
||||
const qualEl = document.createElement('div');
|
||||
qualEl.className = 'file-quality';
|
||||
qualEl.textContent = quality;
|
||||
|
||||
const fpsEl = document.createElement('div');
|
||||
fpsEl.className = 'file-fps';
|
||||
fpsEl.textContent = fps;
|
||||
|
||||
const tagsEl = document.createElement('div');
|
||||
tagsEl.className = 'file-tags';
|
||||
|
||||
const extraTag = document.createElement('span');
|
||||
extraTag.className = 'tag-icon extra-tag';
|
||||
extraTag.title = 'Mark as Extra';
|
||||
extraTag.textContent = '\uD83C\uDFF7\uFE0F';
|
||||
|
||||
const btsTag = document.createElement('span');
|
||||
btsTag.className = 'tag-icon behind-the-scenes-tag';
|
||||
btsTag.title = 'Add Behind the Scenes';
|
||||
btsTag.textContent = '\uD83C\uDFA5';
|
||||
|
||||
const delTag = document.createElement('span');
|
||||
delTag.className = 'tag-icon delete-tag';
|
||||
delTag.title = 'Mark for Deletion';
|
||||
delTag.textContent = '\uD83D\uDDD1\uFE0F';
|
||||
|
||||
const previewBtn = document.createElement('span');
|
||||
previewBtn.className = 'video-preview-btn';
|
||||
previewBtn.title = 'Preview Video';
|
||||
previewBtn.textContent = '\uD83C\uDFAC';
|
||||
|
||||
const playBtn = document.createElement('button');
|
||||
playBtn.className = 'play-button';
|
||||
playBtn.style.cssText = 'opacity: 0.3; cursor: default; flex-shrink: 0;';
|
||||
playBtn.disabled = true;
|
||||
playBtn.textContent = '\u25B6\uFE0F';
|
||||
|
||||
tagsEl.append(extraTag, btsTag, delTag, previewBtn, playBtn);
|
||||
|
||||
fileItem.append(dragHandle, episodeContainer, nameEl, durEl, qualEl, fpsEl, tagsEl);
|
||||
|
||||
// Add drag and drop event listeners
|
||||
fileItem.addEventListener('dragstart', this._handleDragStart.bind(this));
|
||||
fileItem.addEventListener('dragend', this._handleDragEnd.bind(this));
|
||||
fileItem.addEventListener('dragover', this._handleDragOver.bind(this));
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const fs = require('fs');
|
||||
const { ipcRenderer } = window.electronAPI;
|
||||
|
||||
/**
|
||||
* ModalManager - Manages video preview modal
|
||||
@ -142,50 +141,42 @@ class ModalManager {
|
||||
const videoContainer = document.getElementById('video-preview-container');
|
||||
const loadingIndicator = document.getElementById('video-preview-loading');
|
||||
|
||||
// Show loading
|
||||
loadingIndicator.style.display = 'block';
|
||||
videoContainer.innerHTML = '';
|
||||
fileInfo.innerHTML = `<strong>Loading:</strong> ${filePath.split('/').pop()}`;
|
||||
videoContainer.textContent = '';
|
||||
const fileName = filePath.split('/').pop();
|
||||
fileInfo.textContent = `Loading: ${fileName}`;
|
||||
|
||||
try {
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
const statsResult = await ipcRenderer.invoke('get-file-stats', filePath);
|
||||
if (!statsResult.success) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
|
||||
// Get file stats for info
|
||||
const stats = fs.statSync(filePath);
|
||||
const fileName = filePath.split('/').pop();
|
||||
fileInfo.textContent = `File: ${fileName} | Size: ${(statsResult.size / (1024 * 1024)).toFixed(2)} MB | Path: ${filePath}`;
|
||||
|
||||
// Update file info
|
||||
fileInfo.innerHTML = `
|
||||
<strong>File:</strong> ${fileName}<br>
|
||||
<strong>Size:</strong> ${(stats.size / (1024 * 1024)).toFixed(2)} MB<br>
|
||||
<strong>Path:</strong> ${filePath}
|
||||
`;
|
||||
|
||||
// For MKV files, open directly in default player
|
||||
const ext = filePath.toLowerCase().split('.').pop();
|
||||
if (ext === 'mkv') {
|
||||
// Close the modal and open in default player directly
|
||||
this.videoPreviewModal.style.display = 'none';
|
||||
ipcRenderer.invoke('open-file-in-player', filePath);
|
||||
} else {
|
||||
// For other formats, try to create a video player
|
||||
videoContainer.innerHTML = `
|
||||
<video id="preview-video" controls style="width: 100%; max-width: 800px; height: auto; margin: 0 auto; display: block;">
|
||||
<source src="${filePath}" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
`;
|
||||
|
||||
// Hide loading
|
||||
const video = document.createElement('video');
|
||||
video.id = 'preview-video';
|
||||
video.controls = true;
|
||||
video.style.cssText = 'width: 100%; max-width: 800px; height: auto; margin: 0 auto; display: block;';
|
||||
const source = document.createElement('source');
|
||||
source.src = `file://${filePath}`;
|
||||
source.type = 'video/mp4';
|
||||
video.appendChild(source);
|
||||
videoContainer.appendChild(video);
|
||||
loadingIndicator.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading video preview:', error);
|
||||
fileInfo.innerHTML = `<strong>Error:</strong> Could not load preview for ${filePath}`;
|
||||
videoContainer.innerHTML = '<p style="color: #dc3545; text-align: center;">Error loading video preview</p>';
|
||||
fileInfo.textContent = 'Error: Could not load preview';
|
||||
const p = document.createElement('p');
|
||||
p.style.cssText = 'color: #dc3545; text-align: center;';
|
||||
p.textContent = 'Error loading video preview';
|
||||
videoContainer.appendChild(p);
|
||||
loadingIndicator.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const path = require('path');
|
||||
const { ipcRenderer } = window.electronAPI;
|
||||
const AppState = require('./AppState');
|
||||
const FileListManager = require('./FileListManager');
|
||||
const TagManager = require('./TagManager');
|
||||
@ -417,7 +417,10 @@ class UIManager {
|
||||
this.searchResultsEl.innerHTML = '';
|
||||
|
||||
if (results.length === 0) {
|
||||
this.searchResultsEl.innerHTML = '<p style="color: #888; text-align: center; padding: 10px;">No shows found.</p>';
|
||||
const p = document.createElement('p');
|
||||
p.style.cssText = 'color: #888; text-align: center; padding: 10px;';
|
||||
p.textContent = 'No shows found.';
|
||||
this.searchResultsEl.appendChild(p);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -425,14 +428,20 @@ class UIManager {
|
||||
const resultItem = document.createElement('div');
|
||||
resultItem.className = 'search-result-item';
|
||||
|
||||
// Create a more detailed display with separate elements
|
||||
const showName = show.seriesName || show.name;
|
||||
const showYear = show.firstAired ? show.firstAired.split('-')[0] : '';
|
||||
|
||||
resultItem.innerHTML = `
|
||||
<div class="show-name">${showName}</div>
|
||||
${showYear ? `<div class="show-year">${showYear}</div>` : ''}
|
||||
`;
|
||||
const nameEl = document.createElement('div');
|
||||
nameEl.className = 'show-name';
|
||||
nameEl.textContent = showName;
|
||||
resultItem.appendChild(nameEl);
|
||||
|
||||
if (showYear) {
|
||||
const yearEl = document.createElement('div');
|
||||
yearEl.className = 'show-year';
|
||||
yearEl.textContent = showYear;
|
||||
resultItem.appendChild(yearEl);
|
||||
}
|
||||
|
||||
resultItem.addEventListener('click', () => this.selectShow(show));
|
||||
this.searchResultsEl.appendChild(resultItem);
|
||||
@ -1609,60 +1618,74 @@ class UIManager {
|
||||
const videoContainer = document.getElementById('video-preview-container');
|
||||
const loadingIndicator = document.getElementById('video-preview-loading');
|
||||
|
||||
// Show loading
|
||||
loadingIndicator.style.display = 'block';
|
||||
if (videoContainer) videoContainer.innerHTML = '';
|
||||
if (fileInfo) fileInfo.innerHTML = `<strong>Loading:</strong> ${filePath.split('/').pop()}`;
|
||||
if (fileInfo) {
|
||||
const fileName = filePath.split('/').pop();
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = 'Loading: ';
|
||||
fileInfo.textContent = '';
|
||||
fileInfo.appendChild(strong);
|
||||
fileInfo.appendChild(document.createTextNode(fileName));
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if file exists
|
||||
const fs = require('fs');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
const statsResult = await ipcRenderer.invoke('get-file-stats', filePath);
|
||||
if (!statsResult.success) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
|
||||
// Get file stats for info
|
||||
const stats = fs.statSync(filePath);
|
||||
const fileName = filePath.split('/').pop();
|
||||
|
||||
// Update file info
|
||||
if (fileInfo) {
|
||||
fileInfo.innerHTML = `
|
||||
<strong>File:</strong> ${fileName}<br>
|
||||
<strong>Size:</strong> ${(stats.size / (1024 * 1024)).toFixed(2)} MB<br>
|
||||
<strong>Path:</strong> ${filePath}
|
||||
`;
|
||||
fileInfo.textContent = '';
|
||||
const parts = [
|
||||
{ bold: 'File: ', text: fileName },
|
||||
{ bold: '\nSize: ', text: `${(statsResult.size / (1024 * 1024)).toFixed(2)} MB` },
|
||||
{ bold: '\nPath: ', text: filePath }
|
||||
];
|
||||
parts.forEach(p => {
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = p.bold;
|
||||
fileInfo.appendChild(strong);
|
||||
fileInfo.appendChild(document.createTextNode(p.text));
|
||||
});
|
||||
}
|
||||
|
||||
// For MKV files, open directly in default player
|
||||
const ext = filePath.toLowerCase().split('.').pop();
|
||||
if (ext === 'mkv') {
|
||||
// Close the modal and open in default player directly
|
||||
if (this.videoPreviewModal) {
|
||||
this.videoPreviewModal.style.display = 'none';
|
||||
}
|
||||
ipcRenderer.invoke('open-file-in-player', filePath);
|
||||
} else {
|
||||
// For other formats, try to create a video player
|
||||
if (videoContainer) {
|
||||
videoContainer.innerHTML = `
|
||||
<video id="preview-video" controls style="width: 100%; max-width: 800px; height: auto; margin: 0 auto; display: block;">
|
||||
<source src="${filePath}" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
`;
|
||||
const video = document.createElement('video');
|
||||
video.id = 'preview-video';
|
||||
video.controls = true;
|
||||
video.style.cssText = 'width: 100%; max-width: 800px; height: auto; margin: 0 auto; display: block;';
|
||||
const source = document.createElement('source');
|
||||
source.src = `file://${filePath}`;
|
||||
source.type = 'video/mp4';
|
||||
video.appendChild(source);
|
||||
videoContainer.appendChild(video);
|
||||
}
|
||||
|
||||
// Hide loading
|
||||
if (loadingIndicator) loadingIndicator.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading video preview:', error);
|
||||
if (fileInfo) {
|
||||
fileInfo.innerHTML = `<strong>Error:</strong> Could not load preview for ${filePath}`;
|
||||
fileInfo.textContent = '';
|
||||
const strong = document.createElement('strong');
|
||||
strong.textContent = 'Error: ';
|
||||
fileInfo.appendChild(strong);
|
||||
fileInfo.appendChild(document.createTextNode('Could not load preview'));
|
||||
}
|
||||
if (videoContainer) {
|
||||
videoContainer.innerHTML = '<p style="color: #dc3545; text-align: center;">Error loading video preview</p>';
|
||||
const p = document.createElement('p');
|
||||
p.style.cssText = 'color: #dc3545; text-align: center;';
|
||||
p.textContent = 'Error loading video preview';
|
||||
videoContainer.appendChild(p);
|
||||
}
|
||||
if (loadingIndicator) loadingIndicator.style.display = 'none';
|
||||
}
|
||||
@ -1698,14 +1721,13 @@ class UIManager {
|
||||
async handleFolderClick(folderPath, folderName) {
|
||||
console.log('Folder clicked:', folderName, 'at path:', folderPath);
|
||||
|
||||
// Validate folder path exists
|
||||
const fs = require('fs');
|
||||
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
||||
// Validate folder path exists via IPC (no direct fs access)
|
||||
const validation = await ipcRenderer.invoke('validate-folder', folderPath);
|
||||
if (!validation.exists || !validation.isDirectory) {
|
||||
console.error('Invalid folder path:', folderPath);
|
||||
return; // Don't navigate
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the folder
|
||||
await this.openDirectory(folderPath);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user