{
for (let i = 0; i < stream.length; i++) {
if (collide(events[stream[i]], events[index], domain)) {
return false;
}
}
return true;
}
const fit_events = (components, events, domain) => {
components.forEach((component) => {
let events_per_stream = [ [] ];
component.forEach((index) => {
let i = 0;
while (true) {
if (event_fits(events, index, events_per_stream[i], domain)) {
events_per_stream[i].push(index)
events[index].unitoffset = i;
break;
}
i++;
if (i == events_per_stream.length) {
events_per_stream.push([]);
}
}
});
component.forEach((index) => {
events[index].unitwidth = events_per_stream.length;
});
});
}
//Helpers for daily calendar
const setup_ticker = () => {
let ticker = [''];
for (let i = dayjs(startOfWorkDay); dayjs.isSameOrBefore(i, endOfWorkDay); i = i.add(30, 'minutes')) {
if (i.minute() == 0) {
ticker.push(`${i.format("h:mm")} ${i.format("A")}
`);
}
else {
ticker.push(` ${i.format("h:mm")}
`);
}
}
el('timings').innerHTML = ticker.join('');
}
const setup_daily = (dailyEvents, allDayEvents) => {
el('main').replaceChildren(el('t-daily').content.cloneNode(true));
el('main').classList.remove("flex-column");
el('main').classList.add("flex-row");
setup_ticker();
el('events').addEventListener("dblclick", eventadd);
let conflict_list = conflict(dailyEvents, "time");
let components = connected_components(conflict_list);
fit_events(components, dailyEvents, "time");
dailyEvents.forEach(event => {
let left = (99/event.unitwidth) * (event.unitoffset) + 1;
let top = (event.timefromeffective - startOfWorkDay) / (endOfWorkDay - startOfWorkDay) * 100;
let width = 99/event.unitwidth - 1;
let height = (event.timetoeffective - event.timefromeffective) / (endOfWorkDay - startOfWorkDay) * 100 - 0.2;
let node = create_daily_event(event);
node.classList.add("position-absolute");
node.style.width = width + "%";
node.style.height = height + "%";
node.style.top = top + "%";
node.style.left = left + "%";
el("events").append(node);
});
allDayEvents.forEach(event => {
let node = create_daily_event(event);
el("allday").append(node);
});
}
//Helpers for monthly calendar
const setup_day_names = () => {
let day_names = '';
for (let word of ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]) {
day_names += `${word}
`;
}
el('daynames').innerHTML = day_names;
}
const setup_day_cells = () => {
let day_cells = '';
week_starts = [];
weeks = [];
let today = dayjs(startOfCalMonth);
let node = null;
for (; dayjs.isSameOrBefore(today, endOfCalMonth); today = today.add(1, 'days')) {
if (today.day() == 0) {
node = document.createElement("DIV");
node.className = "row";
node.eventcounter = 0;
node.expanded = false;
node.expandable = false;
node.addEventListener("click", (e) => {
let target = e.currentTarget;
if (! target.expandable) { return; }
target.expanded = !target.expanded;
let newheight = target.expanded ? Math.max((target.eventcounter + 1) * 2.5 + 1, 11) : 11;
target.style.height = newheight + "rem";
if (target.expanded) {
target.classList.remove("expandable");
target.classList.add("contractable");
}
else {
target.classList.add("expandable");
target.classList.remove("contractable");
}
} );
weeks.push(node);
week_starts.push(dayjs(today));
}
let current = (today.isBefore(startOfMonth) || today.isAfter(endOfMonth)) ? "notcurrent" : "";
let thisday = today.isSame(dayjs(), 'day') ? "today" : "";
let weekend = (today.day() == 0 || today.day() == 6) ? "weekend" : "";
day_cells += `${today.date()}
`;
if (today.day() == 6) {
node.innerHTML = day_cells;
el('monthlycalendar').append(node);
day_cells = "";
}
}
week_starts.push(dayjs(today));
}
const breakup = (events) => {
let brokenupevents = [];
for (let event of events) {
for (let weekstart of week_starts) {
if (weekstart.isBetween(event.datefromeffective, event.datetoeffective, null, '[]')) {
if (!event.broken) {
let mod = { ...event };
mod.datetoeffective = dayjs(weekstart).subtract(1, 'days');
if (dayjs.isSameOrAfter(mod.datetoeffective, mod.datefromeffective)) {
brokenupevents.push(mod);
}
event.broken = true;
}
let mod = { ...event };
mod.datefromeffective = dayjs(weekstart);
mod.datetoeffective = dayjs.min(mod.datetoeffective, dayjs(weekstart).add(6, "days"));
brokenupevents.push(mod);
}
}
if (!event.broken) {
brokenupevents.push(event);
}
}
return brokenupevents;
}
const setup_monthly = (events) => {
el('main').replaceChildren(el('t-monthly').content.cloneNode(true));
el('main').classList.remove("flex-row");
el('main').classList.add("flex-column");
setup_day_names();
setup_day_cells();
let brokenupevents = breakup(events);
let conflict_list = conflict(brokenupevents, "date");
let components = connected_components(conflict_list);
fit_events(components, brokenupevents, "date");
for (let event of brokenupevents) {
for (let i = 0; i < week_starts.length - 1; i++) {
if (event.datefromeffective.isBetween(week_starts[i], week_starts[i+1], null, '[)')) {
let leftdaysdiff = dayjs.duration(event.datefromeffective.diff(week_starts[i])).asDays();
let left = leftdaysdiff / 7 * 100 + 0.5;
let top = 2.5*(event.unitoffset+1);
let rightdaysdiff = dayjs.duration(week_starts[i+1].diff(event.datetoeffective)).asDays() - 1;
let right = rightdaysdiff / 7 * 100 + 0.5;
let numevents = Math.max(weeks[i].eventcounter, event.unitoffset+1);
if (numevents > 3) {
weeks[i].eventcounter = numevents;
weeks[i].expandable = true;
weeks[i].classList.add("expandable");
}
create_monthly_event(left, top, right, event, weeks[i]);
break;
}
}
}
}
const update_indicator = () => {
let curtime = (dayjs() - startOfWorkDay) / (endOfWorkDay - startOfWorkDay) * 100;
el('now').style.top = curtime + "%";
el('now').style.display = curtime >= 0 && curtime < 100 ? 'block' : 'none';
}
const day_onload = (request) => {
let data = null;
if (request.status >= 200 && request.status < 400) {
data = JSON.parse(request.responseText);
}
//else { }
let dailyEvents = [];
let allDayEvents = [];
for (let event of data) {
const tf = dayjs(event.timefrom, "H:mm:ss");
event.datefrom = dayjs(event.datefrom);
event.timefrom = dayjs(event.datefrom).hour(tf.hour()).minute(tf.minute());
const tt = dayjs(event.timeto, "H:mm:ss");
event.dateto = dayjs(event.dateto);
event.timeto = dayjs(event.dateto).hour(tt.hour()).minute(tt.minute());
event.colour ??= event.deleted ? "#ff0000" : "#32acea";
const startsBefore = event.datefrom.isBefore(startOfDay) || dayjs.isSameOrBefore(event.timefrom, startOfWorkDay);
const endsAfter = event.dateto.isAfter(endOfDay) || dayjs.isSameOrAfter(event.timeto, endOfWorkDay) || event.datetbd;
event.timefromeffective = startsBefore ? startOfWorkDay : dayjs.min(lastEvent, event.timefrom);
if (endsAfter || dayjs(event.timefromeffective).add(30, 'minutes').isAfter(endOfWorkDay)) {
event.timetoeffective = endOfWorkDay;
}
else if (event.timetbd) {
if (event.dateto.isBefore(dayjs(), 'day')) {
event.timetoeffective = endOfWorkDay;
}
else {
event.timetoeffective = dayjs.min(dayjs().add(1, 'hour'), endOfWorkDay);
}
}
else {
event.timetoeffective = dayjs.max(dayjs(event.timefromeffective).add(30, 'minutes'), event.timeto);
}
if (startsBefore && endsAfter) {
allDayEvents.push(event);
}
else {
dailyEvents.push(event);
}
}
setup_daily(dailyEvents, allDayEvents);
update_indicator();
}
const month_onload = (request) => {
let data = null;
if (request.status >= 200 && request.status < 400) {
data = JSON.parse(request.responseText);
}
//else { }
for (let event of data) {
let tf = dayjs(event.timefrom, "H:mm:ss");
event.datefrom = dayjs(event.datefrom);
event.timefrom = dayjs(event.datefrom).hour(tf.hour()).minute(tf.minute());
let tt = dayjs(event.timeto, "H:mm:ss");
event.dateto = dayjs(event.dateto);
event.timeto = dayjs(event.dateto).hour(tt.hour()).minute(tt.minute());
event.datefromeffective = event.datefrom;
event.datetoeffective = event.dateto;
if (event.datefrom.isBefore(startOfCalMonth)) {
event.datefromeffective = startOfCalMonth;
}
if (event.dateto.isAfter(endOfCalMonth)) {
event.datetoeffective = endOfCalMonth;
}
if (event.datetbd) {
event.datetoeffective = endOfDay;
}
event.colour = event.deleted ? "#ff0000" : "#32acea";
}
setup_monthly(data);
}
const adjustField = (field) => {
el(`${field}to`).prop( "readOnly", el(`${field}tbd`).prop("checked") );
}
const eventadd = () => {
selectedEvent = null;
let timenow = dayjs().format("HH:mm");
el('name').value = "";
el('message').value = "";
el('timefrom').value = timenow;
el('datefrom').value = selected_date.format('YYYY-MM-DD');
el('timeto').value = timenow;
el('dateto').value = selected_date.format('YYYY-MM-DD');
el('timetbd').checked = false;
el('datetbd').checked = false;
//adjustField('time');
//adjustField('date');
if (el('eventdelete')) {
el('eventdelete').style.display = 'none';
}
const myModal = new bootstrap.Modal(el('eventModal'))
myModal.show();
}
const eventedit = (event) => {
selectedEvent = event.id;
el('name').value = event.name;
el('message').value = event.message;
el('timefrom').value = event.timefrom.format("HH:mm");
el('datefrom').value = event.datefrom.format('YYYY-MM-DD');
el('timeto').value = event.timeto.format("HH:mm");
el('dateto').value = event.dateto.format('YYYY-MM-DD');
el('timetbd').checked = event.timetbd;
el('datetbd').checked = event.datetbd;
if (el('eventdelete')) {
el('eventdelete').style.display = 'block';
}
const myModal = new bootstrap.Modal(el('eventModal'))
myModal.show();
}
const init_event_listeners = () => {
el('selected_date').addEventListener('input', (e) => {
selected_date = dayjs(e.target.value);
init_times(selected_date);
Cookie.set('date', selected_date.format('YYYY-MM-DD'), selected_date.endOf('day').toDate());
lastPicker = null;
clearTimeout(update_timeout);
update();
});
const showPicker = (e) => {
if (e.currentTarget == lastPicker) {
lastPicker = null;
return;
}
lastPicker = e.currentTarget;
e.currentTarget.showPicker();
}
let pickers = ['selected_date', 'timefrom', 'datefrom', 'timeto', 'dateto'];
for (let picker of pickers) {
el(picker).addEventListener('click', showPicker);
el(picker).addEventListener('blur', (e) => {lastPicker = null;})
}
el('caltype').addEventListener("change", (e) => {
selected_cal_type = el('caltype').value;
Cookie.set('caltype', selected_cal_type);
clearTimeout(update_timeout);
update();
});
el('eventModal').addEventListener('shown.bs.modal', () => {
el('name').focus();
})
}
const init_user_listeners = () => {
el('checkout').addEventListener("click", eventadd);
const eventForm = el('eventform');
eventForm.addEventListener("submit", (e) => {
e.preventDefault();
eventForm.action = selectedEvent ? `/calendar/event/${selectedEvent}/edit` : '/calendar/event/add';
eventForm.submit();
});
el('eventdelete').addEventListener("click", (e) => {
eventForm.action = `/calendar/event/${selectedEvent}/delete`;
eventForm.submit();
});
el('eventsubmit').addEventListener("click", (e) => {
eventForm.action = selectedEvent ? `/calendar/event/${selectedEvent}/edit` : '/calendar/event/add';
eventForm.submit();
});
}