|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
import { get as idb_get,
|
|
|
|
|
set as idb_set,
|
|
|
|
|
del as idb_del } from './idb-keyval.js';
|
|
|
|
|
import Assignment from './assignment.js';
|
|
|
|
|
import Schedule from './schedule.js';
|
|
|
|
|
import * as utils from './utils.js';
|
|
|
|
|
|
|
|
|
@ -36,7 +37,7 @@ async function saveSchedule() {
|
|
|
|
|
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
|
|
|
|
await writable.close();
|
|
|
|
|
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
await refreshDisplay();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function updateBackupsList() {
|
|
|
|
@ -91,13 +92,14 @@ document.getElementById('createBackup').addEventListener('click', async e => {
|
|
|
|
|
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
|
|
|
|
await writable.close();
|
|
|
|
|
|
|
|
|
|
updateBackupsList();
|
|
|
|
|
await updateBackupsList();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const titleInput = document.getElementById('title');
|
|
|
|
|
let allowEdits = false;
|
|
|
|
|
async function updateScheduleSettings() {
|
|
|
|
|
updateBackupsList();
|
|
|
|
|
await updateBackupsList();
|
|
|
|
|
|
|
|
|
|
titleInput.value = schedule.base_title;
|
|
|
|
|
titleInput.disabled = !allowEdits;
|
|
|
|
|
|
|
|
|
@ -142,7 +144,7 @@ async function loadFile(file, isBackup) {
|
|
|
|
|
schedule = new Schedule(JSON.parse(await fileObj.text()));
|
|
|
|
|
schedule.lastModified = fileObj.lastModified;
|
|
|
|
|
updateScheduleSettings();
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
await refreshDisplay();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('schedules').addEventListener('change', async e => {
|
|
|
|
@ -233,15 +235,16 @@ function displayFullScheduleAllDays() {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshDisplay() {
|
|
|
|
|
async function refreshDisplay() {
|
|
|
|
|
// TODO support different displays.
|
|
|
|
|
updateScheduleSettings();
|
|
|
|
|
await updateScheduleSettings();
|
|
|
|
|
updateAssignmentEditor();
|
|
|
|
|
displayFullScheduleAllDays();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('changeTitle').addEventListener('click', async e => {
|
|
|
|
|
schedule.base_title = document.getElementById('title').value;
|
|
|
|
|
saveSchedule();
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const personInput = document.getElementById('person');
|
|
|
|
@ -249,27 +252,320 @@ const personInput = document.getElementById('person');
|
|
|
|
|
document.getElementById('addStudent').addEventListener('click', async e => {
|
|
|
|
|
schedule.addStudent(personInput.value);
|
|
|
|
|
personInput.value = '';
|
|
|
|
|
saveSchedule();
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('addStaff').addEventListener('click', async e => {
|
|
|
|
|
schedule.addStaff(personInput.value);
|
|
|
|
|
personInput.value = '';
|
|
|
|
|
saveSchedule();
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('addTeacher').addEventListener('click', async e => {
|
|
|
|
|
schedule.addTeacher(personInput.value);
|
|
|
|
|
personInput.value = '';
|
|
|
|
|
saveSchedule();
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('delStudent').addEventListener('click', async e => {
|
|
|
|
|
schedule.delStudent(document.getElementById('removeStudent').value);
|
|
|
|
|
saveSchedule();
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('delStaff').addEventListener('click', async e => {
|
|
|
|
|
schedule.delStaff(document.getElementById('removeStaff').value);
|
|
|
|
|
saveSchedule();
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const assignmentFormDiv = document.getElementById('assignmentFormDiv');
|
|
|
|
|
const assignmentForm = document.getElementById('assignmentForm');
|
|
|
|
|
const assignmentSelector = assignmentForm.assignments;
|
|
|
|
|
const peopleTable = document.getElementById('peopleTable');
|
|
|
|
|
let peopleCheckboxes = undefined;
|
|
|
|
|
let selectedAssignment = undefined;
|
|
|
|
|
|
|
|
|
|
assignmentSelector.addEventListener('change', e => {
|
|
|
|
|
initializeAssignmentForm(e.target.selectedOptions[0].assignment);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('delAssignment').addEventListener('click', async e => {
|
|
|
|
|
if (window.confirm("Are you sure you want to delete "
|
|
|
|
|
+ selectedAssignment + "?")) {
|
|
|
|
|
schedule.assignments.splice(
|
|
|
|
|
schedule.assignments.indexOf(selectedAssignment), 1);
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('newAssignment').addEventListener('click', async e => {
|
|
|
|
|
const newAssignment = new Assignment({
|
|
|
|
|
location: "enter event location",
|
|
|
|
|
start_time: schedule.start_time,
|
|
|
|
|
end_time: schedule.end_time,
|
|
|
|
|
days: schedule.all_days,
|
|
|
|
|
});
|
|
|
|
|
schedule.assignments.push(newAssignment);
|
|
|
|
|
selectedAssignment = newAssignment;
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function updateAssignmentEditor() {
|
|
|
|
|
if (!allowEdits) {
|
|
|
|
|
assignmentFormDiv.style.display = 'none';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assignmentFormDiv.style.display = '';
|
|
|
|
|
|
|
|
|
|
utils.clearChildren(assignmentSelector);
|
|
|
|
|
let currentAssignment = schedule.assignments.length === 0
|
|
|
|
|
? undefined
|
|
|
|
|
: schedule.assignments[0];
|
|
|
|
|
schedule.assignments.forEach(a => {
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
option.innerText = a.toString();
|
|
|
|
|
option.assignment = a;
|
|
|
|
|
if (a === selectedAssignment) {
|
|
|
|
|
currentAssignment = a;
|
|
|
|
|
option.selected = true;
|
|
|
|
|
}
|
|
|
|
|
assignmentSelector.appendChild(option);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assignmentForm.start_time.step = schedule.granularity.total_seconds;
|
|
|
|
|
assignmentForm.end_time.step = schedule.granularity.total_seconds;
|
|
|
|
|
|
|
|
|
|
rebuildPeopleTable();
|
|
|
|
|
|
|
|
|
|
if (currentAssignment) initializeAssignmentForm(currentAssignment);
|
|
|
|
|
else selectedAssignment = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rebuildPeopleTable() {
|
|
|
|
|
// Check if necessary.
|
|
|
|
|
if (peopleCheckboxes
|
|
|
|
|
&& utils.setEqual(schedule.all_days, Object.keys(peopleCheckboxes.days))
|
|
|
|
|
&& utils.setEqual(schedule.all_staff, Object.keys(peopleCheckboxes.staff))
|
|
|
|
|
&& utils.setEqual(schedule.all_students, Object.keys(peopleCheckboxes.students))) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peopleCheckboxes = { days: {}, staff: {}, students: {}};
|
|
|
|
|
utils.clearChildren(peopleTable);
|
|
|
|
|
const tbody = document.createElement('tbody');
|
|
|
|
|
|
|
|
|
|
const daysRow = document.createElement('tr');
|
|
|
|
|
daysRow.appendChild(document.createElement('th'));
|
|
|
|
|
for (const day of schedule.all_days) {
|
|
|
|
|
const dayHeader = document.createElement('th');
|
|
|
|
|
const dayCheckbox = document.createElement('input');
|
|
|
|
|
const dayLabel = document.createElement('label');
|
|
|
|
|
|
|
|
|
|
dayCheckbox.type = 'checkbox';
|
|
|
|
|
dayCheckbox.name = 'days';
|
|
|
|
|
dayCheckbox.value = day;
|
|
|
|
|
dayCheckbox.id = day;
|
|
|
|
|
dayCheckbox.classList.add('day');
|
|
|
|
|
dayCheckbox.addEventListener('change', async e => {
|
|
|
|
|
if (e.target.checked) {
|
|
|
|
|
// All staff/students on any day should be included on this new day.
|
|
|
|
|
selectedAssignment.people_by_day[day] = {
|
|
|
|
|
staff: [...selectedAssignment.all_staff],
|
|
|
|
|
students: [...selectedAssignment.all_students],
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
delete selectedAssignment.people_by_day[day];
|
|
|
|
|
}
|
|
|
|
|
selectedAssignment.days = schedule.all_days
|
|
|
|
|
.filter(d => d in selectedAssignment.people_by_day);
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
peopleCheckboxes.days[day] = dayCheckbox;
|
|
|
|
|
|
|
|
|
|
dayLabel.htmlFor = dayCheckbox.id;
|
|
|
|
|
dayLabel.innerText = day;
|
|
|
|
|
dayLabel.title = utils.DayString(day);
|
|
|
|
|
|
|
|
|
|
dayHeader.appendChild(dayCheckbox);
|
|
|
|
|
dayHeader.appendChild(dayLabel);
|
|
|
|
|
daysRow.appendChild(dayHeader);
|
|
|
|
|
}
|
|
|
|
|
tbody.appendChild(daysRow);
|
|
|
|
|
|
|
|
|
|
function addPeopleKindHeader(kind) {
|
|
|
|
|
const headerRow = document.createElement('tr');
|
|
|
|
|
const header = document.createElement('th');
|
|
|
|
|
header.innerText = kind;
|
|
|
|
|
header.colSpan = schedule.all_days.length + 1;
|
|
|
|
|
headerRow.appendChild(header);
|
|
|
|
|
tbody.appendChild(headerRow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let numPeople = 0;
|
|
|
|
|
function addPersonRow(name, kind) {
|
|
|
|
|
const idNum = ++numPeople;
|
|
|
|
|
const id = "person" + idNum;
|
|
|
|
|
const row = document.createElement('tr');
|
|
|
|
|
|
|
|
|
|
const personCell = document.createElement('th');
|
|
|
|
|
const personCheckbox = document.createElement('input');
|
|
|
|
|
const personLabel = document.createElement('label');
|
|
|
|
|
|
|
|
|
|
personCheckbox.type = 'checkbox';
|
|
|
|
|
personCheckbox.name = 'people';
|
|
|
|
|
personCheckbox.value = name;
|
|
|
|
|
personCheckbox.id = id;
|
|
|
|
|
personCheckbox.classList.add('person');
|
|
|
|
|
personCheckbox.addEventListener('change', async e => {
|
|
|
|
|
if (e.target.checked) {
|
|
|
|
|
// All days should be included for this new person.
|
|
|
|
|
for (const people_for_day of Object.values(selectedAssignment.people_by_day)) {
|
|
|
|
|
const list = people_for_day[kind];
|
|
|
|
|
list.push(name);
|
|
|
|
|
list.sort();
|
|
|
|
|
}
|
|
|
|
|
selectedAssignment['all_' + kind].add(name);
|
|
|
|
|
} else {
|
|
|
|
|
for (const people_for_day of Object.values(selectedAssignment.people_by_day)) {
|
|
|
|
|
const list = people_for_day[kind];
|
|
|
|
|
list.splice(list.indexOf(name), 1);
|
|
|
|
|
}
|
|
|
|
|
selectedAssignment['all_' + kind].delete(name);
|
|
|
|
|
}
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
peopleCheckboxes[kind][name] = { all: personCheckbox, days: {} }
|
|
|
|
|
|
|
|
|
|
personLabel.htmlFor = personCheckbox.id;
|
|
|
|
|
personLabel.innerText = name;
|
|
|
|
|
personLabel.title = name;
|
|
|
|
|
|
|
|
|
|
personCell.appendChild(personCheckbox);
|
|
|
|
|
personCell.appendChild(personLabel);
|
|
|
|
|
row.appendChild(personCell);
|
|
|
|
|
|
|
|
|
|
for (const day of schedule.all_days) {
|
|
|
|
|
const personDayCell = document.createElement('td');
|
|
|
|
|
const personDayCheckbox = document.createElement('input');
|
|
|
|
|
const personDayLabel = document.createElement('label');
|
|
|
|
|
|
|
|
|
|
personDayCheckbox.type = 'checkbox';
|
|
|
|
|
personDayCheckbox.name = day + kind;
|
|
|
|
|
personDayCheckbox.value = name;
|
|
|
|
|
personDayCheckbox.id = day + idNum;
|
|
|
|
|
personDayCheckbox.classList.add('person_for_day');
|
|
|
|
|
personDayCheckbox.classList.add('person_for_' + day);
|
|
|
|
|
personDayCheckbox.classList.add(id);
|
|
|
|
|
personDayCheckbox.addEventListener('change', async e => {
|
|
|
|
|
if (e.target.checked) {
|
|
|
|
|
if (!(day in selectedAssignment.people_by_day)) {
|
|
|
|
|
selectedAssignment.people_by_day[day] = {staff: [], students: []};
|
|
|
|
|
selectedAssignment.days = schedule.all_days
|
|
|
|
|
.filter(d => d in selectedAssignment.people_by_day);
|
|
|
|
|
}
|
|
|
|
|
const list = selectedAssignment.people_by_day[day][kind];
|
|
|
|
|
list.push(name);
|
|
|
|
|
list.sort();
|
|
|
|
|
selectedAssignment['all_' + kind].add(name);
|
|
|
|
|
} else {
|
|
|
|
|
const list = selectedAssignment.people_by_day[day][kind];
|
|
|
|
|
list.splice(list.indexOf(name), 1);
|
|
|
|
|
if (!Object.values(selectedAssignment.people_by_day)
|
|
|
|
|
.some(people_for_day => people_for_day[kind].includes(name))) {
|
|
|
|
|
selectedAssignment['all_' + kind].delete(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
peopleCheckboxes[kind][name].days[day] = personDayCheckbox;
|
|
|
|
|
|
|
|
|
|
personDayLabel.htmlFor = personDayCheckbox.id;
|
|
|
|
|
personDayLabel.title = name + ' on ' + utils.DayString(day);
|
|
|
|
|
|
|
|
|
|
personDayCell.appendChild(personDayCheckbox);
|
|
|
|
|
personDayCell.appendChild(personDayLabel);
|
|
|
|
|
row.appendChild(personDayCell);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tbody.appendChild(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addPeopleKindHeader("Teachers");
|
|
|
|
|
schedule.all_teachers.forEach(name => addPersonRow(name, 'staff'));
|
|
|
|
|
addPeopleKindHeader("Staff");
|
|
|
|
|
utils.setDifference(schedule.all_staff, schedule.all_teachers)
|
|
|
|
|
.forEach(name => addPersonRow(name, 'staff'));
|
|
|
|
|
addPeopleKindHeader("Students");
|
|
|
|
|
schedule.all_students.forEach(name => addPersonRow(name, 'students'));
|
|
|
|
|
|
|
|
|
|
peopleTable.appendChild(tbody);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function initializeAssignmentForm(assignment) {
|
|
|
|
|
selectedAssignment = assignment;
|
|
|
|
|
assignmentForm.location.value = assignment.location;
|
|
|
|
|
assignmentForm.start_time.value = assignment.start_time.inputValue;
|
|
|
|
|
assignmentForm.end_time.value = assignment.end_time.inputValue;
|
|
|
|
|
assignmentForm.squishable.value = assignment.squishable;
|
|
|
|
|
assignmentForm.track.value = Array.isArray(assignment.track)
|
|
|
|
|
? assignment.track.join(',')
|
|
|
|
|
: (assignment.track ?? "auto");
|
|
|
|
|
assignmentForm.notes.value = assignment.notes ?? "";
|
|
|
|
|
|
|
|
|
|
for (const day of schedule.all_days) {
|
|
|
|
|
peopleCheckboxes.days[day].checked = day in assignment.people_by_day;
|
|
|
|
|
}
|
|
|
|
|
for (const staff of schedule.all_staff) {
|
|
|
|
|
peopleCheckboxes.staff[staff].all.checked = assignment.all_staff.has(staff);
|
|
|
|
|
for (const day of schedule.all_days) {
|
|
|
|
|
peopleCheckboxes.staff[staff].days[day].checked =
|
|
|
|
|
day in assignment.people_by_day
|
|
|
|
|
&& assignment.people_by_day[day]['staff'].includes(staff);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const student of schedule.all_students) {
|
|
|
|
|
peopleCheckboxes.students[student].all.checked = assignment.all_students.has(student);
|
|
|
|
|
for (const day of schedule.all_days) {
|
|
|
|
|
peopleCheckboxes.students[student].days[day].checked =
|
|
|
|
|
day in assignment.people_by_day
|
|
|
|
|
&& assignment.people_by_day[day]['students'].includes(student);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assignmentForm.location.addEventListener('change', async e => {
|
|
|
|
|
selectedAssignment.location = e.target.value;
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assignmentForm.start_time.addEventListener('change', async e => {
|
|
|
|
|
selectedAssignment.start_time = utils.Time.fromInputValue(e.target.value);
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assignmentForm.end_time.addEventListener('change', async e => {
|
|
|
|
|
selectedAssignment.end_time = utils.Time.fromInputValue(e.target.value);
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assignmentForm.squishable.addEventListener('change', async e => {
|
|
|
|
|
selectedAssignment.squishable = e.target.value;
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assignmentForm.track.addEventListener('change', async e => {
|
|
|
|
|
const str = e.target.selectedOptions[0].value;
|
|
|
|
|
if (str === 'auto') selectedAssignment.track = undefined;
|
|
|
|
|
else if (str === 'all') selectedAssignment.track = 'all';
|
|
|
|
|
else if (str.includes(',')) {
|
|
|
|
|
selectedAssignment.track = str.split(',').map(s => parseInt(s));
|
|
|
|
|
} else selectedAssignment.track = parseInt(str);
|
|
|
|
|
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assignmentForm.notes.addEventListener('change', async e => {
|
|
|
|
|
selectedAssignment.notes = e.target.value;
|
|
|
|
|
await saveSchedule();
|
|
|
|
|
});
|
|
|
|
|