import { api_url } from '/js/settings.js'; import * as api from '/js/api.js'; function create_notification_element_from_notification_object(notification) { const e_container = document.createElement('div'); e_container.classList = `status status-${notification.type}`; e_container.setAttribute('notification_id', "" + notification.id); e_container.setAttribute('notification_hash', "" + notification.hash); const e_title_span = document.createElement('span'); e_title_span.innerText = notification.title; const e_title = document.createElement('div'); e_title.classList = 'status-title'; e_title.appendChild(e_title_span) const e_progress = document.createElement('progress'); if ('progress' in notification) { if(notification.progress !== null) { e_progress.setAttribute('value', notification.progress); } } else e_progress.style.display = 'none'; const e_text = document.createElement('div'); e_text.classList = 'status-text'; e_text.innerHTML = notification.text; const e_delete_button = document.createElement('button'); e_delete_button.classList = 'dismiss'; e_delete_button.innerText = "Dismiss"; e_delete_button.addEventListener('click', () => { api.notifications.remove(notification.id); }) e_container.appendChild(e_title); e_container.appendChild(e_progress); e_container.appendChild(e_text); // e_container.appendChild(e_delete_button); return e_container; } function begin_status_update_loop() { const statuses_container = document.getElementById('statuses'); const clear_button = document.getElementById('clear-notifications-button'); const notif_header = document.getElementById('notif-header'); clear_button.addEventListener('click', () => { api.notifications.remove_all(); }) function get_current_shown_status_ids() { let arr = []; for(const element of statuses_container.children) { arr.push(element.getAttribute("notification_id")) } return arr; } async function check_statuses() { const active = await api.notifications.get_active(); const failed = await api.notifications.get_failed(); const succeeded = await api.notifications.get_succeeded(); notif_header.textContent = `${active.length} Active | ${failed.length} Failed | ${succeeded.length} Succeeded`; let req = await fetch(await api_url('notifications')); let json = await req.json(); const current_status_ids = get_current_shown_status_ids(); const new_status_ids = json.map(v => v.id); const combined_ids = Array.from(new Set([ ...current_status_ids, ...new_status_ids ])); const first = new Map(combined_ids.map(notif_id => [ notif_id, document.querySelector(`[notification_id="${notif_id}"]`)?.getBoundingClientRect() ?? null ])); console.log(first); for(const status_id of combined_ids) { const in_current = current_status_ids.includes(status_id); const in_new = new_status_ids.includes(status_id); if (in_current && !in_new) { const status_element = statuses_container.querySelector(`[notification_id="${status_id}"]`); status_element.outerHTML = ""; } else if (!in_current && in_new) { const status_object = json.find(status => status.id === status_id); const new_element = create_notification_element_from_notification_object(status_object) statuses_container.appendChild(new_element); } else if (in_current && in_new) { const status_object = json.find(status => status.id === status_id); const current_element = statuses_container.querySelector(`[notification_id="${status_id}"]`); const new_element = create_notification_element_from_notification_object(status_object); current_element.replaceWith(new_element); } } new_status_ids.forEach((id, index) => { const el = document.querySelector(`[notification_id="${id}"]`); if (el) el.style.order = index; }); for(const [id, old_rect] of first.entries()) { if(!old_rect) continue; const el = document.querySelector(`[notification_id="${id}"]`); if(!el) continue; const current_rect = el.getBoundingClientRect(); const dy = old_rect.top - current_rect.top; el.style.transition = 'none'; el.style.transform = `translate(0px, ${dy}px)`; requestAnimationFrame(() => { el.style.transition = 'transform 300ms ease'; el.style.transform = ''; }); } // notification_els.forEach(el => { // const prev = first.get(el); // const next = el.getBoundingClientRect(); // // const dx = prev.left - next.left; // const dy = prev.top - next.top; // // if (dx === 0 && dy === 0) return; // // el.style.transition = 'none'; // el.style.transform = `translate(${dx}px, ${dy}px)`; // // }); setTimeout(check_statuses, 500); } check_statuses(); } export function enable() { begin_status_update_loop(); } function alignElements(liveEl, templateEl) { // If node types differ, replace entirely if (liveEl.nodeType !== templateEl.nodeType) { liveEl.replaceWith(templateEl.cloneNode(true)); return; } // If element tag names differ, replace if (liveEl.nodeType === Node.ELEMENT_NODE && liveEl.tagName !== templateEl.tagName) { liveEl.replaceWith(templateEl.cloneNode(true)); return; } // Text node if (liveEl.nodeType === Node.TEXT_NODE) { if (liveEl.textContent !== templateEl.textContent) { liveEl.textContent = templateEl.textContent; } return; } // Element node — sync attributes syncAttributes(liveEl, templateEl); // Sync children syncChildren(liveEl, templateEl); } function syncAttributes(liveEl, templateEl) { // Remove old attributes for (const attr of [...liveEl.attributes]) { if (!templateEl.hasAttribute(attr.name)) { liveEl.removeAttribute(attr.name); } } // Add / update attributes for (const attr of [...templateEl.attributes]) { if (liveEl.getAttribute(attr.name) !== attr.value) { liveEl.setAttribute(attr.name, attr.value); } } } function syncChildren(liveEl, templateEl) { const liveChildren = [...liveEl.childNodes]; const templateChildren = [...templateEl.childNodes]; const max = Math.max(liveChildren.length, templateChildren.length); for (let i = 0; i < max; i++) { const liveChild = liveChildren[i]; const templateChild = templateChildren[i]; if (!liveChild && templateChild) { liveEl.appendChild(templateChild.cloneNode(true)); continue; } if (liveChild && !templateChild) { liveChild.remove(); continue; } alignElements(liveChild, templateChild); } }