try {
// TODO
let session = await SessionHistory.join("test","fghij");
console.log(session);
let con = await session.connectIngest();
window.model = await session.connectModel("history");
model.addEventListener("eventAdded", (e)=>console.log("Added",e));
model.addEventListener("eventUpdated", (e)=>console.log("Updated",e));
/*model.addEventListener("eventRemoved", (e)=>console.log("Removed",e));
model.addEventListener("modelChanged", (e)=>console.log("Changed Model",e));*/
let toast = await con.startEventStream("application.toastbread");
setInterval(()=>{
toast.createInstantEvent({done:true, t: Date.now()});
}, 30000);
let heavyMetal = await con.startEventStream("application.metal.heavyness");
let ev = heavyMetal.createDurationEvent({valueA: 1, valueB: 2, valueC: {nested1: true, nested2: true}});
setTimeout(()=>{
ev.update({valueB:3});
setTimeout(()=>{
ev.stop({valueC:{nested3:true}});
}, 1000);
}, 1000);
/*
let barking = await con.startEventStream("dog.bark");
let state = await con.startEventStream("dog.posture");
console.log(toast);
barking.createInstantEvent({dog: "poodle"});
barking.createInstantEvent({dog: "grand danois"});
barking.createInstantEvent({dog: "poodle"});
state.createStateEvent({position: "standing"});
setTimeout(()=>{
state.createStateEvent({position: "sitting"});
setTimeout(()=>{
state.createStateEvent({position: "lying"});
},100);
},100);
let ev = heavyMetal.createDurationEvent({valueA: 1, valueB: 2, valueC: {nested1: true, nested2: true}});
setTimeout(()=>{
ev.update({valueB:3});
setTimeout(()=>{
ev.stop({valueC:{nested3:true}});
}, 1000);
}, 1000);
let danglingEvent = heavyMetal.createDurationEvent({valueA: 1, valueB: 2, valueC: {nested1: true, nested2: true}});
danglingEvent = null;*/
document.querySelector("#startScreen").addEventListener("click", async ()=>{
let userMedia = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
let screenStream = await con.startMediaStream("user.screen", userMedia, {buffalo: true});
});
document.querySelector("#startCam").addEventListener("click", async ()=>{
let userMedia = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
let camStream = await con.startMediaStream("user.cam", userMedia);
});
document.querySelector("#startVoice").addEventListener("click", async ()=>{
window.userVoice = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
let camStream = await con.startMediaStream("user.cam", window.userVoice);
});
function stopStream(stream) {
stream.getTracks().forEach(track => {
track.stop();
});
console.log("Audio stream stopped.");
}
document.querySelector("#stopVoice").addEventListener("click", async ()=>{
con.stopMediaStream(window.userVoice);
});
} catch (ex){
console.log(ex);
}
// --- 1. Initialize DataSets and Timeline ---
// DataSets for two-way data binding
const items = new vis.DataSet();
const groups = new vis.DataSet();
// Internal set to quickly track which 'event.type' groups already exist
const existingGroupTypes = new Set();
// Configuration options
const options = {
// Show event content on hover (Full event content is set in the data conversion function)
tooltip: {
followMouse: true, // Tooltip follows the mouse cursor
overflowMethod: 'cap' // Keep the tooltip within the timeline boundaries
},
// Set a default start/end range (optional, can be adjusted)
start: Date.now() - (1000 * 60 * 2),
end: Date.now() + (1000 * 60 * 1)
};
// DOM element where the Timeline will be attached
const container = document.getElementById('timeline-container');
// Create the Timeline
const timeline = new vis.Timeline(container, items, groups, options);
// --- 2. Helper Functions for Data Conversion and Group Management ---
/**
* Converts a raw event from the model into a vis.js Item object.
* @param {object} event - The raw event object (must have id, type, and start).
* @returns {object} A vis.js compatible item.
*/
function eventToTimelineItem(event) {
// start is guaranteed (Java's System.getCurrentTimeMillis())
const item = {
id: event.id,
group: event.type, // Group by event.type (the "track")
start: new Date(event.start),
content: `Event: ${event.type}`,
// 3) Shows the full content of the events when hovered
title: JSON.stringify(event, null, 2).replace(/\n/g, "<br/>"), // Full JSON content in the tooltip
className: event.tentative ? 'tentative' : ''
};
// 'end' is not guaranteed, so only add it if present and greater than start
if (event.end && event.end > event.start) {
item.end = new Date(event.end);
item.type = 'range'; // Display as a range/box
} else {
item.type = 'point'; // Display as a point
}
return item;
}
/**
* Adds a new vis.js Group based on the event's type if it doesn't exist.
* @param {string} eventType - The type of the event, used as the group ID and content.
*/
function ensureGroupExists(eventType) {
if (!existingGroupTypes.has(eventType)) {
groups.add({
id: eventType,
content: eventType // Label for the track
});
existingGroupTypes.add(eventType);
}
}
// --- 3. Model Event Listeners for Dynamic Updates ---
// The 'model' is assumed to be an existing object that emits events
window.model.addEventListener("eventAdded", (event) => {
// 2) Add "tracks" based on event.type when it spots new ones
ensureGroupExists(event.type);
// Add the new item to the vis.DataSet (Timeline updates automatically)
const item = eventToTimelineItem(event);
items.add(item);
});
window.model.addEventListener("eventUpdated", (event) => {
// Group must exist before item is added/updated
ensureGroupExists(event.type);
// Update the existing item in the vis.DataSet
const item = eventToTimelineItem(event);
items.update(item);
});
window.model.addEventListener("eventRemoved", (event) => {
// The event payload might just contain {id: '...'} or the full event.
// The only thing needed for removal is the ID
items.remove(event.id);
// NOTE: Group removal logic is complex (only remove group if it has no items)
// and often omitted, but it could be implemented here.
});
video {
background: grey;
max-width: 100%;
}