aboutsummaryrefslogtreecommitdiff
path: root/src/components/EditorActions.svelte
blob: aacbf0ee6a0021d735256f3fa9da2c56db1eee86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<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}
    style="overflow: hidden; margin-left: 0.1em; margin-right: 0.5em;"
  >
    <Label>Export DB</Label>
  </Button>
  <div id="info" />
</div>
<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>