Compare commits

..

No commits in common. "18d4074029a219cf19e25118c7e201c469b7fdb2" and "d95ed2cb14dd035e470ac579340ab65a2fd50d31" have entirely different histories.

4 changed files with 13 additions and 357 deletions

View File

@ -1,7 +1,6 @@
import { get as idb_get, import { get as idb_get,
set as idb_set, set as idb_set,
del as idb_del } from './idb-keyval.js'; del as idb_del } from './idb-keyval.js';
import Assignment from './assignment.js';
import Schedule from './schedule.js'; import Schedule from './schedule.js';
import * as utils from './utils.js'; import * as utils from './utils.js';
@ -37,7 +36,7 @@ async function saveSchedule() {
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2)); await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
await writable.close(); await writable.close();
await refreshDisplay(); refreshDisplay();
} }
async function updateBackupsList() { async function updateBackupsList() {
@ -92,14 +91,13 @@ document.getElementById('createBackup').addEventListener('click', async e => {
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2)); await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
await writable.close(); await writable.close();
await updateBackupsList(); updateBackupsList();
}); });
const titleInput = document.getElementById('title'); const titleInput = document.getElementById('title');
let allowEdits = false; let allowEdits = false;
async function updateScheduleSettings() { async function updateScheduleSettings() {
await updateBackupsList(); updateBackupsList();
titleInput.value = schedule.base_title; titleInput.value = schedule.base_title;
titleInput.disabled = !allowEdits; titleInput.disabled = !allowEdits;
@ -144,7 +142,7 @@ async function loadFile(file, isBackup) {
schedule = new Schedule(JSON.parse(await fileObj.text())); schedule = new Schedule(JSON.parse(await fileObj.text()));
schedule.lastModified = fileObj.lastModified; schedule.lastModified = fileObj.lastModified;
updateScheduleSettings(); updateScheduleSettings();
await refreshDisplay(); refreshDisplay();
} }
document.getElementById('schedules').addEventListener('change', async e => { document.getElementById('schedules').addEventListener('change', async e => {
@ -235,16 +233,15 @@ function displayFullScheduleAllDays() {
}) })
} }
async function refreshDisplay() { function refreshDisplay() {
// TODO support different displays. // TODO support different displays.
await updateScheduleSettings(); updateScheduleSettings();
updateAssignmentEditor();
displayFullScheduleAllDays(); displayFullScheduleAllDays();
} }
document.getElementById('changeTitle').addEventListener('click', async e => { document.getElementById('changeTitle').addEventListener('click', async e => {
schedule.base_title = document.getElementById('title').value; schedule.base_title = document.getElementById('title').value;
await saveSchedule(); saveSchedule();
}); });
const personInput = document.getElementById('person'); const personInput = document.getElementById('person');
@ -252,320 +249,27 @@ const personInput = document.getElementById('person');
document.getElementById('addStudent').addEventListener('click', async e => { document.getElementById('addStudent').addEventListener('click', async e => {
schedule.addStudent(personInput.value); schedule.addStudent(personInput.value);
personInput.value = ''; personInput.value = '';
await saveSchedule(); saveSchedule();
}); });
document.getElementById('addStaff').addEventListener('click', async e => { document.getElementById('addStaff').addEventListener('click', async e => {
schedule.addStaff(personInput.value); schedule.addStaff(personInput.value);
personInput.value = ''; personInput.value = '';
await saveSchedule(); saveSchedule();
}); });
document.getElementById('addTeacher').addEventListener('click', async e => { document.getElementById('addTeacher').addEventListener('click', async e => {
schedule.addTeacher(personInput.value); schedule.addTeacher(personInput.value);
personInput.value = ''; personInput.value = '';
await saveSchedule(); saveSchedule();
}); });
document.getElementById('delStudent').addEventListener('click', async e => { document.getElementById('delStudent').addEventListener('click', async e => {
schedule.delStudent(document.getElementById('removeStudent').value); schedule.delStudent(document.getElementById('removeStudent').value);
await saveSchedule(); saveSchedule();
}); });
document.getElementById('delStaff').addEventListener('click', async e => { document.getElementById('delStaff').addEventListener('click', async e => {
schedule.delStaff(document.getElementById('removeStaff').value); schedule.delStaff(document.getElementById('removeStaff').value);
await saveSchedule(); 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();
}); });

View File

@ -52,11 +52,6 @@ export default class Assignment {
return new utils.TimeRange(this); return new utils.TimeRange(this);
} }
toString() {
return "[" + this.days.join('') + "] (" + this.start_time.to12HourString()
+ "-" + this.end_time.to12HourString() + ") " + this.location;
}
asEvent(day, granularity, location_only) { asEvent(day, granularity, location_only) {
const people = this.people_by_day[day]; const people = this.people_by_day[day];
const staff = people['staff'] ?? []; const staff = people['staff'] ?? [];

View File

@ -22,7 +22,7 @@
<button id="closeDir" style="display: none;">Close data directory</button> <button id="closeDir" style="display: none;">Close data directory</button>
</div> </div>
</div> </div>
<div id="scheduleSettings" class="forms noprint" style="display: none"> <div id="scheduleSettings" class="forms noprint">
<div id="selectScheduleDiv"> <div id="selectScheduleDiv">
<label>Schedule: <select id="schedules"></select></label> <label>Schedule: <select id="schedules"></select></label>
</div> </div>
@ -49,30 +49,6 @@
<button id="delStudent" disabled>Delete Student</button><br> <button id="delStudent" disabled>Delete Student</button><br>
</div> </div>
</div> </div>
<div id="assignmentFormDiv" class="forms noprint" style="display: none">
<form id="assignmentForm" action="javascript:void(0);">
<label>Event: <select name="assignments"></select><br>
<button id="delAssignment">Delete Event</button>
<button id="newAssignment">Create New Event</button><br><br>
<label>Location: <input name="location" value=""></label><br>
<label>Start time: <input type="time" required="" name="start_time"></label><br>
<label>End time: <input type="time" required="" name="end_time"></label><br>
<label>Squishable: <input type="checkbox" name="squishable"></label><br>
<label>Track:
<select name="track">
<option selected="" value="auto">auto</option>
<option value="all">all (full-width)</option>
<option value="0,1">[0,1] left-most two tracks</option>
<option value="1,2">[1,2] second and third tracks</option>
<option value="0">[0] left-most</option>
<option value="1,2,3">[1,2,3] second through fourth tracks</option>
<option value="0,1,2">[0,1,2] left-most three tracks</option>
</select>
</label><br>
<label>Notes: <textarea name="notes"></textarea></label><br>
People: <table id="peopleTable" class="peopleTable"></table>
</form>
</div>
<div id="allSchedules"> <div id="allSchedules">
</div> </div>
</body> </body>

View File

@ -5,11 +5,6 @@ export class Time {
this.second = obj.second ?? 0; this.second = obj.second ?? 0;
} }
static fromInputValue(str) {
const parts = str.split(':');
return new Time({hour: parts[0], minute: parts[1], second: parts[2]});
}
asJsonObject() { asJsonObject() {
let res = {} let res = {}
if (this.hour !== 0) res.hour = this.hour; if (this.hour !== 0) res.hour = this.hour;
@ -47,12 +42,6 @@ export class Time {
if (hour === 0) hour = 12; if (hour === 0) hour = 12;
return hour + ':' + this.minute.toString().padStart(2, '0'); return hour + ':' + this.minute.toString().padStart(2, '0');
} }
get inputValue() {
return this.hour.toString().padStart(2, '0') + ':'
+ this.minute.toString().padStart(2, '0') + ':'
+ this.second.toString().padStart(2, '0');
}
} }
export class Duration { export class Duration {
@ -130,11 +119,3 @@ export function setDifference(setA, setB) {
} }
return _difference; return _difference;
} }
export function setEqual(setA, setB) {
if (!(setA instanceof Set)) setA = new Set(setA);
if (!(setB instanceof Set)) setB = new Set(setB);
if (setA.size != setB.size) return false;
return setDifference(setA, setB).size === 0;
}