@vue-skuilder/platform-ui 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENCE +661 -0
  2. package/README.md +64 -0
  3. package/dist/assets/Roboto-Black-B0ZKieaB.woff +0 -0
  4. package/dist/assets/Roboto-Black-VhoA2qKx.woff2 +0 -0
  5. package/dist/assets/Roboto-BlackItalic-D0gSnuIb.woff +0 -0
  6. package/dist/assets/Roboto-BlackItalic-D4yie1YO.woff2 +0 -0
  7. package/dist/assets/Roboto-Bold-D9plYbeK.woff +0 -0
  8. package/dist/assets/Roboto-Bold-hN3duQhD.woff2 +0 -0
  9. package/dist/assets/Roboto-BoldItalic-BWDm51uc.woff2 +0 -0
  10. package/dist/assets/Roboto-BoldItalic-CyLKvOHD.woff +0 -0
  11. package/dist/assets/Roboto-Light-Cu-PAxXt.woff +0 -0
  12. package/dist/assets/Roboto-Light-DHTugVNA.woff2 +0 -0
  13. package/dist/assets/Roboto-LightItalic-CZg5kHIB.woff +0 -0
  14. package/dist/assets/Roboto-LightItalic-JQyp2Y3P.woff2 +0 -0
  15. package/dist/assets/Roboto-Medium-ByKogCTi.woff2 +0 -0
  16. package/dist/assets/Roboto-Medium-b81vv18W.woff +0 -0
  17. package/dist/assets/Roboto-MediumItalic-DFQ-RYa0.woff +0 -0
  18. package/dist/assets/Roboto-MediumItalic-i1eR0KbF.woff2 +0 -0
  19. package/dist/assets/Roboto-Regular-BX5l9hRW.woff +0 -0
  20. package/dist/assets/Roboto-Regular-C6rbFxYz.woff2 +0 -0
  21. package/dist/assets/Roboto-RegularItalic-BjnLZsam.woff +0 -0
  22. package/dist/assets/Roboto-RegularItalic-CvPUdkvM.woff2 +0 -0
  23. package/dist/assets/Roboto-Thin-BfJvJcog.woff +0 -0
  24. package/dist/assets/Roboto-Thin-NicBC1pN.woff2 +0 -0
  25. package/dist/assets/Roboto-ThinItalic-CKlCjrO_.woff2 +0 -0
  26. package/dist/assets/Roboto-ThinItalic-DnIWFxRE.woff +0 -0
  27. package/dist/assets/index-CQ-sNKGW.css +14 -0
  28. package/dist/assets/index-EbqpUgvM.js +161 -0
  29. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  30. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  31. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  32. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  33. package/dist/assets/workbox-window.prod.es5-p40uij6f.js +1 -0
  34. package/dist/favicon.ico +0 -0
  35. package/dist/img/icons/safari-pinned-tab.svg +149 -0
  36. package/dist/index.html +19 -0
  37. package/dist/manifest.json +20 -0
  38. package/dist/manifest.webmanifest +1 -0
  39. package/dist/robots.txt +2 -0
  40. package/dist/sw.js +1 -0
  41. package/dist/workbox-1be04862.js +1 -0
  42. package/package.json +105 -0
  43. package/src/App.vue +156 -0
  44. package/src/ENVIRONMENT_VARS.ts +79 -0
  45. package/src/components/Classrooms/ClassroomCtrlPanel.vue +206 -0
  46. package/src/components/Classrooms/CreateClassroom.vue +159 -0
  47. package/src/components/Classrooms/JoinCode.vue +83 -0
  48. package/src/components/Courses/CourseCardBrowser.vue +365 -0
  49. package/src/components/Courses/CourseEditor.vue +164 -0
  50. package/src/components/Courses/CourseInformation.vue +164 -0
  51. package/src/components/Courses/CourseRouter.vue +116 -0
  52. package/src/components/Courses/CourseStubCard.vue +76 -0
  53. package/src/components/Courses/EloModeration.vue +122 -0
  54. package/src/components/Courses/TagInformation.vue +209 -0
  55. package/src/components/Edit/BulkImport/CardPreviewList.vue +345 -0
  56. package/src/components/Edit/BulkImportView.vue +633 -0
  57. package/src/components/Edit/CardBrowser.vue +79 -0
  58. package/src/components/Edit/ComponentRegistration/ComponentRegistration.vue +235 -0
  59. package/src/components/Edit/ComponentRegistration/UnregisteredComponentsTable.vue +19 -0
  60. package/src/components/Edit/CourseEditor.vue +162 -0
  61. package/src/components/Edit/NavigationStrategy/NavigationStrategyEditor.vue +170 -0
  62. package/src/components/Edit/NavigationStrategy/NavigationStrategyList.vue +92 -0
  63. package/src/components/Edit/TagsInput.vue +247 -0
  64. package/src/components/Edit/ViewableDataInputForm/DataInputForm.vue +524 -0
  65. package/src/components/Edit/ViewableDataInputForm/FieldInput.types.ts +33 -0
  66. package/src/components/Edit/ViewableDataInputForm/FieldInputs/AudioInput.vue +188 -0
  67. package/src/components/Edit/ViewableDataInputForm/FieldInputs/ChessPuzzleInput.vue +79 -0
  68. package/src/components/Edit/ViewableDataInputForm/FieldInputs/FieldInput.css +12 -0
  69. package/src/components/Edit/ViewableDataInputForm/FieldInputs/ImageInput.vue +231 -0
  70. package/src/components/Edit/ViewableDataInputForm/FieldInputs/IntegerInput.vue +49 -0
  71. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MarkdownInput.vue +34 -0
  72. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MediaDragDropUploader.vue +246 -0
  73. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MidiInput.vue +113 -0
  74. package/src/components/Edit/ViewableDataInputForm/FieldInputs/NumberInput.vue +49 -0
  75. package/src/components/Edit/ViewableDataInputForm/FieldInputs/StringInput.vue +49 -0
  76. package/src/components/Edit/ViewableDataInputForm/FieldInputs/typeValidators.ts +49 -0
  77. package/src/components/Edit/ViewableDataInputForm/OptionsFieldInput.ts +161 -0
  78. package/src/components/Study/SessionConfiguration.vue +371 -0
  79. package/src/components/TextSwap.vue +65 -0
  80. package/src/components/User/UserStats.vue +30 -0
  81. package/src/dev/DataInputFormTester.vue +117 -0
  82. package/src/dev/readme.md +3 -0
  83. package/src/enums.ts +0 -0
  84. package/src/glyphs.txt +933 -0
  85. package/src/main.ts +45 -0
  86. package/src/plugins/vuetify.ts +41 -0
  87. package/src/registerServiceWorker.ts +18 -0
  88. package/src/router.ts +184 -0
  89. package/src/server/index.spec.ts +192 -0
  90. package/src/server/index.ts +71 -0
  91. package/src/shims-vue.d.ts +5 -0
  92. package/src/store.mock.ts +122 -0
  93. package/src/stores/useDataInputFormStore.ts +49 -0
  94. package/src/stores/useFieldInputStore.ts +191 -0
  95. package/src/types/shims-vuetify.d.ts +12 -0
  96. package/src/types/svg.d.ts +4 -0
  97. package/src/utils/bulkImport/index.ts +94 -0
  98. package/src/views/About.vue +29 -0
  99. package/src/views/Admin.vue +128 -0
  100. package/src/views/Classrooms.vue +258 -0
  101. package/src/views/Courses.vue +265 -0
  102. package/src/views/Home.vue +154 -0
  103. package/src/views/Login.vue +75 -0
  104. package/src/views/ReleaseNotes.vue +20 -0
  105. package/src/views/SignUp.vue +32 -0
  106. package/src/views/Study.vue +261 -0
  107. package/src/views/User.vue +109 -0
  108. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,49 @@
1
+ import { defineStore } from 'pinia';
2
+ import { DataShape } from '@/base-course/Interfaces/DataShape';
3
+ import { CourseConfig } from '@/server/types';
4
+ import { FieldInputInstance } from '@/components/Edit/ViewableDataInputForm/FieldInput.types';
5
+ import { useFieldInputStore } from './useFieldInputStore';
6
+ import { ViewComponent } from '@/base-course/Displayable';
7
+
8
+ interface DataInputForm {
9
+ // current props
10
+ dataShape: DataShape | null;
11
+ course: CourseConfig | null;
12
+
13
+ shapeViews: ViewComponent[];
14
+ fields: FieldInputInstance[];
15
+
16
+ fieldStore: ReturnType<typeof useFieldInputStore>;
17
+
18
+ uploading: boolean;
19
+ }
20
+
21
+ export interface DataInputFormState {
22
+ dataInputForm: DataInputForm;
23
+ }
24
+
25
+ export const useDataInputFormStore = defineStore('dataInputForm', {
26
+ state: (): DataInputFormState => ({
27
+ dataInputForm: {
28
+ dataShape: null,
29
+ course: null,
30
+ shapeViews: [],
31
+
32
+ fields: [],
33
+ fieldStore: useFieldInputStore(),
34
+ uploading: false,
35
+ },
36
+ }),
37
+ // actions or getters if needed
38
+ actions: {
39
+ setDataShape(ds: DataShape) {
40
+ this.dataInputForm.dataShape = ds;
41
+ this.dataInputForm.fieldStore.dataShape = ds;
42
+ this.dataInputForm.fieldStore.$reset();
43
+ },
44
+ setCourse(course: CourseConfig) {
45
+ this.dataInputForm.course = course;
46
+ },
47
+ // etc. create any convenience setters or methods you wish
48
+ },
49
+ });
@@ -0,0 +1,191 @@
1
+ import { DataShape, FieldType, Status, fieldConverters } from '@vue-skuilder/common';
2
+ import { defineStore } from 'pinia';
3
+
4
+ export interface MediaInputs {
5
+ [key: `audio-${number}`]: unknown;
6
+ [key: `image-${number}`]: unknown;
7
+ }
8
+
9
+ export interface FieldInputStore {
10
+ // [key: string]: unknown;
11
+
12
+ // // Raw field values by field name
13
+ //
14
+ //
15
+
16
+ /**
17
+ * The
18
+ */
19
+ dataShape: DataShape | null;
20
+
21
+ /**
22
+ * Raw input field values by field name - the user-edited submissions
23
+ */
24
+ inputs: Record<string, unknown>;
25
+
26
+ /**
27
+ * additions from the MediaDragDrop editor.
28
+ */
29
+ mediaInputs: MediaInputs;
30
+
31
+ inputIsValid: boolean;
32
+
33
+ /**
34
+ * Validation status of each field - all must be true for the form to be valid and the data to be submissable
35
+ */
36
+ validation: Record<string, boolean>;
37
+ /**
38
+ * Input prepared for sending to the database
39
+ */
40
+ convertedInput: Record<string, unknown>;
41
+ /**
42
+ * Input prepared for local rendering in a preview
43
+ */
44
+ previewInput: Record<string, unknown>;
45
+
46
+ // getFieldValue(fieldName: string): unknown;
47
+ }
48
+
49
+ export const useFieldInputStore = defineStore('fieldStore', {
50
+ state: (): FieldInputStore => ({
51
+ dataShape: null as DataShape | null,
52
+ inputs: {},
53
+ mediaInputs: {} as MediaInputs,
54
+ inputIsValid: false,
55
+
56
+ validation: {},
57
+ convertedInput: {},
58
+ previewInput: {},
59
+ }),
60
+
61
+ getters: {
62
+ isValidated(): boolean {
63
+ return this.inputIsValid;
64
+ },
65
+
66
+ getPreview: (state) => {
67
+ if (state.inputIsValid) return state.previewInput;
68
+ else return {};
69
+ },
70
+ getConverted: (state) => {
71
+ if (state.inputIsValid) return state.convertedInput;
72
+ else return {};
73
+ },
74
+ },
75
+
76
+ actions: {
77
+ $reset() {
78
+ console.log(`[FieldInputStore].reset()`);
79
+ this.inputs = {};
80
+ this.mediaInputs = {};
81
+ this.validation = {};
82
+ this.convertedInput = {};
83
+ this.previewInput = {};
84
+ this.inputIsValid = false;
85
+
86
+ // Clear all media entries
87
+ const MAX_MEDIA_INPUTS = 10;
88
+ for (let i = 1; i <= MAX_MEDIA_INPUTS; i++) {
89
+ const audioKey = `audio-${i}` as keyof MediaInputs;
90
+ const imageKey = `image-${i}` as keyof MediaInputs;
91
+
92
+ delete this.mediaInputs[audioKey];
93
+ delete this.mediaInputs[imageKey];
94
+ }
95
+ },
96
+
97
+ setFieldValue(fieldName: string, value: unknown) {
98
+ this.inputs[fieldName] = value;
99
+ this.validate();
100
+ this.convert();
101
+ },
102
+
103
+ setMedia(
104
+ fieldName: keyof MediaInputs,
105
+ media: {
106
+ content_type: string;
107
+ data: Blob;
108
+ }
109
+ ) {
110
+ console.log(`[FieldInputStore].setMedia: ${fieldName}`);
111
+ this.mediaInputs[fieldName] = media;
112
+ this.convert();
113
+ },
114
+
115
+ validate() {
116
+ this.validation = {};
117
+
118
+ if (this.dataShape === null) {
119
+ this.inputIsValid = false;
120
+ return;
121
+ }
122
+
123
+ this.inputIsValid = true;
124
+
125
+ for (const field of this.dataShape.fields) {
126
+ if (field.validator) {
127
+ const result = field.validator.test(this.inputs[field.name] as unknown as string);
128
+ if (result.status === Status.ok) {
129
+ this.validation[field.name] = true;
130
+ } else {
131
+ this.inputIsValid = false;
132
+ this.validation[field.name] = false;
133
+ }
134
+ }
135
+ }
136
+ },
137
+
138
+ convert() {
139
+ this.convertedInput = {};
140
+
141
+ this.dataShape?.fields.forEach((f) => {
142
+ this.convertedInput[f.name] = fieldConverters[f.type].databaseConverter(
143
+ this.inputs[f.name]
144
+ );
145
+ this.previewInput[f.name] = fieldConverters[f.type].previewConverter(this.inputs[f.name]);
146
+ });
147
+
148
+ // Check for media entries
149
+ for (let i = 1; i < 11; i++) {
150
+ const index = `audio-${i}` as keyof MediaInputs;
151
+ const value = this.mediaInputs[index];
152
+ if (value) {
153
+ this.convertedInput[index] = fieldConverters[FieldType.AUDIO].databaseConverter(value);
154
+ this.previewInput[index] = fieldConverters[FieldType.AUDIO].previewConverter(value);
155
+ } else {
156
+ break;
157
+ }
158
+ }
159
+
160
+ for (let i = 1; i < 11; i++) {
161
+ const index = `image-${i}` as keyof MediaInputs;
162
+ const value = this.mediaInputs[index];
163
+ if (value) {
164
+ this.convertedInput[index] = fieldConverters[FieldType.IMAGE].databaseConverter(value);
165
+ this.previewInput[index] = fieldConverters[FieldType.IMAGE].previewConverter(value);
166
+ } else {
167
+ break;
168
+ }
169
+ }
170
+ },
171
+
172
+ clearMediaEntries() {
173
+ // Clear all media entries up to a reasonable limit
174
+ for (let i = 1; i <= 10; i++) {
175
+ delete this.mediaInputs[`audio-${i}`];
176
+ delete this.mediaInputs[`image-${i}`];
177
+ delete this.convertedInput[`audio-${i}`];
178
+ delete this.convertedInput[`image-${i}`];
179
+ delete this.previewInput[`audio-${i}`];
180
+ delete this.previewInput[`image-${i}`];
181
+ }
182
+ },
183
+
184
+ clearField(fieldName: string) {
185
+ delete this.inputs[fieldName];
186
+ delete this.validation[fieldName];
187
+ delete this.convertedInput[fieldName];
188
+ delete this.previewInput[fieldName];
189
+ },
190
+ },
191
+ });
@@ -0,0 +1,12 @@
1
+ declare module 'vuetify/lib' {
2
+ import Vuetify from 'vuetify';
3
+ export default Vuetify;
4
+ }
5
+
6
+ declare module 'vuetify/types' {
7
+ import Vue from 'vue';
8
+ interface Vue {
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ $vuetify: any;
11
+ }
12
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.svg?raw' {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -0,0 +1,94 @@
1
+ // Re-export parsing utilities from common
2
+ export {
3
+ ParsedCard,
4
+ CardData,
5
+ parseCard,
6
+ parseBulkTextToCards,
7
+ isValidBulkFormat,
8
+ splitCardsText,
9
+ CardParserConfig,
10
+ CARD_DELIMITER
11
+ } from '@vue-skuilder/common';
12
+
13
+ // Re-export database utilities from db
14
+ export {
15
+ ImportResult,
16
+ BulkCardProcessorConfig,
17
+ importParsedCards,
18
+ validateProcessorConfig
19
+ } from '@vue-skuilder/db';
20
+
21
+ // Example usage for documentation purposes:
22
+ /*
23
+ import { CourseDBInterface } from '@vue-skuilder/db';
24
+ import { DataShape } from '@vue-skuilder/common';
25
+ import {
26
+ importParsedCards, // Renamed from processBulkCards
27
+ validateProcessorConfig,
28
+ parseCard, // Still useful for single card parsing
29
+ splitCardsText, // Still useful for understanding delimiters or manual splitting
30
+ isValidBulkFormat,
31
+ parseBulkTextToCards // New function for parsing bulk text to ParsedCard[]
32
+ } from './index'; // Assuming this index.ts correctly re-exports them
33
+
34
+ async function exampleUsage(courseDB: CourseDBInterface, dataShape: DataShape) {
35
+ // Example bulk text
36
+ const bulkText = `Card 1 Question
37
+ {{blank}}
38
+ tags: tagA, tagB
39
+ elo: 1500
40
+ ---
41
+ ---
42
+ Card 2 Question
43
+ Another {{blank}}
44
+ elo: 1200
45
+ tags: tagC`;
46
+
47
+ // Validate config for processing
48
+ const config = {
49
+ dataShape,
50
+ courseCode: 'COURSE-123',
51
+ userName: 'user123'
52
+ };
53
+
54
+ const validation = validateProcessorConfig(config);
55
+ if (!validation.isValid) {
56
+ console.error(validation.errorMessage);
57
+ return;
58
+ }
59
+
60
+ // Step 1: Parse the bulk text into an array of ParsedCard objects
61
+ // isValidBulkFormat can be used as a preliminary check if needed
62
+ if (!isValidBulkFormat(bulkText)) {
63
+ console.error("Invalid bulk format");
64
+ return;
65
+ }
66
+ const parsedCardsArray = parseBulkTextToCards(bulkText);
67
+
68
+ if (parsedCardsArray.length === 0 && bulkText.trim().length > 0) {
69
+ console.error("No cards could be parsed from the input.");
70
+ // Potentially show an error or return if no cards were parsed,
71
+ // even if the format had delimiters, maybe all cards were empty.
72
+ return;
73
+ }
74
+
75
+ // Step 2: Process the array of parsed cards
76
+ const results = await importParsedCards(parsedCardsArray, courseDB, config);
77
+
78
+ // Check results
79
+ console.log(`Attempted to import ${parsedCardsArray.length} cards.`);
80
+ console.log(`Successfully imported: ${results.filter(r => r.status === 'success').length}`);
81
+ console.log(`Failed: ${results.filter(r => r.status === 'error').length}`);
82
+
83
+ // Example of parsing a single card (remains the same)
84
+ const singleCard = `Example card with a {{blank}}
85
+ elo: 1600
86
+ tags: tag1, tag2`;
87
+ const parsedSingle = parseCard(singleCard); // Renamed 'parsed' to 'parsedSingle' for clarity
88
+ console.log('Parsed single card:', {
89
+ markdown: parsedSingle?.markdown,
90
+ tags: parsedSingle?.tags,
91
+ elo: parsedSingle?.elo
92
+ });
93
+ }
94
+ */
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <v-container class="about">
3
+ <v-row>
4
+ <v-col>
5
+ <h1 class="text-h2 mb-6">Todo.</h1>
6
+
7
+ <p class="text-body-1 mb-4">
8
+ This is a placeholder for the about page. It will be filled in with more information later.
9
+ </p>
10
+
11
+ <v-btn :to="'./notes'" variant="text" color="primary"> Release Notes </v-btn>
12
+ </v-col>
13
+ </v-row>
14
+ </v-container>
15
+ </template>
16
+
17
+ <script lang="ts">
18
+ import { defineComponent } from 'vue';
19
+
20
+ export default defineComponent({
21
+ name: 'AboutView',
22
+ components: {},
23
+ data() {
24
+ return {
25
+ type: 'Viewable',
26
+ };
27
+ },
28
+ });
29
+ </script>
@@ -0,0 +1,128 @@
1
+ <template>
2
+ <v-container>
3
+ <v-row>
4
+ <v-col>
5
+ <h1 class="text-h3 mb-6">{{ title }}</h1>
6
+
7
+ <!-- Users Section -->
8
+ <v-card class="mb-6">
9
+ <v-card-title>
10
+ <h3>Users - {{ registeredUsers.length }}</h3>
11
+ </v-card-title>
12
+ <v-card-text>
13
+ <v-list>
14
+ <v-list-item v-for="u in registeredUsers" :key="u._id">
15
+ <v-list-item-title>User: {{ u.name }}</v-list-item-title>
16
+ </v-list-item>
17
+ </v-list>
18
+ </v-card-text>
19
+ </v-card>
20
+
21
+ <!-- Quilts Section -->
22
+ <v-card class="mb-6">
23
+ <v-card-title>
24
+ <h3>
25
+ <router-link to="/courses" class="text-decoration-none">Quilts</router-link>
26
+ - {{ courses.length }}
27
+ </h3>
28
+ </v-card-title>
29
+ <v-card-text>
30
+ <v-list>
31
+ <v-list-item v-for="c in courses" :key="c._id">
32
+ <v-list-item-title>
33
+ <router-link :to="`/q/${c._id}`" class="text-decoration-none">{{ c.name }}</router-link>
34
+ - {{ c._id }}
35
+ <v-btn density="compact" icon="mdi-close" variant="text" size="small" @click="removeCourse(c._id)" />
36
+ </v-list-item-title>
37
+ </v-list-item>
38
+ </v-list>
39
+ </v-card-text>
40
+ </v-card>
41
+
42
+ <!-- Classrooms Section -->
43
+ <v-card>
44
+ <v-card-title>
45
+ <h3>Classrooms - {{ classrooms.length }}</h3>
46
+ </v-card-title>
47
+ <v-card-text>
48
+ <v-list>
49
+ <v-list-item v-for="c in classrooms" :key="c._id">
50
+ <v-list-item-title>
51
+ <router-link :to="`/c/${c._id}`" class="text-decoration-none">{{ c.name }}</router-link>
52
+ {{ c.name }} - {{ c.teachers }} - {{ c.students.length }} students
53
+ </v-list-item-title>
54
+ </v-list-item>
55
+ </v-list>
56
+ </v-card-text>
57
+ </v-card>
58
+ </v-col>
59
+ </v-row>
60
+ </v-container>
61
+ </template>
62
+
63
+ <script lang="ts">
64
+ import { defineComponent } from 'vue';
65
+ import { AdminDBInterface, getDataLayer } from '@vue-skuilder/db';
66
+ import { GuestUsername } from '@vue-skuilder/db';
67
+ interface User {
68
+ _id: string;
69
+ name: string;
70
+ }
71
+
72
+ interface Course {
73
+ _id: string;
74
+ name: string;
75
+ }
76
+
77
+ interface Classroom {
78
+ _id: string;
79
+ name: string;
80
+ teachers: string[];
81
+ students: string[];
82
+ }
83
+
84
+ export default defineComponent({
85
+ name: 'AdminView',
86
+
87
+ data() {
88
+ return {
89
+ title: 'Admin Panel',
90
+ db: null as AdminDBInterface | null,
91
+ users: [] as User[],
92
+ courses: [] as Course[],
93
+ classrooms: [] as Classroom[],
94
+ };
95
+ },
96
+
97
+ computed: {
98
+ registeredUsers(): User[] {
99
+ return this.users.filter((u) => {
100
+ return !(u.name as string).startsWith(GuestUsername);
101
+ });
102
+ },
103
+ },
104
+
105
+ async created() {
106
+ try {
107
+ this.db = await getDataLayer().getAdminDB();
108
+ this.users = (await this.db.getUsers()) as unknown as User[];
109
+ this.courses = (await this.db.getCourses()) as unknown as Course[];
110
+ this.classrooms = await this.db.getClassrooms();
111
+ } catch (e) {
112
+ this.title = 'This page is for database admins only.';
113
+ throw `${JSON.stringify(e)} - ${e}\n\nNot an admin!`;
114
+ }
115
+ },
116
+
117
+ methods: {
118
+ removeCourse(id: string): void {
119
+ console.log(`Removing ${id}`);
120
+ if (this.db) {
121
+ this.db.removeCourse(id);
122
+ }
123
+ },
124
+ },
125
+ });
126
+ </script>
127
+
128
+ <style scoped></style>