@vue-skuilder/standalone-ui 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ VITE_COUCHDB_SERVER=localhost:5984/
2
+ VITE_COUCHDB_PROTOCOL=http
3
+
4
+ VITE_STATIC_COURSE_IDS=""
5
+
6
+ VITE_DEBUG=true
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Standalone UI Package
2
+
3
+ A template interface for single-course Skuilder applications.
4
+
5
+ Contrasting against `platform-ui`, this package:
6
+
7
+ - Focuses on delivering a specific, scoped curriculum
8
+ - Provides simplified navigation and course-specific theming
9
+
10
+ Within the monorepo, this package acts as a blueprint for the scaffolding tool while providing a reference implementation.
11
+
12
+ When courses are generated via the Skuilder CLI, this template transforms into customized, independent applications.
13
+
14
+ ## Core Dependencies
15
+
16
+ Vue 3, Vuetify 3
17
+
18
+ ## Development
19
+
20
+ The existing `./skuilder.config.js` file contains a devenv config that loads an `anatomy` course from the existing test DB docker image.
21
+
22
+ Before `yarn dev` of the standalone package, the testDB docker image must be loaded via `yarn couchdb:start` in project root.
@@ -0,0 +1,6 @@
1
+ // standalone-ui/cypress/e2e/auth.cy.js
2
+ // Tests for authentication flow including login, registration, and logout
3
+
4
+ describe('Authentication', () => {
5
+ // todo
6
+ });
@@ -0,0 +1,10 @@
1
+ // Basic smoke test for standalone-ui
2
+ describe('Smoke Test', () => {
3
+ it('should load the application', () => {
4
+ cy.visit('/');
5
+
6
+ // Verify that the application loaded
7
+ // Update selector as needed based on your actual UI
8
+ cy.get('body').should('be.visible');
9
+ });
10
+ });
@@ -0,0 +1,36 @@
1
+ // standalone-ui/cypress/support/commands.js
2
+ // Custom commands for Cypress tests
3
+
4
+ // Command for logging in a user
5
+ Cypress.Commands.add('login', (username, password) => {
6
+ cy.visit('/login');
7
+ cy.get('input[name="username"]').type(username);
8
+ cy.get('input[name="password"]').type(password);
9
+ cy.contains('button', 'Log in').click();
10
+
11
+ // Optional: Wait for login to complete and redirect
12
+ cy.url().should('not.include', '/login');
13
+ });
14
+
15
+ // Command for registering a new user
16
+ Cypress.Commands.add('registerUser', (username = null, password = 'securePassword123') => {
17
+ // Generate a unique username if none provided
18
+ const finalUsername = username || `testuser${Date.now()}`;
19
+
20
+ // Visit the signup page
21
+ cy.visit('/signup');
22
+
23
+ // Fill out the registration form
24
+ cy.get('input[name="username"]').type(finalUsername);
25
+ cy.get('input[name="password"]').type(password);
26
+ cy.get('input[name="retypedPassword"]').type(password);
27
+
28
+ // Submit the form
29
+ cy.contains('button', 'Create Account').click();
30
+
31
+ // Wait for registration to complete
32
+ cy.url().should('include', `/u/${finalUsername}/new`);
33
+
34
+ // Return the created username so it can be used in tests
35
+ return cy.wrap(finalUsername);
36
+ });
@@ -0,0 +1,17 @@
1
+ // standalone-ui/cypress/support/e2e.js
2
+ // This is the main support file for Cypress E2E tests
3
+
4
+ // Import commands.js using ES2015 syntax:
5
+ import './commands';
6
+
7
+ // Cypress exception handling
8
+ Cypress.on('uncaught:exception', (err) => {
9
+ // Log the error for debugging
10
+ console.log('Uncaught exception:', err.message);
11
+
12
+ // If the error is from PouchDB, ignore it
13
+ if (err.message.includes('not_found') || err.message.includes('missing')) {
14
+ return false; // Prevents Cypress from failing the test
15
+ }
16
+ return true; // Otherwise, fail the test
17
+ });
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from 'cypress';
2
+
3
+ export default defineConfig({
4
+ e2e: {
5
+ baseUrl: 'http://localhost:6173', // package/standalone-ui vite dev server port
6
+ supportFile: 'cypress/support/e2e.js',
7
+ specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
8
+ setupNodeEvents(on, config) {
9
+ // implement node event listeners here
10
+ },
11
+ },
12
+ // increase the default timeout for slower operations
13
+ defaultCommandTimeout: 10000,
14
+ // Viewport configuration
15
+ viewportWidth: 1280,
16
+ viewportHeight: 800,
17
+ });
package/index.html ADDED
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Skuilder Course</title>
8
+ <link
9
+ href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"
10
+ rel="stylesheet"
11
+ />
12
+ </head>
13
+ <body>
14
+ <div id="app"></div>
15
+ <script type="module" src="/src/main.ts"></script>
16
+ </body>
17
+ </html>
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@vue-skuilder/standalone-ui",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.1",
7
+ "type": "module",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "vite build",
11
+ "preview": "vite preview",
12
+ "test:e2e": "cypress open",
13
+ "test:e2e:headless": "cypress run",
14
+ "ci:e2e": "vite dev & wait-on http://localhost:6173 && cypress run"
15
+ },
16
+ "dependencies": {
17
+ "@mdi/font": "^7.3.67",
18
+ "@vue-skuilder/common-ui": "workspace:*",
19
+ "@vue-skuilder/db": "workspace:*",
20
+ "pinia": "^2.3.0",
21
+ "vue": "^3.5.13",
22
+ "vue-router": "^4.2.0",
23
+ "vuetify": "^3.7.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/cypress": "1.1.6",
27
+ "@vitejs/plugin-vue": "^5.2.1",
28
+ "cypress": "14.1.0",
29
+ "typescript": "^5.7.2",
30
+ "vite": "^6.0.9",
31
+ "vue-tsc": "^1.8.0",
32
+ "wait-on": "8.0.2"
33
+ }
34
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "title": "[dev] Anatomy",
3
+ "course": "e92135c6c3ba6fba79367e7f26001ef3",
4
+ "dataLayerType": "couch"
5
+ }
package/src/App.vue ADDED
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <v-app :theme="theme">
3
+ <course-header :title="courseConfig.title" :logo="courseConfig.logo" />
4
+
5
+ <v-main>
6
+ <router-view />
7
+ </v-main>
8
+
9
+ <!-- <course-footer :links="courseConfig.links" :copyright="courseConfig.copyright" /> -->
10
+ <SkMouseTrap />
11
+
12
+ <v-footer app class="pa-0" color="transparent">
13
+ <v-card flat width="100%" class="text-center">
14
+ <v-card-text class="text-body-2 text-medium-emphasis">
15
+ <v-icon small class="me-1">mdi-keyboard</v-icon>
16
+ Tip: Hold <kbd>Ctrl</kbd> to see keyboard shortcuts or press <kbd>?</kbd> to view all shortcuts
17
+ </v-card-text>
18
+ </v-card>
19
+ </v-footer>
20
+ </v-app>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { computed, onMounted } from 'vue';
25
+ import { useCourseConfig } from './composables/useCourseConfig';
26
+ import CourseHeader from './components/CourseHeader.vue';
27
+ import CourseFooter from './components/CourseFooter.vue';
28
+ import { SkMouseTrap, SkldrMouseTrap } from '@vue-skuilder/common-ui';
29
+
30
+ const { courseConfig } = useCourseConfig();
31
+ const theme = computed(() => (courseConfig.darkMode ? 'dark' : 'light'));
32
+
33
+ onMounted(() => {
34
+ // Add a global shortcut to show the keyboard shortcuts dialog
35
+ SkldrMouseTrap.addBinding({
36
+ hotkey: '?',
37
+ command: 'Show keyboard shortcuts',
38
+ callback: () => {
39
+ const keyboardButton = document.querySelector('.mdi-keyboard');
40
+ if (keyboardButton) {
41
+ (keyboardButton as HTMLElement).click();
42
+ }
43
+ }
44
+ });
45
+ });
46
+ </script>
@@ -0,0 +1,76 @@
1
+ // vue-skuilder/packages/standalone-ui/src/ENVIRONMENT_VARS.ts
2
+ type ProtocolString = 'http' | 'https';
3
+
4
+ import config from '../skuilder.config.json';
5
+
6
+ export interface Environment {
7
+ /**
8
+ * URL to the remote couchDB instance that the app connects to.
9
+ * Loaded from VITE_COUCHDB_SERVER_URL environment variable.
10
+ */
11
+ COUCHDB_SERVER_URL: string;
12
+
13
+ /**
14
+ * Protocol for the CouchDB server.
15
+ * Loaded from VITE_COUCHDB_SERVER_PROTOCOL environment variable.
16
+ */
17
+ COUCHDB_SERVER_PROTOCOL: ProtocolString;
18
+
19
+ /**
20
+ * Static course IDs to load.
21
+ * Loaded from VITE_STATIC_COURSE_IDS environment variable (comma-separated string).
22
+ */
23
+ STATIC_COURSE_ID: string;
24
+
25
+ /**
26
+ * Type of data layer to use - couchdb live backend or
27
+ * statically built and served from the app.
28
+ */
29
+ DATALAYER_TYPE: 'couch' | 'static';
30
+
31
+ /**
32
+ * A global flag to enable debug messaging mode.
33
+ * Loaded from VITE_DEBUG environment variable ('true' or 'false').
34
+ */
35
+ DEBUG: boolean;
36
+ }
37
+
38
+ // Default fallback values if environment variables are not set
39
+ const defaultEnv: Environment = {
40
+ COUCHDB_SERVER_URL: 'localhost:5984/', // Sensible default for local dev
41
+ COUCHDB_SERVER_PROTOCOL: 'http',
42
+ STATIC_COURSE_ID: 'not_set',
43
+ DATALAYER_TYPE: 'couch',
44
+ DEBUG: false, // Default to false if VITE_DEBUG is not 'true'
45
+ };
46
+
47
+ // --- Read Environment Variables using Vite's import.meta.env ---
48
+ // Vite replaces these variables at build time with the values from your .env files.
49
+
50
+ if (config.dataLayerType !== 'couch' && config.dataLayerType !== 'static') {
51
+ throw new Error('Invalid data layer type');
52
+ }
53
+
54
+ const ENV: Environment = {
55
+ // Use the value from import.meta.env if available, otherwise use the default
56
+ COUCHDB_SERVER_URL: import.meta.env.VITE_COUCHDB_SERVER || defaultEnv.COUCHDB_SERVER_URL,
57
+
58
+ // Ensure the protocol is one of the allowed types
59
+ COUCHDB_SERVER_PROTOCOL: (import.meta.env.VITE_COUCHDB_PROTOCOL === 'https'
60
+ ? 'https'
61
+ : 'http') as ProtocolString,
62
+
63
+ STATIC_COURSE_ID: config.course,
64
+
65
+ DATALAYER_TYPE: config.dataLayerType,
66
+
67
+ // Environment variables are always strings, so compare VITE_DEBUG to 'true'
68
+ DEBUG: import.meta.env.VITE_DEBUG === 'true',
69
+ };
70
+
71
+ // Optional: Log the resolved environment in development mode for debugging
72
+ if (import.meta.env.DEV) {
73
+ console.log('Resolved Environment Variables:', ENV);
74
+ }
75
+
76
+ export default ENV;
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <v-footer app padless>
3
+ <v-container fluid>
4
+ <v-row justify="center" align="center">
5
+ <v-col cols="12" sm="8" class="text-center">
6
+ <div v-if="links && links.length" class="d-flex justify-center mb-2">
7
+ <v-btn
8
+ v-for="link in links"
9
+ :key="link.text"
10
+ :href="link.url"
11
+ text
12
+ variant="plain"
13
+ size="small"
14
+ class="mx-1"
15
+ >
16
+ {{ link.text }}
17
+ </v-btn>
18
+ </div>
19
+ <div class="text-body-2 text-medium-emphasis">
20
+ {{ copyright }}
21
+ </div>
22
+ </v-col>
23
+ </v-row>
24
+ </v-container>
25
+ </v-footer>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ defineProps<{
30
+ links?: Array<{ text: string; url: string }>;
31
+ copyright?: string;
32
+ }>();
33
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <v-app-bar>
3
+ <v-app-bar-nav-icon v-if="isMobile"></v-app-bar-nav-icon>
4
+
5
+ <v-toolbar-title class="d-flex align-center">
6
+ <img v-if="logo" :src="logo" alt="Course Logo" height="32" class="mr-2" />
7
+ {{ title }}
8
+ </v-toolbar-title>
9
+
10
+ <v-spacer></v-spacer>
11
+
12
+ <div v-if="!isMobile" class="d-flex">
13
+ <v-btn v-for="item in menuItems" :key="item.text" :to="item.path" text>
14
+ {{ item.text }}
15
+ </v-btn>
16
+ </div>
17
+
18
+ <v-divider vertical class="mx-2"></v-divider>
19
+
20
+ <UserLoginAndRegistrationContainer :show-login-button="true" redirect-to-path="/study" />
21
+ </v-app-bar>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { ref, computed, onMounted } from 'vue';
26
+ import { useDisplay } from 'vuetify';
27
+ import { UserLoginAndRegistrationContainer, useAuthStore } from '@vue-skuilder/common-ui';
28
+ import { getDataLayer } from '@vue-skuilder/db';
29
+
30
+ const props = defineProps<{
31
+ title: string;
32
+ logo?: string;
33
+ }>();
34
+
35
+ const { mobile } = useDisplay();
36
+ const isMobile = computed(() => mobile.value);
37
+
38
+ const menuItems = ref([
39
+ { text: 'Home', path: '/' },
40
+ { text: 'Study', path: '/study' },
41
+ { text: 'Progress', path: '/progress' },
42
+ ]);
43
+ </script>
@@ -0,0 +1,33 @@
1
+ import { ref, readonly } from 'vue';
2
+ import config from '../../skuilder.config.json';
3
+
4
+ // This would be replaced by actual course configuration in a real implementation
5
+ const defaultConfig = {
6
+ title: config.title ? config.title : '[UNSET] Course Title',
7
+ description: 'This is the devenv test course setup.',
8
+ logo: '',
9
+ darkMode: false,
10
+ links: [
11
+ { text: 'About', url: '/about' },
12
+ { text: 'Help', url: '/help' },
13
+ ],
14
+ copyright: '',
15
+ };
16
+
17
+ export function useCourseConfig() {
18
+ const courseConfig = ref(defaultConfig);
19
+
20
+ // Later this would load from a configuration file or API
21
+ const loadConfig = async () => {
22
+ // In a real implementation, this would load configuration
23
+ // courseConfig.value = await loadCourseConfig();
24
+ };
25
+
26
+ // Initialize
27
+ loadConfig();
28
+
29
+ return {
30
+ courseConfig: readonly(courseConfig),
31
+ loadConfig,
32
+ };
33
+ }
package/src/main.ts ADDED
@@ -0,0 +1,60 @@
1
+ import ENV from './ENVIRONMENT_VARS';
2
+ import '@mdi/font/css/materialdesignicons.css';
3
+
4
+ import { createApp } from 'vue';
5
+ import { createPinia } from 'pinia';
6
+ import App from './App.vue';
7
+ import router from './router';
8
+
9
+ // Vuetify
10
+ import 'vuetify/styles';
11
+ import { createVuetify } from 'vuetify';
12
+ import * as components from 'vuetify/components';
13
+ import * as directives from 'vuetify/directives';
14
+ import { aliases, mdi } from 'vuetify/iconsets/mdi';
15
+
16
+ // data layer
17
+ import { initializeDataLayer } from '@vue-skuilder/db';
18
+
19
+ // auth store
20
+ import { useAuthStore } from '@vue-skuilder/common-ui';
21
+
22
+ (async () => {
23
+ await initializeDataLayer({
24
+ type: 'pouch',
25
+ options: {
26
+ COUCHDB_SERVER_URL: ENV.COUCHDB_SERVER_URL,
27
+ COUCHDB_SERVER_PROTOCOL: ENV.COUCHDB_SERVER_PROTOCOL,
28
+ COURSE_IDS: [ENV.STATIC_COURSE_ID],
29
+ },
30
+ });
31
+ const pinia = createPinia();
32
+
33
+ const vuetify = createVuetify({
34
+ components,
35
+ directives,
36
+ theme: {
37
+ defaultTheme: 'light',
38
+ },
39
+ icons: {
40
+ defaultSet: 'mdi',
41
+ aliases,
42
+ sets: {
43
+ mdi,
44
+ },
45
+ },
46
+ });
47
+
48
+ const app = createApp(App);
49
+
50
+ app.use(router);
51
+ app.use(vuetify);
52
+ app.use(pinia);
53
+
54
+ const { piniaPlugin } = await import('@vue-skuilder/common-ui');
55
+ app.use(piniaPlugin, { pinia });
56
+
57
+ await useAuthStore().init();
58
+
59
+ app.mount('#app');
60
+ })();
@@ -0,0 +1,41 @@
1
+ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
2
+ import HomeView from '../views/HomeView.vue';
3
+ import StudyView from '../views/StudyView.vue';
4
+ import ProgressView from '../views/ProgressView.vue';
5
+ import { UserLogin, UserRegistration } from '@vue-skuilder/common-ui';
6
+
7
+ const routes: Array<RouteRecordRaw> = [
8
+ {
9
+ path: '/',
10
+ name: 'home',
11
+ component: HomeView,
12
+ },
13
+ {
14
+ path: '/study',
15
+ name: 'study',
16
+ component: StudyView,
17
+ },
18
+ {
19
+ path: '/progress',
20
+ name: 'progress',
21
+ component: ProgressView,
22
+ },
23
+ {
24
+ path: '/login',
25
+ name: 'login',
26
+ component: UserLogin,
27
+ },
28
+ {
29
+ path: '/register',
30
+ alias: '/signup',
31
+ name: 'Register',
32
+ component: UserRegistration,
33
+ },
34
+ ];
35
+
36
+ const router = createRouter({
37
+ history: createWebHistory(),
38
+ routes,
39
+ });
40
+
41
+ export default router;
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <v-container>
3
+ <v-row>
4
+ <v-col cols="12" class="text-center">
5
+ <h1 class="text-h3 mb-6">{{ courseConfig.title }}</h1>
6
+ <p class="text-body-1 mb-6">{{ courseConfig.description }}</p>
7
+
8
+ <div v-if="isLoggedIn">
9
+ <v-btn color="primary" size="large" to="/study"> Start Learning </v-btn>
10
+ </div>
11
+ <div v-else class="d-flex flex-column align-center">
12
+ <p class="text-body-1 mb-4">Please login to start learning</p>
13
+ <UserLoginAndRegistrationContainer v-model="authDialogOpen" redirect-to-path="/study">
14
+ <template #activator="{ props }">
15
+ <v-btn color="primary" size="large" v-bind="props"> Login / Sign Up </v-btn>
16
+ </template>
17
+ </UserLoginAndRegistrationContainer>
18
+ </div>
19
+ </v-col>
20
+ </v-row>
21
+ </v-container>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { ref, onMounted, computed } from 'vue';
26
+ import { useCourseConfig } from '../composables/useCourseConfig';
27
+ import { UserLoginAndRegistrationContainer, useAuthStore } from '@vue-skuilder/common-ui';
28
+ import { getDataLayer } from '@vue-skuilder/db';
29
+
30
+ const { courseConfig } = useCourseConfig();
31
+ const authStore = useAuthStore();
32
+ const authDialogOpen = ref(false);
33
+
34
+ const isLoggedIn = computed(() => authStore.isLoggedIn);
35
+ </script>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <v-container>
3
+ <v-row>
4
+ <v-col cols="12">
5
+ <h1 class="text-h4 mb-4">Your Progress</h1>
6
+
7
+ <!-- Placeholder for progress component -->
8
+ <v-card class="pa-4">
9
+ <p>Progress tracking will be shown here.</p>
10
+ </v-card>
11
+ </v-col>
12
+ </v-row>
13
+ </v-container>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ // Would import from common-ui or db package
18
+ </script>
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <v-container>
3
+ <v-row>
4
+ <v-col cols="12">
5
+ <h1 class="text-h4 mb-4">Study Session</h1>
6
+
7
+ <div v-if="sessionPrepared">
8
+ <StudySession
9
+ :content-sources="sessionContentSources"
10
+ :session-time-limit="sessionTimeLimit"
11
+ :user="user"
12
+ :session-config="studySessionConfig"
13
+ :data-layer="dataLayer"
14
+ :get-view-component="getViewComponent"
15
+ @session-finished="handleSessionFinished"
16
+ />
17
+ </div>
18
+ <div v-else>
19
+ <v-card class="pa-4">
20
+ <v-progress-circular indeterminate color="primary"></v-progress-circular>
21
+ <p class="mt-4">Preparing study session...</p>
22
+ </v-card>
23
+ </div>
24
+ </v-col>
25
+ </v-row>
26
+ </v-container>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { ref, onMounted } from 'vue';
31
+ import { ContentSourceID, getDataLayer, UserDBInterface } from '@vue-skuilder/db';
32
+ import { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';
33
+ import { allCourses } from '@vue-skuilder/courses';
34
+ import { useRouter } from 'vue-router';
35
+ import ENV from '../ENVIRONMENT_VARS';
36
+
37
+ const router = useRouter();
38
+ const user = getDataLayer().getUserDB();
39
+ const dataLayer = getDataLayer();
40
+ const sessionPrepared = ref(false);
41
+ const sessionTimeLimit = ref(5); // 5 minutes
42
+ const sessionContentSources = ref<ContentSourceID[]>([]);
43
+ const studySessionConfig = ref<StudySessionConfig>({
44
+ likesConfetti: true,
45
+ });
46
+
47
+ // Function to get view component from courses
48
+ const getViewComponent = (view_id: string) => allCourses.getView(view_id);
49
+
50
+ // Initialize study session with course from environment vars
51
+ const initStudySession = async () => {
52
+ // Check if course ID is valid
53
+ if (!ENV.STATIC_COURSE_ID || ENV.STATIC_COURSE_ID === 'not_set') {
54
+ console.error('[StudyView] No course ID specified in environment vars!');
55
+ return;
56
+ }
57
+
58
+ console.log(`[StudyView] Starting study session for course: ${ENV.STATIC_COURSE_ID}`);
59
+
60
+ // Set the content source to the course ID from environment vars
61
+ sessionContentSources.value = [
62
+ {
63
+ type: 'course',
64
+ id: ENV.STATIC_COURSE_ID,
65
+ },
66
+ ];
67
+
68
+ // Mark the session as prepared
69
+ sessionPrepared.value = true;
70
+ };
71
+
72
+ // Handle session finished
73
+ const handleSessionFinished = () => {
74
+ // router.go(0); // Refresh the page
75
+ };
76
+
77
+ // Initialize on component mount
78
+ onMounted(async () => {
79
+ // user.value = await getCurrentUser();
80
+
81
+ // if (user.value) {
82
+ await initStudySession();
83
+ // } else {
84
+ // console.error('[StudyView] No user available!');
85
+ // }
86
+ });
87
+ </script>
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "strict": true,
8
+ "jsx": "preserve",
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "lib": ["ESNext", "DOM"],
13
+ "skipLibCheck": true,
14
+ "noEmit": true,
15
+ "baseUrl": ".",
16
+ "paths": {
17
+ "@/*": ["./src/*"]
18
+ },
19
+ "types": ["vite/client"]
20
+ },
21
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
22
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,66 @@
1
+ // packages/standalone-ui/vite.config.ts
2
+ import { defineConfig } from 'vite';
3
+ import vue from '@vitejs/plugin-vue';
4
+ import { resolve } from 'path';
5
+ import { fileURLToPath, URL } from 'node:url'; // Import necessary Node.js modules
6
+
7
+ export default defineConfig({
8
+ plugins: [vue()],
9
+ resolve: {
10
+ alias: {
11
+ // Alias for internal src paths
12
+ '@': fileURLToPath(new URL('./src', import.meta.url)), // Use fileURLToPath and URL for robustness
13
+
14
+ // --- Link workspace packages to their SOURCE ---
15
+ // Adjust relative paths as needed based on your monorepo structure
16
+ '@vue-skuilder/common-ui': resolve(__dirname, '../../packages/common-ui/src/index.ts'),
17
+
18
+ // Add events alias if needed (often required by dependencies)
19
+ events: 'events',
20
+
21
+ // You might need to alias vue-router if dedupe isn't enough, though unlikely
22
+ // 'vue-router': resolve(__dirname, 'node_modules/vue-router'),
23
+ },
24
+ extensions: ['.js', '.ts', '.json', '.vue'], // Keep standard extensions
25
+ dedupe: [
26
+ // Ensure single instances of core libs and workspace packages
27
+ 'vue',
28
+ 'vuetify',
29
+ 'pinia',
30
+ 'vue-router',
31
+ '@vue-skuilder/db',
32
+ '@vue-skuilder/common',
33
+ '@vue-skuilder/common-ui',
34
+ '@vue-skuilder/courses',
35
+ ],
36
+ },
37
+ // --- Important for linked source dependencies ---
38
+ optimizeDeps: {
39
+ // Help Vite pre-bundle dependencies from linked packages
40
+ include: [
41
+ '@vue-skuilder/common-ui',
42
+ '@vue-skuilder/db',
43
+ '@vue-skuilder/common',
44
+ '@vue-skuilder/courses',
45
+ ],
46
+ },
47
+ server: {
48
+ port: 6173, // distinct from platform-ui
49
+ },
50
+ build: {
51
+ sourcemap: true, // Keep sourcemaps for build
52
+ target: 'es2020', // Match target from platform-ui
53
+ minify: 'terser', // Match minify from platform-ui
54
+ terserOptions: {
55
+ // Match terserOptions from platform-ui
56
+ keep_classnames: true,
57
+ },
58
+ },
59
+ // Add define block if standalone-ui needs process polyfills (like platform-ui did)
60
+ define: {
61
+ global: 'window',
62
+ 'process.env': process.env,
63
+ 'process.browser': true,
64
+ 'process.version': JSON.stringify(process.version),
65
+ },
66
+ });