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() {
|
function displaySchedule() {
|
||||||
|
displayPersonInfoDiv();
|
||||||
utils.clearChildren(schedulesDiv);
|
utils.clearChildren(schedulesDiv);
|
||||||
if (hash.day && !schedule.all_days.includes(hash.day)) {
|
if (hash.day && !schedule.all_days.includes(hash.day)) {
|
||||||
updateHash({day: undefined});
|
updateHash({day: undefined});
|
||||||
|
@ -566,7 +663,7 @@ document.getElementById('changeTitle').addEventListener('click', async e => {
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
});
|
});
|
||||||
|
|
||||||
schStartTime.addEventListener('change', async e => {
|
schStartTime.addEventListener('change', utils.debounceAsync(async e => {
|
||||||
const newStartTime = utils.Time.fromInputValue(e.target.value);
|
const newStartTime = utils.Time.fromInputValue(e.target.value);
|
||||||
if (schedule.assignments) {
|
if (schedule.assignments) {
|
||||||
const earliestStart = schedule.assignments.map(a => a.start_time).sort((a, b) => a.cmp(b))[0];
|
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;
|
schedule.start_time = newStartTime;
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
});
|
}, 1000));
|
||||||
|
|
||||||
schEndTime.addEventListener('change', async e => {
|
schEndTime.addEventListener('change', utils.debounceAsync(async e => {
|
||||||
const newEndTime = utils.Time.fromInputValue(e.target.value);
|
const newEndTime = utils.Time.fromInputValue(e.target.value);
|
||||||
if (schedule.assignments) {
|
if (schedule.assignments) {
|
||||||
const latestEnd = schedule.assignments.map(a => a.end_time).sort((a, b) => b.cmp(a))[0];
|
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;
|
schedule.end_time = newEndTime;
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
});
|
}, 1000));
|
||||||
|
|
||||||
schGranularity.addEventListener('change', async e => {
|
schGranularity.addEventListener('change', utils.debounceAsync(async e => {
|
||||||
const newGranularity = utils.Time.fromInputValue(e.target.value)
|
const newGranularity = utils.Time.fromInputValue(e.target.value)
|
||||||
.durationSinceMidnight;
|
.durationSinceMidnight;
|
||||||
const schDuration = schedule.timeRange.duration;
|
const schDuration = schedule.timeRange.duration;
|
||||||
|
@ -610,7 +707,7 @@ schGranularity.addEventListener('change', async e => {
|
||||||
}
|
}
|
||||||
schedule.granularity = newGranularity;
|
schedule.granularity = newGranularity;
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
});
|
}, 1000));
|
||||||
|
|
||||||
const personInput = document.getElementById('person');
|
const personInput = document.getElementById('person');
|
||||||
|
|
||||||
|
@ -913,15 +1010,15 @@ assignmentForm.location.addEventListener('change', async e => {
|
||||||
await saveSchedule();
|
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);
|
selectedAssignment.start_time = utils.Time.fromInputValue(e.target.value);
|
||||||
await saveSchedule();
|
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);
|
selectedAssignment.end_time = utils.Time.fromInputValue(e.target.value);
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
});
|
}, 1000));
|
||||||
|
|
||||||
assignmentForm.squishable.addEventListener('change', async e => {
|
assignmentForm.squishable.addEventListener('change', async e => {
|
||||||
selectedAssignment.squishable = e.target.checked;
|
selectedAssignment.squishable = e.target.checked;
|
||||||
|
|
|
@ -110,7 +110,7 @@ export default class Assignment {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPersonExplicitlyOnAnyDay(person, day) {
|
hasPersonExplicitlyOnAnyDay(person) {
|
||||||
const kind = person.kind === 'student' ? 'students' : person.kind;
|
const kind = person.kind === 'student' ? 'students' : person.kind;
|
||||||
return Object.values(this.people_by_day)
|
return Object.values(this.people_by_day)
|
||||||
.some(byDay => byDay[kind].includes(person.name));
|
.some(byDay => byDay[kind].includes(person.name));
|
||||||
|
|
|
@ -58,6 +58,11 @@
|
||||||
<label>Days: <select id="displayDays"></select></label>
|
<label>Days: <select id="displayDays"></select></label>
|
||||||
<label>People: <select id="displayPeople"></select></label>
|
<label>People: <select id="displayPeople"></select></label>
|
||||||
</div>
|
</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">
|
<div id="warningsSection" class="warningsSection noprint" style="display: none">
|
||||||
<input type="checkbox" id="showWarnings" class="foldCheckbox" />
|
<input type="checkbox" id="showWarnings" class="foldCheckbox" />
|
||||||
<h4 class="warningHeader foldCheckboxHeader">
|
<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] ?? {} : {};
|
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) {
|
gridFor(person, day, versionName) {
|
||||||
const assignments = this.filterAssignments(person, day);
|
const assignments = this.filterAssignments(person, day);
|
||||||
const forStudent = person && person.kind === 'student';
|
const forStudent = person && person.kind === 'student';
|
||||||
|
|
24
www/utils.js
24
www/utils.js
|
@ -6,7 +6,7 @@ export class Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromInputValue(str) {
|
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]});
|
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;
|
if (setA.size != setB.size) return false;
|
||||||
return setDifference(setA, setB).size === 0;
|
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