diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.svelte | 61 | ||||
| -rw-r--r-- | src/components/AssignLabs/AssignLabs.svelte | 151 | ||||
| -rw-r--r-- | src/components/AssignLabs/LabBox.svelte | 32 | ||||
| -rw-r--r-- | src/components/AssignLabs/PTBox.svelte | 44 | ||||
| -rw-r--r-- | src/components/Card.svelte | 14 | ||||
| -rw-r--r-- | src/components/DarkModeSwitch.svelte | 49 | ||||
| -rw-r--r-- | src/components/Editor.svelte | 20 | ||||
| -rw-r--r-- | src/components/EditorLists.svelte | 213 | ||||
| -rw-r--r-- | src/components/FileUploads.svelte (renamed from src/components/EditorActions.svelte) | 99 | ||||
| -rw-r--r-- | src/components/Sidebar.svelte | 68 | ||||
| -rw-r--r-- | src/components/UploadButton.svelte (renamed from src/components/FileUpload.svelte) | 6 | ||||
| -rw-r--r-- | src/components/helpers/Icon.svelte | 50 | ||||
| -rw-r--r-- | src/models/EventInfo.ts | 2 | ||||
| -rw-r--r-- | src/models/PeerTeacher.ts | 2 | ||||
| -rw-r--r-- | src/util/parser.ts | 24 |
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 |
