aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFurkan Sahin <furkan-dev@proton.me>2021-11-05 09:27:35 -0500
committerFurkan Sahin <furkan-dev@proton.me>2021-11-05 09:27:35 -0500
commit85380b4b60bf74507a01957b29bd6e3808e216db (patch)
treea43784660fdc29e5b95df3e358cf722eec2b092b /src
parentffef3a6be19d1139b6378c8119d444082dd0cbac (diff)
parent29fc563863f561cdc707485289c5580b4397a580 (diff)
Merge pull request #10 from cobraguy/rewrite
Update to Svelte app
Diffstat (limited to 'src')
-rw-r--r--src/App.svelte5
-rw-r--r--src/App.vue30
-rw-r--r--src/components/ActionBar.vue85
-rw-r--r--src/components/Editor.svelte20
-rw-r--r--src/components/EditorActions.svelte128
-rw-r--r--src/components/EditorLists.svelte160
-rw-r--r--src/components/EditorLists.vue152
-rw-r--r--src/components/FileUpload.svelte11
-rw-r--r--src/components/FileUpload.vue51
-rw-r--r--src/components/List.vue35
-rw-r--r--src/components/UIButton.vue32
-rw-r--r--src/features/parser.ts119
-rw-r--r--src/global.d.ts1
-rw-r--r--src/logic/EditorActions.ts33
-rw-r--r--src/main.ts11
-rw-r--r--src/models/EventInfo.ts88
-rw-r--r--src/models/Lab.ts59
-rw-r--r--src/models/PeerTeacher.ts85
-rw-r--r--src/router/index.ts27
-rw-r--r--src/shims-vue.d.ts6
-rw-r--r--src/store/index.ts36
-rw-r--r--src/stores.ts6
-rw-r--r--src/util/error.ts6
-rw-r--r--src/util/parser.ts155
-rw-r--r--src/views/Editor.vue44
-rw-r--r--src/views/Start.vue52
-rw-r--r--src/vuex-shim.d.ts17
27 files changed, 671 insertions, 783 deletions
diff --git a/src/App.svelte b/src/App.svelte
new file mode 100644
index 0000000..4fbef54
--- /dev/null
+++ b/src/App.svelte
@@ -0,0 +1,5 @@
+<script lang="ts">
+ import Editor from "./components/Editor.svelte";
+</script>
+
+<Editor />
diff --git a/src/App.vue b/src/App.vue
deleted file mode 100644
index 5a1f608..0000000
--- a/src/App.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-<template>
- <router-view/>
-</template>
-
-<style>
-*, *:before, *:after {
- box-sizing: inherit;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-}
-
-html {
- box-sizing: border-box;
-}
-
-html, body {
- margin: 0;
- padding: 0;
-}
-
-#app {
- height: 100vh;
- max-height: 100vh;
-}
-
-@media (prefers-color-scheme: dark) {
- body {
- background-color: #121212;
- }
-}
-</style>
diff --git a/src/components/ActionBar.vue b/src/components/ActionBar.vue
deleted file mode 100644
index bfe3cdb..0000000
--- a/src/components/ActionBar.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
- <div id="action-bar">
- <FileUpload
- :accept="'text/plain'"
- :multiple="true"
- @fileChanged="handlePtChange">Upload PT Schedule</FileUpload>
- <FileUpload
- :accept="'application/json'"
- @fileChanged="handleLabChange">Import Lab Schedule</FileUpload>
- <UIButton @click="save">Export</UIButton>
- </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FileUpload from '@/components/FileUpload.vue';
-import { parseLabFile, parsePtSchedule } from '@/features/parser';
-import UIButton from '@/components/UIButton.vue';
-import PeerTeacher from '@/models/PeerTeacher';
-
-export default defineComponent({
- name: 'ActionBar',
- components: {
- FileUpload,
- UIButton,
- },
- methods: {
- async handleLabChange(files: File[]) {
- const data = await parseLabFile(files[0]);
- this.$store.commit('importLabs', data);
- },
- async handlePtChange(files: File[]) {
- const promises: Promise<PeerTeacher>[] = [];
-
- files.forEach((file) => {
- promises.push(parsePtSchedule(file));
- });
-
- const result = await Promise.all(promises);
- this.$store.commit('addPeerTeachers', result);
- },
- save() {
- const database = {
- labs: Object.fromEntries(this.$store.state.labs),
- peerTeachers: Object.fromEntries(this.$store.state.peerTeachers),
- };
-
- const jsonObj = JSON.stringify(database, (_, value) => {
- if (typeof value === 'object' && value instanceof Set) {
- return [...value];
- }
- return value;
- });
-
- const blob = new Blob([jsonObj], { 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>
-
-<style>
-#action-bar {
- max-width: 100vw;
- overflow-x: auto;
- white-space: nowrap;
-}
-
-#action-bar > * {
- margin-left: 0.5rem;
-}
-
-#action-bar > *:first-child {
- margin-left: 0;
-}
-</style>
diff --git a/src/components/Editor.svelte b/src/components/Editor.svelte
new file mode 100644
index 0000000..c8a30c6
--- /dev/null
+++ b/src/components/Editor.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import EditorActions from "./EditorActions.svelte";
+ import EditorLists from "./EditorLists.svelte";
+</script>
+
+<div id="editor">
+ <EditorActions />
+ <EditorLists />
+</div>
+
+<style>
+ #editor {
+ display: flex;
+ flex-direction: column;
+ }
+
+ :global(#action-bar) {
+ flex-shrink: 0;
+ }
+</style>
diff --git a/src/components/EditorActions.svelte b/src/components/EditorActions.svelte
new file mode 100644
index 0000000..fa2a041
--- /dev/null
+++ b/src/components/EditorActions.svelte
@@ -0,0 +1,128 @@
+<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 {
+ parseDatabaseFile,
+ 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) {
+ 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() {
+ 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">
+ <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>
diff --git a/src/components/EditorLists.svelte b/src/components/EditorLists.svelte
new file mode 100644
index 0000000..1b2084a
--- /dev/null
+++ b/src/components/EditorLists.svelte
@@ -0,0 +1,160 @@
+<script lang="ts">
+ import IconButton from "@smui/icon-button";
+ import List, {
+ Item,
+ Meta,
+ PrimaryText,
+ 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())
+ : a.lastname.toUpperCase().localeCompare(b.lastname.toUpperCase())
+ );
+
+ $: labs = [...$labStore.values()].sort((a, b) => a.id - b.id);
+
+ $: 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;
+ }
+</script>
+
+<div id="editor-lists">
+ <div class="column">
+ <h3 class="col-header">Peer Teachers</h3>
+ <List twoLine singleSelection class="editor-list">
+ {#each peerTeachers as pt}
+ <Item on:SMUI:action={() => (selectedPeerTeacher = pt)}>
+ <Text>
+ <PrimaryText>{pt.name}</PrimaryText>
+ <SecondaryText>{pt.id}</SecondaryText>
+ </Text>
+ <Meta>
+ <IconButton
+ class="material-icons"
+ on:click$stopPropagation={() => {
+ deletePT(pt.id);
+ }}
+ >
+ remove_circle
+ </IconButton>
+ </Meta>
+ </Item>
+ {/each}
+ </List>
+ </div>
+ <div class="column">
+ <h3 class="col-header">Labs</h3>
+ <List threeLine class="editor-list">
+ {#each compatibleLabs as lab}
+ <Item>
+ <Text>
+ <PrimaryText>{lab.course}-{lab.section}</PrimaryText>
+ <SecondaryText>{lab.time}</SecondaryText>
+ <SecondaryText>{lab.location}</SecondaryText>
+ </Text>
+ <Meta class="material-icons">
+ <IconButton
+ class="material-icons"
+ on:click$stopPropagation={() => {
+ assignLab(lab.id);
+ }}
+ >
+ add_circle
+ </IconButton>
+ </Meta>
+ </Item>
+ {/each}
+ </List>
+ </div>
+ <div class="column">
+ <h3 class="col-header">
+ {selectedPeerTeacher?.name ?? "PT"} - Assigned Labs
+ </h3>
+ <List threeLine class="editor-list">
+ {#each assignedLabs as lab}
+ <Item>
+ <Text>
+ <PrimaryText>{lab.course}-{lab.section}</PrimaryText>
+ <SecondaryText>{lab.time}</SecondaryText>
+ <SecondaryText>{lab.location}</SecondaryText>
+ </Text>
+ <Meta class="material-icons">
+ <IconButton
+ class="material-icons"
+ on:click$stopPropagation={() => {
+ unassignLab(lab.id);
+ }}
+ >
+ remove_circle
+ </IconButton>
+ </Meta>
+ </Item>
+ {/each}
+ </List>
+ </div>
+</div>
+
+<style>
+ #editor-lists {
+ display: flex;
+ max-height: inherit;
+ min-height: 0;
+ overflow-x: auto;
+ }
+
+ .col-header {
+ font-family: Roboto, sans-serif;
+ }
+
+ .column {
+ flex: 1;
+ min-width: 15em;
+ overflow: auto;
+ }
+
+ * :global(.editor-list) {
+ border: 1px solid black;
+ }
+</style>
diff --git a/src/components/EditorLists.vue b/src/components/EditorLists.vue
deleted file mode 100644
index dff34f4..0000000
--- a/src/components/EditorLists.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<template>
- <div id="editor-lists">
- <div class="column">
- <h3 class="column-header">Peer Teachers</h3>
- <List
- :items="peerTeachers"
- @selection-changed="handlePtClick"
- #default="{ item: { id, name } }">
- <span class="list-item">
- {{ name }} <button @click.stop="deletePeerTeacher(id)">x</button>
- </span>
- </List>
- </div>
- <div class="column">
- <h3 class="column-header">Labs</h3>
- <List :items="compatibleLabs" #default="{ item: { fullInfo, id } }">
- <span class="list-item">
- {{ fullInfo }} <button @click.stop="assignLab(id)">+</button>
- </span>
- </List>
- </div>
- <div class="column">
- <h3 class="column-header">{{ this.selectedPeerTeacher.name }}</h3>
- <List :items="selectedPeerTeacherAssignments" #default="{ item: { fullInfo, id } }">
- <span class="list-item">
- {{ fullInfo }} <button @click.stop="unassignLab(id)">x</button>
- </span>
- </List>
- </div>
- </div>
-</template>
-
-<script lang="ts">
-import { defineComponent, PropType } from 'vue';
-import List from '@/components/List.vue';
-import PeerTeacher from '@/models/PeerTeacher';
-import Lab from '@/models/Lab';
-
-export default defineComponent({
- name: 'EditorLists',
- components: {
- List,
- },
- props: {
- peerTeachers: {
- type: Array as PropType<PeerTeacher[]>,
- default: () => [],
- },
- labs: {
- type: Array as PropType<Lab[]>,
- default: () => [],
- },
- },
- computed: {
- compatibleLabs(): Lab[] {
- const temp = this.labs.filter((lab) => (!this.selectedPeerTeacher.assignedLabs.has(lab.id)
- && !this.selectedPeerTeacher.conflictsWith(lab.event)));
-
- const currentAssignments = this.selectedPeerTeacherAssignments;
- return temp.filter((lab) => !currentAssignments
- .some((assignedLab) => lab.event.conflictsWith(assignedLab.event)));
- },
- selectedPeerTeacherAssignments(): Lab[] {
- return Array.from(this.selectedPeerTeacher.assignedLabs.values())
- .flatMap((id) => {
- const lab = this.$store.state.labs.get(id);
- return (lab === undefined) ? [] : [lab];
- }).sort((a, b) => a.course - b.course || a.section - b.section);
- },
- },
- data() {
- return {
- selectedPeerTeacher: new PeerTeacher(),
- };
- },
- methods: {
- handlePtClick(peerTeacher: PeerTeacher) {
- this.selectedPeerTeacher = peerTeacher;
- },
- deletePeerTeacher(id: number) {
- if (this.selectedPeerTeacher.id === id) {
- this.selectedPeerTeacher = new PeerTeacher();
- }
- this.$store.commit('deletePeerTeacher', id);
- },
- assignLab(id: string) {
- if (this.selectedPeerTeacher.id !== 0) {
- this.selectedPeerTeacher.assignedLabs.add(id);
- }
- },
- unassignLab(id: string) {
- this.selectedPeerTeacher.assignedLabs.delete(id);
- },
- },
-});
-</script>
-
-<style>
-#editor-lists {
- display: flex;
- max-height: inherit;
- min-height: 0;
-}
-
-.column-header {
- margin: 0;
-}
-
-.column {
- flex: 1;
- overflow: auto;
-}
-
-.list-item {
- align-items: center;
- background-color: #f0f0f0;
- display: flex;
- justify-content: space-between;
- margin: 0.5em;
- padding: 0.5em;
-}
-
-.list-item:hover {
- background-color: #c0c0c0;
-}
-
-.list-item > button {
- background-color: #500000;
- border: none;
- color: white;
- font-size: 1em;
-}
-
-@media (prefers-color-scheme: dark) {
- .column-header {
- color: white;
- }
-
- .list-item {
- background-color: #303030;
- color: white;
- }
-
- .list-item:hover {
- background-color: #707070;
- }
-
- .list-item > button {
- background-color: #81302b;
- }
-}
-</style>
diff --git a/src/components/FileUpload.svelte b/src/components/FileUpload.svelte
new file mode 100644
index 0000000..d325e32
--- /dev/null
+++ b/src/components/FileUpload.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+ export let accept = "";
+ export let multiple = false;
+ export let files: FileList | null = null;
+</script>
+
+<label class="mdc-button mdc-button--raised mdc-ripple-upgraded">
+ <div class="mdc-button__ripple" />
+ <slot>Upload</slot>
+ <input type="file" {accept} {multiple} bind:files hidden />
+</label>
diff --git a/src/components/FileUpload.vue b/src/components/FileUpload.vue
deleted file mode 100644
index 082fc9d..0000000
--- a/src/components/FileUpload.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<template>
- <label class="file-upload-lbl">
- <input
- type="file"
- :accept="accept"
- @change="$emit('fileChanged', $event.target.files)"
- :multiple="multiple" hidden />
- <slot>Upload</slot>
- </label>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
- name: 'FileUpload',
- props: {
- accept: {
- type: String,
- required: false,
- },
- multiple: {
- type: Boolean,
- default: false,
- },
- },
- emits: {
- fileChanged: null,
- },
-});
-</script>
-
-<style scoped>
-.file-upload-lbl {
- background-color: #500000;
- color: white;
- padding: 0.5em;
- text-align: center;
-}
-
-.file-upload-lbl:hover {
- color: grey;
- cursor: pointer;
-}
-
-@media (prefers-color-scheme: dark) {
- .file-upload-lbl {
- background-color: #81302b;
- }
-}
-</style>
diff --git a/src/components/List.vue b/src/components/List.vue
deleted file mode 100644
index 4732a9e..0000000
--- a/src/components/List.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-<template>
- <ul class="list">
- <li
- v-for="item in items"
- :key="item.id"
- @click="$emit('selectionChanged', item)">
- <slot :item="item">{{ item }}</slot>
- </li>
- </ul>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
- name: 'List',
- props: {
- items: {
- type: Array,
- default: [] as any[],
- },
- },
- emits: {
- selectionChanged: null,
- },
-});
-</script>
-
-<style scoped>
-.list {
- list-style-type: none;
- margin: 0;
- padding: 0;
-}
-</style>
diff --git a/src/components/UIButton.vue b/src/components/UIButton.vue
deleted file mode 100644
index 57b155b..0000000
--- a/src/components/UIButton.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<template>
- <button class="ui-button"><slot></slot></button>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
- name: 'UIButton',
-});
-</script>
-
-<style>
-.ui-button {
- background-color: #500000;
- border: none;
- color: white;
- font-size: 1em;
- padding: 0.5em;
-}
-
-.ui-button:hover {
- color: grey;
- cursor: pointer;
-}
-
-@media (prefers-color-scheme: dark) {
- .ui-button {
- background-color: #81302b;
- }
-}
-</style>
diff --git a/src/features/parser.ts b/src/features/parser.ts
deleted file mode 100644
index 9d61946..0000000
--- a/src/features/parser.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import Lab from '@/models/Lab';
-import PeerTeacher from '@/models/PeerTeacher';
-import EventInfo from '../models/EventInfo';
-
-export async function parseLabFile(file: File): Promise<Lab[]> {
- const validCourses = [
- '110',
- '111',
- '121',
- '206',
- '221',
- // '222',
- '312',
- '313',
- // '314',
- '315',
- ];
- const result: Lab[] = [];
-
- const text = await file.text();
- let jsonData;
- try {
- jsonData = JSON.parse(text);
- } catch (e) {
- throw new Error(e);
- }
- const labs = jsonData.data;
-
- labs.forEach((lab: any) => {
- if (validCourses.includes(lab.courseNumber)) {
- const newLab = new Lab(lab.courseNumber, lab.sequenceNumber);
-
- lab.meetingsFaculty.every((meeting: any) => {
- const { meetingTime } = meeting;
-
- if (meetingTime.meetingType === 'LAB') {
- let days = '';
- days += meetingTime.monday ? 'M' : '';
- days += meetingTime.tuesday ? 'T' : '';
- days += meetingTime.wednesday ? 'W' : '';
- days += meetingTime.thursday ? 'R' : '';
- days += meetingTime.friday ? 'F' : '';
-
- newLab.event.days = days;
- newLab.event.start = parseInt(meetingTime.beginTime, 10);
- newLab.event.end = parseInt(meetingTime.endTime, 10);
-
- return false;
- }
-
- return true;
- });
-
- result.push(newLab);
- }
- });
-
- return result;
-}
-
-export async function parsePtSchedule(file: File): Promise<PeerTeacher> {
- const text = await file.text();
- const peerTeacher = new PeerTeacher();
- // namePatter: <firstname> <lastname> <uin>
- const namePattern = /^(.*)\s(.*)\s(\d{9})/;
- // eventPattern (24hr time): MTWRF hh:mm - hh:mm
- const eventPattern = /^(M?T?W?R?F?)\s(\d{1,2}:\d{2})\s?-\s?(\d{1,2}:\d{2})/;
- const lines = text.split('\n').filter((line) => line.trim());
-
- lines.forEach((line) => {
- const ptName = line.match(namePattern);
- if (ptName) {
- [, peerTeacher.firstname, peerTeacher.lastname] = ptName;
- peerTeacher.uin = parseInt(ptName[3], 10);
- }
-
- const eventMatch = line.match(eventPattern);
- if (eventMatch) {
- const newEvent = new EventInfo(eventMatch[1]);
- newEvent.start = parseInt(eventMatch[2].replace(':', ''), 10);
- newEvent.end = parseInt(eventMatch[3].replace(':', ''), 10);
- peerTeacher.events.push(newEvent);
- }
- });
-
- return peerTeacher;
-}
-
-export async function parsePtDatabase(file: File): Promise<{
- labs: Map<string, Lab>,
- peerTeachers: Map<number, PeerTeacher>
-}> {
- const text = await file.text();
- const jsonObj = JSON.parse(text);
- const result = {
- labs: new Map(),
- peerTeachers: new Map(),
- };
-
- Object.keys(jsonObj.labs).forEach((key) => {
- const { course, section, event: { days, start, end } } = jsonObj.labs[key];
- result.labs.set(key, new Lab(course,
- section, new EventInfo(days, start, end)));
- });
-
- Object.keys(jsonObj.peerTeachers).forEach((key) => {
- const pt = jsonObj.peerTeachers[key];
- const ptObj = new PeerTeacher(pt.firstname, pt.lastname, pt.uin);
- ptObj.events = pt.events.map((eventObj: any) => new EventInfo(eventObj.days,
- eventObj.start, eventObj.end));
- ptObj.assignedLabs = new Set();
- pt.assignedLabs.forEach((labId: string) => {
- ptObj.assignedLabs.add(labId);
- });
- result.peerTeachers.set(key, ptObj);
- });
-
- return result;
-}
diff --git a/src/global.d.ts b/src/global.d.ts
new file mode 100644
index 0000000..0e72969
--- /dev/null
+++ b/src/global.d.ts
@@ -0,0 +1 @@
+/// <reference types="svelte" /> \ No newline at end of file
diff --git a/src/logic/EditorActions.ts b/src/logic/EditorActions.ts
new file mode 100644
index 0000000..4a83fbc
--- /dev/null
+++ b/src/logic/EditorActions.ts
@@ -0,0 +1,33 @@
+import { parseDatabase, parseLabSchedule, parsePTSchedule } from "../util/parser";
+
+export async function parsePTFile(file: File) {
+ try {
+ const text = await file.text();
+ return parsePTSchedule(text);
+ } catch (error) {
+ console.error(file.name, error);
+ throw error;
+ }
+}
+
+export async function parseLabScheduleFile(file: File) {
+ const text = await file.text();
+ try {
+ const labSchedule = JSON.parse(text);
+ return parseLabSchedule(labSchedule);
+ } catch (error) {
+ console.error(file.name, error);
+ 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/main.ts b/src/main.ts
index c673f53..4c473fa 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,7 @@
-import { createApp } from 'vue';
-import App from './App.vue';
-import router from './router';
-import store from './store';
+import App from './App.svelte';
-createApp(App).use(store).use(router).mount('#app');
+const app = new App({
+ target: document.body
+});
+
+export default app; \ No newline at end of file
diff --git a/src/models/EventInfo.ts b/src/models/EventInfo.ts
index 962ec70..b0ac25b 100644
--- a/src/models/EventInfo.ts
+++ b/src/models/EventInfo.ts