downloader-extension/js/notifications.js

222 lines
6.3 KiB
JavaScript
Raw Normal View History

2026-02-21 22:31:48 -05:00
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);
}
}