Skip to content

Commit b74e052

Browse files
authored
fixes: userConfig, audioData unpack, sui-viewLookup (#821)
- **add SessionTimeLimit as a userConfig setting** - **add settings hooks** - **apply dark mode** - **add SessionTimeLimit as a userConfig setting** - **fix: supply _rev for adding attachments** - **wrap viewLookup fcn**
2 parents 040ac23 + 7581aa2 commit b74e052

File tree

9 files changed

+203
-41
lines changed

9 files changed

+203
-41
lines changed

packages/common-ui/src/stores/useConfigStore.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const useConfigStore = () => {
1717
config: {
1818
darkMode: false,
1919
likesConfetti: false,
20+
sessionTimeLimit: 5, // Default 5 minutes
2021
} as UserConfig,
2122
}),
2223

@@ -40,6 +41,14 @@ export const useConfigStore = () => {
4041
});
4142
},
4243

44+
async updateSessionTimeLimit(sessionTimeLimit: number) {
45+
this.config.sessionTimeLimit = sessionTimeLimit;
46+
const user = await getCurrentUser();
47+
await user.setConfig({
48+
sessionTimeLimit,
49+
});
50+
},
51+
4352
async hydrate() {
4453
const user = await getCurrentUser();
4554
const cfg = await user.getConfig();
@@ -53,6 +62,7 @@ export const useConfigStore = () => {
5362
this.config = {
5463
darkMode: false,
5564
likesConfetti: false,
65+
sessionTimeLimit: 5,
5666
};
5767
},
5868
},

packages/db/src/core/types/user.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Moment } from 'moment';
44
export interface UserConfig {
55
darkMode: boolean;
66
likesConfetti: boolean;
7+
sessionTimeLimit: number; // Session time limit in minutes
78
}
89

910
export interface ActivityRecord {

packages/db/src/impl/common/BaseUserDB.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ Currently logged-in as ${this._username}.`
519519
_id: BaseUser.DOC_IDS.CONFIG,
520520
darkMode: false,
521521
likesConfetti: false,
522+
sessionTimeLimit: 5,
522523
};
523524

524525
try {

packages/db/src/util/migrator/StaticToCouchDBMigrator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ export class StaticToCouchDBMigrator {
425425
const cleanDoc = { ...doc };
426426
// Remove _rev if present (CouchDB will assign new revision)
427427
delete cleanDoc._rev;
428+
// Remove _attachments - these are uploaded separately in Phase 5
429+
delete cleanDoc._attachments;
428430

429431
return cleanDoc;
430432
});
@@ -575,10 +577,14 @@ export class StaticToCouchDBMigrator {
575577
}
576578
}
577579

580+
// Get current document revision (needed for putAttachment)
581+
const doc = await db.get(docId);
582+
578583
// Upload to CouchDB
579584
await db.putAttachment(
580585
docId,
581586
attachmentName,
587+
doc._rev,
582588
attachmentData as any, // PouchDB accepts both ArrayBuffer and Buffer
583589
attachmentMeta.content_type
584590
);

packages/standalone-ui/src/App.vue

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<v-app :theme="theme">
2+
<v-app>
33
<course-header :title="courseConfig.title" :logo="courseConfig.logo" />
44

55
<v-main>
@@ -21,14 +21,30 @@
2121
</template>
2222

2323
<script setup lang="ts">
24-
import { computed, onMounted } from 'vue';
24+
import { computed, onMounted, watch } from 'vue';
25+
import { useTheme } from 'vuetify';
2526
import { useCourseConfig } from './composables/useCourseConfig';
2627
import CourseHeader from './components/CourseHeader.vue';
2728
import CourseFooter from './components/CourseFooter.vue';
28-
import { SkMouseTrap, SkldrMouseTrap } from '@vue-skuilder/common-ui';
29+
import { SkMouseTrap, SkldrMouseTrap, useConfigStore } from '@vue-skuilder/common-ui';
2930
3031
const { courseConfig } = useCourseConfig();
31-
const theme = computed(() => (courseConfig.darkMode ? 'dark' : 'light'));
32+
const configStore = useConfigStore();
33+
const theme = useTheme();
34+
35+
// Use the configStore dark mode instead of courseConfig
36+
const dark = computed(() => {
37+
return configStore.config.darkMode;
38+
});
39+
40+
// Watch for dark mode changes and update Vuetify theme
41+
watch(
42+
dark,
43+
(newVal) => {
44+
theme.global.name.value = newVal ? 'dark' : 'light';
45+
},
46+
{ immediate: true }
47+
);
3248
3349
onMounted(() => {
3450
// Add a global shortcut to show the keyboard shortcuts dialog

packages/standalone-ui/src/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ import config from '../skuilder.config.json';
162162
app.use(piniaPlugin, { pinia });
163163

164164
await useAuthStore().init();
165+
166+
// Initialize config store to load user settings (including dark mode)
167+
const { useConfigStore } = await import('@vue-skuilder/common-ui');
168+
await useConfigStore().init();
165169

166170
// Auto-register user for the course in standalone mode
167171
if (config.course) {

packages/standalone-ui/src/views/StudyView.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,18 @@
2727
<script setup lang="ts">
2828
import { ref, onMounted } from 'vue';
2929
import { ContentSourceID, getDataLayer } from '@vue-skuilder/db';
30-
import { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';
30+
import { StudySession, type StudySessionConfig, useConfigStore } from '@vue-skuilder/common-ui';
3131
import { allCourses } from '@vue-skuilder/courses';
3232
import ENV from '../ENVIRONMENT_VARS';
3333
3434
const user = getDataLayer().getUserDB();
3535
const dataLayer = getDataLayer();
36+
const configStore = useConfigStore();
3637
const sessionPrepared = ref(false);
37-
const sessionTimeLimit = ref(5); // 5 minutes
38+
const sessionTimeLimit = ref(configStore.config.sessionTimeLimit);
3839
const sessionContentSources = ref<ContentSourceID[]>([]);
3940
const studySessionConfig = ref<StudySessionConfig>({
40-
likesConfetti: true,
41+
likesConfetti: configStore.config.likesConfetti,
4142
});
4243
4344
// Function to get view component from courses

packages/standalone-ui/src/views/UserSettingsView.vue

Lines changed: 151 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,169 @@
77
</v-card-title>
88

99
<v-card-text>
10-
<v-alert type="info" class="mb-4">
11-
<v-icon start>mdi-information</v-icon>
12-
Settings panel is coming soon! This will allow you to customize your learning experience.
13-
</v-alert>
10+
<div v-if="loading">
11+
<v-progress-circular indeterminate color="primary"></v-progress-circular>
12+
Loading settings...
13+
</div>
1414

15-
<v-row>
16-
<v-col cols="12" md="6">
17-
<v-card variant="outlined">
18-
<v-card-title class="text-h6">Study Preferences</v-card-title>
19-
<v-card-text>
20-
<p class="text-body-2">Configure session length, difficulty settings, and review scheduling.</p>
21-
</v-card-text>
22-
</v-card>
23-
</v-col>
24-
25-
<v-col cols="12" md="6">
26-
<v-card variant="outlined">
27-
<v-card-title class="text-h6">Interface Options</v-card-title>
28-
<v-card-text>
29-
<p class="text-body-2">Customize theme, keyboard shortcuts, and display preferences.</p>
30-
</v-card-text>
31-
</v-card>
32-
</v-col>
33-
34-
<v-col cols="12">
35-
<v-card variant="outlined">
36-
<v-card-title class="text-h6">Account Management</v-card-title>
37-
<v-card-text>
38-
<p class="text-body-2">Manage your profile information and data privacy settings.</p>
39-
</v-card-text>
40-
</v-card>
41-
</v-col>
42-
</v-row>
15+
<div v-else>
16+
<!-- Study Preferences -->
17+
<v-card variant="outlined" class="mb-4">
18+
<v-card-title class="text-h6">
19+
<v-icon start>mdi-clock-outline</v-icon>
20+
Study Preferences
21+
</v-card-title>
22+
<v-card-text>
23+
<v-row>
24+
<v-col cols="12" md="6">
25+
<v-slider
26+
v-model="sessionTimeLimit"
27+
label="Session Time Limit (minutes)"
28+
:min="1"
29+
:max="30"
30+
:step="1"
31+
thumb-label="always"
32+
@update:model-value="updateSessionTimeLimit"
33+
>
34+
<template #append>
35+
<v-text-field
36+
v-model="sessionTimeLimit"
37+
type="number"
38+
style="width: 80px"
39+
density="compact"
40+
hide-details
41+
variant="outlined"
42+
:min="1"
43+
:max="30"
44+
@update:model-value="updateSessionTimeLimit"
45+
/>
46+
</template>
47+
</v-slider>
48+
</v-col>
49+
</v-row>
50+
</v-card-text>
51+
</v-card>
52+
53+
<!-- Interface Options -->
54+
<v-card variant="outlined" class="mb-4">
55+
<v-card-title class="text-h6">
56+
<v-icon start>mdi-palette-outline</v-icon>
57+
Interface Options
58+
</v-card-title>
59+
<v-card-text>
60+
<v-row>
61+
<v-col cols="12" md="6">
62+
<v-switch
63+
v-model="darkMode"
64+
label="Dark Mode"
65+
color="primary"
66+
@update:model-value="updateDarkMode"
67+
/>
68+
</v-col>
69+
<v-col cols="12" md="6">
70+
<v-switch
71+
v-model="likesConfetti"
72+
label="Enable Confetti"
73+
color="primary"
74+
@update:model-value="updateConfetti"
75+
/>
76+
</v-col>
77+
</v-row>
78+
</v-card-text>
79+
</v-card>
80+
81+
<!-- Account Actions -->
82+
<v-card variant="outlined">
83+
<v-card-title class="text-h6">
84+
<v-icon start>mdi-account-outline</v-icon>
85+
Account Management
86+
</v-card-title>
87+
<v-card-text>
88+
<v-row>
89+
<v-col cols="12">
90+
<v-btn
91+
variant="outlined"
92+
color="warning"
93+
@click="resetToDefaults"
94+
>
95+
<v-icon start>mdi-restore</v-icon>
96+
Reset to Defaults
97+
</v-btn>
98+
</v-col>
99+
</v-row>
100+
</v-card-text>
101+
</v-card>
102+
</div>
43103
</v-card-text>
44104

45105
<v-card-actions>
46106
<v-btn variant="outlined" @click="$router.back()">
47107
<v-icon start>mdi-arrow-left</v-icon>
48108
Back
49109
</v-btn>
110+
<v-spacer />
111+
<v-btn variant="text" color="success" v-if="!loading">
112+
<v-icon start>mdi-check</v-icon>
113+
Settings Saved
114+
</v-btn>
50115
</v-card-actions>
51116
</v-card>
52117
</v-container>
53118
</template>
54119

55120
<script setup lang="ts">
56-
// Placeholder for user settings view
121+
import { ref, onMounted } from 'vue';
122+
import { useConfigStore } from '@vue-skuilder/common-ui';
123+
124+
const loading = ref(true);
125+
const configStore = useConfigStore();
126+
127+
// Reactive references to config values
128+
const sessionTimeLimit = ref(5);
129+
const darkMode = ref(false);
130+
const likesConfetti = ref(false);
131+
132+
// Update methods
133+
const updateSessionTimeLimit = async (value: number) => {
134+
sessionTimeLimit.value = value;
135+
await configStore.updateSessionTimeLimit(value);
136+
};
137+
138+
const updateDarkMode = async (value: boolean) => {
139+
darkMode.value = value;
140+
await configStore.updateDarkMode(value);
141+
};
142+
143+
const updateConfetti = async (value: boolean) => {
144+
likesConfetti.value = value;
145+
await configStore.updateLikesConfetti(value);
146+
};
147+
148+
const resetToDefaults = async () => {
149+
configStore.resetDefaults();
150+
await configStore.updateSessionTimeLimit(5);
151+
await configStore.updateDarkMode(false);
152+
await configStore.updateLikesConfetti(false);
153+
154+
// Update local refs
155+
sessionTimeLimit.value = 5;
156+
darkMode.value = false;
157+
likesConfetti.value = false;
158+
};
159+
160+
// Load current settings on mount
161+
onMounted(async () => {
162+
try {
163+
await configStore.hydrate();
164+
165+
// Update local refs with current config
166+
sessionTimeLimit.value = configStore.config.sessionTimeLimit;
167+
darkMode.value = configStore.config.darkMode;
168+
likesConfetti.value = configStore.config.likesConfetti;
169+
} catch (error) {
170+
console.error('Error loading user settings:', error);
171+
} finally {
172+
loading.value = false;
173+
}
174+
});
57175
</script>

packages/studio-ui/src/views/BrowseView.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</div>
1313

1414
<div v-else-if="courseId">
15-
<course-information :course-id="courseId" :view-lookup-function="allCourses.getView" :edit-mode="'full'">
15+
<course-information :course-id="courseId" :view-lookup-function="viewLookupFunction" :edit-mode="'full'">
1616
<template #actions>&nbsp;</template>
1717
</course-information>
1818
</div>
@@ -36,6 +36,11 @@ const loading = ref(true);
3636
const error = ref<string | null>(null);
3737
const courseId = ref<string | null>(null);
3838
39+
// View lookup function with proper context binding
40+
const viewLookupFunction = (viewDescription: any) => {
41+
return allCourses.getView(viewDescription);
42+
};
43+
3944
// Initialize browse view
4045
onMounted(async () => {
4146
try {

0 commit comments

Comments
 (0)