import Event from './event.js' import * as utils from './utils.js'; export default class Assignment { constructor(obj) { this.original_assignment = obj.original_assignment; this.location = obj.location; this.start_time = new utils.Time(obj.start_time); this.end_time = new utils.Time(obj.end_time); this.notes = obj.notes; this.width = obj.width ?? (Array.isArray(obj.track) ? obj.track.length : 1); this.track = obj.track; this.days = obj.days; this.people_by_day = obj.people_by_day; this.squishable = obj.squishable ?? false; if (!this.people_by_day) { this.people_by_day = {}; for (const day of this.days) { this.people_by_day[day] = {staff: [], students: []}; } } else if ('all_days' in this.people_by_day) { const only = this.people_by_day['all_days']; this.people_by_day = {}; for (const day of this.days) { this.people_by_day[day] = {...only}; } } const expectedDays = Object.keys(this.people_by_day); if (!this.days || !utils.setEqual(this.days, expectedDays)) { // This might not be in order; shouldn't matter. this.days = expectedDays; } this.recomputeAllPeople(); } recomputeAllPeople() { this.all_staff = new Set(Object.values(this.people_by_day) .flatMap(day => day['staff'])); this.all_students = new Set(Object.values(this.people_by_day) .flatMap(day => day['students'])); } asJsonObject() { return { 'location': this.location, 'start_time': this.start_time.asJsonObject(), 'end_time': this.end_time.asJsonObject(), 'notes': this.notes, 'width': this.width, 'track': this.track, 'days': this.days, 'people_by_day': this.people_by_day, 'squishable': this.squishable, } } asJson() { return JSON.stringify(this.asJsonObject(), null, 2); } cloneWithoutTrack() { return new Assignment( {...this.asJsonObject(), track: undefined, original_assignment: this.original_assignment ?? this}); } get timeRange() { return new utils.TimeRange(this); } toString() { return "[" + this.days.join('') + "] (" + this.start_time.to12HourString() + "-" + this.end_time.to12HourString() + ") " + this.location; } isOnRowForTime(time) { return this.start_time.cmp(time) <= 0 && this.end_time.cmp(time) > 0; } hasPersonOnDay(person, day) { if (!(day in this.people_by_day)) { return false; } if (this.isOnEverySchedule()) return true; return this.hasPersonExplicitlyOnDay(person, day); } hasPersonExplicitlyOnDay(person, day) { if (!(day in this.people_by_day)) { return false; } const kind = person.kind === 'student' ? 'students' : person.kind; const byKind = this.people_by_day[day][kind]; return byKind.includes(person.name); } isOnEverySchedule() { if (this.track === 'all') return true; if (!person) return true; // Squishable assignments with no names are for everyone. if (this.squishable && Object.values(this.people_by_day) .flatMap(forDay => [].concat(...Object.values(forDay))) .length === 0) { return true; } return false; } hasPersonExplicitlyOnAnyDay(person) { const kind = person.kind === 'student' ? 'students' : person.kind; return Object.values(this.people_by_day) .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(); } renamePerson(person, newName) { 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); kindList.push(newName); kindList.sort(); } }); this.recomputeAllPeople(); } asEvent(day, granularity, location_only) { const people = this.people_by_day[day]; const staff = people['staff'] ?? []; const students = people['students'] ?? []; const num_people = staff.length + students.length; let compact_mode = false; if (granularity) { const row_count = this.timeRange.duration.dividedBy(granularity); const needed_rows = location_only ? 2 : 2 + num_people; compact_mode = needed_rows > row_count; } const classes = []; if (compact_mode) classes.push('compact'); const description = document.createElement('span'); description.classList.add('event_description'); const locationSpan = document.createElement('span'); locationSpan.innerText = this.location; locationSpan.classList.add('location'); if (location_only) { classes.push('location_only'); description.appendChild(locationSpan); description.appendChild(document.createElement('br')); } else { if (compact_mode) locationSpan.classList.add('compact'); description.appendChild(locationSpan); if (!compact_mode || num_people > 0) { description.appendChild(document.createElement('br')); } function addNames(names, className) { let first = true; for (const name of names.sort()) { if (first) { first = false; } else { if (compact_mode) { description.appendChild(document.createTextNode(',\n')); } else { description.appendChild(document.createElement('br')); description.appendChild(document.createTextNode('\n')); } } const nameSpan = document.createElement('span'); nameSpan.classList.add(className); if (compact_mode) nameSpan.classList.add('compact'); nameSpan.innerText = name; description.appendChild(nameSpan); } } addNames(staff, 'staff'); description.appendChild(document.createElement('br')); description.appendChild(document.createTextNode('\n')); addNames(students, 'student'); if (this.notes) { const notesSpan = document.createElement('span'); notesSpan.classList.add('notes'); notesSpan.innerText = this.notes; if (compact_mode) { notesSpan.classList.add('compact'); description.appendChild(document.createTextNode('\n')); } else { description.appendChild(document.createElement('br')); } description.appendChild(notesSpan); } } let originalAssignment = this; while (originalAssignment.original_assignment) { originalAssignment = originalAssignment.original_assignment; } return new Event({ 'assignment': originalAssignment, 'start_time': this.start_time, 'end_time': this.end_time, 'day': day, 'width': this.width, 'track': this.track, 'description': description, 'classes': classes, 'title': this.location, }); } }