let activeFeedbox = null; //#region Messagebox function getMessageColorByLevelId(levelId) { return levelId == -1 ? 'test' : levelId == 0 ? 'success' : levelId == 1 ? 'info' : levelId == 2 ? 'warn' : levelId == 4 ? 'error' : 'throw_exception'; } /** * Shows messages in a containerbox on the right * @param {string} title - Title * @param {string} text - Message content * @param {string} levelId - 1=info | 2=warn | 4=error | 8=throw_exception | 16=success * @param {string} [targetId] - Which element is getting focused * @param {number} duration - Time in ms until auto-close */ // function showMessage(title, text, levelId = 1, targetId = null, duration = 4000) { function showMessage(title, text, levelId = 1, onclick = null, duration = 4000) { // Falls kein Container existiert → automatisch anlegen let container = document.getElementById('message-container'); if (!container) { container = document.createElement('div'); container.id = 'message-container'; container.style.position = 'fixed'; container.style.top = '20px'; container.style.right = '20px'; container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.alignItems = 'flex-end'; container.style.zIndex = '9999'; document.body.appendChild(container); } const message = document.createElement('div'); message.classList.add('message', getMessageColorByLevelId(levelId)); // --- HEADER --- const header = document.createElement('div'); header.className = 'message-header'; const titleContainer = document.createElement('div'); titleContainer.className = 'message-title'; titleContainer.innerHTML = title; const pinDiv = document.createElement('div'); pinDiv.className = 'pin-div'; pinDiv.innerHTML = '📌'; pinDiv.title = 'Anpinnen'; const countdown = document.createElement('span'); countdown.className = 'countdown'; countdown.textContent = `${(duration / 1000).toFixed(1)}s`; // Titel + Countdown + Pin in eine Zeile header.appendChild(titleContainer); header.appendChild(countdown); header.appendChild(pinDiv); // --- BODY --- const body = document.createElement('div'); body.className = 'message-text'; body.innerHTML = text; message.appendChild(header); message.appendChild(body); container.appendChild(message); // --- LOGIK --- let pinned = false; let remainingTime = duration; let interval = null; let lastTick = Date.now(); function startCountdown() { lastTick = Date.now(); clearInterval(interval); interval = setInterval(() => { const now = Date.now(); const delta = now - lastTick; lastTick = now; remainingTime -= delta; if (remainingTime <= 0 && !pinned) { clearInterval(interval); slideOutMessage(message); } else if (!pinned) { countdown.textContent = `${(remainingTime / 1000).toFixed(1)}s`; } }, 100); } function stopCountdown() { clearInterval(interval); } // Start Countdown startCountdown(); // Hover pausiert Countdown message.addEventListener('mouseenter', stopCountdown); message.addEventListener('mouseleave', () => { if (!pinned) startCountdown(); }); // Klick auf Nachricht → schließen (nur wenn nicht gepinnt) message.addEventListener('click', (e) => { if(onclick) { onclick(); } // if (e.target === pinDiv) return; // if (targetId) { // const target = document.getElementById(targetId); // if (target) target.scrollIntoView({ behavior: 'smooth' }); // } if (!pinned) slideOutMessage(message); }); // Klick auf Pin → anheften / lösen pinDiv.addEventListener('click', (e) => { e.stopPropagation(); pinned = !pinned; if (pinned) { pinDiv.classList.add('pinned'); pinDiv.title = 'Loslösen'; stopCountdown(); countdown.textContent = '📍 Angeheftet'; } else { pinDiv.classList.remove('pinned'); pinDiv.title = 'Anpinnen'; startCountdown(); } }); } function slideOutMessage(message) { if (!message) return; message.style.animation = 'slideOut 0.5s forwards'; setTimeout(() => { if (message.parentNode) message.parentNode.removeChild(message); }, 300); } //#region Feedbox /** feedbox({ title: `⚠ Upload abbrechen?`, message: `
Es laufen noch aktive Uploads.
Möchtest du wirklich alle abbrechen?
`, buttons: { yes: { text: 'Ja, abbrechen', onClick: () => stopUploadQueue() }, no: { text: 'Weiter hochladen' }, cancel: { text: 'Zurück' } } }); */ function feedbox({ title = '', message = '', buttons = {}, // buttons: { yes: { text: 'Yes', onClick: () => { } } } primary = null, // name of the primary button, to accept with enter-key lock = false, // locks desktop replace = false // replaces an actually shown feedbox }) { // 🚫 Nested verhindern if (activeFeedbox) { if (!replace) { return Promise.resolve('blocked'); } activeFeedbox.close('replaced'); } return new Promise(resolve => { const overlay = document.createElement('div'); overlay.className = 'feedbox-overlay'; const box = document.createElement('div'); box.className = 'feedbox'; const h = document.createElement('h3'); h.innerHTML = title; const msg = document.createElement('div'); msg.className = 'feedbox-message'; msg.innerHTML = message; const actions = document.createElement('div'); actions.className = 'feedbox-actions'; const btnMap = {}; Object.entries(buttons).forEach(([key, cfg]) => { if (!cfg) return; const btn = document.createElement('button'); btn.className = `feedbox-btn ${key}`; btn.innerHTML = cfg.text ?? key; btn.onclick = () => { cfg.onClick?.(); close(key); }; btnMap[key] = btn; actions.appendChild(btn); }); function close(result) { document.removeEventListener('keydown', keyHandler); overlay.remove(); activeFeedbox = null; resolve(result); } function keyHandler(e) { if (e.key === 'Escape') close('cancel'); if (e.key === 'Enter' && primary && btnMap[primary]) { btnMap[primary].click(); } } if (!lock) { overlay.onclick = e => { if (e.target === overlay) close('cancel'); }; } document.addEventListener('keydown', keyHandler); box.append(h, msg, actions); overlay.appendChild(box); document.body.appendChild(overlay); // Fokus if (primary && btnMap[primary]) { setTimeout(() => btnMap[primary].focus(), 0); } // 🔐 Singleton setzen activeFeedbox = { close }; }); } //#endregion