1260 lines
43 KiB
JavaScript
1260 lines
43 KiB
JavaScript
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';
|
|
|
|
|
|
const openDirButton = document.getElementById('openDir');
|
|
const reopenDirButton = document.getElementById('reopenDir');
|
|
const closeDirButton = document.getElementById('closeDir');
|
|
|
|
const importButton = document.getElementById('import');
|
|
const importFileInput = document.getElementById('importFile');
|
|
const exportButton = document.getElementById('export');
|
|
|
|
const schedulesDiv = document.getElementById('allSchedules');
|
|
const scheduleSettingsDiv = document.getElementById('scheduleSettings');
|
|
const scheduleMetadataDiv = document.getElementById('scheduleMetadata')
|
|
|
|
const displaySettingsDiv = document.getElementById('displaySettings');
|
|
const displayDays = document.getElementById('displayDays');
|
|
const displayPeople = document.getElementById('displayPeople');
|
|
|
|
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;
|
|
|
|
const personInfoDiv = document.getElementById('personInfo');
|
|
const personStartTime = document.getElementById('person_start_time');
|
|
const personEndTime = document.getElementById('person_end_time');
|
|
const personDays = document.getElementById('person_days');
|
|
|
|
const warningsSection = document.getElementById('warningsSection');
|
|
const warningsDiv = document.getElementById('warnings');
|
|
|
|
const errorTitle = "ERROR loading schedule; try a backup";
|
|
|
|
|
|
let dirHandle = null;
|
|
let backupsDir = null;
|
|
let fileHandle = null;
|
|
let basefileHandle = null;
|
|
let schedule = null;
|
|
let hash = {};
|
|
try {
|
|
if (window.location.hash) {
|
|
hash = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
|
|
}
|
|
} catch (SyntaxError) {
|
|
// Ignore invalid hash.
|
|
hash = {};
|
|
}
|
|
|
|
function updateHash(obj) {
|
|
for (const key in obj) {
|
|
hash[key] = obj[key];
|
|
}
|
|
window.location.hash = JSON.stringify(hash);
|
|
}
|
|
|
|
function closeDir() {
|
|
dirHandle = null;
|
|
idb_del('dirHandle');
|
|
openDirButton.style.display = '';
|
|
closeDirButton.style.display = 'none';
|
|
window.location.reload();
|
|
}
|
|
|
|
async function saveSchedule() {
|
|
if (fileHandle !== basefileHandle) {
|
|
alert("Cannot modify backups. This should not be reachable.");
|
|
return;
|
|
}
|
|
|
|
schedule.lastModified = new Date();
|
|
const writable = await basefileHandle.createWritable();
|
|
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
|
await writable.close();
|
|
|
|
await refreshDisplay();
|
|
}
|
|
|
|
async function updateBackupsList() {
|
|
backupsDir = await dirHandle.getDirectoryHandle(
|
|
basefileHandle.name.slice(0, -5), {create: true});
|
|
|
|
const backupsList = document.getElementById('backups');
|
|
utils.clearChildren(backupsList);
|
|
const options = [];
|
|
|
|
for await (const entry of backupsDir.values()) {
|
|
if (entry.kind === 'file' && entry.name.endsWith('.json')) {
|
|
const fileObj = await entry.getFile()
|
|
const timestamp = fileObj.lastModified;
|
|
const option = document.createElement('option');
|
|
option.innerText = entry.name.slice(0, -5) + " ["
|
|
+ new Date(timestamp) + "]";
|
|
option.timestamp = timestamp;
|
|
option.file = entry;
|
|
option.isBackup = true;
|
|
if (entry.name === fileHandle.name) option.selected = true;
|
|
options.push(option);
|
|
}
|
|
}
|
|
|
|
options.sort((a, b) => b.timestamp - a.timestamp);
|
|
|
|
const current = document.createElement('option');
|
|
const currentTimestamp = (await basefileHandle.getFile()).lastModified;
|
|
current.innerText = "(latest) [" + new Date(currentTimestamp) + "]";
|
|
current.timestamp = currentTimestamp;
|
|
current.file = basefileHandle;
|
|
current.isBackup = false;
|
|
options.unshift(current);
|
|
|
|
options.forEach(opt => backupsList.appendChild(opt));
|
|
}
|
|
|
|
async function createBackup(force) {
|
|
let name = document.getElementById('backupName').value;
|
|
|
|
let attempts = 0;
|
|
const dateName = new Date().toISOString().replaceAll(':', '-');
|
|
if (name.length === 0) {
|
|
name = dateName;
|
|
attempts++;
|
|
}
|
|
let file = null;
|
|
do {
|
|
file = await backupsDir.getFileHandle(name + '.json', {create: true});
|
|
if ((await file.getFile()).size !== 0) {
|
|
if (!force) {
|
|
alert("Failed to create backup. Backup \"" + name + "\" already exists.");
|
|
return;
|
|
} else {
|
|
file = null;
|
|
name = dateName + "-" + attempts++;
|
|
}
|
|
}
|
|
} while (!file);
|
|
|
|
const writable = await file.createWritable();
|
|
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
|
await writable.close();
|
|
|
|
await updateBackupsList();
|
|
}
|
|
|
|
async function restoreBackup() {
|
|
if (!hash.backup) return;
|
|
// swap back to base file...
|
|
const backupFile = fileHandle;
|
|
await loadFile(basefileHandle, false);
|
|
// ... create a new backup...
|
|
await createBackup(true);
|
|
// ... then reload the backup file...
|
|
await loadFile(backupFile, true);
|
|
// ... and save over the base file.
|
|
fileHandle = basefileHandle;
|
|
await saveSchedule();
|
|
await loadFile(basefileHandle, false);
|
|
}
|
|
|
|
document.getElementById('createBackup').addEventListener('click', async e => {
|
|
createBackup();
|
|
});
|
|
|
|
document.getElementById('restoreBackup').addEventListener('click', async e => {
|
|
restoreBackup();
|
|
});
|
|
|
|
function download(filename, text, mimeType) {
|
|
const element = document.createElement('a');
|
|
element.setAttribute('href', 'data:' + mimeType + ',' + encodeURIComponent(text));
|
|
element.setAttribute('download', filename);
|
|
|
|
element.style.display = 'none';
|
|
document.body.appendChild(element);
|
|
|
|
element.click();
|
|
|
|
document.body.removeChild(element);
|
|
}
|
|
|
|
async function exportToFile() {
|
|
const name = basefileHandle.name.slice(0, -5);
|
|
const dateName = new Date().toISOString().replaceAll(':', '-');
|
|
download(name + '-' + dateName + '.json',
|
|
JSON.stringify(schedule.asJsonObject(), null, 2),
|
|
'application/json');
|
|
}
|
|
|
|
async function importFromFile() {
|
|
const fileList = importFileInput.files;
|
|
const file = fileList[0];
|
|
const reader = new FileReader();
|
|
|
|
if (hash.backup) {
|
|
// swap back to base file...
|
|
await loadFile(basefileHandle, false);
|
|
}
|
|
// ... create a new backup...
|
|
await createBackup(true);
|
|
|
|
reader.onload = async (e) => {
|
|
const json = e.target.result;
|
|
|
|
schedule = new Schedule(JSON.parse(json));
|
|
schedule.lastModified = file.lastModified;
|
|
|
|
await saveSchedule();
|
|
await loadFile(basefileHandle, false);
|
|
}
|
|
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
exportButton.addEventListener('click', async e => {
|
|
exportToFile();
|
|
});
|
|
|
|
importButton.addEventListener('click', async e => {
|
|
importFileInput.click();
|
|
});
|
|
|
|
importFileInput.addEventListener('change', async e => {
|
|
importFromFile();
|
|
});
|
|
|
|
async function cloneSchedule(includeAssignments) {
|
|
let name = document.getElementById('cloneName').value;
|
|
if (name.length === 0) {
|
|
name = new Date().toISOString().replaceAll(':', '-');
|
|
}
|
|
|
|
const file = await dirHandle.getFileHandle(name + '.json', {create: true});
|
|
if ((await file.getFile()).size !== 0) {
|
|
alert("Failed to create new schedule. Schedule \"" + name
|
|
+ "\" already exists.");
|
|
return;
|
|
}
|
|
|
|
const writable = await file.createWritable();
|
|
if (!includeAssignments) schedule.assignments = [];
|
|
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
|
await writable.close();
|
|
|
|
window.location.reload();
|
|
}
|
|
|
|
document.getElementById('cloneSch').addEventListener('click', async e => {
|
|
await cloneSchedule(true);
|
|
});
|
|
|
|
document.getElementById('cloneSchNoAssignments').addEventListener('click', async e => {
|
|
await cloneSchedule(false);
|
|
});
|
|
|
|
const titleInput = document.getElementById('title');
|
|
const schStartTime = document.getElementById('sch_start_time')
|
|
const schEndTime = document.getElementById('sch_end_time')
|
|
const schGranularity = document.getElementById('granularity')
|
|
let allowEdits = false;
|
|
async function updateScheduleSettings() {
|
|
await updateBackupsList();
|
|
|
|
titleInput.value = schedule.base_title;
|
|
schStartTime.value = schedule.start_time.inputValue;
|
|
schEndTime.value = schedule.end_time.inputValue;
|
|
schGranularity.value = schedule.granularity.inputValue;
|
|
|
|
const daysSpan = document.getElementById('sch-days');
|
|
utils.clearChildren(daysSpan);
|
|
for (const day of utils.allDays) {
|
|
const dayLabel = document.createElement('label');
|
|
const dayName = utils.DayString(day);
|
|
const dayCheckbox = document.createElement('input');
|
|
|
|
dayCheckbox.type = 'checkbox';
|
|
dayCheckbox.checked = schedule.all_days.includes(day);
|
|
dayCheckbox.addEventListener('change', async e => {
|
|
if (!e.target.checked) {
|
|
if (schedule.assignments.some(a => day in a.people_by_day)) {
|
|
alert("Cannot remove schedule for " + dayName
|
|
+ " because there are events on that day.");
|
|
e.target.checked = true;
|
|
return;
|
|
}
|
|
schedule.all_days.splice(schedule.all_days.indexOf(day), 1);
|
|
} else {
|
|
schedule.all_days = utils.allDays.filter(d => schedule.all_days.includes(d) || d === day)
|
|
}
|
|
await saveSchedule();
|
|
});
|
|
|
|
dayLabel.appendChild(dayCheckbox);
|
|
dayLabel.appendChild(document.createTextNode(dayName));
|
|
daysSpan.appendChild(dayLabel);
|
|
}
|
|
|
|
scheduleMetadataDiv
|
|
.querySelectorAll('*')
|
|
.forEach(el => el.disabled = !allowEdits);
|
|
|
|
schedule.recomputeNotAssigned();
|
|
|
|
const staffList = document.getElementById('removeStaff');
|
|
utils.clearChildren(staffList);
|
|
schedule.all_staff_teachers_first.forEach(name => {
|
|
const option = document.createElement('option');
|
|
option.value = name;
|
|
option.innerText = (schedule.assignedStaff.has(name) ? '(!) ' : '') + name;
|
|
staffList.appendChild(option);
|
|
});
|
|
const canDelStaff = allowEdits;
|
|
staffList.disabled = !canDelStaff;
|
|
document.getElementById('delStaff').disabled = !canDelStaff;
|
|
|
|
const studentList = document.getElementById('removeStudent');
|
|
utils.clearChildren(studentList);
|
|
schedule.all_students.forEach(name => {
|
|
const option = document.createElement('option');
|
|
option.value = name;
|
|
option.innerText = (schedule.assignedStudents.has(name) ? '(!) ' : '') + name;
|
|
studentList.appendChild(option);
|
|
});
|
|
const canDelStudent = allowEdits;
|
|
studentList.disabled = !canDelStudent;
|
|
document.getElementById('delStudent').disabled = !canDelStudent;
|
|
|
|
updateToTeacherButtons();
|
|
|
|
scheduleSettingsDiv.style.display = '';
|
|
}
|
|
|
|
async function loadFile(file, isBackup) {
|
|
const name = file.name.slice(0, -5);
|
|
fileHandle = file;
|
|
if (!isBackup) {
|
|
basefileHandle = fileHandle;
|
|
updateHash({backup: undefined, schedule: name});
|
|
} else {
|
|
updateHash({backup: name});
|
|
}
|
|
document.getElementById('restoreBackup').disabled = !isBackup;
|
|
allowEdits = !isBackup;
|
|
const fileObj = await file.getFile();
|
|
try {
|
|
schedule = new Schedule(JSON.parse(await fileObj.text()));
|
|
schedulesDiv.style.display = '';
|
|
scheduleMetadataDiv.style.display = '';
|
|
displaySettingsDiv.style.display = '';
|
|
} catch {
|
|
schedule = new Schedule({
|
|
title: errorTitle,
|
|
assignments: [],
|
|
all_days: ['M', 'T', 'W', 'R', 'F'],
|
|
start_time: { hour: 9 },
|
|
end_time: { hour: 17 },
|
|
});
|
|
schedulesDiv.style.display = 'none';
|
|
scheduleMetadataDiv.style.display = 'none';
|
|
displaySettingsDiv.style.display = 'none';
|
|
}
|
|
schedule.lastModified = fileObj.lastModified;
|
|
await refreshDisplay();
|
|
}
|
|
|
|
document.getElementById('schedules').addEventListener('change', async e => {
|
|
loadFile(e.target.selectedOptions[0].file, false);
|
|
});
|
|
|
|
document.getElementById('backups').addEventListener('change', async e => {
|
|
const opt = e.target.selectedOptions[0];
|
|
loadFile(opt.file, opt.isBackup);
|
|
});
|
|
|
|
async function loadDir(dir) {
|
|
dirHandle = dir;
|
|
openDirButton.style.display = 'none';
|
|
reopenDirButton.style.display = 'none';
|
|
closeDirButton.style.display = '';
|
|
|
|
const schedulesList = document.getElementById('schedules');
|
|
utils.clearChildren(schedulesList);
|
|
const options = [];
|
|
|
|
let newest = undefined;
|
|
let newestTimestamp = undefined;
|
|
for await (const entry of dir.values()) {
|
|
if (entry.kind === 'file' && !entry.name.startsWith('.') && entry.name.endsWith('.json')) {
|
|
const fileObj = await entry.getFile()
|
|
const timestamp = fileObj.lastModified;
|
|
if (!newestTimestamp || timestamp > newestTimestamp) {
|
|
newest = entry;
|
|
newestTimestamp = timestamp;
|
|
}
|
|
const option = document.createElement('option');
|
|
let title;
|
|
try {
|
|
title = JSON.parse(await fileObj.text()).title;
|
|
} catch {
|
|
title = errorTitle;
|
|
}
|
|
const name = entry.name.slice(0, -5);
|
|
option.innerText = title + " (" + name + ") ["
|
|
+ new Date(timestamp) + "]";
|
|
option.timestamp = timestamp;
|
|
option.file = entry;
|
|
option.value = name;
|
|
options.push(option);
|
|
}
|
|
}
|
|
|
|
options.sort((a, b) => b.timestamp - a.timestamp).forEach(opt =>
|
|
schedulesList.appendChild(opt));
|
|
|
|
if (newest === undefined) {
|
|
const file = await dirHandle.getFileHandle('default.json', {create: true});
|
|
|
|
const writable = await file.createWritable();
|
|
await writable.write(JSON.stringify(new Schedule({
|
|
title: "DEFAULT TITLE",
|
|
assignments: [],
|
|
all_days: ['M', 'T', 'W', 'R', 'F'],
|
|
start_time: { hour: 9 },
|
|
end_time: { hour: 17 },
|
|
}).asJsonObject(), null, 2));
|
|
await writable.close();
|
|
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
|
|
if (hash.schedule) {
|
|
const schOpt = options.find(o => o.value === hash.schedule);
|
|
if (schOpt) {
|
|
schOpt.selected = true;
|
|
// If there's an invalid backup name, just load the base schedule.
|
|
basefileHandle = newest = schOpt.file;
|
|
if (hash.backup) {
|
|
backupsDir = await dirHandle.getDirectoryHandle(
|
|
basefileHandle.name.slice(0, -5), {create: true});
|
|
const backupFile = await backupsDir
|
|
.getFileHandle(hash.backup + '.json');
|
|
if (backupFile) {
|
|
await loadFile(backupFile, true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await loadFile(newest, false);
|
|
}
|
|
|
|
if (window.showDirectoryPicker) {
|
|
document.getElementById('browserVersionWarning').style.display = 'none';
|
|
|
|
idb_get('dirHandle').then(async dir => {
|
|
if (dir) {
|
|
if (await dir.queryPermission({mode: 'readwrite'}) === 'granted') {
|
|
await loadDir(dir);
|
|
} else {
|
|
reopenDirButton.style.display = '';
|
|
reopenDirButton.addEventListener('click', async e => {
|
|
if (await dir.requestPermission({mode: 'readwrite'}) === 'granted') {
|
|
await loadDir(dir);
|
|
}
|
|
reopenDirButton.style.display = 'none';
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
closeDirButton.addEventListener('click', e => closeDir());
|
|
|
|
openDirButton.style.display = '';
|
|
openDirButton.addEventListener('click', async e => {
|
|
const dir = await window.showDirectoryPicker({mode: 'readwrite'});
|
|
if (dir) {
|
|
await idb_set('dirHandle', dir);
|
|
await loadDir(dir);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
// Can't use real file system, so use OPFS instead.
|
|
await loadDir(await navigator.storage.getDirectory());
|
|
closeDirButton.addEventListener('click', e => closeDir());
|
|
}
|
|
|
|
function displayFullSchedule() {
|
|
(hash.day ? [hash.day] : schedule.all_days)
|
|
.forEach(day => {
|
|
const grid = schedule.fullGridFor(day);
|
|
const title = document.createElement('h1');
|
|
title.innerText = schedule.titleFor(null, day, hash.backup);
|
|
schedulesDiv.appendChild(title);
|
|
schedulesDiv.appendChild(grid.toHtml([], allowEdits ? selectAssignment : null));
|
|
})
|
|
}
|
|
|
|
function displayIndividualSchedule(person) {
|
|
if (person.kind === 'staff' && !schedule.all_staff.includes(person.name)
|
|
|| person.kind === 'student' && !schedule.all_students.includes(person.name)) {
|
|
updateHash({person: undefined});
|
|
displaySchedule();
|
|
return;
|
|
}
|
|
|
|
if (hash.day) {
|
|
const grid = schedule.gridFor(person, hash.day);
|
|
const title = document.createElement('h1');
|
|
title.innerText = schedule.titleFor(person, hash.day, hash.backup);
|
|
schedulesDiv.appendChild(title);
|
|
schedulesDiv.appendChild(grid.toHtml([], allowEdits ? selectAssignment : null));
|
|
} else {
|
|
const title = document.createElement('h1');
|
|
title.innerText = schedule.titleFor(person, null, hash.backup);
|
|
schedulesDiv.appendChild(title);
|
|
|
|
const daysTable = document.createElement('table');
|
|
daysTable.classList.add('multiday');
|
|
const row = document.createElement('tr');
|
|
|
|
(schedule.infoFor(person).days ?? schedule.all_days)
|
|
.forEach(day => {
|
|
const cell = document.createElement('td');
|
|
const grid = schedule.gridFor(person, day, hash.backup);
|
|
const dayTitle = document.createElement('h2');
|
|
dayTitle.innerText = person.name + ' on ' + utils.DayString(day);
|
|
cell.appendChild(dayTitle);
|
|
cell.appendChild(grid.toHtml(['narrow'], allowEdits ? selectAssignment : null));
|
|
row.appendChild(cell);
|
|
});
|
|
|
|
daysTable.appendChild(row);
|
|
schedulesDiv.appendChild(daysTable);
|
|
}
|
|
}
|
|
|
|
function updateDisplayOptions() {
|
|
utils.clearChildren(displayDays);
|
|
for (const day of [undefined, ...schedule.all_days]) {
|
|
const dayOption = document.createElement('option');
|
|
dayOption.value = day;
|
|
dayOption.dayHash = day;
|
|
dayOption.selected = hash.day === day;
|
|
dayOption.innerText = day ? utils.DayString(day) : "All Days";
|
|
displayDays.appendChild(dayOption);
|
|
}
|
|
|
|
utils.clearChildren(displayPeople);
|
|
for (const person of ['(full)', null,
|
|
'All Teachers', 'All Staff', 'All Students', null,
|
|
...schedule.all_teachers
|
|
.map(name => ({kind: 'staff', name})), null,
|
|
...schedule.all_non_teacher_staff
|
|
.map(name => ({kind: 'staff', name})), null,
|
|
...schedule.all_students
|
|
.map(name => ({kind: 'student', name}))]) {
|
|
const personOption = document.createElement('option');
|
|
if (person) {
|
|
personOption.personHash = person === '(full)' ? undefined : person;
|
|
personOption.selected = !hash.person
|
|
? person === '(full)'
|
|
: JSON.stringify(hash.person) === JSON.stringify(person);
|
|
personOption.innerText = person.name ?? person;
|
|
} else {
|
|
personOption.disabled = true;
|
|
personOption.innerText = "--------";
|
|
}
|
|
displayPeople.appendChild(personOption);
|
|
}
|
|
|
|
displaySettingsDiv.style.display = '';
|
|
}
|
|
|
|
displayDays.addEventListener('change', e => {
|
|
updateHash({day: e.target.selectedOptions[0].dayHash});
|
|
displaySchedule();
|
|
});
|
|
|
|
displayPeople.addEventListener('change', e => {
|
|
updateHash({person: e.target.selectedOptions[0].personHash});
|
|
displaySchedule();
|
|
});
|
|
|
|
async function refreshDisplay() {
|
|
await updateScheduleSettings();
|
|
updateDisplayOptions();
|
|
updateAssignmentEditor();
|
|
displayWarnings();
|
|
displaySchedule();
|
|
}
|
|
|
|
function displayWarnings() {
|
|
utils.clearChildren(warningsDiv);
|
|
|
|
const allWarningGroups = schedule.generateWarnings();
|
|
warningsSection.style.display = allWarningGroups.length === 0 ? 'none' : '';
|
|
for (const warningGroup of allWarningGroups) {
|
|
if (!warningGroup.person) {
|
|
const groupHeader = document.createElement('h5');
|
|
groupHeader.innerText = 'Events';
|
|
warningsDiv.appendChild(groupHeader);
|
|
} else {
|
|
const groupHeader = document.createElement('h5');
|
|
groupHeader.innerText = warningGroup.person.name;
|
|
groupHeader.classList.add('clickable');
|
|
groupHeader.addEventListener('click', e => {
|
|
updateHash({person: warningGroup.person});
|
|
[...displayPeople.getElementsByTagName('option')]
|
|
.find(o => o.personHash
|
|
&& o.personHash.name === warningGroup.person.name
|
|
&& o.personHash.kind === warningGroup.person.kind)
|
|
.selected = true;
|
|
displaySchedule();
|
|
});
|
|
warningsDiv.appendChild(groupHeader);
|
|
}
|
|
|
|
const list = document.createElement('ul');
|
|
for (const warning of warningGroup.warnings) {
|
|
const el = document.createElement('li');
|
|
el.appendChild(document.createTextNode(warning.message));
|
|
let seenFirst = false;
|
|
for (const a of warning.assignments) {
|
|
el.appendChild(document.createTextNode(seenFirst ? ', ' : ': '));
|
|
seenFirst = true;
|
|
const aLink = document.createElement('a');
|
|
aLink.innerText = a.toString();
|
|
if (allowEdits) {
|
|
aLink.classList.add('clickable');
|
|
aLink.addEventListener('click', e => selectAssignment(a));
|
|
}
|
|
el.appendChild(aLink);
|
|
}
|
|
list.appendChild(el);
|
|
}
|
|
warningsDiv.appendChild(list);
|
|
}
|
|
}
|
|
|
|
function displayPersonInfoDiv() {
|
|
const singlePerson = hash.person && hash.person.name;
|
|
personInfoDiv.style.display = singlePerson ? '' : 'none';
|
|
if (!singlePerson) return;
|
|
|
|
const info = schedule.infoFor(hash.person);
|
|
|
|
personStartTime.value = new utils.Time(info.start_time ?? schedule.start_time).inputValue;
|
|
personEndTime.value = new utils.Time(info.end_time ?? schedule.end_time).inputValue;
|
|
|
|
personStartTime.step = schedule.granularity.total_seconds;
|
|
personEndTime.step = schedule.granularity.total_seconds;
|
|
|
|
utils.clearChildren(personDays);
|
|
|
|
for (const day of utils.allDays.filter(d => schedule.all_days.includes(d)
|
|
|| info.days && info.days.includes(d))) {
|
|
const dayLabel = document.createElement('label');
|
|
const dayName = utils.DayString(day);
|
|
const dayCheckbox = document.createElement('input');
|
|
|
|
dayCheckbox.type = 'checkbox';
|
|
dayCheckbox.checked = (info.days ?? schedule.all_days).includes(day);
|
|
dayCheckbox.addEventListener('change', async e => {
|
|
let newDays = info.days;
|
|
if (!e.target.checked) {
|
|
if (schedule.assignments.some(a => a.hasPersonExplicitlyOnDay(hash.person, day))) {
|
|
alert("Cannot remove schedule for " + dayName
|
|
+ " because " + hash.person.name
|
|
+ " is in events on that day.");
|
|
e.target.checked = true;
|
|
return;
|
|
}
|
|
if (!newDays) newDays = [...schedule.all_days];
|
|
newDays.splice(schedule.all_days.indexOf(day), 1);
|
|
} else {
|
|
newDays = schedule.all_days.filter(d => (info.days ?? schedule.all_days).includes(d) || d === day);
|
|
}
|
|
if (newDays && utils.setEqual(newDays, schedule.all_days)) {
|
|
newDays = undefined;
|
|
}
|
|
schedule.setInfoFor(hash.person, {days: newDays});
|
|
await saveSchedule();
|
|
});
|
|
|
|
dayLabel.appendChild(dayCheckbox);
|
|
dayLabel.appendChild(document.createTextNode(dayName));
|
|
personDays.appendChild(dayLabel);
|
|
}
|
|
}
|
|
|
|
personStartTime.addEventListener('change', utils.debounceAsync(async e => {
|
|
const newStartTime = utils.Time.fromInputValue(e.target.value);
|
|
const assignments = schedule.assignments.filter(a => a.hasPersonExplicitlyOnAnyDay(hash.person));
|
|
if (assignments.length > 0) {
|
|
const earliestStart = assignments.map(a => a.start_time).sort((a, b) => a.cmp(b))[0];
|
|
if (earliestStart.cmp(newStartTime) < 0) {
|
|
alert("Cannot change start time to " + newStartTime.to12HourString()
|
|
+ " because it is after the earliest assignment for "
|
|
+ hash.person.name + " starts at "
|
|
+ earliestStart.to12HourString() + ".");
|
|
return;
|
|
}
|
|
}
|
|
schedule.setInfoFor(hash.person, {start_time:
|
|
newStartTime.cmp(schedule.start_time) !== 0
|
|
? newStartTime.asJsonObject()
|
|
: undefined});
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
personEndTime.addEventListener('change', utils.debounceAsync(async e => {
|
|
const newEndTime = utils.Time.fromInputValue(e.target.value);
|
|
const assignments = schedule.assignments.filter(a => a.hasPersonExplicitlyOnAnyDay(hash.person));
|
|
if (assignments.length > 0) {
|
|
const latestEnd = assignments.map(a => a.end_time).sort((a, b) => b.cmp(a))[0];
|
|
if (latestEnd.cmp(newEndTime) > 0) {
|
|
alert("Cannot change end time to " + newEndTime.to12HourString()
|
|
+ " because it is before the latest assignment for "
|
|
+ hash.person.name + " ends at "
|
|
+ latestEnd.to12HourString() + ".");
|
|
return;
|
|
}
|
|
}
|
|
schedule.setInfoFor(hash.person, {end_time:
|
|
newEndTime.cmp(schedule.end_time) !== 0
|
|
? newEndTime.asJsonObject()
|
|
: undefined});
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
|
|
function displaySchedule() {
|
|
displayPersonInfoDiv();
|
|
utils.clearChildren(schedulesDiv);
|
|
if (hash.day && !schedule.all_days.includes(hash.day)) {
|
|
updateHash({day: undefined});
|
|
}
|
|
if (!hash.person) {
|
|
displayFullSchedule();
|
|
} else if (hash.person.kind) {
|
|
displayIndividualSchedule(hash.person);
|
|
} else if (hash.person === 'All Teachers') {
|
|
schedule.all_teachers.forEach(name =>
|
|
displayIndividualSchedule({kind: 'staff', name}));
|
|
} else if (hash.person === 'All Staff') {
|
|
schedule.all_staff.forEach(name =>
|
|
displayIndividualSchedule({kind: 'staff', name}));
|
|
} else if (hash.person === 'All Students') {
|
|
schedule.all_students.forEach(name =>
|
|
displayIndividualSchedule({kind: 'student', name}));
|
|
} else {
|
|
updateHash({person: undefined});
|
|
displayFullSchedule();
|
|
}
|
|
}
|
|
|
|
document.getElementById('changeTitle').addEventListener('click', async e => {
|
|
schedule.base_title = document.getElementById('title').value;
|
|
await saveSchedule();
|
|
});
|
|
|
|
schStartTime.addEventListener('change', utils.debounceAsync(async e => {
|
|
const newStartTime = utils.Time.fromInputValue(e.target.value);
|
|
if (schedule.assignments && schedule.assignments.length > 0) {
|
|
const earliestStart = schedule.assignments.map(a => a.start_time).sort((a, b) => a.cmp(b))[0];
|
|
if (earliestStart.cmp(newStartTime) < 0) {
|
|
alert("Cannot change start time to " + newStartTime.to12HourString()
|
|
+ " because it is after the earliest assignment starts at "
|
|
+ earliestStart.to12HourString() + ".");
|
|
return;
|
|
}
|
|
}
|
|
schedule.start_time = newStartTime;
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
schEndTime.addEventListener('change', utils.debounceAsync(async e => {
|
|
const newEndTime = utils.Time.fromInputValue(e.target.value);
|
|
if (schedule.assignments && schedule.assignments.length > 0) {
|
|
const latestEnd = schedule.assignments.map(a => a.end_time).sort((a, b) => b.cmp(a))[0];
|
|
if (latestEnd.cmp(newEndTime) > 0) {
|
|
alert("Cannot change end time to " + newEndTime.to12HourString()
|
|
+ " because it is before the latest assignment ends at "
|
|
+ latestEnd.to12HourString() + ".");
|
|
return;
|
|
}
|
|
}
|
|
schedule.end_time = newEndTime;
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
schGranularity.addEventListener('change', utils.debounceAsync(async e => {
|
|
const newGranularity = utils.Time.fromInputValue(e.target.value)
|
|
.durationSinceMidnight;
|
|
const schDuration = schedule.timeRange.duration;
|
|
const newNumRows = schDuration.dividedBy(newGranularity);
|
|
if (!Number.isInteger(newNumRows)) {
|
|
alert("Cannot change granularity to " + newGranularity.toString()
|
|
+ " because it is does not divide the " + schDuration.toString()
|
|
+ " duration from " + schedule.start_time.to12HourString()
|
|
+ " to " + schedule.end_time.to12HourString() + ".");
|
|
return;
|
|
}
|
|
schedule.granularity = newGranularity;
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
const personInput = document.getElementById('person');
|
|
|
|
document.getElementById('addStudent').addEventListener('click', async e => {
|
|
schedule.addStudent(personInput.value);
|
|
personInput.value = '';
|
|
await saveSchedule();
|
|
});
|
|
|
|
document.getElementById('addStaff').addEventListener('click', async e => {
|
|
schedule.addStaff(personInput.value);
|
|
personInput.value = '';
|
|
await saveSchedule();
|
|
});
|
|
|
|
document.getElementById('addTeacher').addEventListener('click', async e => {
|
|
schedule.addTeacher(personInput.value);
|
|
personInput.value = '';
|
|
await saveSchedule();
|
|
});
|
|
|
|
document.getElementById('delStudent').addEventListener('click', async e => {
|
|
const name = document.getElementById('removeStudent').value;
|
|
const person = {name, kind: 'student'};
|
|
if (schedule.assignedStudents.has(name)) {
|
|
if (!window.confirm("Are you sure you want to delete the student " + name
|
|
+ " who is still assigned to events?")) {
|
|
return;
|
|
}
|
|
schedule.assignments.filter(a => a.hasPersonExplicitlyOnAnyDay(person))
|
|
.forEach(a => a.removePerson(person));
|
|
schedule.recomputeNotAssigned();
|
|
}
|
|
|
|
schedule.delStudent(name);
|
|
await saveSchedule();
|
|
});
|
|
|
|
document.getElementById('delStaff').addEventListener('click', async e => {
|
|
const name = document.getElementById('removeStaff').value;
|
|
const person = {name, kind: 'staff'};
|
|
if (schedule.assignedStudents.has(name)) {
|
|
if (!window.confirm("Are you sure you want to delete the staff person "
|
|
+ name + " who is still assigned to events?")) {
|
|
return;
|
|
}
|
|
schedule.assignments.filter(a => a.hasPersonExplicitlyOnAnyDay(person))
|
|
.forEach(a => a.removePerson(person));
|
|
schedule.recomputeNotAssigned();
|
|
}
|
|
|
|
schedule.delStaff(name);
|
|
await saveSchedule();
|
|
});
|
|
|
|
document.getElementById('renameStudent').addEventListener('click', async e => {
|
|
const newName = document.getElementById('nameStudent').value;
|
|
if (schedule.all_students.includes(newName)) {
|
|
window.alert("A student named " + newName + " already exists.");
|
|
return;
|
|
}
|
|
const name = document.getElementById('removeStudent').value;
|
|
const person = {name, kind: 'student'};
|
|
|
|
schedule.all_students.splice(schedule.all_students.indexOf(name), 1);
|
|
schedule.all_students.push(newName);
|
|
schedule.all_students.sort();
|
|
|
|
schedule.assignments.filter(a => a.hasPersonExplicitlyOnAnyDay(person))
|
|
.forEach(a => a.renamePerson(person, newName));
|
|
schedule.recomputeNotAssigned();
|
|
|
|
await saveSchedule();
|
|
});
|
|
|
|
document.getElementById('renameStaff').addEventListener('click', async e => {
|
|
const newName = document.getElementById('nameStaff').value;
|
|
if (schedule.all_staff.includes(newName)) {
|
|
window.alert("A staff person named " + newName + " already exists.");
|
|
return;
|
|
}
|
|
const name = document.getElementById('removeStaff').value;
|
|
const person = {name, kind: 'staff'};
|
|
|
|
if (schedule.all_teachers.includes(name)) {
|
|
schedule.all_teachers.splice(schedule.all_teachers.indexOf(name), 1);
|
|
schedule.all_teachers.push(newName);
|
|
schedule.all_teachers.sort();
|
|
}
|
|
|
|
schedule.all_staff.splice(schedule.all_staff.indexOf(name), 1);
|
|
schedule.all_staff.push(newName);
|
|
schedule.all_staff.sort();
|
|
|
|
schedule.assignments.filter(a => a.hasPersonExplicitlyOnAnyDay(person))
|
|
.forEach(a => a.renamePerson(person, newName));
|
|
schedule.recomputeNotAssigned();
|
|
|
|
await saveSchedule();
|
|
});
|
|
|
|
function updateToTeacherButtons() {
|
|
const staffOptions = document.getElementById('removeStaff').selectedOptions;
|
|
const isTeacher = staffOptions.length > 0 && schedule.all_teachers.includes(
|
|
staffOptions[0].value);
|
|
|
|
document.getElementById('toTeacher').style.display = isTeacher ? 'none' : '';
|
|
document.getElementById('toStaff').style.display = isTeacher ? '' : 'none';
|
|
}
|
|
|
|
document.getElementById('removeStaff').addEventListener('change', updateToTeacherButtons);
|
|
|
|
document.getElementById('toTeacher').addEventListener('click', async e => {
|
|
const name = document.getElementById('removeStaff').value;
|
|
schedule.all_teachers.push(name);
|
|
schedule.all_teachers.sort();
|
|
|
|
await saveSchedule();
|
|
[...document.getElementById('removeStaff').getElementsByTagName('option')]
|
|
.forEach(o => o.selected = o.value === name);
|
|
updateToTeacherButtons();
|
|
});
|
|
|
|
document.getElementById('toStaff').addEventListener('click', async e => {
|
|
const name = document.getElementById('removeStaff').value;
|
|
schedule.all_teachers.splice(schedule.all_teachers.indexOf(name), 1);
|
|
|
|
await saveSchedule();
|
|
[...document.getElementById('removeStaff').getElementsByTagName('option')]
|
|
.forEach(o => o.selected = o.value === name);
|
|
updateToTeacherButtons();
|
|
});
|
|
|
|
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_teachers, peopleCheckboxes.teachers)
|
|
&& utils.setEqual(schedule.all_staff, Object.keys(peopleCheckboxes.staff))
|
|
&& utils.setEqual(schedule.all_students, Object.keys(peopleCheckboxes.students))) {
|
|
return;
|
|
}
|
|
|
|
peopleCheckboxes = { days: {}, staff: {}, students: {},
|
|
teachers: [...schedule.all_teachers] };
|
|
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);
|
|
}
|
|
|
|
const assignmentFoldCheckbox = document.getElementById('showAssignmentForm');
|
|
function selectAssignment(assignment) {
|
|
assignmentFoldCheckbox.checked = true;
|
|
[...assignmentSelector.getElementsByTagName('option')]
|
|
.filter(o => o.assignment === assignment)
|
|
.forEach(o => o.selected = true);
|
|
initializeAssignmentForm(assignment);
|
|
}
|
|
|
|
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.width.value = assignment.width;
|
|
assignmentForm.track.value = Array.isArray(assignment.track)
|
|
? assignment.track[0]
|
|
: (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', utils.debounceAsync(async e => {
|
|
selectedAssignment.start_time = utils.Time.fromInputValue(e.target.value);
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
assignmentForm.end_time.addEventListener('change', utils.debounceAsync(async e => {
|
|
selectedAssignment.end_time = utils.Time.fromInputValue(e.target.value);
|
|
await saveSchedule();
|
|
}, 1000));
|
|
|
|
assignmentForm.squishable.addEventListener('change', async e => {
|
|
selectedAssignment.squishable = e.target.checked;
|
|
await saveSchedule();
|
|
});
|
|
|
|
async function setAssignmentTrackAndWidth() {
|
|
const width = parseInt(assignmentForm.width.value);
|
|
selectedAssignment.width = width;
|
|
|
|
const str = assignmentForm.track.selectedOptions[0].value;
|
|
if (str === 'auto') selectedAssignment.track = undefined;
|
|
else if (str === 'all') selectedAssignment.track = 'all';
|
|
else {
|
|
const track = parseInt(str);
|
|
if (width === 1) selectedAssignment.track = track;
|
|
else {
|
|
let tracks = [];
|
|
for (let i = track; i < track + width; i++) tracks.push(i);
|
|
selectedAssignment.track = tracks;
|
|
}
|
|
}
|
|
|
|
await saveSchedule();
|
|
}
|
|
|
|
assignmentForm.width.addEventListener('change', setAssignmentTrackAndWidth);
|
|
assignmentForm.track.addEventListener('change', setAssignmentTrackAndWidth);
|
|
|
|
assignmentForm.notes.addEventListener('change', async e => {
|
|
selectedAssignment.notes = e.target.value;
|
|
await saveSchedule();
|
|
});
|