From d0975a6e7ee57de4debda94e823011d813fbf4a1 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 21:39:24 -0500 Subject: Initial rewrite in svelte --- src/models/EventInfo.ts | 79 ++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 40 deletions(-) (limited to 'src/models/EventInfo.ts') diff --git a/src/models/EventInfo.ts b/src/models/EventInfo.ts index 962ec70..d20682c 100644 --- a/src/models/EventInfo.ts +++ b/src/models/EventInfo.ts @@ -1,46 +1,45 @@ export default class EventInfo { - days: string; - - start: number; - - end: number; - - constructor(days: string = '', start = 0, end = 0) { - this.days = days; - this.start = start; - this.end = end; - } - - static timeToStr(time: number) { - let hour = Math.floor(time / 100); - const minute = time % 100; - const meridiem = (hour < 12) ? 'AM' : 'PM'; - - if (hour === 0) { - hour = 12; - } else if (hour > 12) { - hour -= 12; - } - - if (minute < 10) { - return `${hour}:0${minute} ${meridiem}`; + days: string; + start: number; + end: number; + + constructor(days: string, start: number | string, end: number | string) { + if (typeof start === "string") { + start = parseInt(start, 10); + } + if (typeof end === "string") { + end = parseInt(end, 10); + } + + this.days = days; + this.start = start; + this.end = end; } - return `${hour}:${minute} ${meridiem}`; - } - - conflictsWith(event: EventInfo) { - const daysConflict = event.days.match(new RegExp(`[${this.days}]`)); - if (daysConflict) { - return (this.start <= event.end) && (event.start <= this.end); + static timeToStr(time: number) { + let hour = Math.floor(time / 100); + const minute = time % 100; + const meridiem = (hour < 12) ? 'AM' : 'PM'; + + if (hour === 0) { + hour = 12; + } else if (hour > 12) { + hour -= 12; + } + + if (minute < 10) { + return `${hour}:0${minute} ${meridiem}`; + } + return `${hour}:${minute} ${meridiem}`; } - return false; - } - get info() { - if (this.days === '') { - return 'WEB'; + get info() { + if(this.days === "") { + return `WEB`; + }else if(this.start === -1 || this.end === -1) { + return `${this.days}`; + } else { + return `${this.days} ${EventInfo.timeToStr(this.start)}-${EventInfo.timeToStr(this.end)}`; + } } - return `${this.days} ${EventInfo.timeToStr(this.start)}-${EventInfo.timeToStr(this.end)}`; - } -} +} \ No newline at end of file -- cgit v1.2.3 From fea56ae09cd612003e1bafd2459556b67a5950e9 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 22:56:45 -0500 Subject: Add import/export functionality --- src/components/EditorActions.svelte | 48 ++++++++++++++++++++++++++++++++++--- src/logic/EditorActions.ts | 13 +++++++++- src/models/EventInfo.ts | 10 ++++++++ src/models/Lab.ts | 18 +++++++++++++- src/models/PeerTeacher.ts | 21 +++++++++++++++- src/util/parser.ts | 48 +++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 6 deletions(-) (limited to 'src/models/EventInfo.ts') diff --git a/src/components/EditorActions.svelte b/src/components/EditorActions.svelte index a0372ed..fa2a041 100644 --- a/src/components/EditorActions.svelte +++ b/src/components/EditorActions.svelte @@ -3,7 +3,11 @@ import IconButton from "@smui/icon-button"; import Snackbar, { Actions } from "@smui/snackbar"; import FileUpload from "./FileUpload.svelte"; - import { parseLabScheduleFile, parsePTFile } from "../logic/EditorActions"; + import { + parseDatabaseFile, + parseLabScheduleFile, + parsePTFile, + } from "../logic/EditorActions"; import { labStore, ptStore } from "../stores"; let ptSchedules: FileList | null; @@ -50,10 +54,48 @@ } $: { - if (dbFile?.length) console.log(dbFile); + if (dbFile?.length) { + parseDatabaseFile(dbFile[0]) + .then((database) => { + labStore.set(database.labs); + ptStore.set(database.peerTeachers); + }) + .catch(() => { + snackbarText = "Failed to import database. See console for details."; + snackbar.open(); + }); + } } - function exportDB() {} + function exportDB() { + const peerTeachers = [...$ptStore.values()]; + const labs = [...$labStore.values()]; + const database = { + labs: labs, + peerTeachers: peerTeachers, + }; + + const dbObj = JSON.stringify(database, (_, value) => { + // Need to manually convert the PeerTeacher objects' + // `labs` set to an array because `JSON.stringify` doesn't + // support "stringing" it out of the box + if (typeof value === "object" && value instanceof Set) { + return [...value]; + } + return value; + }); + + const blob = new Blob([dbObj], { type: "text/json" }); + const anchor = document.createElement("a"); + const url = window.URL.createObjectURL(blob); + anchor.href = url; + anchor.download = "pt-db.json"; + anchor.style.display = "none"; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + window.URL.revokeObjectURL(url); + }
diff --git a/src/logic/EditorActions.ts b/src/logic/EditorActions.ts index 875fdc4..4a83fbc 100644 --- a/src/logic/EditorActions.ts +++ b/src/logic/EditorActions.ts @@ -1,4 +1,4 @@ -import { parseLabSchedule, parsePTSchedule } from "../util/parser"; +import { parseDatabase, parseLabSchedule, parsePTSchedule } from "../util/parser"; export async function parsePTFile(file: File) { try { @@ -20,3 +20,14 @@ export async function parseLabScheduleFile(file: File) { throw error; } } + +export async function parseDatabaseFile(file: File) { + const text = await file.text(); + try { + const database = JSON.parse(text); + return parseDatabase(database); + } catch (error) { + console.error(file.name, error); + throw error; + } +} diff --git a/src/models/EventInfo.ts b/src/models/EventInfo.ts index d20682c..6fce60b 100644 --- a/src/models/EventInfo.ts +++ b/src/models/EventInfo.ts @@ -1,3 +1,9 @@ +interface EventInfoSerializeInfo { + days: string, + start: number, + end: number +} + export default class EventInfo { days: string; start: number; @@ -16,6 +22,10 @@ export default class EventInfo { this.end = end; } + static fromJSON({days, start, end}: EventInfoSerializeInfo) { + return new EventInfo(days, start, end); + } + static timeToStr(time: number) { let hour = Math.floor(time / 100); const minute = time % 100; diff --git a/src/models/Lab.ts b/src/models/Lab.ts index 8636f74..d23cffb 100644 --- a/src/models/Lab.ts +++ b/src/models/Lab.ts @@ -1,4 +1,16 @@ -import type EventInfo from "./EventInfo"; +import EventInfo from "./EventInfo"; + +interface LabSerializeInfo { + course: number, + section: number, + event: { + days: string, + start: number, + end: number + }, + building: string, + room: string +} export default class Lab { id: number; @@ -24,6 +36,10 @@ export default class Lab { this.room = room; } + static fromJSON({course, section, event, building, room}: LabSerializeInfo) { + return new Lab(course, section, EventInfo.fromJSON(event), building, room); + } + get time() { return this.event.info; } diff --git a/src/models/PeerTeacher.ts b/src/models/PeerTeacher.ts index 6518a80..f1a8739 100644 --- a/src/models/PeerTeacher.ts +++ b/src/models/PeerTeacher.ts @@ -1,4 +1,16 @@ -import type EventInfo from "./EventInfo"; +import EventInfo from "./EventInfo"; + +interface PeerTeacherSerializeInfo { + id: number, + firstname: string, + lastname: string, + events: { + days: string, + start: number, + end: number + }[], + labs: number[] +} export default class PeerTeacher { id: number; @@ -18,4 +30,11 @@ export default class PeerTeacher { this.events = []; this.labs = new Set(); } + + static fromJSON({id, firstname, lastname, events, labs}: PeerTeacherSerializeInfo) { + const pt = new PeerTeacher(id, firstname, lastname); + pt.events = events.map(e => EventInfo.fromJSON(e)); + pt.labs = new Set(labs); + return pt; + } } \ No newline at end of file diff --git a/src/util/parser.ts b/src/util/parser.ts index 6d706d7..2f1bdd4 100644 --- a/src/util/parser.ts +++ b/src/util/parser.ts @@ -24,6 +24,32 @@ interface LabSchedule { }[] }; +interface DatabaseFile { + labs: { + id: number, + course: number, + section: number, + event: { + days: string, + start: number, + end: number + }, + building: string, + room: string + }[], + peerTeachers: { + id: number, + firstname: string, + lastname: string, + events: { + days: string, + start: number, + end: number + }[], + labs: number[] + }[] +} + /** * Parses a peer teacher schedule * @param schedule The schedule to parse @@ -104,4 +130,26 @@ export function parseLabSchedule(schedule: LabSchedule) { } return results; +} + +/** + * Parses a database file into maps of Lab and PeerTeacher objects + * @param database The database object from a database file + * @returns And object with lab and peer teacher maps + */ +export function parseDatabase(database: DatabaseFile) { + const result = { + labs: new Map(), + peerTeachers: new Map() + } + + database.labs.forEach(lab => { + result.labs.set(lab.id, Lab.fromJSON(lab)); + }); + + database.peerTeachers.forEach(pt => { + result.peerTeachers.set(pt.id, PeerTeacher.fromJSON(pt)); + }); + + return result; } \ No newline at end of file -- cgit v1.2.3 From 9bce000c7b5799837fac15bd6339dfd27401b630 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 5 Sep 2021 23:57:27 -0500 Subject: Add ability to assign labs to PTs --- src/components/EditorLists.svelte | 99 ++++++++++++++++++++++++++++----------- src/models/EventInfo.ts | 5 ++ src/models/PeerTeacher.ts | 8 ++++ 3 files changed, 84 insertions(+), 28 deletions(-) (limited to 'src/models/EventInfo.ts') diff --git a/src/components/EditorLists.svelte b/src/components/EditorLists.svelte index 2d9b9e9..1b2084a 100644 --- a/src/components/EditorLists.svelte +++ b/src/components/EditorLists.svelte @@ -7,8 +7,11 @@ SecondaryText, Text, } from "@smui/list"; + import type PeerTeacher from "../models/PeerTeacher"; import { labStore, ptStore } from "../stores"; + let selectedPeerTeacher: PeerTeacher | undefined; + $: peerTeachers = [...$ptStore.values()].sort((a, b) => a.lastname.toUpperCase() === b.lastname.toUpperCase() ? a.firstname.toUpperCase().localeCompare(b.firstname.toUpperCase()) @@ -17,7 +20,44 @@ $: labs = [...$labStore.values()].sort((a, b) => a.id - b.id); - const nothing: any[] = []; + $: assignedLabs = [...(selectedPeerTeacher?.labs.values() ?? [])] + .flatMap((labId) => { + const lab = $labStore.get(labId); + return lab === undefined ? [] : [lab]; + }) + .sort((a, b) => a.id - b.id); + + $: compatibleLabs = labs.filter( + (lab) => + !selectedPeerTeacher?.labs.has(lab.id) && + !selectedPeerTeacher?.conflictsWith(lab.event) && + !assignedLabs.some((assignment) => + assignment.event.conflictsWith(lab.event) + ) + ); + + function deletePT(id: number) { + if (selectedPeerTeacher?.id === id) { + selectedPeerTeacher = undefined; + } + + ptStore.update((val) => { + val.delete(id); + return val; + }); + } + + function assignLab(id: number) { + selectedPeerTeacher?.labs.add(id); + // Self assignemnt to update `assignedLabs` and `compatibleLabs` + selectedPeerTeacher = selectedPeerTeacher; + } + + function unassignLab(id: number) { + selectedPeerTeacher?.labs.delete(id); + // Self assignemnt to update `assignedLabs` and `compatibleLabs` + selectedPeerTeacher = selectedPeerTeacher; + }
@@ -25,21 +65,20 @@

Peer Teachers

{#each peerTeachers as pt} - + (selectedPeerTeacher = pt)}> - {pt.firstname} {pt.lastname} + {pt.name} {pt.id} { - ptStore.update((val) => { - val.delete(pt.id); - return val; - }); - }}>remove_circle + remove_circle + {/each} @@ -48,45 +87,49 @@

Labs

- {#each labs as lab} + {#each compatibleLabs as lab} {lab.course}-{lab.section} {lab.time} {lab.location} - + { - console.log("hello"); - }}>add_circle + assignLab(lab.id); + }} + > + add_circle + + {/each}
-

PT - Assigned Labs

+

+ {selectedPeerTeacher?.name ?? "PT"} - Assigned Labs +

- {#each nothing as n} + {#each assignedLabs as lab} - {n.course} {n.section} - - {n.event.days} - {n.event.start}-{n.event.end} - + {lab.course}-{lab.section} + {lab.time} + {lab.location} - + { - console.log("hello"); - }}>remove_circle + unassignLab(lab.id); + }} + > + remove_circle + + {/each} diff --git a/src/models/EventInfo.ts b/src/models/EventInfo.ts index 6fce60b..b0ac25b 100644 --- a/src/models/EventInfo.ts +++ b/src/models/EventInfo.ts @@ -43,6 +43,11 @@ export default class EventInfo { return `${hour}:${minute} ${meridiem}`; } + conflictsWith(event: EventInfo) { + const daysConflict = event.days.match(new RegExp(`[${this.days}]`)); + return daysConflict && this.start <= event.end && event.start <= this.end; + } + get info() { if(this.days === "") { return `WEB`; diff --git a/src/models/PeerTeacher.ts b/src/models/PeerTeacher.ts index f1a8739..5d955fd 100644 --- a/src/models/PeerTeacher.ts +++ b/src/models/PeerTeacher.ts @@ -37,4 +37,12 @@ export default class PeerTeacher { pt.labs = new Set(labs); return pt; } + + conflictsWith(event: EventInfo) { + return this.events.some(item => item.conflictsWith(event)); + } + + get name() { + return `${this.firstname} ${this.lastname}`; + } } \ No newline at end of file -- cgit v1.2.3