Skip to content

Commit ed4d434

Browse files
authored
Datalayer abstraction (#658)
2 parents 9fb299b + a1e6b57 commit ed4d434

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1605
-692
lines changed

packages/common-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
},
4747
"devDependencies": {
4848
"@cypress/vite-dev-server": "^6.0.3",
49+
"@types/pouchdb": "^6.4.2",
4950
"@typescript-eslint/eslint-plugin": "^8.25.0",
5051
"@typescript-eslint/parser": "^8.25.0",
5152
"@vitejs/plugin-vue": "^5.2.1",

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

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
Start <a @click="$emit('session-finished')">another study session</a>, or try
2020
<router-link :to="`/edit/${courseID}`">adding some new content</router-link> to challenge yourself and others!
2121
</p>
22-
<heat-map activity-records-getter="user.getActivityRecords" />
22+
<heat-map :activity-records-getter="user.getActivityRecords" />
2323
</div>
2424

2525
<div v-else ref="shadowWrapper">
@@ -72,34 +72,27 @@
7272
import { defineComponent, PropType } from 'vue';
7373
import { isQuestionView } from '../composables/CompositionViewable';
7474
import { alertUser } from './SnackbarService';
75-
import ViewComponent from '../composables/Displayable';
75+
import { ViewComponent } from '@/composables';
7676
import SkMouseTrap from './SkMouseTrap.vue';
7777
import StudySessionTimer from './StudySessionTimer.vue';
7878
import HeatMap from './HeatMap.vue';
7979
import CardViewer from './cardRendering/CardViewer.vue';
8080
8181
import {
82-
getCourseDoc,
83-
removeScheduledCardReview,
84-
scheduleCardReview,
8582
ContentSourceID,
8683
getStudySource,
8784
isReview,
8885
StudyContentSource,
8986
StudySessionItem,
90-
CourseDB,
91-
getCourseName,
92-
updateCardElo,
9387
docIsDeleted,
9488
CardData,
9589
CardHistory,
9690
CardRecord,
9791
DisplayableData,
9892
isQuestionRecord,
9993
CourseRegistrationDoc,
100-
updateUserElo,
101-
User,
102-
StudentClassroomDB,
94+
DataLayerProvider,
95+
UserDBInterface,
10396
} from '@vue-skuilder/db';
10497
import { SessionController, StudySessionRecord } from '@vue-skuilder/db';
10598
import { newInterval } from '@vue-skuilder/db';
@@ -148,7 +141,11 @@ export default defineComponent({
148141
required: true,
149142
},
150143
user: {
151-
type: Object as PropType<User>,
144+
type: Object as PropType<UserDBInterface>,
145+
required: true,
146+
},
147+
dataLayer: {
148+
type: Object as PropType<DataLayerProvider>,
152149
required: true,
153150
},
154151
sessionConfig: {
@@ -242,7 +239,7 @@ export default defineComponent({
242239
handleClassroomMessage() {
243240
return (v: unknown) => {
244241
alertUser({
245-
text: this.user?.username || '[Unknown user]',
242+
text: this.user?.getUsername() || '[Unknown user]',
246243
status: Status.ok,
247244
});
248245
console.log(`[StudySession] There was a change in the classroom DB:`);
@@ -294,11 +291,11 @@ export default defineComponent({
294291
const sessionClassroomDBs = await Promise.all(
295292
this.contentSources
296293
.filter((s) => s.type === 'classroom')
297-
.map(async (c) => StudentClassroomDB.factory(c.id, this.user))
294+
.map(async (c) => await this.dataLayer.getClassroomDB(c.id, 'student'))
298295
);
299296
300297
sessionClassroomDBs.forEach((db) => {
301-
db.setChangeFcn(this.handleClassroomMessage());
298+
// db.setChangeFcn(this.handleClassroomMessage());
302299
});
303300
304301
this.sessionController = new SessionController(this.sessionContentSources, 60 * this.sessionTimeLimit);
@@ -311,7 +308,9 @@ export default defineComponent({
311308
312309
this.contentSources
313310
.filter((s) => s.type === 'course')
314-
.forEach(async (c) => (this.courseNames[c.id] = await getCourseName(c.id)));
311+
.forEach(
312+
async (c) => (this.courseNames[c.id] = (await this.dataLayer.getCoursesDB().getCourseConfig(c.id)).name)
313+
);
315314
316315
console.log(`[StudySession] Session created:
317316
${this.sessionController.toString()}
@@ -429,20 +428,17 @@ export default defineComponent({
429428
if (k) {
430429
console.warn(`k value interpretation not currently implemented`);
431430
}
431+
const courseDB = this.dataLayer.getCourseDB(this.currentCard.card.course_id);
432432
const userElo = toCourseElo(this.userCourseRegDoc!.courses.find((c) => c.courseID === course_id)!.elo);
433-
const cardElo = (
434-
await new CourseDB(this.currentCard.card.course_id, () => Promise.resolve(this.user)).getCardEloData([
435-
this.currentCard.card.card_id,
436-
])
437-
)[0];
433+
const cardElo = (await courseDB.getCardEloData([this.currentCard.card.card_id]))[0];
438434
439435
if (cardElo && userElo) {
440436
const eloUpdate = adjustCourseScores(userElo, cardElo, userScore);
441437
this.userCourseRegDoc!.courses.find((c) => c.courseID === course_id)!.elo = eloUpdate.userElo;
442438
443439
Promise.all([
444-
updateUserElo(this.user!.username, course_id, eloUpdate.userElo),
445-
updateCardElo(course_id, card_id, eloUpdate.cardElo),
440+
this.user!.updateUserElo(course_id, eloUpdate.userElo),
441+
courseDB.updateCardElo(card_id, eloUpdate.cardElo),
446442
]).then((results) => {
447443
const user = results[0];
448444
const card = results[1];
@@ -482,11 +478,11 @@ export default defineComponent({
482478
483479
if (isReview(item)) {
484480
console.log(`[StudySession] Removing previously scheduled review for: ${item.cardID}`);
485-
removeScheduledCardReview(this.user!.username, item.reviewID);
481+
this.user!.removeScheduledCardReview(this.user!.getUsername(), item.reviewID);
486482
}
487483
488-
scheduleCardReview({
489-
user: this.user!.username,
484+
this.user!.scheduleCardReview({
485+
user: this.user!.getUsername(),
490486
course_id: history.courseID,
491487
card_id: history.cardID,
492488
time: nextReviewTime,
@@ -516,15 +512,15 @@ export default defineComponent({
516512
console.log(`[StudySession] Now displaying: ${qualified_id}`);
517513
518514
try {
519-
const tmpCardData = await getCourseDoc<CardData>(_courseID, _cardID);
515+
const tmpCardData = await this.dataLayer.getCourseDB(_courseID).getCourseDoc<CardData>(_cardID);
520516
521517
if (!isCourseElo(tmpCardData.elo)) {
522518
tmpCardData.elo = toCourseElo(tmpCardData.elo);
523519
}
524520
525521
const tmpView: ViewComponent = this.getViewComponent(tmpCardData.id_view);
526522
const tmpDataDocs = tmpCardData.id_displayable_data.map((id) => {
527-
return getCourseDoc<DisplayableData>(_courseID, id, {
523+
return this.dataLayer.getCourseDB(_courseID).getCourseDoc<DisplayableData>(id, {
528524
attachments: true,
529525
binary: true,
530526
});
@@ -566,7 +562,7 @@ export default defineComponent({
566562
const err = e as Error;
567563
if (docIsDeleted(err) && isReview(item)) {
568564
console.warn(`Card was deleted: ${qualified_id}`);
569-
removeScheduledCardReview(this.user!.username, item.reviewID);
565+
this.user!.removeScheduledCardReview(this.user!.getUsername(), item.reviewID);
570566
}
571567
572568
this.loadCard(this.sessionController!.nextCard('dismiss-error'));

packages/common-ui/src/components/cardRendering/CardLoader.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<script lang="ts">
1616
import { defineComponent, PropType } from 'vue';
17-
import { getCourseDoc, CardData, CardRecord, DisplayableData } from '@vue-skuilder/db';
17+
import { getDataLayer, CardData, CardRecord, DisplayableData } from '@vue-skuilder/db';
1818
import { log, displayableDataToViewData, ViewData, ViewDescriptor } from '@vue-skuilder/common';
1919
import { ViewComponent } from '@/composables';
2020
import CardViewer from './CardViewer.vue';
@@ -72,12 +72,13 @@ export default defineComponent({
7272
this.loading = true;
7373
const _courseID = qualified_id.split('-')[0];
7474
const _cardID = qualified_id.split('-')[1];
75+
const courseDB = getDataLayer().getCourseDB(_courseID);
7576
7677
try {
77-
const tmpCardData = await getCourseDoc<CardData>(_courseID, _cardID);
78+
const tmpCardData = await courseDB.getCourseDoc<CardData>(_cardID);
7879
const tmpView = this.viewLookup(tmpCardData.id_view);
7980
const tmpDataDocs = tmpCardData.id_displayable_data.map((id) => {
80-
return getCourseDoc<DisplayableData>(_courseID, id, {
81+
return courseDB.getCourseDoc<DisplayableData>(id, {
8182
attachments: true,
8283
binary: true,
8384
});

packages/common-ui/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"paths": {
1717
"@/*": ["src/*"]
1818
},
19-
"types": ["vite/client", "vitest/globals", "cypress"]
19+
"types": ["vite/client", "vitest/globals", "cypress", "pouchdb"]
2020
},
2121
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
2222
"exclude": ["node_modules", "dist"]

packages/courses/src/piano/utility/MidiConfig.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import { defineComponent, ref, watch, onMounted } from 'vue';
8181
import { alertUser } from '@vue-skuilder/common-ui';
8282
import SkMidi from './midi';
8383
import { Status } from '@vue-skuilder/common';
84-
import { User } from '@vue-skuilder/db';
84+
import { UserDBInterface } from '@vue-skuilder/db';
8585
import { InputEventNoteon } from 'webmidi';
8686
import PianoRangeVisualizer from './PianoRangeVisualizer.vue';
8787
@@ -102,7 +102,7 @@ export default defineComponent({
102102
required: true,
103103
},
104104
user: {
105-
type: Object as () => User,
105+
type: Object as () => UserDBInterface,
106106
required: true,
107107
},
108108
},
@@ -390,7 +390,7 @@ export default defineComponent({
390390
});
391391
392392
const retrieveSettings = async () => {
393-
const s = await props.user.getCourseSettings(props._id);
393+
const s = await (await props.user.getCourseInterface(props._id)).getCourseSettings();
394394
395395
if (s?.midiinput) {
396396
const savedInput = s.midiinput.toString();

packages/courses/src/piano/utility/midi.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import webmidi, {
1111
import { Note, Interval } from '@tonaljs/tonal';
1212
import { alertUser } from '@vue-skuilder/common-ui';
1313
import { Status } from '@vue-skuilder/common';
14-
import { User } from '@vue-skuilder/db';
14+
import { UserDBInterface } from '@vue-skuilder/db';
1515
// import Navigator from '@types/webmidi';
1616

1717
export interface NoteEvent {
@@ -307,9 +307,9 @@ class SkMidi {
307307
return this._state;
308308
}
309309

310-
private _userLookup: (() => Promise<User>) | null;
310+
private _userLookup: (() => Promise<UserDBInterface>) | null;
311311

312-
private constructor(userLookup?: () => Promise<User>) {
312+
private constructor(userLookup?: () => Promise<UserDBInterface>) {
313313
this._userLookup = userLookup ?? null;
314314
}
315315

@@ -395,7 +395,8 @@ class SkMidi {
395395
// Get all course settings that might have MIDI configurations
396396
const courses = await user.getActiveCourses();
397397
for (const course of courses) {
398-
const settings = await user.getCourseSettings(course.courseID);
398+
const cdb = await user.getCourseInterface(course.courseID);
399+
const settings = await cdb.getCourseSettings();
399400
if (settings?.midiinput || settings?.midioutput) {
400401
// We found MIDI settings, use the first valid ones
401402
if (settings.midiinput && this.webmidi.getInputById(settings.midiinput.toString())) {

packages/db/src/core/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Export all core interfaces and types
2-
// This will be populated with interface definitions extracted from the Vue package
3-
export * from './types';
2+
43
export * from './interfaces';
5-
export * from './types-legacy';
6-
export * from './Loggable';
4+
export * from './types/types-legacy';
5+
export * from './types/user';
6+
export * from '../util/Loggable';
7+
export * from './util';

packages/db/src/core/interfaces.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ClassroomConfig } from '@vue-skuilder/common';
2+
3+
/**
4+
* Admin functionality
5+
*/
6+
export interface AdminDBInterface {
7+
/**
8+
* Get all users
9+
*/
10+
getUsers(): Promise<PouchDB.Core.Document<{}>[]>;
11+
12+
/**
13+
* Get all courses
14+
*/
15+
getCourses(): Promise<PouchDB.Core.Document<{}>[]>;
16+
17+
/**
18+
* Remove a course
19+
*/
20+
removeCourse(id: string): Promise<PouchDB.Core.Response>;
21+
22+
/**
23+
* Get all classrooms
24+
*/
25+
getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;
26+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ClassroomConfig } from '@vue-skuilder/common';
2+
import { ScheduledCard } from '../types/user';
3+
import { StudySessionNewItem, StudySessionReviewItem } from './contentSource';
4+
5+
/**
6+
* Classroom management
7+
*/
8+
export interface ClassroomDBInterface {
9+
/**
10+
* Get classroom config
11+
*/
12+
getConfig(): ClassroomConfig;
13+
14+
/**
15+
* Get assigned content
16+
*/
17+
getAssignedContent(): Promise<AssignedContent[]>;
18+
}
19+
20+
export interface TeacherClassroomDBInterface extends ClassroomDBInterface {
21+
/**
22+
* For teacher interfaces: assign content
23+
*/
24+
assignContent?(content: AssignedContent): Promise<boolean>;
25+
26+
/**
27+
* For teacher interfaces: remove content
28+
*/
29+
removeContent?(content: AssignedContent): Promise<void>;
30+
}
31+
32+
export interface StudentClassroomDBInterface extends ClassroomDBInterface {
33+
/**
34+
* For student interfaces: get pending reviews
35+
*/
36+
getPendingReviews?(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
37+
38+
/**
39+
* For student interfaces: get new cards
40+
*/
41+
getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;
42+
}
43+
44+
export type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;
45+
46+
export interface AssignedTag extends ContentBase {
47+
type: 'tag';
48+
tagID: string;
49+
}
50+
export interface AssignedCourse extends ContentBase {
51+
type: 'course';
52+
}
53+
export interface AssignedCard extends ContentBase {
54+
type: 'card';
55+
cardID: string;
56+
}
57+
58+
interface ContentBase {
59+
type: 'course' | 'tag' | 'card';
60+
/**
61+
* Username of the assigning teacher.
62+
*/
63+
assignedBy: string;
64+
/**
65+
* Date the content was assigned.
66+
*/
67+
assignedOn: moment.Moment;
68+
/**
69+
* A 'due' date for this assigned content, for scheduling content
70+
* in advance. Content will not be actively pushed to students until
71+
* this date.
72+
*/
73+
activeOn: moment.Moment;
74+
courseID: string;
75+
}

0 commit comments

Comments
 (0)