Skip to content

Commit 0568a6b

Browse files
authored
ux.loadingSession (#691)
- **add wait spinner while fetching studySession items** - **type nudges**
2 parents bd159f8 + 35a73ce commit 0568a6b

File tree

2 files changed

+160
-55
lines changed

2 files changed

+160
-55
lines changed

packages/common-ui/src/components/StudySession.vue

Lines changed: 81 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import {
9393
CourseRegistrationDoc,
9494
DataLayerProvider,
9595
UserDBInterface,
96+
ClassroomDBInterface,
9697
} from '@vue-skuilder/db';
9798
import { SessionController, StudySessionRecord } from '@vue-skuilder/db';
9899
import { newInterval } from '@vue-skuilder/db';
@@ -158,7 +159,15 @@ export default defineComponent({
158159
},
159160
},
160161
161-
emits: ['session-finished', 'session-started', 'card-loaded', 'card-response', 'time-changed'],
162+
emits: [
163+
'session-finished',
164+
'session-started',
165+
'card-loaded',
166+
'card-response',
167+
'time-changed',
168+
'session-prepared',
169+
'session-error',
170+
],
162171
163172
data() {
164173
return {
@@ -224,7 +233,9 @@ export default defineComponent({
224233
225234
async created() {
226235
this.userCourseRegDoc = await this.user.getCourseRegistrationsDoc();
227-
this.initSession();
236+
console.log('[StudySession] Created lifecycle hook - starting initSession');
237+
await this.initSession();
238+
console.log('[StudySession] InitSession completed in created hook');
228239
},
229240
230241
methods: {
@@ -271,58 +282,84 @@ export default defineComponent({
271282
},
272283
273284
async initSession() {
274-
console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`);
275-
276-
this.sessionContentSources = (
277-
await Promise.all(
278-
this.contentSources.map(async (s) => {
279-
try {
280-
return await getStudySource(s, this.user);
281-
} catch (e) {
282-
console.error(`Failed to load study source: ${s.type}/${s.id}`, e);
283-
return null;
284-
}
285-
})
286-
)
287-
).filter((s) => s !== null);
285+
let sessionClassroomDBs: ClassroomDBInterface[] = [];
286+
try {
287+
console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`);
288+
console.log('[StudySession] Beginning preparation process');
289+
290+
this.sessionContentSources = (
291+
await Promise.all(
292+
this.contentSources.map(async (s) => {
293+
try {
294+
return await getStudySource(s, this.user);
295+
} catch (e) {
296+
console.error(`Failed to load study source: ${s.type}/${s.id}`, e);
297+
return null;
298+
}
299+
})
300+
)
301+
).filter((s) => s !== null);
288302
289-
this.timeRemaining = this.sessionTimeLimit * 60;
303+
this.timeRemaining = this.sessionTimeLimit * 60;
290304
291-
const sessionClassroomDBs = await Promise.all(
292-
this.contentSources
293-
.filter((s) => s.type === 'classroom')
294-
.map(async (c) => await this.dataLayer.getClassroomDB(c.id, 'student'))
295-
);
305+
sessionClassroomDBs = await Promise.all(
306+
this.contentSources
307+
.filter((s) => s.type === 'classroom')
308+
.map(async (c) => await this.dataLayer.getClassroomDB(c.id, 'student'))
309+
);
296310
297-
sessionClassroomDBs.forEach((db) => {
298-
// db.setChangeFcn(this.handleClassroomMessage());
299-
});
311+
sessionClassroomDBs.forEach((db) => {
312+
// db.setChangeFcn(this.handleClassroomMessage());
313+
});
300314
301-
this.sessionController = new SessionController(this.sessionContentSources, 60 * this.sessionTimeLimit);
302-
this.sessionController.sessionRecord = this.sessionRecord;
315+
this.sessionController = new SessionController(this.sessionContentSources, 60 * this.sessionTimeLimit);
316+
this.sessionController.sessionRecord = this.sessionRecord;
303317
304-
await this.sessionController.prepareSession();
305-
this.intervalHandler = setInterval(this.tick, 1000);
318+
await this.sessionController.prepareSession();
319+
this.intervalHandler = setInterval(this.tick, 1000);
306320
307-
this.sessionPrepared = true;
321+
this.sessionPrepared = true;
308322
309-
this.contentSources
310-
.filter((s) => s.type === 'course')
311-
.forEach(
312-
async (c) => (this.courseNames[c.id] = (await this.dataLayer.getCoursesDB().getCourseConfig(c.id)).name)
313-
);
323+
console.log('[StudySession] Session preparation complete, emitting session-prepared event');
324+
this.$emit('session-prepared');
325+
console.log('[StudySession] Event emission completed');
326+
} catch (error) {
327+
console.error('[StudySession] Error during session preparation:', error);
328+
// Notify parent component about the error
329+
this.$emit('session-error', { message: 'Failed to prepare study session', error });
330+
}
314331
315-
console.log(`[StudySession] Session created:
316-
${this.sessionController.toString()}
317-
User courses: ${this.contentSources
332+
try {
333+
this.contentSources
318334
.filter((s) => s.type === 'course')
319-
.map((c) => c.id)
320-
.toString()}
321-
User classrooms: ${sessionClassroomDBs.map((db) => db._id)}
322-
`);
335+
.forEach(
336+
async (c) => (this.courseNames[c.id] = (await this.dataLayer.getCoursesDB().getCourseConfig(c.id)).name)
337+
);
338+
339+
console.log(`[StudySession] Session created:
340+
${this.sessionController?.toString() || 'Session controller not initialized'}
341+
User courses: ${this.contentSources
342+
.filter((s) => s.type === 'course')
343+
.map((c) => c.id)
344+
.toString()}
345+
User classrooms: ${sessionClassroomDBs.map((db: any) => db._id).toString() || 'No classrooms'}
346+
`);
347+
} catch (error) {
348+
console.error('[StudySession] Error during final session setup:', error);
349+
}
323350
324-
this.$emit('session-started');
325-
this.loadCard(this.sessionController.nextCard());
351+
if (this.sessionController) {
352+
try {
353+
this.$emit('session-started');
354+
this.loadCard(this.sessionController.nextCard());
355+
} catch (error) {
356+
console.error('[StudySession] Error loading next card:', error);
357+
this.$emit('session-error', { message: 'Failed to load study card', error });
358+
}
359+
} else {
360+
console.error('[StudySession] Cannot load card: session controller not initialized');
361+
this.$emit('session-error', { message: 'Study session initialization failed' });
362+
}
326363
},
327364
328365
countCardViews(course_id: string, card_id: string): number {

packages/platform-ui/src/views/Study.vue

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,53 @@
2929
</v-row>
3030
</div>
3131

32-
<StudySession
33-
v-if="sessionPrepared"
34-
:content-sources="sessionContentSources"
35-
:session-time-limit="sessionTimeLimit"
36-
:user="user as UserDBInterface"
37-
:session-config="studySessionConfig"
38-
:data-layer="dataLayer"
39-
:get-view-component="getViewComponent"
40-
@session-finished="handleSessionFinished"
41-
/>
32+
<!-- Study Session Component (may be in loading state) -->
33+
<div v-if="inSession">
34+
<!-- Loading indicator while session is being prepared -->
35+
<div v-if="!sessionPrepared && !sessionError" class="session-loading">
36+
<v-container class="text-center">
37+
<v-row justify="center" align="center" style="min-height: 50vh">
38+
<v-col cols="12">
39+
<v-progress-circular
40+
size="70"
41+
width="7"
42+
color="primary"
43+
indeterminate
44+
></v-progress-circular>
45+
<div class="text-h5 mt-4">Preparing your study session...</div>
46+
<div class="text-subtitle-1 mt-2">Getting your learning materials ready</div>
47+
</v-col>
48+
</v-row>
49+
</v-container>
50+
</div>
51+
52+
<!-- Error state -->
53+
<div v-if="sessionError" class="session-error">
54+
<v-container class="text-center">
55+
<v-row justify="center" align="center" style="min-height: 50vh">
56+
<v-col cols="12">
57+
<v-icon size="64" color="error">mdi-alert-circle</v-icon>
58+
<div class="text-h5 mt-4 text-error">Session Preparation Failed</div>
59+
<div class="text-subtitle-1 mt-2">{{ errorMessage || 'There was a problem preparing your study session.' }}</div>
60+
<v-btn color="primary" class="mt-6" @click="refreshRoute">Try Again</v-btn>
61+
</v-col>
62+
</v-row>
63+
</v-container>
64+
</div>
65+
66+
<StudySession
67+
:content-sources="sessionContentSources"
68+
:session-time-limit="sessionTimeLimit"
69+
:user="user as UserDBInterface"
70+
:session-config="studySessionConfig"
71+
:data-layer="dataLayer"
72+
:get-view-component="getViewComponent"
73+
:class="{ 'hidden-session': !sessionPrepared }"
74+
@session-finished="handleSessionFinished"
75+
@session-prepared="handleSessionPrepared"
76+
@session-error="handleSessionError"
77+
/>
78+
</div>
4279
</div>
4380
</template>
4481

@@ -107,6 +144,8 @@ export default defineComponent({
107144
sessionTimeLimit: 5,
108145
inSession: false,
109146
sessionPrepared: false,
147+
sessionError: false,
148+
errorMessage: '',
110149
sessionContentSources: [] as ContentSourceID[],
111150
dataInputFormStore: useDataInputFormStore(),
112151
getViewComponent: (view_id: string) => allCourses.getView(view_id),
@@ -176,7 +215,10 @@ export default defineComponent({
176215
this.sessionContentSources = sources;
177216
this.sessionTimeLimit = timeLimit;
178217
this.inSession = true;
179-
this.sessionPrepared = true;
218+
this.sessionPrepared = false;
219+
220+
// Adding a console log to debug event handling
221+
console.log('[Study] Waiting for session-prepared event from StudySession component');
180222
},
181223
182224
registerUserForPreviewCourse() {
@@ -188,6 +230,32 @@ export default defineComponent({
188230
handleSessionFinished() {
189231
this.refreshRoute();
190232
},
233+
234+
handleSessionPrepared() {
235+
console.log('[Study] Session preparation complete - received session-prepared event');
236+
this.sessionPrepared = true;
237+
this.sessionError = false;
238+
this.errorMessage = '';
239+
},
240+
241+
handleSessionError({ message, error }) {
242+
console.error('[Study] Session error:', message, error);
243+
this.sessionError = true;
244+
this.errorMessage = message || 'An error occurred while preparing your study session.';
245+
this.sessionPrepared = false;
246+
},
191247
},
192248
});
193249
</script>
250+
251+
<style scoped>
252+
.hidden-session {
253+
visibility: hidden;
254+
position: absolute;
255+
z-index: -1;
256+
}
257+
258+
.session-error {
259+
color: var(--v-error-base);
260+
}
261+
</style>

0 commit comments

Comments
 (0)