/**
* Version 1.8
**/
"use strict";
renderTimeline();
async function renderTimeline() {
// Initialize libraries and context
const vis = require('vis.min.js');
const container = document.getElementById("timeline");
const menu = document.getElementById("menu");
// Get widget attribute values and fetch notes from defined tags
const events = await api.runOnBackend(() => {
const parentNote = api.startNote.getParentNotes()[0];
// fetch what labels to look for from widget attributes
// event notes (start/end)
const event_note_label_start = parentNote.getLabelValue('event_label_start').toString();
const event_note_label_end = parentNote.getLabelValue('event_label_end').toString();
// agent notes (birth/death)
const person_note_label_start = parentNote.getLabelValue('person_label_start').toString();
const person_note_label_end = parentNote.getLabelValue('person_label_end').toString();
// date types (categories)
const event_label_type = parentNote.getLabelValue('event_label_type').toString();
// fetch notes with those labels
const event_notes = api.getNotesWithLabel(event_note_label_start);
const person_notes_birth = api.getNotesWithLabel(person_note_label_start);
const person_notes_death = api.getNotesWithLabel(person_note_label_end);
let events = [];
for (let note of event_notes) {
let id = note.noteId;
let content = note.title;
let start = note.getLabelValue(event_note_label_start);
let end = note.getLabelValue(event_note_label_end);
let group = note.getLabelValue(event_label_type) ? note.getLabelValue(event_label_type) : (parentNote.getLabelValue('event_type_default') ? parentNote.getLabelValue('event_type_default') : 'default');
if (content && start) {
events.push({id, content, start, end, group});
}
}
// make a 'birth' event for each person note with start date
for (let note of person_notes_birth) {
let id = note.noteId;
let start = note.getLabelValue(person_note_label_start);
let group = note.getLabelValue(event_label_type) ? note.getLabelValue(event_label_type) : (parentNote.getLabelValue('event_type_default') ? parentNote.getLabelValue('event_type_default') : 'default');
if (start) {
let content = note.title + ' (Birth)';
events.push({id, content, start, group});
}
}
// make a 'death' event for each person note with end date
for (let note of person_notes_death) {
let id = note.noteId;
let start = note.getLabelValue(person_note_label_end);
let group = note.getLabelValue(event_label_type) ? note.getLabelValue(event_label_type) : (parentNote.getLabelValue('event_type_default') ? parentNote.getLabelValue('event_type_default') : 'default');
if (start) {
let content = note.title + ' (Death)';
events.push({id, content, start, group});
}
}
return events;
});
// Get event types, make groups
const types = await api.runOnBackend(() => {
const parentNote = api.startNote.getParentNotes()[0];
const event_type_list = parentNote.getLabelValue('event_type_list').toString().split(';');
let types = [];
let i = 1;
for (let type of event_type_list) {
// type format each type seperated by ;
// id(string), order(int), label(string), color(string), visible(bool), type(string), forceGroup(int)
types.push({
id: type.split(',')[0].toString(),
order: type.split(',')[1] ? (parseInt(type.split(',')[1], 10)) : i,
content: type.split(',')[2] ? (type.split(',')[2].toString()) : type.split(',')[0].toString(),
color: type.split(',')[3] ? (type.split(',')[3].toString()) : null,
visible: type.split(',')[4] ? (type.split(',')[4] === 'true') : true,
type: type.split(',')[5] ? type.split(',')[5].toString() : null,
forceGroup: type.split(',')[6] ? (type.split(',')[6].toString()) : null
});
i++;
}
return types
});
// merge the default group with attribute-added groups
let groups = new vis.DataSet([{
id: 'default', order: 100, content: '', color: 'white', visible: true, type: null, forceGroup: null
}, ...types]);
// Create a DataSet for all event items
let items = new vis.DataSet();
for (let i = 0; i < events.length; i++) {
// get date values and note link
let note_id = events[i].id;
let note_link = await api.createNoteLink(note_id);
note_link[0].firstChild.innerText = events[i].content;
let event_start = vis.moment(new Date(events[i].start), 'YYYY-MM-DD-hh:mm:ss');
let event_end = events[i].end ? vis.moment(new Date(events[i].end), 'YYYY-MM-DD-hh:mm:ss') : null;
// set event group
let event_group = events[i].group ? (groups.get({
filter: function (item) {
return (item.id === events[i].group)
}
})[0] ? groups.get({
filter: function (item) {
return (item.id === events[i].group)
}
})[0] : groups.get({
filter: function (item) {
return (item.id === 'default')
}
})[0]) : groups.get({
filter: function (item) {
return (item.id === 'default')
}
})[0];
// fill event data
items.add({
id: i,
content: note_link[0].firstChild,
start: event_start,
end: event_end,
group: event_group.forceGroup ? event_group.forceGroup : event_group.id,
type: (event_group.type === 'box' || event_group.type === 'point' || event_group.type === 'range' || event_group.type === 'background') ? event_group.type : null,
className: event_group.id,
style: `background-color:${event_group.color};border-color:${event_group.color};`
});
}
// Timeline options
const timeline_options = await api.runOnBackend(() => {
const parentNote = api.startNote.getParentNotes()[0];
const timeline_start = parentNote.getLabelValue('timeline_start');
const timeline_end = parentNote.getLabelValue('timeline_end');
const timeline_present = parentNote.getLabelValue('timeline_present');
const can_click_day = parentNote.getLabelValue('can_click_day') === 'true';
return {timeline_start, timeline_end, timeline_present, can_click_day};
});
console.log(timeline_options.can_click_day);
let options = {
clickToUse: false,
showCurrentTime: true,
height: '95%',
start: timeline_options.timeline_start,
end: timeline_options.timeline_end
};
// Set timeline wrapper height
container.parentNode.style.height = '100%';
container.parentNode.parentNode.style.height = '100%';
// Fix timeline display
container.parentNode.parentNode.parentNode.parentNode.style.display = 'initial';
// Create Timeline
const timeline = new vis.Timeline(container, items, groups, options);
// Add present time marker based on "timeline_present" label
timeline.setCurrentTime(timeline_options.timeline_present);
// Add event listener to activate a day note on click
if(timeline_options.can_click_day) {
timeline.on("click", async (e) => {
const timelineHeight = timeline.dom.centerContainer.getBoundingClientRect().height;
if (e.y <= timelineHeight) {
// ignore clicks on the timeline
return;
}
const effectiveY = e.y - timelineHeight;
if (effectiveY > timeline.dom.bottom.children[0].children[0].getBoundingClientRect().height) {
// ignore clicks on the month axis
return;
}
const day = e.time.toISOString().substr(0, 10);
const todayNote = await api.getDayNote(day);
api.waitUntilSynced();
await api.openTabWithNote(todayNote.noteId, true);
});
}
// Create UI menu buttons from added groups (that are not forced into another group)
menu.innerHTML = '';
let toggle_groups = groups.get({
filter: function (item) {
return (item.forceGroup === null && item.id !== 'default')
}
});
for (let group of toggle_groups) {
let button = document.createElement('input');
button.type = 'button';
button.id = `toggle-${group.id}`;
button.value = group.content;
button.className = (group.visible === true) ? 'toggled' : '';
menu.appendChild(button);
}
// Bind toggle buttons
for (let group of toggle_groups) {
document.getElementById(`toggle-${group.id}`).onclick = function () {
toggleGroupVisibility(group.id);
}
}
// Function to toggle group visibility
function toggleGroupVisibility(group_id) {
let visibility = groups.get({
filter: function (item) {
return (item.id === group_id)
}
})[0].visible;
groups.update({id: group_id, visible: !visibility});
console.log(document.getElementById(`toggle-${group_id}`));
document.getElementById(`toggle-${group_id}`).className = (!visibility === true) ? 'toggled' : '';
timeline.setGroups(groups);
timeline.redraw();
}
}