/**
* Dateimanager / Browser JavaScript
* Dual-Pane Datei-Browser mit separatem Vorschau-Fenster
*/
// ============ State ============
let state = {
currentPath: '/srv/http/dateiverwaltung/data',
selectedFile: null,
selectedFilePath: null,
files: [],
folders: [],
modalBrowserPath: '/',
verschiebenPath: '/',
folderTree: {}, // Baum-Cache
expandedFolders: new Set(), // Aufgeklappte Ordner
previewWindow: null, // Referenz zum Vorschau-Fenster
previewWindowOpen: false,
previewPanelHidden: false, // Preview-Panel ausgeblendet
isVerticalMode: false // Vertikaler Layout-Modus
};
// ============ BroadcastChannel für Vorschau-Fenster ============
const previewChannel = new BroadcastChannel('dateiverwaltung-preview');
previewChannel.onmessage = (event) => {
const { type } = event.data;
switch (type) {
case 'preview-window-ready':
state.previewWindowOpen = true;
aktualisierePreviewButton();
aktualisiereHidePreviewButton();
// Aktuelle Datei an Vorschau senden
if (state.selectedFilePath) {
sendeAnVorschau(state.selectedFilePath, state.selectedFile);
}
break;
case 'preview-window-closed':
state.previewWindowOpen = false;
state.previewWindow = null;
aktualisierePreviewButton();
aktualisiereHidePreviewButton();
break;
case 'pong':
state.previewWindowOpen = true;
aktualisierePreviewButton();
aktualisiereHidePreviewButton();
break;
}
};
function sendeAnVorschau(pfad, name) {
previewChannel.postMessage({
type: 'preview',
data: { path: pfad, name: name }
});
}
function aktualisierePreviewButton() {
const btn = document.getElementById('btn-open-preview');
if (btn) {
if (state.previewWindowOpen) {
btn.textContent = '🖥️ Vorschau-Fenster aktiv';
btn.classList.add('active');
} else {
btn.textContent = '🖥️ Vorschau-Fenster öffnen';
btn.classList.remove('active');
}
}
}
function aktualisiereHidePreviewButton() {
const btnHide = document.getElementById('btn-hide-preview');
const btnShow = document.getElementById('btn-show-preview');
// "Ausblenden" Button nur zeigen wenn Preview-Fenster aktiv
if (btnHide) {
if (state.previewWindowOpen && !state.previewPanelHidden) {
btnHide.classList.remove('hidden');
} else {
btnHide.classList.add('hidden');
}
}
// "Einblenden" Button zeigen wenn Panel ausgeblendet
if (btnShow) {
if (state.previewPanelHidden) {
btnShow.classList.remove('hidden');
} else {
btnShow.classList.add('hidden');
}
}
}
// ============ API Helper ============
async function api(endpoint, options = {}) {
const url = `/api${endpoint}`;
const config = {
headers: { 'Content-Type': 'application/json' },
...options
};
try {
const response = await fetch(url, config);
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
// ============ Initialisierung ============
document.addEventListener('DOMContentLoaded', () => {
ladeTheme();
pruefeLayoutModus();
initResizeHandles();
// Gespeicherten Pfad laden
const gespeicherterPfad = localStorage.getItem('browser_path');
if (gespeicherterPfad) {
state.currentPath = gespeicherterPfad;
}
// Aufgeklappte Ordner laden
const gespeicherteExpanded = localStorage.getItem('browser_expanded');
if (gespeicherteExpanded) {
try {
state.expandedFolders = new Set(JSON.parse(gespeicherteExpanded));
} catch (e) {}
}
// Preview-Panel Status laden
const previewHidden = localStorage.getItem('browser_preview_hidden');
if (previewHidden === 'true') {
state.previewPanelHidden = true;
togglePreviewPanelUI(true);
}
// Baum initialisieren
ladeBaum('/');
ladeOrdnerInhalt(state.currentPath);
// Keyboard shortcuts
document.addEventListener('keydown', handleKeydown);
// Context menu schließen bei Klick außerhalb
document.addEventListener('click', () => {
const menu = document.querySelector('.context-menu');
if (menu) menu.remove();
});
// Prüfen ob Vorschau-Fenster bereits offen
previewChannel.postMessage({ type: 'ping' });
// Layout bei Resize prüfen
window.addEventListener('resize', pruefeLayoutModus);
});
// ============ Layout Modus ============
function pruefeLayoutModus() {
const istVertical = window.innerWidth <= 1000;
if (istVertical !== state.isVerticalMode) {
state.isVerticalMode = istVertical;
const main = document.getElementById('browser-main');
if (main) {
if (istVertical) {
main.classList.add('vertical');
} else {
main.classList.remove('vertical');
}
}
// Resize-Handles neu initialisieren
initResizeHandles();
}
}
// ============ 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);
}
// ============ Preview Panel Toggle ============
function togglePreviewPanel() {
state.previewPanelHidden = !state.previewPanelHidden;
togglePreviewPanelUI(state.previewPanelHidden);
localStorage.setItem('browser_preview_hidden', state.previewPanelHidden);
aktualisiereHidePreviewButton();
}
function togglePreviewPanelUI(hidden) {
const previewPane = document.getElementById('pane-preview');
const resizeHandle = document.getElementById('resize-handle-2');
const listPane = document.getElementById('pane-list');
if (previewPane) {
if (hidden) {
previewPane.classList.add('hidden-panel');
} else {
previewPane.classList.remove('hidden-panel');
}
}
if (resizeHandle) {
if (hidden) {
resizeHandle.classList.add('hidden-panel');
} else {
resizeHandle.classList.remove('hidden-panel');
}
}
// Dateiliste expandieren wenn Preview ausgeblendet
if (listPane) {
if (hidden) {
listPane.classList.add('expanded');
listPane.style.width = ''; // Inline-Style entfernen
} else {
listPane.classList.remove('expanded');
}
}
}
// ============ Ordner-Baum ============
async function ladeBaum(startPfad) {
try {
const result = await api(`/browse?path=${encodeURIComponent(startPfad)}`);
if (result.error) return;
// Root-Knoten
if (startPfad === '/') {
state.folderTree['/'] = {
name: '/',
path: '/',
children: result.entries
.filter(e => e.type === 'directory')
.map(e => e.path)
};
}
// Unterordner cachen
result.entries.forEach(entry => {
if (entry.type === 'directory') {
if (!state.folderTree[entry.path]) {
state.folderTree[entry.path] = {
name: entry.name,
path: entry.path,
children: null, // Noch nicht geladen
loaded: false
};
}
}
});
aktualisiereBaumAnzeige();
} catch (e) {
console.error('Fehler beim Laden des Baums:', e);
}
}
async function ladeBaumKnoten(pfad) {
try {
const result = await api(`/browse?path=${encodeURIComponent(pfad)}`);
if (result.error) return;
const children = result.entries
.filter(e => e.type === 'directory')
.map(e => e.path);
state.folderTree[pfad] = {
...state.folderTree[pfad],
children: children,
loaded: true
};
// Kinder registrieren
result.entries.forEach(entry => {
if (entry.type === 'directory' && !state.folderTree[entry.path]) {
state.folderTree[entry.path] = {
name: entry.name,
path: entry.path,
children: null,
loaded: false
};
}
});
aktualisiereBaumAnzeige();
} catch (e) {
console.error('Fehler beim Laden des Knotens:', e);
}
}
function aktualisiereBaumAnzeige() {
const container = document.getElementById('folder-tree');
if (!container) return;
// Standard-Startordner
const startPfade = ['/', '/mnt', '/srv', '/home'];
let html = '';
startPfade.forEach(pfad => {
html += renderBaumKnoten(pfad, 0);
});
container.innerHTML = html || '
Keine Ordner
';
}
function renderBaumKnoten(pfad, tiefe) {
const knoten = state.folderTree[pfad];
if (!knoten) {
// Knoten existiert nicht im Cache - erstelle Platzhalter
return `
▶
📁
${pfad.split('/').pop() || pfad}
`;
}
const istExpanded = state.expandedFolders.has(pfad);
const istAktiv = state.currentPath === pfad || state.currentPath.startsWith(pfad + '/');
const hatKinder = knoten.children && knoten.children.length > 0;
const istGeladen = knoten.loaded;
let html = `
${hatKinder || !istGeladen ? (istExpanded ? '▼' : '▶') : ''}
${istExpanded ? '📂' : '📁'}
${knoten.name}
`;
// Kinder rendern wenn aufgeklappt
if (istExpanded && knoten.children) {
knoten.children.forEach(childPfad => {
html += renderBaumKnoten(childPfad, tiefe + 1);
});
}
return html;
}
async function toggleBaum(pfad) {
if (state.expandedFolders.has(pfad)) {
state.expandedFolders.delete(pfad);
} else {
state.expandedFolders.add(pfad);
// Laden wenn noch nicht geladen
const knoten = state.folderTree[pfad];
if (!knoten || !knoten.loaded) {
await ladeBaumKnoten(pfad);
}
}
// Speichern
localStorage.setItem('browser_expanded', JSON.stringify([...state.expandedFolders]));
aktualisiereBaumAnzeige();
}
async function expandiereBaum(pfad) {
if (!state.expandedFolders.has(pfad)) {
state.expandedFolders.add(pfad);
const knoten = state.folderTree[pfad];
if (!knoten || !knoten.loaded) {
await ladeBaumKnoten(pfad);
}
localStorage.setItem('browser_expanded', JSON.stringify([...state.expandedFolders]));
aktualisiereBaumAnzeige();
}
}
function waehleBaumOrdner(pfad) {
navigiereZu(pfad);
}
// ============ Ordner Navigation ============
async function ladeOrdnerInhalt(pfad) {
try {
const result = await api(`/browse/files?path=${encodeURIComponent(pfad)}`);
if (result.error) {
toast(result.error, 'error');
return;
}
state.currentPath = result.current;
state.folders = result.folders || [];
state.files = result.files || [];
// Pfad speichern
localStorage.setItem('browser_path', state.currentPath);
// UI aktualisieren
aktualisiereBreadcrumb();
aktualisiereDateiliste();
aktualisiereBaumAnzeige();
// Vorschau zurücksetzen
if (state.selectedFile) {
const existiert = state.files.some(f => f.name === state.selectedFile);
if (!existiert) {
state.selectedFile = null;
state.selectedFilePath = null;
if (!state.previewWindowOpen && !state.previewPanelHidden) {
zeigeLeereVorschau();
}
}
}
} catch (error) {
toast('Fehler beim Laden: ' + error.message, 'error');
}
}
function aktualisiereBreadcrumb() {
const container = document.getElementById('breadcrumb');
const teile = state.currentPath.split('/').filter(t => t);
let html = `/`;
let pfad = '';
teile.forEach((teil, index) => {
pfad += '/' + teil;
const istLetzter = index === teile.length - 1;
if (istLetzter) {
html += `/`;
html += `${teil}`;
} else {
html += `/`;
html += `${teil}`;
}
});
container.innerHTML = html;
}
function aktualisiereDateiliste() {
const container = document.getElementById('file-list');
const countEl = document.getElementById('file-count');
// Ordner + Dateien zählen
const totalCount = state.folders.length + state.files.length;
countEl.textContent = `${state.folders.length} Ordner, ${state.files.length} Dateien`;
let html = '';
// Ordner zuerst
state.folders.forEach(folder => {
html += `
📁
${folder.name}
`;
});
// Dateien
state.files.forEach(file => {
const isSelected = state.selectedFile === file.name;
const icon = getFileIcon(file.name);
const size = formatSize(file.size);
html += `
${icon}
${file.name}
${size}
`;
});
if (!html) {
html = 'Keine Dateien in diesem Ordner
';
}
container.innerHTML = html;
}
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 formatSize(bytes) {
if (!bytes) return '';
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unit = 0;
while (size >= 1024 && unit < units.length - 1) {
size /= 1024;
unit++;
}
return `${size.toFixed(unit > 0 ? 1 : 0)} ${units[unit]}`;
}
function navigiereZu(pfad) {
ladeOrdnerInhalt(pfad);
// Ordner im Baum aufklappen
expandiereBaumPfad(pfad);
}
function expandiereBaumPfad(pfad) {
// Alle übergeordneten Ordner aufklappen
const teile = pfad.split('/').filter(t => t);
let aktuell = '';
teile.forEach(teil => {
aktuell += '/' + teil;
if (!state.expandedFolders.has(aktuell) && aktuell !== pfad) {
state.expandedFolders.add(aktuell);
}
});
localStorage.setItem('browser_expanded', JSON.stringify([...state.expandedFolders]));
}
function ordnerHoch() {
const parent = state.currentPath.split('/').slice(0, -1).join('/') || '/';
navigiereZu(parent);
}
function ordnerAktualisieren() {
ladeOrdnerInhalt(state.currentPath);
toast('Aktualisiert', 'info');
}
// ============ Vorschau-Fenster ============
function oeffneVorschauFenster() {
if (state.previewWindow && !state.previewWindow.closed) {
state.previewWindow.focus();
return;
}
const width = 800;
const height = 600;
const left = window.screenX + window.outerWidth - width - 50;
const top = window.screenY + 50;
state.previewWindow = window.open(
'/browser/preview',
'dateiverwaltung-preview',
`width=${width},height=${height},left=${left},top=${top},resizable=yes`
);
}
// ============ Datei-Auswahl und Vorschau ============
function waehltDatei(name) {
state.selectedFile = name;
state.selectedFilePath = state.currentPath + '/' + name;
// UI aktualisieren
document.querySelectorAll('.file-item').forEach(el => {
el.classList.remove('selected');
});
event.currentTarget.classList.add('selected');
// Datei-Info anzeigen (nur wenn Panel sichtbar)
const file = state.files.find(f => f.name === name);
if (file && !state.previewPanelHidden) {
document.getElementById('file-info').classList.remove('hidden');
document.getElementById('preview-filename').textContent = name;
document.getElementById('preview-size').textContent = formatSize(file.size);
document.getElementById('btn-extern').classList.remove('hidden');
}
// Vorschau laden - entweder in separatem Fenster oder inline
if (state.previewWindowOpen) {
sendeAnVorschau(state.selectedFilePath, name);
// Inline-Vorschau zeigt Hinweis (nur wenn Panel sichtbar)
if (!state.previewPanelHidden) {
document.getElementById('preview-container').innerHTML = `
Vorschau wird im separaten Fenster angezeigt
`;
}
} else if (!state.previewPanelHidden) {
ladeVorschau(state.selectedFilePath, name);
}
}
async function ladeVorschau(pfad, name) {
const container = document.getElementById('preview-container');
if (!container) return;
const ext = name.split('.').pop().toLowerCase();
// PDF Vorschau - mit fit-to-page für erste Seite
if (ext === 'pdf') {
container.innerHTML = ``;
return;
}
// Bild Vorschau
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff'].includes(ext)) {
container.innerHTML = `
`;
return;
}
// Text-basierte Dateien
if (['txt', 'md', 'log', 'xml', 'json', 'csv', 'html', 'css', 'js'].includes(ext)) {
try {
const result = await api(`/file/text?path=${encodeURIComponent(pfad)}`);
if (result.content) {
container.innerHTML = `${escapeHtml(result.content)}`;
} 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);
const container = document.getElementById('preview-container');
if (container) {
container.innerHTML = `
${icon}
Keine Vorschau für .${ext} Dateien
`;
}
}
function zeigeLeereVorschau() {
const fileInfo = document.getElementById('file-info');
const btnExtern = document.getElementById('btn-extern');
const container = document.getElementById('preview-container');
if (fileInfo) fileInfo.classList.add('hidden');
if (btnExtern) btnExtern.classList.add('hidden');
if (container) {
container.innerHTML = `
Datei auswählen um Vorschau zu sehen
`;
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ============ Datei-Operationen ============
function dateiUmbenennen() {
if (!state.selectedFile) return;
document.getElementById('neuer-name').value = state.selectedFile;
oeffneModal('umbenennen-modal');
setTimeout(() => {
const input = document.getElementById('neuer-name');
input.focus();
const dotIndex = state.selectedFile.lastIndexOf('.');
if (dotIndex > 0) {
input.setSelectionRange(0, dotIndex);
} else {
input.select();
}
}, 100);
}
async function umbenennenBestaetigen() {
const neuerName = document.getElementById('neuer-name').value.trim();
if (!neuerName) {
toast('Bitte Namen eingeben', 'warning');
return;
}
if (neuerName === state.selectedFile) {
schliesseModal('umbenennen-modal');
return;
}
try {
const result = await api('/file/rename', {
method: 'POST',
body: JSON.stringify({
pfad: state.selectedFilePath,
neuer_name: neuerName
})
});
if (result.erfolg) {
toast('Datei umbenannt', 'success');
schliesseModal('umbenennen-modal');
state.selectedFile = neuerName;
state.selectedFilePath = state.currentPath + '/' + neuerName;
ladeOrdnerInhalt(state.currentPath);
} else {
toast(result.fehler || 'Umbenennen fehlgeschlagen', 'error');
}
} catch (e) {
toast('Fehler: ' + e.message, 'error');
}
}
function dateiVerschieben() {
if (!state.selectedFile) return;
document.getElementById('verschieben-datei').textContent = state.selectedFile;
state.verschiebenPath = state.currentPath;
ladeVerschiebenBrowser(state.verschiebenPath);
oeffneModal('verschieben-modal');
}
async function ladeVerschiebenBrowser(pfad) {
try {
const result = await api(`/browse?path=${encodeURIComponent(pfad)}`);
if (result.error) {
toast(result.error, 'error');
return;
}
state.verschiebenPath = result.current;
document.getElementById('verschieben-browser-path').textContent = result.current;
let html = '';
if (result.parent) {
html += `
📁 ..
`;
}
result.entries.forEach(entry => {
if (entry.type === 'directory') {
html += `
📁 ${entry.name}
`;
}
});
if (!html) {
html = 'Keine Unterordner';
}
document.getElementById('verschieben-browser-list').innerHTML = html;
} catch (e) {
toast('Fehler beim Laden', 'error');
}
}
async function verschiebenBestaetigen() {
if (state.verschiebenPath === state.currentPath) {
toast('Zielordner ist der aktuelle Ordner', 'warning');
return;
}
try {
const result = await api('/file/move', {
method: 'POST',
body: JSON.stringify({
pfad: state.selectedFilePath,
ziel_ordner: state.verschiebenPath
})
});
if (result.erfolg) {
toast('Datei verschoben', 'success');
schliesseModal('verschieben-modal');
state.selectedFile = null;
state.selectedFilePath = null;
zeigeLeereVorschau();
ladeOrdnerInhalt(state.currentPath);
} else {
toast(result.fehler || 'Verschieben fehlgeschlagen', 'error');
}
} catch (e) {
toast('Fehler: ' + e.message, 'error');
}
}
function dateiLoeschen() {
if (!state.selectedFile) return;
document.getElementById('loeschen-datei').textContent = state.selectedFile;
oeffneModal('loeschen-modal');
}
async function loeschenBestaetigen() {
try {
const result = await api('/file/delete', {
method: 'DELETE',
body: JSON.stringify({
pfad: state.selectedFilePath
})
});
if (result.erfolg) {
toast('Datei gelöscht', 'success');
schliesseModal('loeschen-modal');
state.selectedFile = null;
state.selectedFilePath = null;
zeigeLeereVorschau();
ladeOrdnerInhalt(state.currentPath);
} else {
toast(result.fehler || 'Löschen fehlgeschlagen', 'error');
}
} catch (e) {
toast('Fehler: ' + e.message, 'error');
}
}
function dateiExternOeffnen() {
if (!state.selectedFilePath) return;
window.open(`/api/file/download?path=${encodeURIComponent(state.selectedFilePath)}`, '_blank');
}
// ============ Drag & Drop ============
function handleDragStart(event, pfad) {
event.dataTransfer.setData('text/plain', pfad);
event.dataTransfer.effectAllowed = 'move';
}
async function handleDrop(event, zielOrdner) {
event.preventDefault();
event.currentTarget.classList.remove('drop-target');
const quellPfad = event.dataTransfer.getData('text/plain');
if (!quellPfad || quellPfad === zielOrdner) return;
try {
const result = await api('/file/move', {
method: 'POST',
body: JSON.stringify({
pfad: quellPfad,
ziel_ordner: zielOrdner
})
});
if (result.erfolg) {
toast('Datei verschoben', 'success');
ladeOrdnerInhalt(state.currentPath);
} else {
toast(result.fehler || 'Verschieben fehlgeschlagen', 'error');
}
} catch (e) {
toast('Fehler: ' + e.message, 'error');
}
}
// ============ Context Menu ============
function zeigeContextMenu(event, dateiname) {
event.preventDefault();
event.stopPropagation();
const vorher = document.querySelector('.context-menu');
if (vorher) vorher.remove();
state.selectedFile = dateiname;
state.selectedFilePath = state.currentPath + '/' + dateiname;
const menu = document.createElement('div');
menu.className = 'context-menu';
menu.innerHTML = `
`;
menu.style.left = event.clientX + 'px';
menu.style.top = event.clientY + 'px';
document.body.appendChild(menu);
const rect = menu.getBoundingClientRect();
if (rect.right > window.innerWidth) {
menu.style.left = (window.innerWidth - rect.width - 10) + 'px';
}
if (rect.bottom > window.innerHeight) {
menu.style.top = (window.innerHeight - rect.height - 10) + 'px';
}
}
// ============ Keyboard Shortcuts ============
function handleKeydown(event) {
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') return;
switch (event.key) {
case 'F2':
event.preventDefault();
if (state.selectedFile) dateiUmbenennen();
break;
case 'Delete':
event.preventDefault();
if (state.selectedFile) dateiLoeschen();
break;
case 'F5':
event.preventDefault();
ordnerAktualisieren();
break;
case 'Backspace':
event.preventDefault();
ordnerHoch();
break;
case 'Enter':
if (state.selectedFile) {
dateiExternOeffnen();
}
break;
}
}
// ============ Resize Handles ============
function initResizeHandles() {
const main = document.getElementById('browser-main');
const treePane = document.getElementById('pane-tree');
const listPane = document.getElementById('pane-list');
const handle1 = document.getElementById('resize-handle-1');
const handle2 = document.getElementById('resize-handle-2');
if (!main || !treePane || !listPane) return;
const isVertical = main.classList.contains('vertical') || window.innerWidth <= 1000;
// Handle 1: Zwischen Baum und Liste
if (handle1) {
let isResizing = false;
handle1.onmousedown = (e) => {
isResizing = true;
handle1.classList.add('active');
document.body.style.cursor = isVertical ? 'row-resize' : 'col-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
};
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
if (isVertical) {
// Vertikaler Modus: Höhe ändern
const containerTop = main.getBoundingClientRect().top;
const newHeight = Math.max(80, Math.min(e.clientY - containerTop, 300));
treePane.style.height = newHeight + 'px';
} else {
// Horizontaler Modus: Breite ändern
const containerLeft = main.getBoundingClientRect().left;
const newWidth = Math.max(150, Math.min(e.clientX - containerLeft, 400));
treePane.style.width = newWidth + 'px';
}
});
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
handle1.classList.remove('active');
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
});
}
// Handle 2: Zwischen Liste und Vorschau
if (handle2) {
let isResizing = false;
handle2.onmousedown = (e) => {
isResizing = true;
handle2.classList.add('active');
document.body.style.cursor = isVertical ? 'row-resize' : 'col-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
};
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
if (isVertical) {
// Vertikaler Modus: Höhe ändern
const treePaneHeight = treePane.offsetHeight;
const handle1Height = handle1 ? handle1.offsetHeight : 0;
const containerTop = main.getBoundingClientRect().top;
const offset = treePaneHeight + handle1Height;
const newHeight = Math.max(100, Math.min(e.clientY - containerTop - offset, 400));
listPane.style.height = newHeight + 'px';
} else {
// Horizontaler Modus: Breite ändern
const treePaneWidth = treePane.offsetWidth;
const handle1Width = handle1 ? handle1.offsetWidth : 0;
const containerLeft = main.getBoundingClientRect().left;
const offset = treePaneWidth + handle1Width;
const newWidth = Math.max(200, Math.min(e.clientX - containerLeft - offset, 600));
listPane.style.width = newWidth + 'px';
}
});
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
handle2.classList.remove('active');
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
});
}
}
// ============ Modal Helper ============
function oeffneModal(id) {
document.getElementById(id).classList.remove('hidden');
}
function schliesseModal(id) {
document.getElementById(id).classList.add('hidden');
}
// ============ Toast Notifications ============
function toast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'fadeOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}