Compare commits
No commits in common. "0a26315e049a104274720158bfb8e53bc658ed3c" and "36a0844951f0683341d3770640265369ab215456" have entirely different histories.
0a26315e04
...
36a0844951
66
www/app.js
66
www/app.js
|
@ -224,25 +224,25 @@ async function updateScheduleSettings() {
|
||||||
|
|
||||||
const staffList = document.getElementById('removeStaff');
|
const staffList = document.getElementById('removeStaff');
|
||||||
utils.clearChildren(staffList);
|
utils.clearChildren(staffList);
|
||||||
schedule.all_staff_teachers_first.forEach(name => {
|
schedule.notAssignedStaff.forEach(name => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = name;
|
option.value = name;
|
||||||
option.innerText = (schedule.assignedStaff.has(name) ? '(!) ' : '') + name;
|
option.innerText = name;
|
||||||
staffList.appendChild(option);
|
staffList.appendChild(option);
|
||||||
});
|
});
|
||||||
const canDelStaff = allowEdits;
|
const canDelStaff = allowEdits && schedule.notAssignedStaff.size > 0;
|
||||||
staffList.disabled = !canDelStaff;
|
staffList.disabled = !canDelStaff;
|
||||||
document.getElementById('delStaff').disabled = !canDelStaff;
|
document.getElementById('delStaff').disabled = !canDelStaff;
|
||||||
|
|
||||||
const studentList = document.getElementById('removeStudent');
|
const studentList = document.getElementById('removeStudent');
|
||||||
utils.clearChildren(studentList);
|
utils.clearChildren(studentList);
|
||||||
schedule.all_students.forEach(name => {
|
schedule.notAssignedStudents.forEach(name => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = name;
|
option.value = name;
|
||||||
option.innerText = (schedule.assignedStudents.has(name) ? '(!) ' : '') + name;
|
option.innerText = name;
|
||||||
studentList.appendChild(option);
|
studentList.appendChild(option);
|
||||||
});
|
});
|
||||||
const canDelStudent = allowEdits;
|
const canDelStudent = allowEdits && schedule.notAssignedStudents.size > 0;
|
||||||
studentList.disabled = !canDelStudent;
|
studentList.disabled = !canDelStudent;
|
||||||
document.getElementById('delStudent').disabled = !canDelStudent;
|
document.getElementById('delStudent').disabled = !canDelStudent;
|
||||||
|
|
||||||
|
@ -731,36 +731,12 @@ document.getElementById('addTeacher').addEventListener('click', async e => {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('delStudent').addEventListener('click', async e => {
|
document.getElementById('delStudent').addEventListener('click', async e => {
|
||||||
const name = document.getElementById('removeStudent').value;
|
schedule.delStudent(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();
|
await saveSchedule();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('delStaff').addEventListener('click', async e => {
|
document.getElementById('delStaff').addEventListener('click', async e => {
|
||||||
const name = document.getElementById('removeStaff').value;
|
schedule.delStaff(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();
|
await saveSchedule();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1005,7 +981,7 @@ function initializeAssignmentForm(assignment) {
|
||||||
assignmentForm.end_time.value = assignment.end_time.inputValue;
|
assignmentForm.end_time.value = assignment.end_time.inputValue;
|
||||||
assignmentForm.squishable.value = assignment.squishable;
|
assignmentForm.squishable.value = assignment.squishable;
|
||||||
assignmentForm.track.value = Array.isArray(assignment.track)
|
assignmentForm.track.value = Array.isArray(assignment.track)
|
||||||
? assignment.track[0]
|
? assignment.track.join(',')
|
||||||
: (assignment.track ?? "auto");
|
: (assignment.track ?? "auto");
|
||||||
assignmentForm.notes.value = assignment.notes ?? "";
|
assignmentForm.notes.value = assignment.notes ?? "";
|
||||||
|
|
||||||
|
@ -1050,28 +1026,16 @@ assignmentForm.squishable.addEventListener('change', async e => {
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function setAssignmentTrackAndWidth() {
|
assignmentForm.track.addEventListener('change', async e => {
|
||||||
const width = parseInt(assignmentForm.width.value);
|
const str = e.target.selectedOptions[0].value;
|
||||||
selectedAssignment.width = width;
|
|
||||||
|
|
||||||
const str = assignmentForm.track.selectedOptions[0].value;
|
|
||||||
if (str === 'auto') selectedAssignment.track = undefined;
|
if (str === 'auto') selectedAssignment.track = undefined;
|
||||||
else if (str === 'all') selectedAssignment.track = 'all';
|
else if (str === 'all') selectedAssignment.track = 'all';
|
||||||
else {
|
else if (str.includes(',')) {
|
||||||
const track = parseInt(str);
|
selectedAssignment.track = str.split(',').map(s => parseInt(s));
|
||||||
if (width === 1) selectedAssignment.track = track;
|
} else selectedAssignment.track = parseInt(str);
|
||||||
else {
|
|
||||||
let tracks = [];
|
|
||||||
for (let i = track; i < track + width; i++) tracks.push(i);
|
|
||||||
selectedAssignment.track = tracks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveSchedule();
|
await saveSchedule();
|
||||||
}
|
});
|
||||||
|
|
||||||
assignmentForm.width.addEventListener('change', setAssignmentTrackAndWidth);
|
|
||||||
assignmentForm.track.addEventListener('change', setAssignmentTrackAndWidth);
|
|
||||||
|
|
||||||
assignmentForm.notes.addEventListener('change', async e => {
|
assignmentForm.notes.addEventListener('change', async e => {
|
||||||
selectedAssignment.notes = e.target.value;
|
selectedAssignment.notes = e.target.value;
|
||||||
|
|
|
@ -8,7 +8,6 @@ export default class Assignment {
|
||||||
this.start_time = new utils.Time(obj.start_time);
|
this.start_time = new utils.Time(obj.start_time);
|
||||||
this.end_time = new utils.Time(obj.end_time);
|
this.end_time = new utils.Time(obj.end_time);
|
||||||
this.notes = obj.notes;
|
this.notes = obj.notes;
|
||||||
this.width = obj.width ?? (Array.isArray(obj.track) ? obj.track.length : 1);
|
|
||||||
this.track = obj.track;
|
this.track = obj.track;
|
||||||
this.days = obj.days;
|
this.days = obj.days;
|
||||||
this.people_by_day = obj.people_by_day;
|
this.people_by_day = obj.people_by_day;
|
||||||
|
@ -33,10 +32,6 @@ export default class Assignment {
|
||||||
this.days = expectedDays;
|
this.days = expectedDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recomputeAllPeople();
|
|
||||||
}
|
|
||||||
|
|
||||||
recomputeAllPeople() {
|
|
||||||
this.all_staff = new Set(Object.values(this.people_by_day)
|
this.all_staff = new Set(Object.values(this.people_by_day)
|
||||||
.flatMap(day => day['staff']));
|
.flatMap(day => day['staff']));
|
||||||
this.all_students = new Set(Object.values(this.people_by_day)
|
this.all_students = new Set(Object.values(this.people_by_day)
|
||||||
|
@ -49,7 +44,6 @@ export default class Assignment {
|
||||||
'start_time': this.start_time.asJsonObject(),
|
'start_time': this.start_time.asJsonObject(),
|
||||||
'end_time': this.end_time.asJsonObject(),
|
'end_time': this.end_time.asJsonObject(),
|
||||||
'notes': this.notes,
|
'notes': this.notes,
|
||||||
'width': this.width,
|
|
||||||
'track': this.track,
|
'track': this.track,
|
||||||
'days': this.days,
|
'days': this.days,
|
||||||
'people_by_day': this.people_by_day,
|
'people_by_day': this.people_by_day,
|
||||||
|
@ -122,17 +116,6 @@ export default class Assignment {
|
||||||
.some(byDay => byDay[kind].includes(person.name));
|
.some(byDay => byDay[kind].includes(person.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
removePerson(person) {
|
|
||||||
const kind = person.kind === 'student' ? 'students' : person.kind;
|
|
||||||
Object.values(this.people_by_day)
|
|
||||||
.forEach(byDay => {
|
|
||||||
const kindList = byDay[kind];
|
|
||||||
const idx = kindList.indexOf(person.name);
|
|
||||||
if (idx !== -1) kindList.splice(idx, 1);
|
|
||||||
});
|
|
||||||
this.recomputeAllPeople();
|
|
||||||
}
|
|
||||||
|
|
||||||
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'] ?? [];
|
||||||
|
@ -220,8 +203,6 @@ export default class Assignment {
|
||||||
'assignment': originalAssignment,
|
'assignment': originalAssignment,
|
||||||
'start_time': this.start_time,
|
'start_time': this.start_time,
|
||||||
'end_time': this.end_time,
|
'end_time': this.end_time,
|
||||||
'day': day,
|
|
||||||
'width': this.width,
|
|
||||||
'track': this.track,
|
'track': this.track,
|
||||||
'description': description,
|
'description': description,
|
||||||
'classes': classes,
|
'classes': classes,
|
||||||
|
|
|
@ -5,16 +5,14 @@ export default class Event {
|
||||||
this.assignment = args.assignment;
|
this.assignment = args.assignment;
|
||||||
this.start_time = args.start_time;
|
this.start_time = args.start_time;
|
||||||
this.end_time = args.end_time;
|
this.end_time = args.end_time;
|
||||||
this.width = args.width;
|
|
||||||
this.track = args.track;
|
this.track = args.track;
|
||||||
this.description = args.description ?? "ERROR: MISSING DESCRIPTION";
|
this.description = args.description ?? "ERROR: MISSING DESCRIPTION";
|
||||||
this.classes = args.classes ?? [];
|
this.classes = args.classes ?? [];
|
||||||
this.title = args.title;
|
this.title = args.title;
|
||||||
this.day = args.day;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return new Event({...this, description: this.description.cloneNode(true)});
|
return new Event(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get tracks() {
|
get tracks() {
|
||||||
|
|
|
@ -84,19 +84,16 @@
|
||||||
<label>Location: <input name="location" value=""></label><br>
|
<label>Location: <input name="location" value=""></label><br>
|
||||||
<label>Start time: <input type="time" required="" name="start_time"></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>End time: <input type="time" required="" name="end_time"></label><br>
|
||||||
<label>
|
<label>Squishable: <input type="checkbox" name="squishable"></label><br>
|
||||||
<acronym title="This event can be shortened (squished) or omitted in an indivudal schedule if overlapped by another event.">Squishable</acronym>:
|
|
||||||
<input type="checkbox" name="squishable">
|
|
||||||
</label><br>
|
|
||||||
<label>Width: <input name="width" type="number" min="1" max="9" value="1"></label><br>
|
|
||||||
<label>Track:
|
<label>Track:
|
||||||
<select name="track">
|
<select name="track">
|
||||||
<option selected="" value="auto">auto</option>
|
<option selected="" value="auto">auto</option>
|
||||||
<option value="all">all (full-width)</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="0">[0] left-most</option>
|
||||||
<option value="1">[1] second</option>
|
<option value="1,2,3">[1,2,3] second through fourth tracks</option>
|
||||||
<option value="2">[2] third</option>
|
<option value="0,1,2">[0,1,2] left-most three tracks</option>
|
||||||
<option value="3">[3] fourth</option>
|
|
||||||
</select>
|
</select>
|
||||||
</label><br>
|
</label><br>
|
||||||
<label>Notes: <textarea name="notes"></textarea></label><br>
|
<label>Notes: <textarea name="notes"></textarea></label><br>
|
||||||
|
|
|
@ -187,10 +187,6 @@ export default class Schedule {
|
||||||
return [...utils.setDifference(this.all_staff, this.all_teachers)].sort();
|
return [...utils.setDifference(this.all_staff, this.all_teachers)].sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
get all_staff_teachers_first() {
|
|
||||||
return [...this.all_teachers, ...this.all_non_teacher_staff];
|
|
||||||
}
|
|
||||||
|
|
||||||
get all_people() {
|
get all_people() {
|
||||||
return [...this.all_teachers.concat(this.all_non_teacher_staff)
|
return [...this.all_teachers.concat(this.all_non_teacher_staff)
|
||||||
.map(name => ({name, kind: 'staff'})),
|
.map(name => ({name, kind: 'staff'})),
|
||||||
|
@ -202,7 +198,6 @@ export default class Schedule {
|
||||||
this.all_students.push(name);
|
this.all_students.push(name);
|
||||||
this.all_students.sort();
|
this.all_students.sort();
|
||||||
this.notAssignedStudents.add(name);
|
this.notAssignedStudents.add(name);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addStaff(name) {
|
addStaff(name) {
|
||||||
|
@ -210,7 +205,6 @@ export default class Schedule {
|
||||||
this.all_staff.push(name);
|
this.all_staff.push(name);
|
||||||
this.all_staff.sort();
|
this.all_staff.sort();
|
||||||
this.notAssignedStaff.add(name);
|
this.notAssignedStaff.add(name);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addTeacher(name) {
|
addTeacher(name) {
|
||||||
|
@ -218,7 +212,6 @@ export default class Schedule {
|
||||||
this.all_teachers.push(name);
|
this.all_teachers.push(name);
|
||||||
this.all_teachers.sort();
|
this.all_teachers.sort();
|
||||||
this.notAssignedStaff.add(name);
|
this.notAssignedStaff.add(name);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delStaff(name) {
|
delStaff(name) {
|
||||||
|
|
|
@ -9,8 +9,7 @@ export default class ScheduleGrid {
|
||||||
|
|
||||||
this.events = ScheduleGrid.assignTracks(args.events);
|
this.events = ScheduleGrid.assignTracks(args.events);
|
||||||
|
|
||||||
this.tracks = [...new Set(this.events.flatMap(e => e.tracks))]
|
this.tracks = [...new Set(this.events.flatMap(e => e.tracks))].sort()
|
||||||
.sort((a, b) => a-b);
|
|
||||||
|
|
||||||
// If there's supposed to be only a single column but there's
|
// If there's supposed to be only a single column but there's
|
||||||
// actually multiple tracks, make as many events as possible full width.
|
// actually multiple tracks, make as many events as possible full width.
|
||||||
|
@ -51,41 +50,21 @@ export default class ScheduleGrid {
|
||||||
eventsWithoutTracks.sort((a, b) => a.start_time.cmp(b.start_time));
|
eventsWithoutTracks.sort((a, b) => a.start_time.cmp(b.start_time));
|
||||||
for (const event of eventsWithoutTracks) {
|
for (const event of eventsWithoutTracks) {
|
||||||
// Put the event in the first track with space for it.
|
// Put the event in the first track with space for it.
|
||||||
const tracksWithSpace = Object.keys(tracks).map(t => parseInt(t)).filter(trackNum => {
|
tracks.forEach((track, trackNum) => {
|
||||||
const end = Math.min(trackNum + event.width, tracks.length);
|
if (event.track === undefined) {
|
||||||
for (let i = trackNum; i < end; i++) {
|
const hasOverlappingEvents = track.some(e => e.at_same_time_as(event));
|
||||||
if (tracks[i].some(e => e.at_same_time_as(event))) {
|
if (!hasOverlappingEvents) {
|
||||||
return false;
|
track.push(event);
|
||||||
|
event.track = trackNum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
});
|
||||||
})
|
|
||||||
.map(trackNum => {
|
// No track had space for it, create a new track to put it in.
|
||||||
const staffLists = [...tracks[trackNum], event]
|
if (event.track === undefined) {
|
||||||
.map(e => e.assignment.people_by_day[e.day].staff)
|
event.track = tracks.length;
|
||||||
.filter(staff => staff.length > 0);
|
tracks.push([event]);
|
||||||
const matchingStaff = staffLists.length === 0
|
|
||||||
? []
|
|
||||||
: staffLists.reduce(utils.setIntersection);
|
|
||||||
return {
|
|
||||||
trackNum,
|
|
||||||
matchingStaff,
|
|
||||||
matchingStaffCount: matchingStaff.size,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a, b) => b.matchingStaffCount - a.matchingStaffCount)
|
|
||||||
.map(t => t.trackNum);
|
|
||||||
const trackNum = tracksWithSpace.length > 0 ? tracksWithSpace[0] : tracks.length;
|
|
||||||
const eventTracks = [];
|
|
||||||
for (let i = trackNum; i < trackNum + event.width; i++) {
|
|
||||||
eventTracks.push(i);
|
|
||||||
if (i < tracks.length) {
|
|
||||||
tracks[i].push(event);
|
|
||||||
} else {
|
|
||||||
tracks.push([event]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
event.track = eventTracks.length === 1 ? eventTracks[0] : eventTracks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The above code doesn't consider events with track "all".
|
// The above code doesn't consider events with track "all".
|
||||||
|
@ -162,7 +141,7 @@ export default class ScheduleGrid {
|
||||||
// Select events of this track in this row.
|
// Select events of this track in this row.
|
||||||
const events = this.events
|
const events = this.events
|
||||||
.filter(e => (e.track === 'all' || e.tracks.includes(track))
|
.filter(e => (e.track === 'all' || e.tracks.includes(track))
|
||||||
&& e.start_time.cmp(nextRowTime) < 0
|
&& e.start_time.cmp(nextRowTime) <0
|
||||||
&& rowTime.cmp(e.end_time) < 0);
|
&& rowTime.cmp(e.end_time) < 0);
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
row.appendChild(document.createElement('td'));
|
row.appendChild(document.createElement('td'));
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export class Time {
|
export class Time {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
this.hour = parseInt(obj.hour ?? 0);
|
this.hour = obj.hour ?? 0;
|
||||||
this.minute = parseInt(obj.minute ?? 0);
|
this.minute = obj.minute ?? 0;
|
||||||
this.second = parseInt(obj.second ?? 0);
|
this.second = obj.second ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromInputValue(str) {
|
static fromInputValue(str) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user