From d0975a6e7ee57de4debda94e823011d813fbf4a1 Mon Sep 17 00:00:00 2001
From: Furkan Sahin <furkan-dev@proton.me>
Date: Sun, 5 Sep 2021 21:39:24 -0500
Subject: Initial rewrite in svelte

---
 src/components/EditorActions.svelte | 86 +++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100644 src/components/EditorActions.svelte

(limited to 'src/components/EditorActions.svelte')

diff --git a/src/components/EditorActions.svelte b/src/components/EditorActions.svelte
new file mode 100644
index 0000000..a0372ed
--- /dev/null
+++ b/src/components/EditorActions.svelte
@@ -0,0 +1,86 @@
+<script lang="ts">
+  import Button, { Label } from "@smui/button";
+  import IconButton from "@smui/icon-button";
+  import Snackbar, { Actions } from "@smui/snackbar";
+  import FileUpload from "./FileUpload.svelte";
+  import { parseLabScheduleFile, parsePTFile } from "../logic/EditorActions";
+  import { labStore, ptStore } from "../stores";
+
+  let ptSchedules: FileList | null;
+  let labSchedule: FileList | null;
+  let dbFile: FileList | null;
+  let snackbar;
+  let snackbarText;
+
+  $: {
+    if (ptSchedules?.length) {
+      const promises = [...ptSchedules].map((file) => parsePTFile(file));
+      Promise.allSettled(promises)
+        .then((results) =>
+          results.flatMap((result) => {
+            if (result.status === "fulfilled") {
+              ptStore.update((val) => val.set(result.value.id, result.value));
+              return [];
+            } else {
+              return [result];
+            }
+          })
+        )
+        .then((failed) => {
+          if (failed.length) {
+            snackbarText = `Failed to add ${failed.length} PTs. See console for details.`;
+            snackbar.open();
+          }
+        });
+    }
+  }
+
+  $: {
+    if (labSchedule?.length) {
+      parseLabScheduleFile(labSchedule[0])
+        .then((labs) => {
+          labStore.update(() => new Map(labs.map((lab) => [lab.id, lab])));
+        })
+        .catch(() => {
+          snackbarText =
+            "Failed to import lab schedule. See console for details.";
+          snackbar.open();
+        });
+    }
+  }
+
+  $: {
+    if (dbFile?.length) console.log(dbFile);
+  }
+
+  function exportDB() {}
+</script>
+
+<div id="action-bar">
+  <FileUpload accept="text/plain" multiple={true} bind:files={ptSchedules}>
+    <Label>Add PT</Label>
+  </FileUpload>
+  <FileUpload accept="application/json" bind:files={labSchedule}>
+    <Label>Import Labs</Label>
+  </FileUpload>
+  <FileUpload accept="application/json" bind:files={dbFile}>
+    <Label>Import DB</Label>
+  </FileUpload>
+  <Button variant="raised" ripple={false} on:click={exportDB}>
+    <Label>Export DB</Label>
+  </Button>
+</div>
+<Snackbar bind:this={snackbar} labelText={snackbarText}>
+  <Label />
+  <Actions>
+    <IconButton class="material-icons" title="Dismiss">close</IconButton>
+  </Actions>
+</Snackbar>
+
+<style>
+  #action-bar {
+    max-width: 100vw;
+    overflow-x: auto;
+    white-space: nowrap;
+  }
+</style>
-- 
cgit v1.2.3


From fea56ae09cd612003e1bafd2459556b67a5950e9 Mon Sep 17 00:00:00 2001
From: Furkan Sahin <furkan-dev@proton.me>
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/components/EditorActions.svelte')

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);
+  }
 </script>
 
 <div id="action-bar">
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<number, Lab>(),
+        peerTeachers: new Map<number, PeerTeacher>()
+    }
+
+    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

