aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.svelte61
-rw-r--r--src/components/AssignLabs/AssignLabs.svelte151
-rw-r--r--src/components/AssignLabs/LabBox.svelte32
-rw-r--r--src/components/AssignLabs/PTBox.svelte44
-rw-r--r--src/components/Card.svelte14
-rw-r--r--src/components/DarkModeSwitch.svelte49
-rw-r--r--src/components/Editor.svelte20
-rw-r--r--src/components/EditorLists.svelte213
-rw-r--r--src/components/FileUploads.svelte (renamed from src/components/EditorActions.svelte)99
-rw-r--r--src/components/Sidebar.svelte68
-rw-r--r--src/components/UploadButton.svelte (renamed from src/components/FileUpload.svelte)6
-rw-r--r--src/components/helpers/Icon.svelte50
-rw-r--r--src/models/EventInfo.ts2
-rw-r--r--src/models/PeerTeacher.ts2
-rw-r--r--src/util/parser.ts24
15 files changed, 558 insertions, 277 deletions
diff --git a/src/App.svelte b/src/App.svelte
index 4fbef54..a9084c4 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -1,5 +1,62 @@
+<!-- This is the Root component that loads other components -->
<script lang="ts">
- import Editor from "./components/Editor.svelte";
+ import Sidebar from "./components/Sidebar.svelte";
</script>
-<Editor />
+<Sidebar />
+
+<style global>
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer base {
+ html {
+ font-family: sans-serif, Proxima Nova;
+ }
+ }
+
+ /* Custom Styles */
+ @layer utilities {
+ .assign-box {
+ /* w-1/3 bg-green-500 flex flex-col border-solid hover:border-4 border-2 border-slate-400 */
+ flex: none;
+ flex-direction: column;
+ width: 33.33%;
+ display: flex;
+ border-width: 4px;
+ border-color: rgb(148 163 184);
+ }
+ .assign-box:hover {
+ /* border-width: 6px; */
+ border-color: black;
+ border-width: 6px;
+ /* border-width: 8px; */
+ /* transition-timing-function: ease-in-out; */
+ }
+
+ .assign-box:hover > .assign-box-header {
+ border-bottom-color: black;
+ border-bottom-width: 6px;
+ }
+
+ .assign-box-header {
+ font-size: 1.5rem;
+ font-family: sans;
+ border-bottom-color: rgb(148 163 184);
+ border-bottom-width: 4px;
+ text-align: center;
+ /* height: 9%; */
+ height: 10vh;
+ overflow: hidden;
+ flex: none;
+ }
+
+ .assign-box-body {
+ /* flex-direction: row; */
+ flex: 1 1 auto;
+ overflow-y: auto;
+ height: 100%;
+ }
+ }
+</style>
diff --git a/src/components/AssignLabs/AssignLabs.svelte b/src/components/AssignLabs/AssignLabs.svelte
new file mode 100644
index 0000000..3a326ad
--- /dev/null
+++ b/src/components/AssignLabs/AssignLabs.svelte
@@ -0,0 +1,151 @@
+<script lang="ts">
+ import type PeerTeacher from "../../models/PeerTeacher";
+ import { labStore, ptStore } from "../../stores";
+ import Lab from "./LabBox.svelte";
+ import PT from "./PTBox.svelte";
+ import { onMount } from "svelte";
+ import { parseDatabaseLocal } from "../../util/parser";
+
+ 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);
+
+ $: unassignedLabs = labs.filter((lab) => !lab.assigned);
+
+ $: compatibleLabs = labs.filter(
+ (lab) =>
+ // Lab not already assigned
+ !lab.assigned &&
+ // PT schedule not conflict with lab
+ !selectedPeerTeacher?.conflictsWith(lab.event) &&
+ // PT's labs not conflict with this lab
+ !assignedLabs.some((assignment) =>
+ assignment.event.conflictsWith(lab.event)
+ )
+ );
+
+ function updateReactiveDeclarations() {
+ selectedPeerTeacher = selectedPeerTeacher;
+ peerTeachers = peerTeachers;
+ labs = labs;
+ }
+
+ function assignLab(id: number) {
+ const lab = $labStore.get(id);
+ if (lab === undefined) return;
+ lab.assigned = true;
+ selectedPeerTeacher?.labs.add(id);
+ updateReactiveDeclarations();
+ }
+
+ function unassignLab(id: number) {
+ const lab = $labStore.get(id);
+ if (lab === undefined) return;
+ lab.assigned = false;
+ selectedPeerTeacher?.labs.delete(id);
+ updateReactiveDeclarations();
+ }
+
+ // Load db from local storage so I don't have to keep uploading
+ onMount(() => {
+ const db = localStorage.getItem("db");
+ if (db) {
+ parseDatabaseLocal(JSON.parse(db));
+ }
+ });
+</script>
+
+<div
+ class="flex-none overflow-hidden flex-col h-[100vh] w-[80vw] px-[2vw] pt-[1vh]"
+>
+ <!-- Top half: 3 Columns -->
+ <div class="flex flex-row h-[80vh]">
+ <!-- PT Box -->
+ <div class="assign-box rounded-l-xl">
+ <!-- PT Header -->
+ <div class="assign-box-header">Peer Teacher</div>
+ <!-- PT Body -->
+ <div class="assign-box-body">
+ {#each peerTeachers as pt}
+ <div
+ class={selectedPeerTeacher == pt
+ ? "border-l-8 border-blue-500"
+ : ""}
+ on:click={() => {
+ selectedPeerTeacher = pt;
+ }}
+ >
+ <svelte:component this={PT} {pt} />
+ </div>
+ {/each}
+ </div>
+ </div>
+
+ <!-- Available Labs -->
+ <div class="assign-box">
+ <div class="assign-box-header">Labs</div>
+ <div class="assign-box-body">
+ {#each compatibleLabs as lab}
+ <svelte:component
+ this={Lab}
+ {lab}
+ iconName="plus-circle"
+ iconClick={() => {
+ assignLab(lab.id);
+ }}
+ />
+ {/each}
+ </div>
+ </div>
+
+ <!-- Selected PT's Labs -->
+ <div class="assign-box rounded-r-xl">
+ <div class="assign-box-header">
+ {selectedPeerTeacher?.name ?? "PT's Labs"}
+ </div>
+ <div class="assign-box-body">
+ {#each assignedLabs as lab}
+ <svelte:component
+ this={Lab}
+ {lab}
+ iconName="minus-circle"
+ iconClick={() => {
+ unassignLab(lab.id);
+
+ }}
+ />
+ {/each}
+ </div>
+ </div>
+ </div>
+
+ <!-- Bottom half: Universal unassigned labs -->
+ <div class="flex flex-col mt-2 text-center">
+ <h1>Unassigned Labs</h1>
+ <div
+ class="flex flex-row overflow-auto border-y-4 mt-1 border-slate-500 w-full items-center text-sm"
+ >
+ {#each unassignedLabs as lab}
+ <div
+ class="hover:animate-bounce border rounded-xl hover:bg-sky-100 hover:text-black px-3 py-1 mx-2"
+ >
+ <p>{lab.course}</p>
+ <p>{lab.section}</p>
+ </div>
+ {/each}
+ </div>
+ </div>
+</div>
diff --git a/src/components/AssignLabs/LabBox.svelte b/src/components/AssignLabs/LabBox.svelte
new file mode 100644
index 0000000..9805c51
--- /dev/null
+++ b/src/components/AssignLabs/LabBox.svelte
@@ -0,0 +1,32 @@
+<script lang="ts">
+ import type Lab from "../../models/Lab";
+ import Icon from "../helpers/Icon.svelte";
+ export let lab: Lab;
+ export let iconClick = () => {};
+ export let iconName: string;
+</script>
+
+<!-- Lab box -->
+<div
+ class="block border-b px-3 py-3 hover:bg-sky-100 hover:text-black h-20 overflow-hidden"
+>
+ <!-- Lab content -->
+ <div class="flex flex-col">
+ <!-- Top Half -->
+ <div class="flex flex-row">
+ <strong class="flex-grow">CSCE {lab.course} - {lab.section}</strong>
+ <Icon
+ name={iconName}
+ class="h-6 w-6"
+ handleClick={() => {
+ iconClick();
+ }}
+ />
+ </div>
+ </div>
+ <!-- Bottom half -->
+ <div>
+ <p class="text-xs">{lab.event.info}</p>
+ <p class="text-xs">{lab.building} {lab.room}</p>
+ </div>
+</div>
diff --git a/src/components/AssignLabs/PTBox.svelte b/src/components/AssignLabs/PTBox.svelte
new file mode 100644
index 0000000..3ad50d1
--- /dev/null
+++ b/src/components/AssignLabs/PTBox.svelte
@@ -0,0 +1,44 @@
+<script lang="ts">
+ import type PeerTeacher from "../../models/PeerTeacher";
+ import Icon from "../helpers/Icon.svelte";
+ export let pt: PeerTeacher;
+
+ let modalID = () => {
+ return `my-modal-${pt.id}`;
+ }
+</script>
+
+<!-- PT Box -->
+<div
+ class="block border-b px-3 py-3 hover:bg-sky-100 hover:text-black h-20 overflow-hidden group"
+>
+ <!-- Top half, name and button -->
+ <div class="flex flex-row items-center ">
+ <!-- Left half, name -->
+ <strong class="flex-grow text-sm">{pt.name}</strong>
+
+ <!-- Right half, button -->
+ <!-- The button to open modal -->
+ <label for={modalID()} class="">
+ <Icon name="info" class="h-6 w-6" />
+ </label>
+
+ <!-- Modal PT event info -->
+ <input type="checkbox" id={modalID()} class="modal-toggle" />
+ <label for={modalID()} class="modal cursor-pointer">
+ <label class="modal-box relative bg-slate-300" for="">
+ <h3 class="text-lg font-bold font-serif underline">
+ {pt.name}
+ </h3>
+ {#each pt.events as e}
+ <p class="py-2">
+ {e.info}
+ </p>
+ {/each}
+ </label>
+ </label>
+ </div>
+
+ <!-- Bottom half, hours -->
+ <div class="">Hours: {pt.lab_hours}</div>
+</div>
diff --git a/src/components/Card.svelte b/src/components/Card.svelte
new file mode 100644
index 0000000..a063e96
--- /dev/null
+++ b/src/components/Card.svelte
@@ -0,0 +1,14 @@
+<script lang="ts">
+ export let title = "";
+ export let desc = "";
+</script>
+
+<div class="card w-96 bg-primary text-primary-content">
+ <div class="card-body">
+ <h2 class="card-title">{title}</h2>
+ <p>{desc}</p>
+ <div class="card-actions justify-end">
+ <slot/>
+ </div>
+ </div>
+</div>
diff --git a/src/components/DarkModeSwitch.svelte b/src/components/DarkModeSwitch.svelte
new file mode 100644
index 0000000..709b717
--- /dev/null
+++ b/src/components/DarkModeSwitch.svelte
@@ -0,0 +1,49 @@
+<script>
+ function selectDaisyTheme(new_theme) {
+ document.querySelector(":root").setAttribute("data-theme", new_theme);
+ }
+
+ function toggleDarkMode() {
+ const theme = localStorage.theme;
+ if (theme == "dark") {
+ localStorage.theme = "light";
+ selectDaisyTheme("light");
+ } else if (theme == "light") {
+ localStorage.theme = "dark";
+ selectDaisyTheme("dark");
+ }
+ }
+
+ function isDarkMode() {
+ if (localStorage.theme == "dark") return "";
+ return "checked"
+ }
+</script>
+
+<label class="swap swap-rotate">
+ <!-- this hidden checkbox controls the state -->
+ <!-- Make Moon/Sun icon based on current theme -->
+ <input type="checkbox" checked={isDarkMode()} on:click={toggleDarkMode} />
+
+ <!-- sun icon -->
+ <svg
+ class="swap-on fill-current w-10 h-10"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ >
+ <path
+ d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"
+ />
+ </svg>
+
+ <!-- moon icon -->
+ <svg
+ class="swap-off fill-current w-10 h-10"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ >
+ <path
+ d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"
+ />
+ </svg>
+</label>
diff --git a/src/components/Editor.svelte b/src/components/Editor.svelte
deleted file mode 100644
index c8a30c6..0000000
--- a/src/components/Editor.svelte
+++ /dev/null
@@ -1,20 +0,0 @@
-<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/EditorLists.svelte b/src/components/EditorLists.svelte
deleted file mode 100644
index ce68dca..0000000
--- a/src/components/EditorLists.svelte
+++ /dev/null
@@ -1,213 +0,0 @@
-<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) =>
- // Lab not already assigned
- !lab.assigned &&
- // PT schedule not conflict with lab
- !selectedPeerTeacher?.conflictsWith(lab.event) &&
- // PT's labs not conflict with this lab
- !assignedLabs.some((assignment) =>
- assignment.event.conflictsWith(lab.event)
- )
- );
-
- function deletePT(id: number) {
- if (selectedPeerTeacher?.id === id) {
- selectedPeerTeacher = undefined;
- }
-
- $ptStore.get(id)?.labs.forEach((lab_id) => {
- const lab = $labStore?.get(lab_id);
- if (lab !== undefined) lab.assigned = false;
- });
-
- ptStore.update((map) => {
- map.delete(id);
- return map;
- });
-
- // Self assignemnt to update `assignedLabs` and `compatibleLabs`
- selectedPeerTeacher = selectedPeerTeacher;
- }
-
- function assignLab(id: number) {
- // Mark lab as assigned
- const lab = $labStore.get(id);
- if (lab === undefined) return;
- lab.assigned = true;
-
- selectedPeerTeacher?.labs.add(id);
-
- // Self assignemnt to update `assignedLabs` and `compatibleLabs`
- selectedPeerTeacher = selectedPeerTeacher;
- // Self assignment to update PT values used in `Peer Teacher` column
- peerTeachers = peerTeachers;
- }
-
- function unassignLab(id: number) {
- const lab = $labStore.get(id);
- if (lab === undefined) return;
- lab.assigned = false;
-
- selectedPeerTeacher?.labs.delete(id);
-
- // Mark lab as unassigned
-
- // Self assignemnt to update `assignedLabs` and `compatibleLabs`
- selectedPeerTeacher = selectedPeerTeacher;
- // Self assignment to update PT values used in `Peer Teacher` column
- peerTeachers = peerTeachers;
- }
-
- $: clicked = 0;
-</script>
-
-<div class="assign-labs">
- <div class="column">
- <div class="col-header">Peer Teachers</div>
- <List threeLine avatarList singleSelection class="editor-list">
- {#each peerTeachers as pt}
- <Item on:SMUI:action={() => (selectedPeerTeacher = pt)}>
- <Text>
- <PrimaryText>{pt.name}</PrimaryText>
- <SecondaryText>{pt.id}</SecondaryText>
- <SecondaryText>Assigned hours: {pt.lab_hours}</SecondaryText>
- </Text>
- <Meta>
- <IconButton
- class="material-icons"
- on:click$stopPropagation={() => {
- deletePT(pt.id);
- }}
- >
- remove_circle
- </IconButton>
- </Meta>
- </Item>
- {/each}
- </List>
- </div>
- <div class="column">
- <div class="col-header">Labs</div>
- <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">
- <div class="col-header">
- {selectedPeerTeacher?.name ?? "PT"}
- </div>
- <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>
- .assign-labs {
- display: flex;
- min-height: 0;
- max-width: 100vw;
- overflow: hidden;
- font-family: "Fira Code", sans-serif;
- border-radius: 2em 2em 2em 2em;
- margin-top: 1.5em;
- }
-
- .col-header {
- font-family: inherit;
- text-align: center;
- max-height: 1em;
- overflow: hidden;
- font-size: x-large;
- font-weight: 600;
- border: 0.1em solid rgb(6, 69, 48);
- border-radius: 20em 20em;
- margin: 0.3em 1em 0em 1em;
- /* font-size: 0.90em; */
-
- /* border: 5px solid red;
- border-style: solid;
- background-color: pink; */
- }
-
- .column {
- display: flex row;
- font-family: inherit;
- flex: 1;
-
- background-color: rgb(192, 192, 164);
- }
-
- * :global(.editor-list) {
- margin: 0.5em 0em 0.5em 0.5em;
- font-family: inherit;
- font-weight: 600;
- height: 70vh;
- overflow: auto;
- }
-</style>
diff --git a/src/components/EditorActions.svelte b/src/components/FileUploads.svelte
index aacbf0e..2c246f7 100644
--- a/src/components/EditorActions.svelte
+++ b/src/components/FileUploads.svelte
@@ -1,8 +1,9 @@
<script lang="ts">
- import Button, { Label } from "@smui/button";
+ import { Label } from "@smui/button";
import IconButton from "@smui/icon-button";
import Snackbar, { Actions } from "@smui/snackbar";
- import FileUpload from "./FileUpload.svelte";
+ import UploadButton from "./UploadButton.svelte";
+ import Card from "./Card.svelte";
import {
parseDatabaseFile,
parseLabScheduleFile,
@@ -35,6 +36,10 @@
snackbarText = `Failed to add ${failed.length} PTs. See console for details.`;
snackbar.open();
}
+ })
+ .finally( () => {
+ snackbarText = "Sucessfullyed imported Peer Teacher/s!";
+ snackbar.open();
});
}
}
@@ -49,6 +54,10 @@
snackbarText =
"Failed to import lab schedule. See console for details.";
snackbar.open();
+ })
+ .finally(() => {
+ snackbarText = "Sucessfullyed imported Lab/s!";
+ snackbar.open();
});
}
}
@@ -63,6 +72,10 @@
.catch(() => {
snackbarText = "Failed to import database. See console for details.";
snackbar.open();
+ })
+ .finally(() => {
+ snackbarText = "Sucessfullyed imported database!";
+ snackbar.open();
});
}
}
@@ -98,45 +111,57 @@
}
</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}
- style="overflow: hidden; margin-left: 0.1em; margin-right: 0.5em;"
- >
- <Label>Export DB</Label>
- </Button>
- <div id="info" />
+<div class="flex flex-col items-center justify-center h-full ">
+ <div class="flex grid grid-cols-2 gap-6">
+ <Card
+ title="Peer Teacher"
+ desc="Upload one or more Peer Teacher schedule txt files"
+ >
+ <UploadButton
+ accept="text/plain"
+ multiple={true}
+ bind:files={ptSchedules}
+ />
+ </Card>
+
+ <Card
+ title="Labs"
+ desc="Upload one or more Labs as json file. Acquired from Howdy"
+ >
+ <UploadButton
+ color="btn-success"
+ accept="application/json"
+ multiple={true}
+ bind:files={labSchedule}
+ />
+ </Card>
+
+ <Card
+ title="Data Base"
+ desc="Upload the json database file to continue working"
+ >
+ <UploadButton
+ color="btn-info"
+ accept="application/json"
+ multiple={true}
+ bind:files={dbFile}
+ />
+ </Card>
+
+ <Card
+ title="Export DB"
+ desc="Download the json database file to save your work. Remember to save it on the cloud somewhere!"
+ >
+ <button class="btn btn-warning" on:click={exportDB}>Download</button>
+ </Card>
+ </div>
</div>
+
+<!-- https://github.com/saadeghi/daisyui/issues/221 -->
+<!-- Snackbar is a work in progress for Daisyui. Until then, keep smui -->
<Snackbar bind:this={snackbar} labelText={snackbarText}>
<Label />
<Actions>
<IconButton class="material-icons" title="Dismiss">close</IconButton>
</Actions>
</Snackbar>
-
-<style>
-
- #action-bar {
- display: flex;
- justify-content: space-evenly;
- align-content: center;
- border-radius: 1em;
- /* background-image: linear-gradient(to right, red, purple); */
- background-size: 100vw;
- max-height: 2em;
- max-width: 100vw;
- overflow: hidden;
- padding: 0.6em;
- }
-</style>
diff --git a/src/components/Sidebar.svelte b/src/components/Sidebar.svelte
new file mode 100644
index 0000000..4a53452
--- /dev/null
+++ b/src/components/Sidebar.svelte
@@ -0,0 +1,68 @@
+<script lang="ts">
+ import DarkModeSwitch from "./DarkModeSwitch.svelte";
+ import AssignLabs from "./AssignLabs/AssignLabs.svelte";
+ import FileUploads from "./FileUploads.svelte";
+ import { onMount } from "svelte";
+ import { parseDatabase, parseDatabaseLocal } from "../util/parser";
+ import { parseDatabaseFile } from "../logic/EditorActions";
+ import * as local_db from "../../pt-db.json";
+
+ let sections = [
+ { name: "File Uploads", component: FileUploads },
+ { name: "Peer Teachers", component: null }, // TODO
+ { name: "Assign Labs", component: AssignLabs },
+ { name: "Labs", component: null }, // TODO
+ { name: "Active Peer Teachers", component: null }, // TODO
+ { name: "Stats", component: null }, // TODO
+ { name: "TAMU Html Output", component: null }, // TODO
+ ];
+
+ // Set initial page to Assign Labs while I work on it
+ let selected = sections[2];
+
+ onMount(async () => {
+ // load database on mount for testing purposes
+ console.log("reading local database");
+ parseDatabaseLocal(local_db);
+ });
+</script>
+
+<!-- Entire Page -->
+<div class="flex flex-row h-screen">
+ <!-- SIDEBAR -->
+ <div class="w-2/12 flex flex-col border-r">
+ <!-- Header for sidebar sections -->
+ <div
+ class="font-serif flex-none text-center text-2xl text-neutral-900 p-4 border-b font-black overflow-hidden"
+ >
+ <!-- Text -->
+ <div>Peer Teacher Manager</div>
+ <div>
+ <DarkModeSwitch />
+ </div>
+ </div>
+
+ <!-- Sidebar sections -->
+ <div class="flex-col overflow-y-auto">
+ <ul class="menu bg-base-100 w-full text-xl">
+ {#each sections as sec}
+ <li>
+ <div
+ class={selected == sec ? "active" : ""}
+ on:click={() => {
+ selected = sec;
+ }}
+ >
+ {sec.name}
+ </div>
+ </li>
+ {/each}
+ </ul>
+ </div>
+ </div>
+
+ <!-- Chosen Section / Component -->
+ <div class="flex-auto">
+ <svelte:component this={selected.component} />
+ </div>
+</div>
diff --git a/src/components/FileUpload.svelte b/src/components/UploadButton.svelte
index d325e32..a592a25 100644
--- a/src/components/FileUpload.svelte
+++ b/src/components/UploadButton.svelte
@@ -2,10 +2,10 @@
export let accept = "";
export let multiple = false;
export let files: FileList | null = null;
+ export let color = "";
</script>
-<label class="mdc-button mdc-button--raised mdc-ripple-upgraded">
- <div class="mdc-button__ripple" />
- <slot>Upload</slot>
+<label class="btn {color}">
+ Upload
<input type="file" {accept} {multiple} bind:files hidden />
</label>
diff --git a/src/components/helpers/Icon.svelte b/src/components/helpers/Icon.svelte
new file mode 100644
index 0000000..e7b68d6
--- /dev/null
+++ b/src/components/helpers/Icon.svelte
@@ -0,0 +1,50 @@
+<script lang="ts">
+ export let name;
+ export let width = "1rem";
+ export let height = "1rem";
+ export let focusable = "false";
+ export let handleClick = () => {
+ console.log(`No click handler passed to icon "${name}"`);
+ };
+ let icons = [
+ {
+ box: 24,
+ name: "plus-circle",
+ path: `<path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"
+ />`,
+ },
+ {
+ box: 24,
+ name: "minus-circle",
+ path: `<path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />`,
+ },
+ {
+ name: "info",
+ box: 24,
+ path: `<path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
+ />`,
+ },
+ ];
+ let displayIcon = icons.find((e) => e.name === name);
+</script>
+
+<svg
+ on:click={handleClick}
+ class={$$props.class}
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 {displayIcon?.box} {displayIcon?.box}"
+ stroke="currentColor"
+ stroke-width={2}
+ {width}
+ {height}
+ {focusable}
+>
+ {@html displayIcon?.path}</svg
+>
diff --git a/src/models/EventInfo.ts b/src/models/EventInfo.ts
index 693c8bb..d42cd94 100644
--- a/src/models/EventInfo.ts
+++ b/src/models/EventInfo.ts
@@ -54,7 +54,7 @@ export default class EventInfo {
} 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)}`;
}
}
diff --git a/src/models/PeerTeacher.ts b/src/models/PeerTeacher.ts
index 557b36b..4020a00 100644
--- a/src/models/PeerTeacher.ts
+++ b/src/models/PeerTeacher.ts
@@ -11,7 +11,7 @@ interface PeerTeacherSerializeInfo {
start: number,
end: number
}[],
- labs: number[]
+ labs: number[],
}
export default class PeerTeacher {
diff --git a/src/util/parser.ts b/src/util/parser.ts
index 844f87d..0f82996 100644
--- a/src/util/parser.ts
+++ b/src/util/parser.ts
@@ -1,6 +1,7 @@
import EventInfo from "../models/EventInfo";
import Lab from "../models/Lab";
import PeerTeacher from "../models/PeerTeacher";
+import { labStore, ptStore } from "../stores";
import { PeerTeacherImportError } from "./error";
interface LabSchedule {
@@ -156,4 +157,27 @@ export function parseDatabase(database: DatabaseFile) {
});
return result;
+}
+
+/**
+ * Parses a JSON database into maps of Lab and Peer Teachers
+ * and updates local storage
+ * @param database The database object from a db file
+ */
+export function parseDatabaseLocal(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));
+ });
+
+ labStore.set(result.labs);
+ ptStore.set(result.peerTeachers)
} \ No newline at end of file