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
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
{
"name": "vue-skuilder",
"scripts": {
"setup": "yarn && git submodule update --init --recursive",
"setup": "yarn && git submodule update --init --recursive && yarn build:lib",
"dev": "yarn workspace @vue-skuilder/common build && yarn workspace @vue-skuilder/db build && yarn workspace @vue-skuilder/common-ui build && yarn workspace @vue-skuilder/courses build && yarn workspace @vue-skuilder/edit-ui build && node scripts/dev-couchdb.js start && concurrently \"yarn dev:platform-ui\" \"yarn dev:express\"",
"dev:platform-ui": "yarn workspace @vue-skuilder/platform-ui dev",
"dev:express": "yarn workspace @vue-skuilder/express dev",
"dev:couchdb": "node scripts/dev-couchdb.js start",
"dev:watch": "node scripts/dev-watch.js",
"dev:platform": "yarn couchdb:start && yarn workspace @vue-skuilder/express dev && yarn workspace @vue-skuilder/platform-ui dev",
"couchdb:start": "node scripts/dev-couchdb.js start",
"couchdb:stop": "node scripts/dev-couchdb.js stop",
"couchdb:status": "node scripts/dev-couchdb.js status",
"couchdb:remove": "node scripts/dev-couchdb.js remove",
"postdev": "node scripts/dev-couchdb.js stop",
"build": "yarn workspace @vue-skuilder/common build && yarn workspace @vue-skuilder/db build && yarn workspace @vue-skuilder/common-ui build && yarn workspace @vue-skuilder/courses build && yarn workspace @vue-skuilder/edit-ui build && yarn workspace @vue-skuilder/platform-ui build && yarn workspace @vue-skuilder/express build",
"build:lib": "yarn workspace @vue-skuilder/common build && yarn workspace @vue-skuilder/db build && yarn workspace @vue-skuilder/common-ui build && yarn workspace @vue-skuilder/courses build && yarn workspace @vue-skuilder/edit-ui build",
"build:pui": "yarn build:lib && yarn workspace @vue-skuilder/platform-ui build && yarn workspace @vue-skuilder/express build",
"build:sui": "yarn build:lib && yarn workspace @vue-skuilder/standalone-ui build",
"clean": "yarn clean:dist && yarn clean:node_modules",
"clean:dist": "find packages -name 'dist' -type d -exec rm -rf {} +",
"clean:node_modules": "find . -name 'node_modules' -type d -exec rm -rf {} +",
Expand Down
10 changes: 10 additions & 0 deletions packages/common-ui/src/stores/useConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const useConfigStore = () => {
config: {
darkMode: false,
likesConfetti: false,
sessionTimeLimit: 5, // Default 5 minutes
} as UserConfig,
}),

Expand All @@ -40,6 +41,14 @@ export const useConfigStore = () => {
});
},

async updateSessionTimeLimit(sessionTimeLimit: number) {
this.config.sessionTimeLimit = sessionTimeLimit;
const user = await getCurrentUser();
await user.setConfig({
sessionTimeLimit,
});
},

async hydrate() {
const user = await getCurrentUser();
const cfg = await user.getConfig();
Expand All @@ -53,6 +62,7 @@ export const useConfigStore = () => {
this.config = {
darkMode: false,
likesConfetti: false,
sessionTimeLimit: 5,
};
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/db/src/core/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Moment } from 'moment';
export interface UserConfig {
darkMode: boolean;
likesConfetti: boolean;
sessionTimeLimit: number; // Session time limit in minutes
}

export interface ActivityRecord {
Expand Down
1 change: 1 addition & 0 deletions packages/db/src/impl/common/BaseUserDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ Currently logged-in as ${this._username}.`
_id: BaseUser.DOC_IDS.CONFIG,
darkMode: false,
likesConfetti: false,
sessionTimeLimit: 5,
};

try {
Expand Down
6 changes: 6 additions & 0 deletions packages/db/src/util/migrator/StaticToCouchDBMigrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ export class StaticToCouchDBMigrator {
const cleanDoc = { ...doc };
// Remove _rev if present (CouchDB will assign new revision)
delete cleanDoc._rev;
// Remove _attachments - these are uploaded separately in Phase 5
delete cleanDoc._attachments;

return cleanDoc;
});
Expand Down Expand Up @@ -575,10 +577,14 @@ export class StaticToCouchDBMigrator {
}
}

// Get current document revision (needed for putAttachment)
const doc = await db.get(docId);

// Upload to CouchDB
await db.putAttachment(
docId,
attachmentName,
doc._rev,
attachmentData as any, // PouchDB accepts both ArrayBuffer and Buffer
attachmentMeta.content_type
);
Expand Down
24 changes: 20 additions & 4 deletions packages/standalone-ui/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<v-app :theme="theme">
<v-app>
<course-header :title="courseConfig.title" :logo="courseConfig.logo" />

<v-main>
Expand All @@ -21,14 +21,30 @@
</template>

<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { computed, onMounted, watch } from 'vue';
import { useTheme } from 'vuetify';
import { useCourseConfig } from './composables/useCourseConfig';
import CourseHeader from './components/CourseHeader.vue';
import CourseFooter from './components/CourseFooter.vue';
import { SkMouseTrap, SkldrMouseTrap } from '@vue-skuilder/common-ui';
import { SkMouseTrap, SkldrMouseTrap, useConfigStore } from '@vue-skuilder/common-ui';

const { courseConfig } = useCourseConfig();
const theme = computed(() => (courseConfig.darkMode ? 'dark' : 'light'));
const configStore = useConfigStore();
const theme = useTheme();

// Use the configStore dark mode instead of courseConfig
const dark = computed(() => {
return configStore.config.darkMode;
});

// Watch for dark mode changes and update Vuetify theme
watch(
dark,
(newVal) => {
theme.global.name.value = newVal ? 'dark' : 'light';
},
{ immediate: true }
);

onMounted(() => {
// Add a global shortcut to show the keyboard shortcuts dialog
Expand Down
4 changes: 4 additions & 0 deletions packages/standalone-ui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ import config from '../skuilder.config.json';
app.use(piniaPlugin, { pinia });

await useAuthStore().init();

// Initialize config store to load user settings (including dark mode)
const { useConfigStore } = await import('@vue-skuilder/common-ui');
await useConfigStore().init();

// Auto-register user for the course in standalone mode
if (config.course) {
Expand Down
7 changes: 4 additions & 3 deletions packages/standalone-ui/src/views/StudyView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ContentSourceID, getDataLayer } from '@vue-skuilder/db';
import { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';
import { StudySession, type StudySessionConfig, useConfigStore } from '@vue-skuilder/common-ui';
import { allCourses } from '@vue-skuilder/courses';
import ENV from '../ENVIRONMENT_VARS';

const user = getDataLayer().getUserDB();
const dataLayer = getDataLayer();
const configStore = useConfigStore();
const sessionPrepared = ref(false);
const sessionTimeLimit = ref(5); // 5 minutes
const sessionTimeLimit = ref(configStore.config.sessionTimeLimit);
const sessionContentSources = ref<ContentSourceID[]>([]);
const studySessionConfig = ref<StudySessionConfig>({
likesConfetti: true,
likesConfetti: configStore.config.likesConfetti,
});

// Function to get view component from courses
Expand Down
184 changes: 151 additions & 33 deletions packages/standalone-ui/src/views/UserSettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,169 @@
</v-card-title>

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

<v-row>
<v-col cols="12" md="6">
<v-card variant="outlined">
<v-card-title class="text-h6">Study Preferences</v-card-title>
<v-card-text>
<p class="text-body-2">Configure session length, difficulty settings, and review scheduling.</p>
</v-card-text>
</v-card>
</v-col>

<v-col cols="12" md="6">
<v-card variant="outlined">
<v-card-title class="text-h6">Interface Options</v-card-title>
<v-card-text>
<p class="text-body-2">Customize theme, keyboard shortcuts, and display preferences.</p>
</v-card-text>
</v-card>
</v-col>

<v-col cols="12">
<v-card variant="outlined">
<v-card-title class="text-h6">Account Management</v-card-title>
<v-card-text>
<p class="text-body-2">Manage your profile information and data privacy settings.</p>
</v-card-text>
</v-card>
</v-col>
</v-row>
<div v-else>
<!-- Study Preferences -->
<v-card variant="outlined" class="mb-4">
<v-card-title class="text-h6">
<v-icon start>mdi-clock-outline</v-icon>
Study Preferences
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-slider
v-model="sessionTimeLimit"
label="Session Time Limit (minutes)"
:min="1"
:max="30"
:step="1"
thumb-label="always"
@update:model-value="updateSessionTimeLimit"
>
<template #append>
<v-text-field
v-model="sessionTimeLimit"
type="number"
style="width: 80px"
density="compact"
hide-details
variant="outlined"
:min="1"
:max="30"
@update:model-value="updateSessionTimeLimit"
/>
</template>
</v-slider>
</v-col>
</v-row>
</v-card-text>
</v-card>

<!-- Interface Options -->
<v-card variant="outlined" class="mb-4">
<v-card-title class="text-h6">
<v-icon start>mdi-palette-outline</v-icon>
Interface Options
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-switch
v-model="darkMode"
label="Dark Mode"
color="primary"
@update:model-value="updateDarkMode"
/>
</v-col>
<v-col cols="12" md="6">
<v-switch
v-model="likesConfetti"
label="Enable Confetti"
color="primary"
@update:model-value="updateConfetti"
/>
</v-col>
</v-row>
</v-card-text>
</v-card>

<!-- Account Actions -->
<v-card variant="outlined">
<v-card-title class="text-h6">
<v-icon start>mdi-account-outline</v-icon>
Account Management
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12">
<v-btn
variant="outlined"
color="warning"
@click="resetToDefaults"
>
<v-icon start>mdi-restore</v-icon>
Reset to Defaults
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</div>
</v-card-text>

<v-card-actions>
<v-btn variant="outlined" @click="$router.back()">
<v-icon start>mdi-arrow-left</v-icon>
Back
</v-btn>
<v-spacer />
<v-btn variant="text" color="success" v-if="!loading">
<v-icon start>mdi-check</v-icon>
Settings Saved
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>

<script setup lang="ts">
// Placeholder for user settings view
import { ref, onMounted } from 'vue';
import { useConfigStore } from '@vue-skuilder/common-ui';

const loading = ref(true);
const configStore = useConfigStore();

// Reactive references to config values
const sessionTimeLimit = ref(5);
const darkMode = ref(false);
const likesConfetti = ref(false);

// Update methods
const updateSessionTimeLimit = async (value: number) => {
sessionTimeLimit.value = value;
await configStore.updateSessionTimeLimit(value);
};

const updateDarkMode = async (value: boolean) => {
darkMode.value = value;
await configStore.updateDarkMode(value);
};

const updateConfetti = async (value: boolean) => {
likesConfetti.value = value;
await configStore.updateLikesConfetti(value);
};

const resetToDefaults = async () => {
configStore.resetDefaults();
await configStore.updateSessionTimeLimit(5);
await configStore.updateDarkMode(false);
await configStore.updateLikesConfetti(false);

// Update local refs
sessionTimeLimit.value = 5;
darkMode.value = false;
likesConfetti.value = false;
};

// Load current settings on mount
onMounted(async () => {
try {
await configStore.hydrate();

// Update local refs with current config
sessionTimeLimit.value = configStore.config.sessionTimeLimit;
darkMode.value = configStore.config.darkMode;
likesConfetti.value = configStore.config.likesConfetti;
} catch (error) {
console.error('Error loading user settings:', error);
} finally {
loading.value = false;
}
});
</script>
7 changes: 6 additions & 1 deletion packages/studio-ui/src/views/BrowseView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</div>

<div v-else-if="courseId">
<course-information :course-id="courseId" :view-lookup-function="allCourses.getView" :edit-mode="'full'">
<course-information :course-id="courseId" :view-lookup-function="viewLookupFunction" :edit-mode="'full'">
<template #actions>&nbsp;</template>
</course-information>
</div>
Expand All @@ -36,6 +36,11 @@ const loading = ref(true);
const error = ref<string | null>(null);
const courseId = ref<string | null>(null);

// View lookup function with proper context binding
const viewLookupFunction = (viewDescription: any) => {
return allCourses.getView(viewDescription);
};

// Initialize browse view
onMounted(async () => {
try {
Expand Down
Loading