@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,206 @@
1
+ <template>
2
+ <div v-if="!updatePending">
3
+ <h1><router-link to="/classrooms">My Classrooms</router-link> / {{ classroomCfg!.name }}</h1>
4
+
5
+ <h3>
6
+ Join code: {{ classroomCfg!.joinCode }}
7
+ <router-link :to="`/classrooms/${classroomId}/code`">
8
+ <v-btn size="x-small" icon="mdi-fullscreen" color="accent" alt="Make Fullscreen"> </v-btn>
9
+ </router-link>
10
+ </h3>
11
+ <v-row>
12
+ <v-col cols="12" sm="6" md="4">
13
+ <v-checkbox v-model="classroomCfg!.peerAssist" label="Allow peer instruction"></v-checkbox>
14
+ </v-col>
15
+ <v-col v-if="classroomDB" cols="12">
16
+ <h2>Assigned Content:</h2>
17
+ <h3>Quilts:</h3>
18
+ <ul></ul>
19
+ <ul>
20
+ <li v-for="c in _assignedCourses" :key="c.courseID">
21
+ {{ c.courseID }} <a @click="removeContent(c)">Remove</a>
22
+ </li>
23
+ </ul>
24
+ <h3>Tags:</h3>
25
+ <ul>
26
+ <li v-for="(c, i) in _assignedTags" :key="i">
27
+ {{ c.courseID }} - {{ c.tagID }} <a @click="removeContent(c)">Remove</a>
28
+ </li>
29
+ </ul>
30
+ <v-fade-transition>
31
+ <v-btn v-if="!addingContent" color="primary" @click="addingContent = true">
32
+ Assign New Content
33
+ <v-icon end>mdi-plus</v-icon>
34
+ </v-btn>
35
+ </v-fade-transition>
36
+ <v-card v-if="addingContent">
37
+ <v-toolbar>
38
+ <v-toolbar-title>Add Content</v-toolbar-title>
39
+ <v-spacer></v-spacer>
40
+ <v-btn icon="mdi-close" color="error" @click="addingContent = false"> </v-btn>
41
+ </v-toolbar>
42
+ <v-card-text>
43
+ <v-select
44
+ v-model="selectedCourse"
45
+ label="Select Quilt"
46
+ :items="availableCourses"
47
+ item-title="name"
48
+ item-value="_id"
49
+ title="Select Quilt"
50
+ ></v-select>
51
+
52
+ <v-select
53
+ v-model="selectedTags"
54
+ label="Select Tags"
55
+ :items="availableTags"
56
+ item-title="name"
57
+ item-value="name"
58
+ multiple
59
+ chips
60
+ hint=""
61
+ persistent-hint
62
+ ></v-select>
63
+ </v-card-text>
64
+
65
+ <v-card-actions>
66
+ <v-btn v-if="selectedCourse !== ''" color="primary" @click="assignContent">
67
+ {{ selectedTags.length == 0 ? 'Add Entire Quilt' : 'Add Tags' }}
68
+ <v-icon end>mdi-plus</v-icon>
69
+ </v-btn>
70
+ </v-card-actions>
71
+ </v-card>
72
+ </v-col>
73
+ </v-row>
74
+ </div>
75
+ </template>
76
+
77
+ <script lang="ts">
78
+ import moment from 'moment';
79
+ import { TeacherClassroomDBInterface, AssignedContent, AssignedTag, Tag, getDataLayer } from '@vue-skuilder/db';
80
+ import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';
81
+ import { defineComponent } from 'vue';
82
+ import { getCurrentUser } from '@vue-skuilder/common-ui';
83
+
84
+ export default defineComponent({
85
+ name: 'ClassroomCtrlPanel',
86
+
87
+ props: {
88
+ classroomId: {
89
+ type: String,
90
+ required: true,
91
+ },
92
+ },
93
+
94
+ data() {
95
+ return {
96
+ classroomCfg: null as ClassroomConfig | null,
97
+ classroomDB: null as TeacherClassroomDBInterface | null,
98
+ assignedContent: [] as AssignedContent[],
99
+ nameRules: [
100
+ (value: string): string | boolean => {
101
+ const max = 30;
102
+ return value.length > max ? `Course name must be ${max} characters or less` : true;
103
+ },
104
+ ],
105
+ updatePending: true,
106
+ addingContent: false,
107
+ availableCourses: [] as CourseConfig[],
108
+ selectedCourse: '',
109
+ availableTags: [] as Tag[],
110
+ selectedTags: [] as string[],
111
+ };
112
+ },
113
+
114
+ computed: {
115
+ _assignedCourses(): AssignedContent[] {
116
+ return this.assignedContent.filter((c) => c.type === 'course');
117
+ },
118
+ _assignedTags(): AssignedTag[] {
119
+ return this.assignedContent.filter((c) => c.type === 'tag') as AssignedTag[];
120
+ },
121
+ },
122
+
123
+ watch: {
124
+ async selectedCourse() {
125
+ if (this.selectedCourse) {
126
+ // Get course DB from data layer
127
+ const courseDB = getDataLayer().getCourseDB(this.selectedCourse);
128
+ // Get tags from course DB
129
+ const tagResponse = await courseDB.getCourseTagStubs();
130
+ this.availableTags = tagResponse.rows.map((row) => row.doc!);
131
+ } else {
132
+ this.availableTags = [];
133
+ }
134
+ },
135
+ },
136
+
137
+ async created() {
138
+ try {
139
+ // Get classroom DB from data layer
140
+ this.classroomDB = (await getDataLayer().getClassroomDB(
141
+ this.classroomId,
142
+ 'teacher'
143
+ )) as TeacherClassroomDBInterface;
144
+ this.assignedContent = await this.classroomDB.getAssignedContent();
145
+ this.classroomCfg = this.classroomDB.getConfig();
146
+
147
+ console.log(`[ClassroomCtrlPanel] Route loaded w/ (prop) _id: ${this.classroomId}`);
148
+ console.log(`[ClassroomCtrlPanel] Config: ${JSON.stringify(this.classroomCfg)}`);
149
+
150
+ // Get course list from data layer
151
+ this.availableCourses = await getDataLayer().getCoursesDB().getCourseList();
152
+
153
+ this.updatePending = false;
154
+ } catch (error) {
155
+ console.error('[ClassroomCtrlPanel] Error initializing:', error);
156
+ }
157
+ },
158
+
159
+ methods: {
160
+ async assignContent() {
161
+ if (!this.classroomDB) return;
162
+ const u = await getCurrentUser();
163
+
164
+ if (this.selectedTags.length === 0) {
165
+ await this.classroomDB.assignContent?.({
166
+ assignedOn: moment(),
167
+ activeOn: moment(),
168
+ type: 'course',
169
+ courseID: this.selectedCourse,
170
+ assignedBy: u.getUsername(),
171
+ });
172
+ } else {
173
+ await Promise.all(
174
+ this.selectedTags.map((tag) =>
175
+ this.classroomDB!.assignContent?.({
176
+ assignedOn: moment(),
177
+ activeOn: moment(),
178
+ type: 'tag',
179
+ courseID: this.selectedCourse,
180
+ tagID: tag,
181
+ assignedBy: u.getUsername(),
182
+ })
183
+ )
184
+ );
185
+ }
186
+
187
+ this.assignedContent = await this.classroomDB.getAssignedContent();
188
+ this.addingContent = false;
189
+ this.selectedCourse = '';
190
+ this.selectedTags = [];
191
+ this.availableTags = [];
192
+ },
193
+
194
+ async removeContent(c: AssignedContent) {
195
+ if (this.classroomDB && this.classroomDB.removeContent) {
196
+ await this.classroomDB.removeContent(c);
197
+ this.assignedContent = await this.classroomDB.getAssignedContent();
198
+ }
199
+ },
200
+
201
+ async submit() {
202
+ this.updatePending = true;
203
+ },
204
+ },
205
+ });
206
+ </script>
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <v-card>
3
+ <v-toolbar color="primary">
4
+ <v-card-title class="text-h6 font-weight-regular">Start a New Class</v-card-title>
5
+ <v-spacer></v-spacer>
6
+ <v-btn icon @click="clearFormAndDismiss">
7
+ <v-icon>mdi-close</v-icon>
8
+ </v-btn>
9
+ </v-toolbar>
10
+ <v-form>
11
+ <v-container>
12
+ <v-row>
13
+ <v-col cols="12" sm="6" md="4">
14
+ <v-text-field
15
+ v-model="name"
16
+ counter="30"
17
+ :rules="nameRules"
18
+ label="Class Name"
19
+ required
20
+ hint="Eg: Smith, Chemistry, Period 3"
21
+ ></v-text-field>
22
+ </v-col>
23
+ <v-col cols="12" sm="6" md="4">
24
+ <v-checkbox v-model="peerAssist" label="Allow peer instruction"></v-checkbox>
25
+ </v-col>
26
+ <v-col cols="12" sm="6" md="4">
27
+ <v-select
28
+ v-model="birthYear"
29
+ :items="birthYears"
30
+ label="Approximate Birth Year of Students"
31
+ item-title="text"
32
+ item-value="value"
33
+ ></v-select>
34
+ </v-col>
35
+ <v-col cols="12" sm="6" md="4">
36
+ <v-btn :loading="updatePending" color="primary" @click="submit">Create This Class</v-btn>
37
+ </v-col>
38
+ </v-row>
39
+ </v-container>
40
+ </v-form>
41
+ </v-card>
42
+ </template>
43
+
44
+ <script lang="ts">
45
+ import moment from 'moment';
46
+ import Mousetrap from 'mousetrap';
47
+ import { log } from '@vue-skuilder/common';
48
+ import { Status, ClassroomConfig, CreateClassroom, ServerRequestType } from '@vue-skuilder/common';
49
+ import serverRequest from '../../server';
50
+ import { alertUser } from '@vue-skuilder/common-ui';
51
+ import { defineComponent } from 'vue';
52
+ import { getCurrentUser } from '@vue-skuilder/common-ui';
53
+
54
+ export default defineComponent({
55
+ emits: ['ClassroomEditingComplete'],
56
+
57
+ data() {
58
+ return {
59
+ mousetrap: new Mousetrap(this.$el),
60
+ peerAssist: true,
61
+ name: '',
62
+ birthYear: undefined as number | undefined,
63
+ id: '',
64
+ nameRules: [
65
+ (value: string): string | boolean => {
66
+ const max = 30;
67
+ if (value.length > max) {
68
+ return `Course name must be ${max} characters or less`;
69
+ } else {
70
+ return true;
71
+ }
72
+ },
73
+ ],
74
+ description: '',
75
+ banner: undefined as Blob | undefined,
76
+ thumb: undefined as Blob | undefined,
77
+ updatePending: false,
78
+ birthYears: [] as Array<{
79
+ text: string;
80
+ value: number;
81
+ }>,
82
+ };
83
+ },
84
+
85
+ created() {
86
+ this.mousetrap.bind('esc', this.clearFormAndDismiss);
87
+
88
+ const year: number = moment().year();
89
+
90
+ this.birthYears.push({
91
+ text: `< ${year - 17} (Adult Students)`,
92
+ value: 0,
93
+ });
94
+
95
+ for (let age = 17; age >= 6; age--) {
96
+ this.birthYears.push({
97
+ text: `${year - age} (Grade ${age - 5})`,
98
+ value: year - age,
99
+ });
100
+ }
101
+
102
+ this.birthYears.push({
103
+ text: `>${year - 6} (K or younger)`,
104
+ value: year - 5,
105
+ });
106
+ },
107
+
108
+ methods: {
109
+ async submit() {
110
+ this.updatePending = true;
111
+ const u = await getCurrentUser();
112
+
113
+ const config: ClassroomConfig = {
114
+ name: this.name,
115
+ teachers: [u.getUsername()],
116
+ students: [],
117
+ birthYear: this.birthYear,
118
+ classMeetingSchedule: '',
119
+ peerAssist: this.peerAssist,
120
+ joinCode: '',
121
+ };
122
+
123
+ log(`Class Config:
124
+ ${JSON.stringify(config)}`);
125
+
126
+ const result = await serverRequest<CreateClassroom>({
127
+ data: config,
128
+ type: ServerRequestType.CREATE_CLASSROOM,
129
+ response: null,
130
+ user: u.getUsername(),
131
+ });
132
+
133
+ if (result.response && result.response.ok) {
134
+ alertUser({
135
+ text: `Class created successfully. Join code: ${result.response.joincode}`,
136
+ status: Status.ok,
137
+ });
138
+
139
+ u.registerForClassroom(result.response.uuid, 'teacher');
140
+ } else {
141
+ alertUser({
142
+ text: `Failed to create class. Please try again.`,
143
+ status: Status.error,
144
+ });
145
+ }
146
+
147
+ this.clearFormAndDismiss();
148
+ this.updatePending = false;
149
+ },
150
+
151
+ clearFormAndDismiss() {
152
+ this.name = '';
153
+ this.description = '';
154
+
155
+ this.$emit('ClassroomEditingComplete');
156
+ },
157
+ },
158
+ });
159
+ </script>
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <v-container fluid class="fill-height pa-0">
3
+ <!-- Close button in top-right corner -->
4
+ <v-btn icon="mdi-close" color="red" variant="elevated" class="close-btn" @click="close"> </v-btn>
5
+
6
+ <!-- Main content -->
7
+ <v-row align="center" justify="center" class="fill-height">
8
+ <v-col cols="12" class="text-center">
9
+ <div v-if="!updatePending" class="join-code">
10
+ {{ classroomCfg?.joinCode }}
11
+ </div>
12
+ <v-progress-circular v-else indeterminate size="64"></v-progress-circular>
13
+ </v-col>
14
+ </v-row>
15
+ </v-container>
16
+ </template>
17
+
18
+ <script lang="ts">
19
+ import { log } from '@vue-skuilder/common';
20
+ import { TeacherClassroomDBInterface, getDataLayer } from '@vue-skuilder/db';
21
+ import { ClassroomConfig } from '@vue-skuilder/common';
22
+ import { defineComponent } from 'vue';
23
+
24
+ export default defineComponent({
25
+ name: 'JoinCode',
26
+
27
+ props: {
28
+ classroomId: {
29
+ type: String,
30
+ required: true,
31
+ },
32
+ },
33
+
34
+ data() {
35
+ return {
36
+ classroomCfg: null as ClassroomConfig | null,
37
+ classroomDB: null as TeacherClassroomDBInterface | null,
38
+ updatePending: true,
39
+ };
40
+ },
41
+
42
+ async created() {
43
+ const dl = getDataLayer();
44
+
45
+ this.classroomDB = await dl.getClassroomDB(this.classroomId, 'teacher');
46
+ this.classroomCfg = this.classroomDB.getConfig();
47
+ await Promise.all([]);
48
+ log(`Route loaded w/ (prop) _id: ${this.classroomId}`);
49
+ log(`Config:
50
+ ${JSON.stringify(this.classroomCfg)}`);
51
+ this.updatePending = false;
52
+ },
53
+
54
+ methods: {
55
+ close() {
56
+ this.$router.back();
57
+ },
58
+ },
59
+ });
60
+ </script>
61
+
62
+ <style>
63
+ .close-btn {
64
+ position: fixed;
65
+ top: 80px;
66
+ right: 16px;
67
+ z-index: 100;
68
+ }
69
+
70
+ .join-code {
71
+ font-size: 8rem;
72
+ font-weight: bold;
73
+ line-height: 1.2;
74
+ letter-spacing: 0.1em;
75
+ }
76
+
77
+ /* Responsive adjustments */
78
+ @media (max-width: 600px) {
79
+ .join-code {
80
+ font-size: 4rem;
81
+ }
82
+ }
83
+ </style>