Neue Features: - 3-Panel Dateimanager (Ordnerbaum, Dateiliste, Vorschau) - Separates Vorschau-Fenster für zweiten Monitor - Resize-Handles für flexible Panel-Größen (horizontal & vertikal) - Vorschau-Panel ausblendbar wenn externes Fenster aktiv - Natürliche Sortierung (Sonderzeichen → Zahlen → Buchstaben) - PDF-Vorschau mit Fit-to-Page - Email-Attachment Abruf erweitert Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
376 lines
12 KiB
HTML
376 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Vorschau - Dateiverwaltung</title>
|
|
<link rel="stylesheet" href="/static/css/style.css">
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* Header */
|
|
.preview-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0.5rem 1rem;
|
|
background: var(--bg-secondary);
|
|
border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.preview-header h1 {
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.preview-header .filename {
|
|
font-weight: normal;
|
|
color: var(--text-secondary);
|
|
max-width: 400px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.preview-header .controls {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: var(--danger);
|
|
}
|
|
|
|
.status-indicator.connected {
|
|
background: var(--success);
|
|
}
|
|
|
|
.btn {
|
|
padding: 0.375rem 0.75rem;
|
|
font-size: 0.8rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
/* Preview Container */
|
|
.preview-container {
|
|
flex: 1;
|
|
overflow: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--bg-tertiary);
|
|
padding: 1rem;
|
|
}
|
|
|
|
.preview-placeholder {
|
|
text-align: center;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.preview-placeholder .icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.preview-placeholder p {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.preview-placeholder .hint {
|
|
font-size: 0.85rem;
|
|
margin-top: 0.5rem;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* PDF Preview */
|
|
.preview-pdf {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: none;
|
|
background: white;
|
|
}
|
|
|
|
/* Image Preview */
|
|
.preview-image {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
/* Text Preview */
|
|
.preview-text {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
padding: 1rem;
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
font-size: 0.85rem;
|
|
overflow: auto;
|
|
border-radius: var(--radius);
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
/* No Preview */
|
|
.preview-unavailable {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.preview-unavailable .file-type-icon {
|
|
font-size: 5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.preview-unavailable p {
|
|
color: var(--text-secondary);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.preview-unavailable .btn {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
/* Theme Select */
|
|
.theme-select {
|
|
padding: 0.25rem 0.5rem;
|
|
font-size: 0.8rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="preview-header">
|
|
<h1>
|
|
👁 Vorschau
|
|
<span id="filename" class="filename">Keine Datei ausgewählt</span>
|
|
</h1>
|
|
<div class="controls">
|
|
<div id="status" class="status-indicator" title="Nicht verbunden"></div>
|
|
<select id="theme-select" class="theme-select" onchange="wechsleTheme(this.value)">
|
|
<option value="auto">🎨 Auto</option>
|
|
<option value="dark">🌙 Dark</option>
|
|
<option value="breeze-dark">🌙 Breeze Dark</option>
|
|
<option value="breeze-light">☀️ Breeze Light</option>
|
|
</select>
|
|
<button class="btn" onclick="window.close()">✕ Schließen</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div id="preview-container" class="preview-container">
|
|
<div class="preview-placeholder">
|
|
<div class="icon">📂</div>
|
|
<p>Warte auf Dateiauswahl...</p>
|
|
<p class="hint">Wähle eine Datei im Hauptfenster aus</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// ============ BroadcastChannel für Kommunikation ============
|
|
const channel = new BroadcastChannel('dateiverwaltung-preview');
|
|
let currentFile = null;
|
|
|
|
// Status anzeigen
|
|
function setConnected(connected) {
|
|
const status = document.getElementById('status');
|
|
if (connected) {
|
|
status.classList.add('connected');
|
|
status.title = 'Verbunden mit Hauptfenster';
|
|
} else {
|
|
status.classList.remove('connected');
|
|
status.title = 'Nicht verbunden';
|
|
}
|
|
}
|
|
|
|
// Nachricht vom Hauptfenster empfangen
|
|
channel.onmessage = (event) => {
|
|
const { type, data } = event.data;
|
|
|
|
switch (type) {
|
|
case 'preview':
|
|
ladeVorschau(data.path, data.name);
|
|
setConnected(true);
|
|
break;
|
|
case 'clear':
|
|
zeigeWartePlaceholder();
|
|
break;
|
|
case 'ping':
|
|
// Bestätigung senden
|
|
channel.postMessage({ type: 'pong' });
|
|
setConnected(true);
|
|
break;
|
|
}
|
|
};
|
|
|
|
// Beim Start Ping senden
|
|
channel.postMessage({ type: 'preview-window-ready' });
|
|
|
|
// ============ Theme ============
|
|
function ladeTheme() {
|
|
const gespeichertesTheme = localStorage.getItem('theme') || 'auto';
|
|
wendeThemeAn(gespeichertesTheme);
|
|
document.getElementById('theme-select').value = gespeichertesTheme;
|
|
}
|
|
|
|
function wendeThemeAn(theme) {
|
|
const html = document.documentElement;
|
|
if (theme === 'auto') {
|
|
html.removeAttribute('data-theme');
|
|
} else {
|
|
html.setAttribute('data-theme', theme);
|
|
}
|
|
}
|
|
|
|
function wechsleTheme(theme) {
|
|
wendeThemeAn(theme);
|
|
localStorage.setItem('theme', theme);
|
|
}
|
|
|
|
// ============ Vorschau ============
|
|
async function ladeVorschau(pfad, name) {
|
|
currentFile = { path: pfad, name: name };
|
|
document.getElementById('filename').textContent = name;
|
|
document.title = `${name} - Vorschau`;
|
|
|
|
const container = document.getElementById('preview-container');
|
|
const ext = name.split('.').pop().toLowerCase();
|
|
|
|
// PDF Vorschau - mit fit-to-page für erste Seite
|
|
if (ext === 'pdf') {
|
|
container.innerHTML = `<iframe class="preview-pdf" src="/api/file/preview?path=${encodeURIComponent(pfad)}&t=${Date.now()}#page=1&view=FitV"></iframe>`;
|
|
return;
|
|
}
|
|
|
|
// Bild Vorschau
|
|
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff'].includes(ext)) {
|
|
container.innerHTML = `<img class="preview-image" src="/api/file/preview?path=${encodeURIComponent(pfad)}&t=${Date.now()}" alt="${name}">`;
|
|
return;
|
|
}
|
|
|
|
// Text-basierte Dateien
|
|
if (['txt', 'md', 'log', 'xml', 'json', 'csv', 'html', 'css', 'js'].includes(ext)) {
|
|
try {
|
|
const response = await fetch(`/api/file/text?path=${encodeURIComponent(pfad)}`);
|
|
const result = await response.json();
|
|
if (result.content) {
|
|
container.innerHTML = `<pre class="preview-text">${escapeHtml(result.content)}</pre>`;
|
|
} else {
|
|
zeigeKeineVorschau(name, ext);
|
|
}
|
|
} catch (e) {
|
|
zeigeKeineVorschau(name, ext);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Keine Vorschau verfügbar
|
|
zeigeKeineVorschau(name, ext);
|
|
}
|
|
|
|
function zeigeKeineVorschau(name, ext) {
|
|
const icon = getFileIcon(name);
|
|
document.getElementById('preview-container').innerHTML = `
|
|
<div class="preview-unavailable">
|
|
<div class="file-type-icon">${icon}</div>
|
|
<p>Keine Vorschau für .${ext} Dateien</p>
|
|
<button class="btn" onclick="dateiExternOeffnen()">🔗 Extern öffnen</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function zeigeWartePlaceholder() {
|
|
document.getElementById('filename').textContent = 'Keine Datei ausgewählt';
|
|
document.title = 'Vorschau - Dateiverwaltung';
|
|
document.getElementById('preview-container').innerHTML = `
|
|
<div class="preview-placeholder">
|
|
<div class="icon">📂</div>
|
|
<p>Warte auf Dateiauswahl...</p>
|
|
<p class="hint">Wähle eine Datei im Hauptfenster aus</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function getFileIcon(name) {
|
|
const ext = name.split('.').pop().toLowerCase();
|
|
const icons = {
|
|
'pdf': '📄',
|
|
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'bmp': '🖼️', 'tiff': '🖼️', 'webp': '🖼️',
|
|
'doc': '📝', 'docx': '📝', 'odt': '📝',
|
|
'xls': '📊', 'xlsx': '📊', 'ods': '📊', 'csv': '📊',
|
|
'zip': '📦', 'rar': '📦', '7z': '📦', 'tar': '📦', 'gz': '📦',
|
|
'txt': '📃', 'md': '📃', 'log': '📃',
|
|
'mp3': '🎵', 'wav': '🎵', 'flac': '🎵',
|
|
'mp4': '🎬', 'avi': '🎬', 'mkv': '🎬',
|
|
'xml': '📋', 'json': '📋', 'html': '📋'
|
|
};
|
|
return icons[ext] || '📎';
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function dateiExternOeffnen() {
|
|
if (currentFile) {
|
|
window.open(`/api/file/download?path=${encodeURIComponent(currentFile.path)}`, '_blank');
|
|
}
|
|
}
|
|
|
|
// ============ Init ============
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
ladeTheme();
|
|
|
|
// Falls Pfad als URL-Parameter übergeben wurde
|
|
const params = new URLSearchParams(window.location.search);
|
|
const path = params.get('path');
|
|
if (path) {
|
|
const name = path.split('/').pop();
|
|
ladeVorschau(path, name);
|
|
}
|
|
});
|
|
|
|
// Fenster-Schließen mitteilen
|
|
window.addEventListener('beforeunload', () => {
|
|
channel.postMessage({ type: 'preview-window-closed' });
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|