Compare commits

..

2 Commits

5 changed files with 143 additions and 12 deletions

View File

@ -537,7 +537,104 @@ function displayWarnings() {
}
}
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');
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) {
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) {
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});
@ -566,7 +663,7 @@ document.getElementById('changeTitle').addEventListener('click', async e => {
await saveSchedule();
});
schStartTime.addEventListener('change', async e => {
schStartTime.addEventListener('change', utils.debounceAsync(async e => {
const newStartTime = utils.Time.fromInputValue(e.target.value);
if (schedule.assignments) {
const earliestStart = schedule.assignments.map(a => a.start_time).sort((a, b) => a.cmp(b))[0];
@ -579,9 +676,9 @@ schStartTime.addEventListener('change', async e => {
}
schedule.start_time = newStartTime;
await saveSchedule();
});
}, 1000));
schEndTime.addEventListener('change', async e => {
schEndTime.addEventListener('change', utils.debounceAsync(async e => {
const newEndTime = utils.Time.fromInputValue(e.target.value);
if (schedule.assignments) {
const latestEnd = schedule.assignments.map(a => a.end_time).sort((a, b) => b.cmp(a))[0];
@ -594,9 +691,9 @@ schEndTime.addEventListener('change', async e => {
}
schedule.end_time = newEndTime;
await saveSchedule();
});
}, 1000));
schGranularity.addEventListener('change', async e => {
schGranularity.addEventListener('change', utils.debounceAsync(async e => {
const newGranularity = utils.Time.fromInputValue(e.target.value)
.durationSinceMidnight;
const schDuration = schedule.timeRange.duration;
@ -610,7 +707,7 @@ schGranularity.addEventListener('change', async e => {
}
schedule.granularity = newGranularity;
await saveSchedule();
});
}, 1000));
const personInput = document.getElementById('person');
@ -913,15 +1010,15 @@ assignmentForm.location.addEventListener('change', async e => {
await saveSchedule();
});
assignmentForm.start_time.addEventListener('change', async e => {
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', async e => {
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;

View File

@ -110,7 +110,7 @@ export default class Assignment {
return false;
}
hasPersonExplicitlyOnAnyDay(person, day) {
hasPersonExplicitlyOnAnyDay(person) {
const kind = person.kind === 'student' ? 'students' : person.kind;
return Object.values(this.people_by_day)
.some(byDay => byDay[kind].includes(person.name));

View File

@ -58,6 +58,11 @@
<label>Days: <select id="displayDays"></select></label>
<label>People: <select id="displayPeople"></select></label>
</div>
<div id="personInfo" class="forms noprint" style="display: none">
<label>Start time: <input type="time" id="person_start_time"></label><br>
<label>End time: <input type="time" id="person_end_time"></label><br>
Days: <span id="person_days"></span><br>
</div>
<div id="warningsSection" class="warningsSection noprint" style="display: none">
<input type="checkbox" id="showWarnings" class="foldCheckbox" />
<h4 class="warningHeader foldCheckboxHeader">

View File

@ -87,6 +87,13 @@ export default class Schedule {
return person ? this.people_info[person.kind === 'student' ? 'students' : person.kind][person.name] ?? {} : {};
}
setInfoFor(person, args) {
const kind = person.kind === 'student' ? 'students' : person.kind;
const newInfo = {...this.infoFor(person), ...args};
this.people_info[kind][person.name] =
Object.values(newInfo).some(i => i !== undefined) ? newInfo : undefined;
}
gridFor(person, day, versionName) {
const assignments = this.filterAssignments(person, day);
const forStudent = person && person.kind === 'student';

View File

@ -6,7 +6,7 @@ export class Time {
}
static fromInputValue(str) {
const parts = str.split(':');
const parts = str.split(':').map(part => parseInt(part));
return new Time({hour: parts[0], minute: parts[1], second: parts[2]});
}
@ -184,3 +184,25 @@ export function setEqual(setA, setB) {
if (setA.size != setB.size) return false;
return setDifference(setA, setB).size === 0;
}
// From https://stackoverflow.com/a/68227965
export function debounceAsync(task, ms) {
function deferred() {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
let t = { promise: null, cancel: _ => void 0 }
return async (...args) => {
try {
t.cancel()
t = deferred()
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
}
}