Files
Alfredv2/alfred/app/static/js/alfred-timers-context.js
T

158 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ALFRED_TIMERS_CONTEXT_V1 — chronos/minuteurs dans un coin, illustrations en popup. */
(() => {
'use strict';
const root = document.getElementById('alfredTimersDock');
const list = document.getElementById('alfredTimersList');
const context = document.getElementById('alfredContextPopup');
const contextImage = document.getElementById('alfredContextImage');
const contextTitle = document.getElementById('alfredContextTitle');
const contextText = document.getElementById('alfredContextText');
const contextSource = document.getElementById('alfredContextSource');
const contextClose = document.getElementById('alfredContextClose');
const toast = document.getElementById('alfredTimerToast');
if (!root || !list) return;
let items = [];
let anchorMs = Date.now();
let lastSnapshotClientMs = Date.now();
let toastTimeout = null;
function format(seconds) {
const s = Math.max(0, Math.floor(seconds));
const h = Math.floor(s / 3600);
const m = Math.floor(s % 3600 / 60);
const sec = s % 60;
return h > 0 ? `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}` :
`${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}`;
}
function textNode(tag, cls, text) {
const el = document.createElement(tag);
if (cls) el.className = cls;
if (text !== undefined) el.textContent = text;
return el;
}
async function action(op, id, seconds) {
const payload = {action:op, id};
if (seconds !== undefined) payload.seconds = seconds;
const r = await fetch('/api/timers/action', {method:'POST',
headers:{'Content-Type':'application/json', 'X-Alfred-Request':'1'},
body:JSON.stringify(payload)});
const data = await r.json();
if (!r.ok) throw new Error(data.error || `HTTP ${r.status}`);
update(data.items || []);
}
function update(next) {
items = Array.isArray(next) ? next : [];
lastSnapshotClientMs = Date.now();
list.replaceChildren();
root.hidden = !items.length;
for (const item of items) {
const card = textNode('div','alfred-time-card');
card.dataset.itemId = item.id;
const label = textNode('span','alfred-time-label', (item.kind === 'timer' ? '⌛ ' : '⏱ ') + item.label);
const counter = textNode('strong','alfred-time-value','00:00');
counter.dataset.kind = item.kind;
card.append(label,counter);
const actions = textNode('div','alfred-time-actions');
const pause = textNode('button','alfred-time-icon', item.state === 'paused' ? '▶' : 'Ⅱ');
pause.type='button'; pause.title=item.state === 'paused' ? 'Reprendre':'Mettre en pause';
pause.setAttribute('aria-label',pause.title);
pause.onclick=()=>action(item.state==='paused'?'resume':'pause',item.id).catch(showError);
const stop=textNode('button','alfred-time-icon','×');
stop.type='button'; stop.title='Annuler'; stop.setAttribute('aria-label','Annuler '+item.label);
stop.onclick=()=>action('cancel',item.id).catch(showError);
actions.append(pause,stop);card.append(actions);list.append(card);
}
redraw();
}
function redraw() {
const delta = Math.max(0, (Date.now()-lastSnapshotClientMs)/1000);
for (const item of items) {
const node=[...list.querySelectorAll('[data-item-id]')].find(el=>el.dataset.itemId===item.id);
if (!node) continue;
let elapsed=Number(item.elapsed)||0;
if(item.state==='running') elapsed+=delta;
const seconds = item.kind==='timer' ? Math.max(0,(Number(item.duration)||0)-elapsed) :
elapsed;
const counter=node.querySelector('.alfred-time-value');
counter.textContent=format(seconds);
counter.classList.toggle('is-paused',item.state==='paused');
if(item.kind==='stopwatch' && item.duration!==null)
counter.title='Limite '+format(item.duration);
}
}
function notify(message) {
if (!toast) return;
toast.textContent=message;toast.hidden=false;
clearTimeout(toastTimeout);
toastTimeout=setTimeout(()=>{toast.hidden=true;},9000);
}
function showError(error) {notify(error.message || String(error));}
async function refresh() {
try {
const r=await fetch('/api/timers',{cache:'no-store'});
if(!r.ok) throw new Error(`HTTP ${r.status}`);
const data=await r.json(); update(data.items || []);
} catch (err) { console.warn('[ALFRED TIMERS]',err); }
}
const socket = window.alfredSocket;
socket?.on('alfred_timers_changed', data => update(data.items || []));
socket?.on('alfred_timer_finished', data => {
notify(`⏰ ${data.label} : terminé !`);
refresh();
});
socket?.on('connect', refresh);
refresh();
setInterval(redraw, 250);
setInterval(()=>{if(!document.hidden)refresh();}, 15000);
// Illustration des demandes « qui est ... ? » : source = Wikipédia, réponse = Hermes.
function closeContext() {
if (context) context.hidden=true;
if (contextImage) contextImage.removeAttribute('src');
}
if (context && contextImage && contextTitle) {
socket?.on('alfred_context_image', data => {
try {
const image = new URL(data.image_url);
const source = new URL(data.source_url);
if (image.protocol !== 'https:' || !['upload.wikimedia.org','commons.wikimedia.org'].includes(image.hostname))return;
if (source.protocol !== 'https:' || source.hostname !== 'fr.wikipedia.org')return;
contextTitle.textContent = String(data.title || 'Illustration');
contextText.textContent = String(data.text || '').slice(0,500);
contextImage.src = image.href;
contextSource.href = source.href;
contextSource.textContent = String(data.credit || 'Source : Wikipédia');
context.hidden=false;
} catch (err) { console.warn('[ALFRED CONTEXT]',err); }
});
contextClose?.addEventListener('click', closeContext);
context.addEventListener('pointerdown', e => {if(e.target === context)closeContext();});
document.addEventListener('keydown', e=>{if(e.key === 'Escape' && !context.hidden)closeContext();});
}
// Masque uniquement les anciennes fenêtres intitulées exactement « Activité Hermes ».
// N'interfère ni avec la session Hermes ni avec ses réponses dans la scène.
function hideLegacyHermesActivity() {
const titlePattern=/^activit[eé]\s+hermes$/i;
for (const el of document.querySelectorAll('h1,h2,h3,h4,[role="heading"],.popup-title,.modal-title')) {
if (!titlePattern.test((el.textContent || '').trim()))continue;
const popup = el.closest('[role="dialog"],.modal,.popup,.popover,.overlay,.hermes-activity-panel,.hermes-activity-popup');
if (popup && popup !== document.body) {
popup.hidden = true;
popup.style.display = 'none';
}
}
for (const el of document.querySelectorAll('[aria-label="Activité Hermes"],[aria-label="Activite Hermes"],#hermesActivityPopup,#hermesActivityModal')) {
el.hidden=true;el.style.display='none';
}
}
hideLegacyHermesActivity();
let pending=false;
const observer = new MutationObserver(() => {
if(pending)return;
pending=true;
queueMicrotask(()=>{pending=false;hideLegacyHermesActivity();});
});
observer.observe(document.body,{childList:true,subtree:true});
})();