@vue-skuilder/common-ui 0.1.4 → 0.1.6

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 (65) hide show
  1. package/dist/assets/index.css +2 -2
  2. package/dist/common-ui.es.js +1471 -295
  3. package/dist/common-ui.es.js.map +1 -1
  4. package/dist/common-ui.umd.js +2 -2
  5. package/dist/common-ui.umd.js.map +1 -1
  6. package/dist/components/HeatMap.types.d.ts +1 -0
  7. package/dist/components/HeatMap.types.d.ts.map +1 -0
  8. package/dist/components/PaginatingToolbar.types.d.ts +1 -0
  9. package/dist/components/PaginatingToolbar.types.d.ts.map +1 -0
  10. package/dist/components/SkMouseTrap.types.d.ts +1 -0
  11. package/dist/components/SkMouseTrap.types.d.ts.map +1 -0
  12. package/dist/components/SkMouseTrapToolTip.types.d.ts +1 -0
  13. package/dist/components/SkMouseTrapToolTip.types.d.ts.map +1 -0
  14. package/dist/components/SnackbarService.d.ts +1 -0
  15. package/dist/components/SnackbarService.d.ts.map +1 -0
  16. package/dist/components/StudySession.types.d.ts +1 -0
  17. package/dist/components/StudySession.types.d.ts.map +1 -0
  18. package/dist/components/auth/index.d.ts +1 -0
  19. package/dist/components/auth/index.d.ts.map +1 -0
  20. package/dist/components/cardRendering/MarkdownRendererHelpers.d.ts +1 -0
  21. package/dist/components/cardRendering/MarkdownRendererHelpers.d.ts.map +1 -0
  22. package/dist/components/studentInputs/BaseUserInput.d.ts +1 -0
  23. package/dist/components/studentInputs/BaseUserInput.d.ts.map +1 -0
  24. package/dist/components/studentInputs/RadioMultipleChoice.types.d.ts +1 -0
  25. package/dist/components/studentInputs/RadioMultipleChoice.types.d.ts.map +1 -0
  26. package/dist/composables/CompositionViewable.d.ts +1 -0
  27. package/dist/composables/CompositionViewable.d.ts.map +1 -0
  28. package/dist/composables/Displayable.d.ts +1 -0
  29. package/dist/composables/Displayable.d.ts.map +1 -0
  30. package/dist/composables/__tests__/useAuthUI.test.d.ts +2 -0
  31. package/dist/composables/__tests__/useAuthUI.test.d.ts.map +1 -0
  32. package/dist/composables/index.d.ts +2 -0
  33. package/dist/composables/index.d.ts.map +1 -0
  34. package/dist/composables/useAuthUI.d.ts +15 -0
  35. package/dist/composables/useAuthUI.d.ts.map +1 -0
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/plugins/pinia.d.ts +1 -0
  39. package/dist/plugins/pinia.d.ts.map +1 -0
  40. package/dist/stores/useAuthStore.d.ts +21 -0
  41. package/dist/stores/useAuthStore.d.ts.map +1 -0
  42. package/dist/stores/useCardPreviewModeStore.d.ts +1 -0
  43. package/dist/stores/useCardPreviewModeStore.d.ts.map +1 -0
  44. package/dist/stores/useConfigStore.d.ts +1 -0
  45. package/dist/stores/useConfigStore.d.ts.map +1 -0
  46. package/dist/utils/SkldrMouseTrap.d.ts +1 -0
  47. package/dist/utils/SkldrMouseTrap.d.ts.map +1 -0
  48. package/package.json +8 -3
  49. package/src/components/CardBrowser.vue +81 -0
  50. package/src/components/CourseCardBrowser.vue +384 -0
  51. package/src/components/CourseInformation.vue +194 -0
  52. package/src/components/PaginatingToolbar.vue +1 -1
  53. package/src/components/SnackbarService.vue +1 -3
  54. package/src/components/StudySession.vue +52 -23
  55. package/src/components/TagsInput.vue +247 -0
  56. package/src/components/auth/UserChip.vue +146 -58
  57. package/src/components/auth/UserLoginAndRegistrationContainer.vue +17 -2
  58. package/src/components/cardRendering/MarkdownRendererHelpers.ts +2 -2
  59. package/src/components/studentInputs/BaseUserInput.ts +0 -1
  60. package/src/composables/__tests__/useAuthUI.test.ts +103 -0
  61. package/src/composables/index.ts +1 -0
  62. package/src/composables/useAuthUI.ts +67 -0
  63. package/src/index.ts +8 -0
  64. package/src/plugins/pinia.ts +1 -1
  65. package/src/stores/useAuthStore.ts +19 -0
@@ -0,0 +1,384 @@
1
+ <template>
2
+ <v-card>
3
+ <div v-if="updatePending" class="d-flex justify-center align-center pa-6">
4
+ <v-progress-circular indeterminate color="primary" />
5
+ </div>
6
+ <div v-else>
7
+ <paginating-toolbar
8
+ title="Exercises"
9
+ :page="page"
10
+ :pages="pages"
11
+ :subtitle="`(${questionCount})`"
12
+ @first="first"
13
+ @prev="prev"
14
+ @next="next"
15
+ @last="last"
16
+ @set-page="(n) => setPage(n)"
17
+ />
18
+
19
+ <v-list>
20
+ <template v-for="c in cards" :key="c.id">
21
+ <v-list-item
22
+ :class="{
23
+ 'bg-blue-grey-lighten-5': c.isOpen,
24
+ 'elevation-4': c.isOpen,
25
+ }"
26
+ density="compact"
27
+ data-cy="course-card"
28
+ >
29
+ <template #prepend>
30
+ <div>
31
+ <v-list-item-title :class="{ 'text-blue-grey-darken-1': c.isOpen }" class="font-weight-medium">
32
+ {{ cardPreview[c.id] }}
33
+ </v-list-item-title>
34
+ <v-list-item-subtitle>
35
+ {{ c.id.split('-').length === 3 ? c.id.split('-')[2] : '' }}
36
+ </v-list-item-subtitle>
37
+ </div>
38
+ </template>
39
+
40
+ <template #append>
41
+ <v-speed-dial
42
+ v-if="editMode === 'full'"
43
+ v-model="c.isOpen"
44
+ location="left center"
45
+ transition="slide-x-transition"
46
+ style="display: flex; flex-direction: row-reverse"
47
+ persistent
48
+ >
49
+ <template #activator="{ props }">
50
+ <v-btn
51
+ v-bind="props"
52
+ :icon="c.isOpen ? 'mdi-close' : 'mdi-plus'"
53
+ size="small"
54
+ variant="text"
55
+ @click="clearSelections(c.id)"
56
+ />
57
+ </template>
58
+
59
+ <v-btn
60
+ key="tags"
61
+ icon
62
+ size="small"
63
+ :variant="internalEditMode !== 'tags' ? 'outlined' : 'elevated'"
64
+ :color="internalEditMode === 'tags' ? 'teal' : 'teal-darken-3'"
65
+ @click.stop="internalEditMode = 'tags'"
66
+ >
67
+ <v-icon>mdi-bookmark</v-icon>
68
+ </v-btn>
69
+
70
+ <v-btn
71
+ key="flag"
72
+ icon
73
+ size="small"
74
+ :variant="internalEditMode !== 'flag' ? 'outlined' : 'elevated'"
75
+ :color="internalEditMode === 'flag' ? 'error' : 'error-darken-3'"
76
+ @click.stop="internalEditMode = 'flag'"
77
+ >
78
+ <v-icon>mdi-flag</v-icon>
79
+ </v-btn>
80
+ </v-speed-dial>
81
+ </template>
82
+ </v-list-item>
83
+
84
+ <div v-if="c.isOpen" class="px-4 py-2 bg-blue-grey-lighten-5">
85
+ <card-loader :qualified_id="c.id" :view-lookup="viewLookup" class="elevation-1" />
86
+
87
+ <tags-input
88
+ v-show="internalEditMode === 'tags'"
89
+ :course-i-d="courseId"
90
+ :card-i-d="c.id.split('-')[1]"
91
+ class="mt-4"
92
+ />
93
+
94
+ <div v-show="internalEditMode === 'flag'" class="mt-4">
95
+ <v-btn color="error" variant="outlined" @click="c.delBtn = true"> Delete this card </v-btn>
96
+ <span v-if="c.delBtn" class="ml-4">
97
+ <span class="mr-2">Are you sure?</span>
98
+ <v-btn color="error" variant="elevated" @click="deleteCard(c.id)"> Confirm </v-btn>
99
+ </span>
100
+ </div>
101
+ </div>
102
+ </template>
103
+ </v-list>
104
+
105
+ <paginating-toolbar
106
+ class="elevation-0"
107
+ :page="page"
108
+ :pages="pages"
109
+ @first="first"
110
+ @prev="prev"
111
+ @next="next"
112
+ @last="last"
113
+ @set-page="(n) => setPage(n)"
114
+ />
115
+ </div>
116
+ </v-card>
117
+ </template>
118
+
119
+ <script lang="ts">
120
+ import { defineComponent, PropType } from 'vue';
121
+ import { displayableDataToViewData, Status } from '@vue-skuilder/common';
122
+ import { getDataLayer, CourseDBInterface, CardData, DisplayableData, Tag } from '@vue-skuilder/db';
123
+ // local imports
124
+ import TagsInput from './TagsInput.vue';
125
+ import PaginatingToolbar from './PaginatingToolbar.vue';
126
+ import { ViewComponent } from '../composables/Displayable';
127
+ import CardLoader from './cardRendering/CardLoader.vue';
128
+ import { alertUser } from './SnackbarService';
129
+
130
+ function isConstructor(obj: unknown) {
131
+ try {
132
+ // @ts-expect-error - we are specifically probing an unknown object
133
+ new obj();
134
+ return true;
135
+ } catch (e) {
136
+ console.warn(`not a constructor: ${obj}, err: ${e}`);
137
+ return false;
138
+ }
139
+ }
140
+
141
+ export default defineComponent({
142
+ name: 'CourseCardBrowser',
143
+
144
+ components: {
145
+ CardLoader,
146
+ TagsInput,
147
+ PaginatingToolbar,
148
+ },
149
+
150
+ props: {
151
+ courseId: {
152
+ type: String,
153
+ required: true,
154
+ },
155
+ tagId: {
156
+ type: String,
157
+ required: false,
158
+ default: '',
159
+ },
160
+ viewLookupFunction: {
161
+ type: Function,
162
+ required: true,
163
+ default: (x: unknown) => {
164
+ console.warn('No viewLookupFunction provided to CourseCardBrowser');
165
+ return null;
166
+ },
167
+ },
168
+ editMode: {
169
+ type: String as PropType<'none' | 'readonly' | 'full'>,
170
+ required: false,
171
+ default: 'full',
172
+ },
173
+ },
174
+
175
+ data() {
176
+ return {
177
+ courseDB: null as CourseDBInterface | null,
178
+ page: 1,
179
+ pages: [] as number[],
180
+ cards: [] as { id: string; isOpen: boolean; delBtn: boolean }[],
181
+ cardData: {} as { [card: string]: string[] },
182
+ cardPreview: {} as { [card: string]: string },
183
+ internalEditMode: 'none' as 'tags' | 'flag' | 'none',
184
+ delBtn: false,
185
+ updatePending: true,
186
+ userIsRegistered: false,
187
+ questionCount: 0,
188
+ tags: [] as Tag[],
189
+ viewLookup: this.viewLookupFunction,
190
+ };
191
+ },
192
+
193
+ async created() {
194
+ try {
195
+ this.courseDB = getDataLayer().getCourseDB(this.courseId);
196
+
197
+ if (this.tagId) {
198
+ this.questionCount = (await this.courseDB.getTag(this.tagId)).taggedCards.length;
199
+ } else {
200
+ this.questionCount = (await this.courseDB!.getCourseInfo()).cardCount;
201
+ }
202
+
203
+ for (let i = 1; (i - 1) * 25 < this.questionCount; i++) {
204
+ this.pages.push(i);
205
+ }
206
+
207
+ await this.populateTableData();
208
+ } catch (error) {
209
+ console.error('Error initializing CourseCardBrowser:', error);
210
+ } finally {
211
+ this.updatePending = false;
212
+ }
213
+ },
214
+
215
+ methods: {
216
+ first() {
217
+ this.page = 1;
218
+ this.populateTableData();
219
+ },
220
+ prev() {
221
+ this.page--;
222
+ this.populateTableData();
223
+ },
224
+ next() {
225
+ this.page++;
226
+ this.populateTableData();
227
+ },
228
+ last() {
229
+ this.page = this.pages.length;
230
+ this.populateTableData();
231
+ },
232
+ setPage(n: number) {
233
+ this.page = n;
234
+ this.populateTableData();
235
+ },
236
+ clearSelections(exception: string = '') {
237
+ this.cards.forEach((card) => {
238
+ if (card.id !== exception) {
239
+ card.isOpen = false;
240
+ }
241
+ });
242
+ this.internalEditMode = 'none';
243
+ this.delBtn = false;
244
+ },
245
+ async deleteCard(c: string) {
246
+ console.log(`Deleting card ${c}`);
247
+ const res = await this.courseDB!.removeCard(c.split('-')[1]);
248
+ if (res.ok) {
249
+ this.cards = this.cards.filter((card) => card.id != c);
250
+ this.clearSelections();
251
+ } else {
252
+ console.error(`Failed to delete card:\n\n${JSON.stringify(res)}`);
253
+ alertUser({
254
+ text: 'Failed to delete card',
255
+ status: Status.error,
256
+ });
257
+ }
258
+ },
259
+ async populateTableData() {
260
+ this.updatePending = true;
261
+ if (this.tagId) {
262
+ const tag = await this.courseDB!.getTag(this.tagId);
263
+ this.cards = tag.taggedCards.map((c) => {
264
+ return { id: `${this.courseId}-${c}`, isOpen: false, delBtn: false };
265
+ });
266
+ } else {
267
+ this.cards = (await this.courseDB!.getCardsByELO(0, 25)).map((c) => {
268
+ return {
269
+ id: c,
270
+ isOpen: false,
271
+ delBtn: false,
272
+ };
273
+ });
274
+ }
275
+
276
+ const toRemove: string[] = [];
277
+ const hydratedCardData = (
278
+ await this.courseDB!.getCourseDocs<CardData>(
279
+ this.cards.map((c) => c.id.split('-')[1]),
280
+ {
281
+ include_docs: true,
282
+ }
283
+ )
284
+ ).rows
285
+ .filter((r) => {
286
+ if (r.doc) {
287
+ return true;
288
+ } else {
289
+ console.error(`Card ${r.id}.doc not found.\ncard: ${JSON.stringify(r)}`);
290
+ // toRemove.push(r.id);
291
+ // if (this.tagId) {
292
+ // this.courseDB!.removeTagFromCard(r.id, this.tagId);
293
+ // }
294
+ return false;
295
+ }
296
+ })
297
+ .map((r) => r.doc!);
298
+
299
+ this.cards = this.cards.filter((c) => !toRemove.includes(c.id.split('-')[1]));
300
+
301
+ hydratedCardData.forEach((c) => {
302
+ if (c && c.id_displayable_data) {
303
+ this.cardData[c._id] = c.id_displayable_data;
304
+ }
305
+ });
306
+
307
+ try {
308
+ await Promise.all(
309
+ this.cards.map(async (c) => {
310
+ const _cardID: string = c.id.split('-')[1];
311
+
312
+ const tmpCardData = hydratedCardData.find((c) => c._id == _cardID);
313
+ if (!tmpCardData || !tmpCardData.id_displayable_data) {
314
+ console.error(`No valid data found for card ${_cardID}`);
315
+ return;
316
+ }
317
+ const tmpView: ViewComponent = this.viewLookupFunction(
318
+ tmpCardData.id_view || 'default.question.BlanksCard.FillInView'
319
+ );
320
+
321
+ const tmpDataDocs = tmpCardData.id_displayable_data.map((id) => {
322
+ return this.courseDB!.getCourseDoc<DisplayableData>(id, {
323
+ attachments: false,
324
+ binary: true,
325
+ });
326
+ });
327
+
328
+ const allDocs = await Promise.all(tmpDataDocs);
329
+ await Promise.all(
330
+ allDocs.map((doc) => {
331
+ const tmpData = [];
332
+ tmpData.unshift(displayableDataToViewData(doc));
333
+
334
+ // [ ] remove/replace this after the vue 3 migration is complete
335
+ // see PR #510
336
+ if (isConstructor(tmpView)) {
337
+ const view = new tmpView();
338
+ view.data = tmpData;
339
+
340
+ this.cardPreview[c.id] = view.toString();
341
+ } else {
342
+ this.cardPreview[c.id] = tmpView.name ? tmpView.name : 'Unknown';
343
+ }
344
+ })
345
+ );
346
+ })
347
+ );
348
+ } catch (error) {
349
+ console.error('Error populating table data:', error);
350
+ } finally {
351
+ this.updatePending = false;
352
+ this.$forceUpdate();
353
+ }
354
+ },
355
+ },
356
+ });
357
+ </script>
358
+
359
+ <style scoped>
360
+ .component-fade-enter-active,
361
+ .component-fade-leave-active {
362
+ transition: opacity 0.5s ease;
363
+ }
364
+ .component-fade-enter, .component-fade-leave-to
365
+ /* .component-fade-leave-active below version 2.1.8 */ {
366
+ opacity: 0;
367
+ }
368
+
369
+ .component-scale-enter-active,
370
+ .component-scale-leave-active {
371
+ max-height: auto;
372
+ transform: scale(1, 1);
373
+ transform-origin: top;
374
+ transition:
375
+ transform 0.3s ease,
376
+ max-height 0.3s ease;
377
+ }
378
+ .component-scale-enter,
379
+ .component-fade-leave-to {
380
+ max-height: 0px;
381
+ transform: scale(1, 0);
382
+ overflow: hidden;
383
+ }
384
+ </style>
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <div v-if="!updatePending">
3
+ <slot name="header" :course-config="courseConfig" :course-id="courseId">
4
+ <h1 class="text-h4 mb-2">{{ courseConfig.name }}</h1>
5
+ </slot>
6
+
7
+ <p class="text-body-2">
8
+ {{ courseConfig.description }}
9
+ </p>
10
+
11
+ <slot
12
+ name="actions"
13
+ :user-is-registered="userIsRegistered"
14
+ :course-id="courseId"
15
+ :edit-mode="editMode"
16
+ :register="register"
17
+ :drop="drop"
18
+ >
19
+ <!-- Default fallback content if no actions slot provided -->
20
+ <transition name="component-fade" mode="out-in">
21
+ <div v-if="userIsRegistered">
22
+ <v-btn color="success" class="me-2">Start a study session</v-btn>
23
+ <v-btn v-if="editMode === 'full'" data-cy="add-content-btn" color="indigo-lighten-1" class="me-2">
24
+ <v-icon start>mdi-plus</v-icon>
25
+ Add content
26
+ </v-btn>
27
+ <v-btn
28
+ v-if="editMode === 'full'"
29
+ color="green-darken-2"
30
+ title="Rank course content for difficulty"
31
+ class="me-2"
32
+ >
33
+ <v-icon start>mdi-format-list-numbered</v-icon>
34
+ Arrange
35
+ </v-btn>
36
+ <v-btn v-if="editMode === 'full'" color="error" size="small" variant="outlined" @click="drop">
37
+ Drop this course
38
+ </v-btn>
39
+ </div>
40
+ <div v-else>
41
+ <v-btn data-cy="register-btn" color="primary" class="me-2" @click="register">Register</v-btn>
42
+ <v-btn variant="outlined" color="primary" class="me-2">Start a trial study session</v-btn>
43
+ </div>
44
+ </transition>
45
+ </slot>
46
+
47
+ <slot name="additional-content"></slot>
48
+
49
+ <v-card class="my-2">
50
+ <v-toolbar density="compact">
51
+ <v-toolbar-title>Tags</v-toolbar-title>
52
+ <v-toolbar-items>
53
+ <v-btn variant="text">({{ tags.length }})</v-btn>
54
+ </v-toolbar-items>
55
+ </v-toolbar>
56
+ <v-card-text>
57
+ <span v-for="(tag, i) in tags" :key="i">
58
+ <slot name="tag-link" :tag="tag" :course-id="courseId">
59
+ <v-chip variant="tonal" class="me-2 mb-2">
60
+ {{ tag.name }}
61
+ </v-chip>
62
+ </slot>
63
+ </span>
64
+ </v-card-text>
65
+ </v-card>
66
+
67
+ <course-card-browser
68
+ class="my-3"
69
+ :course-id="courseId"
70
+ :view-lookup-function="viewLookupFunction"
71
+ :edit-mode="editMode"
72
+ />
73
+ </div>
74
+ </template>
75
+
76
+ <script lang="ts">
77
+ import { defineComponent, PropType } from 'vue';
78
+ // import { MidiConfig } from '@vue-skuilder/courses'; // Removed to break circular dependency
79
+ import CourseCardBrowser from './CourseCardBrowser.vue';
80
+ import { log } from '@vue-skuilder/common';
81
+ import { CourseDBInterface, Tag, UserDBInterface, getDataLayer } from '@vue-skuilder/db';
82
+ import { CourseConfig } from '@vue-skuilder/common';
83
+ import { getCurrentUser } from '../stores/useAuthStore';
84
+
85
+ export default defineComponent({
86
+ name: 'CourseInformation',
87
+
88
+ components: {
89
+ // MidiConfig, // Removed to break circular dependency
90
+ CourseCardBrowser,
91
+ },
92
+
93
+ props: {
94
+ courseId: {
95
+ type: String as PropType<string>,
96
+ required: true,
97
+ },
98
+ viewLookupFunction: {
99
+ type: Function,
100
+ required: false,
101
+ default: (x: unknown) => {
102
+ console.warn('No viewLookupFunction provided to CourseInformation');
103
+ return null;
104
+ },
105
+ },
106
+ editMode: {
107
+ type: String as PropType<'none' | 'readonly' | 'full'>,
108
+ required: false,
109
+ default: 'full',
110
+ },
111
+ },
112
+
113
+ data() {
114
+ return {
115
+ courseDB: null as CourseDBInterface | null,
116
+ nameRules: [
117
+ (value: string): string | boolean => {
118
+ const max = 30;
119
+ return value.length > max ? `Course name must be ${max} characters or less` : true;
120
+ },
121
+ ],
122
+ updatePending: true,
123
+ courseConfig: {} as CourseConfig,
124
+ userIsRegistered: false,
125
+ tags: [] as Tag[],
126
+ user: null as UserDBInterface | null,
127
+ };
128
+ },
129
+
130
+ computed: {
131
+ // isPianoCourse removed - piano-specific logic should be in wrapper component
132
+ },
133
+
134
+ async created() {
135
+ this.courseDB = getDataLayer().getCourseDB(this.courseId);
136
+ this.user = await getCurrentUser();
137
+
138
+ const userCourses = await this.user.getCourseRegistrationsDoc();
139
+ this.userIsRegistered =
140
+ userCourses.courses.filter((c) => {
141
+ return c.courseID === this.courseId && (c.status === 'active' || c.status === undefined);
142
+ }).length === 1;
143
+
144
+ this.courseConfig = (await this.courseDB!.getCourseConfig())!;
145
+ this.tags = (await this.courseDB!.getCourseTagStubs()).rows.map((r) => r.doc!);
146
+ this.updatePending = false;
147
+ },
148
+
149
+ methods: {
150
+ async register() {
151
+ log(`Registering for ${this.courseId}`);
152
+ const res = await this.user!.registerForCourse(this.courseId);
153
+ if (res.ok) {
154
+ this.userIsRegistered = true;
155
+ }
156
+ },
157
+
158
+ async drop() {
159
+ log(`Dropping course ${this.courseId}`);
160
+ const res = await this.user!.dropCourse(this.courseId);
161
+ if (res.ok) {
162
+ this.userIsRegistered = false;
163
+ }
164
+ },
165
+ },
166
+ });
167
+ </script>
168
+
169
+ <style scoped>
170
+ .component-fade-enter-active,
171
+ .component-fade-leave-active {
172
+ transition: opacity 0.5s ease;
173
+ }
174
+ .component-fade-enter,
175
+ .component-fade-leave-to {
176
+ opacity: 0;
177
+ }
178
+
179
+ .component-scale-enter-active,
180
+ .component-scale-leave-active {
181
+ max-height: auto;
182
+ transform: scale(1, 1);
183
+ transform-origin: top;
184
+ transition:
185
+ transform 0.3s ease,
186
+ max-height 0.3s ease;
187
+ }
188
+ .component-scale-enter,
189
+ .component-fade-leave-to {
190
+ max-height: 0px;
191
+ transform: scale(1, 0);
192
+ overflow: hidden;
193
+ }
194
+ </style>
@@ -2,7 +2,7 @@
2
2
  <v-toolbar density="compact">
3
3
  <v-toolbar-title>
4
4
  <span>{{ title }}</span>
5
- <span v-if="subtitle" class="ms-2 text-subtitle-2">{{ subtitle }}</span>
5
+ <span v-if="subtitle" class="ms-2 text-subtitle-2" data-cy="paginating-toolbar-subtitle">{{ subtitle }}</span>
6
6
  </v-toolbar-title>
7
7
  <v-spacer></v-spacer>
8
8
  <v-btn variant="text" icon color="secondary" :disabled="page == 1" @click="$emit('first')">
@@ -23,7 +23,7 @@ import { defineComponent } from 'vue';
23
23
  import { Status } from '@vue-skuilder/common';
24
24
  import { SnackbarOptions, setInstance } from './SnackbarService';
25
25
 
26
- const SnackbarService = defineComponent({
26
+ export default defineComponent({
27
27
  name: 'SnackbarService',
28
28
 
29
29
  data() {
@@ -66,6 +66,4 @@ const SnackbarService = defineComponent({
66
66
  },
67
67
  },
68
68
  });
69
-
70
- export default SnackbarService;
71
69
  </script>