All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m38s
124 lines
5 KiB
JavaScript
124 lines
5 KiB
JavaScript
/* Live klasdashboard: rendert actieve en recente leersessies en houdt de
|
|
weergave via Server-Sent Events actueel. Een 30-secondencontrole is alleen
|
|
een vangnet voor tijdelijk weggevallen mobiele/proxyverbindingen. */
|
|
"use strict";
|
|
|
|
function liveSessionTime(value){
|
|
const seconds=Math.max(0,Math.round((Date.now()-new Date(value).getTime())/1000));
|
|
if(seconds<60)return T("amLiveJustNow");
|
|
const minutes=Math.floor(seconds/60);
|
|
if(minutes<60)return T("amLiveMinutes").replace("{count}",minutes);
|
|
const hours=Math.floor(minutes/60);
|
|
return T("amLiveHours").replace("{count}",hours);
|
|
}
|
|
|
|
function updateLiveConnectionState(host,state){
|
|
const badge=host.querySelector(".live-connection");
|
|
if(!badge)return;
|
|
badge.className="live-connection "+state;
|
|
badge.textContent=T(state==="connected"?"amLiveConnected":"amLiveReconnecting");
|
|
}
|
|
|
|
function renderLiveSessionOverview(host,data,onPupil){
|
|
const section=document.createElement("section");
|
|
section.className="live-session-overview";
|
|
const head=document.createElement("div");
|
|
head.className="live-session-head";
|
|
const title=document.createElement("strong");
|
|
title.textContent=T("amLiveSessions");
|
|
const connection=document.createElement("span");
|
|
connection.className="live-connection connecting";
|
|
connection.textContent=T("amLiveReconnecting");
|
|
head.append(title,connection);
|
|
section.appendChild(head);
|
|
|
|
const sessions=data.sessions||[];
|
|
const active=sessions.filter(session=>session.status==="live");
|
|
const recent=sessions.filter(session=>session.status!=="live").slice(0,20);
|
|
const renderGroup=(label,items,emptyKey)=>{
|
|
const group=document.createElement("div");
|
|
group.className="live-session-group";
|
|
const groupHead=document.createElement("div");
|
|
groupHead.className="live-session-group-head";
|
|
const groupTitle=document.createElement("strong");
|
|
groupTitle.textContent=label;
|
|
const count=document.createElement("span");
|
|
count.textContent=String(items.length);
|
|
groupHead.append(groupTitle,count);
|
|
group.appendChild(groupHead);
|
|
if(!items.length){
|
|
const empty=document.createElement("div");
|
|
empty.className="guestnote";
|
|
empty.textContent=T(emptyKey);
|
|
group.appendChild(empty);
|
|
section.appendChild(group);
|
|
return;
|
|
}
|
|
const grid=document.createElement("div");
|
|
grid.className="live-session-grid";
|
|
items.forEach(session=>{
|
|
const card=document.createElement("button");
|
|
card.type="button";
|
|
const justStarted=session.status==="live"
|
|
&& new Date(data.serverTime||Date.now()).getTime()-new Date(session.startedAt).getTime()<60000;
|
|
card.className="live-session-card "+(session.status==="live"?"live":"recent");
|
|
const status=document.createElement("span");
|
|
status.className="live-session-status";
|
|
status.textContent=T(justStarted?"amLiveNew":session.status==="live"?"amLiveWorking":"amLiveEnded");
|
|
const name=document.createElement("strong");
|
|
name.className="live-session-name";
|
|
name.textContent=session.pupilName;
|
|
const widget=document.createElement("span");
|
|
widget.className="live-session-widget";
|
|
widget.textContent=(T("wg_"+session.widgetType)||session.widgetType)+" · "+T(session.mode==="kijken"?"amModeWatch":"amModeWork");
|
|
const time=document.createElement("span");
|
|
time.className="live-session-time";
|
|
time.textContent=T(session.status==="live"?"amLiveStarted":"amLiveLastSeen")+" "+
|
|
liveSessionTime(session.status==="live"?session.startedAt:session.lastSeenAt);
|
|
card.append(status,name,widget,time);
|
|
if(session.progressCount){
|
|
const score=document.createElement("span");
|
|
score.className="live-session-score";
|
|
score.textContent=T("amLiveUpdates").replace("{count}",session.progressCount);
|
|
if(session.attempts)score.textContent+=" · "+T("amLiveScore")
|
|
.replace("{correct}",session.correct).replace("{attempts}",session.attempts);
|
|
card.appendChild(score);
|
|
}
|
|
card.addEventListener("click",()=>onPupil(session.pupilId));
|
|
grid.appendChild(card);
|
|
});
|
|
group.appendChild(grid);
|
|
section.appendChild(group);
|
|
};
|
|
renderGroup(T("amLiveNow"),active,"amLiveNone");
|
|
renderGroup(T("amLiveRecent"),recent,"amLiveRecentNone");
|
|
host.appendChild(section);
|
|
}
|
|
|
|
function connectLiveClassDashboard(classId,onChange,isActive,onState){
|
|
let source=null;
|
|
let stopped=false;
|
|
let reloadTimer=null;
|
|
const queueReload=()=>{
|
|
clearTimeout(reloadTimer);
|
|
reloadTimer=setTimeout(()=>{if(!stopped&&isActive())onChange();},180);
|
|
};
|
|
if(typeof EventSource!=="undefined"){
|
|
source=new EventSource("/api/progress/live/class/"+encodeURIComponent(classId)+"/stream");
|
|
source.onopen=()=>onState("connected");
|
|
source.onerror=()=>onState("reconnecting");
|
|
source.addEventListener("sessions",queueReload);
|
|
}
|
|
const fallback=setInterval(()=>{
|
|
if(!isActive()){stop();return;}
|
|
onChange();
|
|
},30000);
|
|
function stop(){
|
|
if(stopped)return;
|
|
stopped=true;
|
|
clearInterval(fallback);
|
|
clearTimeout(reloadTimer);
|
|
if(source)source.close();
|
|
}
|
|
return stop;
|
|
}
|