Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 81 additions & 44 deletions packages/common-ui/src/components/StudySession.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import {
CourseRegistrationDoc,
DataLayerProvider,
UserDBInterface,
ClassroomDBInterface,
} from '@vue-skuilder/db';
import { SessionController, StudySessionRecord } from '@vue-skuilder/db';
import { newInterval } from '@vue-skuilder/db';
Expand Down Expand Up @@ -158,7 +159,15 @@ export default defineComponent({
},
},

emits: ['session-finished', 'session-started', 'card-loaded', 'card-response', 'time-changed'],
emits: [
'session-finished',
'session-started',
'card-loaded',
'card-response',
'time-changed',
'session-prepared',
'session-error',
],

data() {
return {
Expand Down Expand Up @@ -224,7 +233,9 @@ export default defineComponent({

async created() {
this.userCourseRegDoc = await this.user.getCourseRegistrationsDoc();
this.initSession();
console.log('[StudySession] Created lifecycle hook - starting initSession');
await this.initSession();
console.log('[StudySession] InitSession completed in created hook');
},

methods: {
Expand Down Expand Up @@ -271,58 +282,84 @@ export default defineComponent({
},

async initSession() {
console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`);

this.sessionContentSources = (
await Promise.all(
this.contentSources.map(async (s) => {
try {
return await getStudySource(s, this.user);
} catch (e) {
console.error(`Failed to load study source: ${s.type}/${s.id}`, e);
return null;
}
})
)
).filter((s) => s !== null);
let sessionClassroomDBs: ClassroomDBInterface[] = [];
try {
console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`);
console.log('[StudySession] Beginning preparation process');

this.sessionContentSources = (
await Promise.all(
this.contentSources.map(async (s) => {
try {
return await getStudySource(s, this.user);
} catch (e) {
console.error(`Failed to load study source: ${s.type}/${s.id}`, e);
return null;
}
})
)
).filter((s) => s !== null);

this.timeRemaining = this.sessionTimeLimit * 60;
this.timeRemaining = this.sessionTimeLimit * 60;

const sessionClassroomDBs = await Promise.all(
this.contentSources
.filter((s) => s.type === 'classroom')
.map(async (c) => await this.dataLayer.getClassroomDB(c.id, 'student'))
);
sessionClassroomDBs = await Promise.all(
this.contentSources
.filter((s) => s.type === 'classroom')
.map(async (c) => await this.dataLayer.getClassroomDB(c.id, 'student'))
);

sessionClassroomDBs.forEach((db) => {
// db.setChangeFcn(this.handleClassroomMessage());
});
sessionClassroomDBs.forEach((db) => {
// db.setChangeFcn(this.handleClassroomMessage());
});

this.sessionController = new SessionController(this.sessionContentSources, 60 * this.sessionTimeLimit);
this.sessionController.sessionRecord = this.sessionRecord;
this.sessionController = new SessionController(this.sessionContentSources, 60 * this.sessionTimeLimit);
this.sessionController.sessionRecord = this.sessionRecord;

await this.sessionController.prepareSession();
this.intervalHandler = setInterval(this.tick, 1000);
await this.sessionController.prepareSession();
this.intervalHandler = setInterval(this.tick, 1000);

this.sessionPrepared = true;
this.sessionPrepared = true;

this.contentSources
.filter((s) => s.type === 'course')
.forEach(
async (c) => (this.courseNames[c.id] = (await this.dataLayer.getCoursesDB().getCourseConfig(c.id)).name)
);
console.log('[StudySession] Session preparation complete, emitting session-prepared event');
this.$emit('session-prepared');
console.log('[StudySession] Event emission completed');
} catch (error) {
console.error('[StudySession] Error during session preparation:', error);
// Notify parent component about the error
this.$emit('session-error', { message: 'Failed to prepare study session', error });
}

console.log(`[StudySession] Session created:
${this.sessionController.toString()}
User courses: ${this.contentSources
try {
this.contentSources
.filter((s) => s.type === 'course')
.map((c) => c.id)
.toString()}
User classrooms: ${sessionClassroomDBs.map((db) => db._id)}
`);
.forEach(
async (c) => (this.courseNames[c.id] = (await this.dataLayer.getCoursesDB().getCourseConfig(c.id)).name)
);

console.log(`[StudySession] Session created:
${this.sessionController?.toString() || 'Session controller not initialized'}
User courses: ${this.contentSources
.filter((s) => s.type === 'course')
.map((c) => c.id)
.toString()}
User classrooms: ${sessionClassroomDBs.map((db: any) => db._id).toString() || 'No classrooms'}
`);
} catch (error) {
console.error('[StudySession] Error during final session setup:', error);
}

this.$emit('session-started');
this.loadCard(this.sessionController.nextCard());
if (this.sessionController) {
try {
this.$emit('session-started');
this.loadCard(this.sessionController.nextCard());
} catch (error) {
console.error('[StudySession] Error loading next card:', error);
this.$emit('session-error', { message: 'Failed to load study card', error });
}
} else {
console.error('[StudySession] Cannot load card: session controller not initialized');
this.$emit('session-error', { message: 'Study session initialization failed' });
}
},

countCardViews(course_id: string, card_id: string): number {
Expand Down
90 changes: 79 additions & 11 deletions packages/platform-ui/src/views/Study.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,53 @@
</v-row>
</div>

<StudySession
v-if="sessionPrepared"
:content-sources="sessionContentSources"
:session-time-limit="sessionTimeLimit"
:user="user as UserDBInterface"
:session-config="studySessionConfig"
:data-layer="dataLayer"
:get-view-component="getViewComponent"
@session-finished="handleSessionFinished"
/>
<!-- Study Session Component (may be in loading state) -->
<div v-if="inSession">
<!-- Loading indicator while session is being prepared -->
<div v-if="!sessionPrepared && !sessionError" class="session-loading">
<v-container class="text-center">
<v-row justify="center" align="center" style="min-height: 50vh">
<v-col cols="12">
<v-progress-circular
size="70"
width="7"
color="primary"
indeterminate
></v-progress-circular>
<div class="text-h5 mt-4">Preparing your study session...</div>
<div class="text-subtitle-1 mt-2">Getting your learning materials ready</div>
</v-col>
</v-row>
</v-container>
</div>

<!-- Error state -->
<div v-if="sessionError" class="session-error">
<v-container class="text-center">
<v-row justify="center" align="center" style="min-height: 50vh">
<v-col cols="12">
<v-icon size="64" color="error">mdi-alert-circle</v-icon>
<div class="text-h5 mt-4 text-error">Session Preparation Failed</div>
<div class="text-subtitle-1 mt-2">{{ errorMessage || 'There was a problem preparing your study session.' }}</div>
<v-btn color="primary" class="mt-6" @click="refreshRoute">Try Again</v-btn>
</v-col>
</v-row>
</v-container>
</div>

<StudySession
:content-sources="sessionContentSources"
:session-time-limit="sessionTimeLimit"
:user="user as UserDBInterface"
:session-config="studySessionConfig"
:data-layer="dataLayer"
:get-view-component="getViewComponent"
:class="{ 'hidden-session': !sessionPrepared }"
@session-finished="handleSessionFinished"
@session-prepared="handleSessionPrepared"
@session-error="handleSessionError"
/>
</div>
</div>
</template>

Expand Down Expand Up @@ -107,6 +144,8 @@ export default defineComponent({
sessionTimeLimit: 5,
inSession: false,
sessionPrepared: false,
sessionError: false,
errorMessage: '',
sessionContentSources: [] as ContentSourceID[],
dataInputFormStore: useDataInputFormStore(),
getViewComponent: (view_id: string) => allCourses.getView(view_id),
Expand Down Expand Up @@ -176,7 +215,10 @@ export default defineComponent({
this.sessionContentSources = sources;
this.sessionTimeLimit = timeLimit;
this.inSession = true;
this.sessionPrepared = true;
this.sessionPrepared = false;

// Adding a console log to debug event handling
console.log('[Study] Waiting for session-prepared event from StudySession component');
},

registerUserForPreviewCourse() {
Expand All @@ -188,6 +230,32 @@ export default defineComponent({
handleSessionFinished() {
this.refreshRoute();
},

handleSessionPrepared() {
console.log('[Study] Session preparation complete - received session-prepared event');
this.sessionPrepared = true;
this.sessionError = false;
this.errorMessage = '';
},

handleSessionError({ message, error }) {
console.error('[Study] Session error:', message, error);
this.sessionError = true;
this.errorMessage = message || 'An error occurred while preparing your study session.';
this.sessionPrepared = false;
},
},
});
</script>

<style scoped>
.hidden-session {
visibility: hidden;
position: absolute;
z-index: -1;
}

.session-error {
color: var(--v-error-base);
}
</style>