Compare commits
2 Commits
c775d6b9e9
...
5d0e70ed6c
Author | SHA1 | Date | |
---|---|---|---|
5d0e70ed6c | |||
805aeefd29 |
117
www/app.js
117
www/app.js
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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';
|
||||
|
|
24
www/utils.js
24
www/utils.js
|
@ -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 */ }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user