@vue-skuilder/cli 0.1.8 → 0.1.10
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.
- package/dist/standalone-ui-template/package.json +61 -0
- package/dist/standalone-ui-template/src/App.vue +62 -0
- package/dist/standalone-ui-template/src/ENVIRONMENT_VARS.ts +76 -0
- package/dist/standalone-ui-template/src/components/CourseFooter.vue +33 -0
- package/dist/standalone-ui-template/src/components/CourseHeader.vue +55 -0
- package/dist/standalone-ui-template/src/composables/useCourseConfig.ts +33 -0
- package/dist/standalone-ui-template/src/main.ts +201 -0
- package/dist/standalone-ui-template/src/questions/MultipleChoiceQuestion.ts +46 -0
- package/dist/standalone-ui-template/src/questions/MultipleChoiceQuestionView.vue +50 -0
- package/dist/standalone-ui-template/src/questions/NumberRangeQuestion.ts +44 -0
- package/dist/standalone-ui-template/src/questions/NumberRangeQuestionView.vue +43 -0
- package/dist/standalone-ui-template/src/questions/README.md +129 -0
- package/dist/standalone-ui-template/src/questions/SimpleTextQuestion.test.ts +25 -0
- package/dist/standalone-ui-template/src/questions/SimpleTextQuestion.ts +40 -0
- package/dist/standalone-ui-template/src/questions/SimpleTextQuestionView.vue +46 -0
- package/dist/standalone-ui-template/src/questions/exampleCourse.ts +10 -0
- package/dist/standalone-ui-template/src/questions/index.ts +117 -0
- package/dist/standalone-ui-template/src/router/index.ts +59 -0
- package/dist/standalone-ui-template/src/views/BrowseView.vue +41 -0
- package/dist/standalone-ui-template/src/views/HomeView.vue +35 -0
- package/dist/standalone-ui-template/src/views/ProgressView.vue +18 -0
- package/dist/standalone-ui-template/src/views/StudyView.vue +84 -0
- package/dist/standalone-ui-template/src/views/UserSettingsView.vue +175 -0
- package/dist/standalone-ui-template/src/views/UserStatsView.vue +76 -0
- package/dist/standalone-ui-template/tsconfig.json +17 -0
- package/dist/standalone-ui-template/vite.config.ts +103 -0
- package/dist/utils/template.d.ts +1 -1
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +8 -3
- package/dist/utils/template.js.map +1 -1
- package/package.json +11 -11
- package/src/utils/template.ts +9 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<p>{{ questionText }}</p>
|
|
4
|
+
<input type="number" v-model.number="userAnswer" @keyup.enter="submitAnswer" placeholder="Enter a number" />
|
|
5
|
+
<button @click="submitAnswer">Submit</button>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { ref, PropType } from 'vue';
|
|
11
|
+
import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
|
|
12
|
+
import { NumberRangeQuestion } from './NumberRangeQuestion';
|
|
13
|
+
import { ViewData } from '@vue-skuilder/common';
|
|
14
|
+
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
questionText: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
data: {
|
|
21
|
+
type: Array as PropType<ViewData[]>,
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const userAnswer = ref<number | null>(null);
|
|
27
|
+
|
|
28
|
+
const viewableUtils = useViewable(props, () => {}, 'NumberRangeQuestionView');
|
|
29
|
+
const questionUtils = useQuestionView<NumberRangeQuestion>(viewableUtils);
|
|
30
|
+
|
|
31
|
+
// Initialize question
|
|
32
|
+
questionUtils.question.value = new NumberRangeQuestion(props.data);
|
|
33
|
+
|
|
34
|
+
const submitAnswer = () => {
|
|
35
|
+
if (userAnswer.value !== null) {
|
|
36
|
+
questionUtils.submitAnswer({ response: userAnswer.value });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<style scoped>
|
|
42
|
+
/* Add some basic styling if needed */
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Custom Questions in Standalone UI
|
|
2
|
+
|
|
3
|
+
This directory contains example implementations of custom question types for the Vue Skuilder platform. These examples demonstrate how to create new `Question` subclasses and their corresponding Vue components, and how to integrate them into your application.
|
|
4
|
+
|
|
5
|
+
## Example Questions Provided
|
|
6
|
+
|
|
7
|
+
- **SimpleTextQuestion**: A basic question that asks for a text input and checks for an exact string match.
|
|
8
|
+
- **MultipleChoiceQuestion**: Presents a question with multiple options and checks for the correct selection.
|
|
9
|
+
- **NumberRangeQuestion**: Asks for a numeric input and validates if it falls within a specified range.
|
|
10
|
+
|
|
11
|
+
## How to Use These Examples
|
|
12
|
+
|
|
13
|
+
Each question type consists of two main parts:
|
|
14
|
+
1. A TypeScript file (`.ts`) defining the `Question` subclass, which handles the question logic, data shapes, and answer evaluation.
|
|
15
|
+
2. A Vue component file (`.vue`) that provides the user interface for the question.
|
|
16
|
+
|
|
17
|
+
These examples are already integrated into the `exampleCourse.ts` file, which you can use to see them in action.
|
|
18
|
+
|
|
19
|
+
## Integrating Custom Questions into Your Course at Runtime
|
|
20
|
+
|
|
21
|
+
To use your custom questions in a course, you need to:
|
|
22
|
+
|
|
23
|
+
1. **Define your Question Class**: Create a new TypeScript file (e.g., `MyCustomQuestion.ts`) that extends the `Question` class from `@vue-skuilder/courseware`. Define its `dataShapes` and `views` static properties.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// MyCustomQuestion.ts
|
|
27
|
+
import { Question, DataShape, ViewData, Answer } from '@vue-skuilder/courseware';
|
|
28
|
+
import { FieldType } from '@vue-skuilder/common';
|
|
29
|
+
import MyCustomQuestionView from './MyCustomQuestionView.vue';
|
|
30
|
+
|
|
31
|
+
export class MyCustomQuestion extends Question {
|
|
32
|
+
public static dataShapes: DataShape[] = [
|
|
33
|
+
new DataShape('MyCustomQuestion', [
|
|
34
|
+
{ name: 'myField', type: FieldType.STRING },
|
|
35
|
+
]),
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
public static views = [
|
|
39
|
+
{ name: 'MyCustomQuestionView', component: MyCustomQuestionView },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
constructor(data: ViewData[]) {
|
|
43
|
+
super(data);
|
|
44
|
+
// Initialize your question data from `data`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public dataShapes(): DataShape[] {
|
|
48
|
+
return MyCustomQuestion.dataShapes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public views() {
|
|
52
|
+
return MyCustomQuestion.views;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected isCorrect(answer: Answer): boolean {
|
|
56
|
+
// Implement your answer evaluation logic here
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. **Create Your Vue Component**: Create a Vue component (e.g., `MyCustomQuestionView.vue`) that will render your question and allow user interaction. This component will receive props based on the `ViewData` you define for your question.
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<!-- MyCustomQuestionView.vue -->
|
|
66
|
+
<template>
|
|
67
|
+
<div>
|
|
68
|
+
<p>{{ questionData.myField }}</p>
|
|
69
|
+
<!-- Your input elements and UI -->
|
|
70
|
+
<button @click="submitAnswer">Submit</button>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<script setup lang="ts">
|
|
75
|
+
import { ref } from 'vue';
|
|
76
|
+
import { useStudySessionStore } from '@vue-skuilder/common-ui';
|
|
77
|
+
|
|
78
|
+
const props = defineProps({
|
|
79
|
+
// Define props based on your question's data
|
|
80
|
+
questionData: { type: Object, required: true },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const studySessionStore = useStudySessionStore();
|
|
84
|
+
const userAnswer = ref('');
|
|
85
|
+
|
|
86
|
+
const submitAnswer = () => {
|
|
87
|
+
// Collect user's answer and submit it
|
|
88
|
+
studySessionStore.submitAnswer({ response: userAnswer.value });
|
|
89
|
+
};
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
3. **Register Your Question and Course**: In your application's entry point (e.g., `src/main.ts` or `src/App.vue`), you need to import your custom question and include it in a `Course` instance. Then, register this course with the `allCourses` list.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// src/main.ts (example)
|
|
97
|
+
import { createApp } from 'vue';
|
|
98
|
+
import App from './App.vue';
|
|
99
|
+
import { createPinia } from 'pinia';
|
|
100
|
+
import { allCourses, Course } from '@vue-skuilder/courseware';
|
|
101
|
+
|
|
102
|
+
// Import your custom question
|
|
103
|
+
import { MyCustomQuestion } from './questions/MyCustomQuestion';
|
|
104
|
+
|
|
105
|
+
// Create a new Course instance with your custom question
|
|
106
|
+
const myCustomCourse = new Course('MyCustomCourse', [
|
|
107
|
+
new MyCustomQuestion([{ myField: 'Hello Custom Question!' }]),
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
// Add your custom course to the global allCourses list
|
|
111
|
+
allCourses.courses.push(myCustomCourse);
|
|
112
|
+
|
|
113
|
+
const app = createApp(App);
|
|
114
|
+
app.use(createPinia());
|
|
115
|
+
app.mount('#app');
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Note**: The `allCourses` object is a singleton that manages all available courses and their associated questions and views. By adding your custom course to `allCourses.courses`, it becomes discoverable by the `CardViewer` and other components that rely on the course registry.
|
|
119
|
+
|
|
120
|
+
## Developing New Questions
|
|
121
|
+
|
|
122
|
+
When developing new questions, consider the following:
|
|
123
|
+
|
|
124
|
+
- **DataShape Definition**: Carefully define the `DataShape` for your question. This dictates the structure of the data that will be passed to your question's constructor and Vue component.
|
|
125
|
+
- **Answer Evaluation**: Implement the `isCorrect` method in your `Question` subclass to define how user answers are evaluated.
|
|
126
|
+
- **Vue Component Props**: Ensure your Vue component's `props` match the data fields defined in your `DataShape` and any additional data you pass from your `Question` instance.
|
|
127
|
+
- **StudySessionStore**: Use the `useStudySessionStore()` composable from `@vue-skuilder/common-ui` to submit user answers and interact with the study session logic.
|
|
128
|
+
|
|
129
|
+
Feel free to modify and extend the provided examples to suit your needs.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SimpleTextQuestion } from './SimpleTextQuestion';
|
|
3
|
+
|
|
4
|
+
describe('SimpleTextQuestion', () => {
|
|
5
|
+
it('should correctly evaluate a correct answer', () => {
|
|
6
|
+
const question = new SimpleTextQuestion([
|
|
7
|
+
{ questionText: 'What is the capital of France?', correctAnswer: 'Paris' },
|
|
8
|
+
]);
|
|
9
|
+
expect(question.evaluate({ response: 'Paris' }, 0).isCorrect).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should correctly evaluate an incorrect answer', () => {
|
|
13
|
+
const question = new SimpleTextQuestion([
|
|
14
|
+
{ questionText: 'What is the capital of France?', correctAnswer: 'Paris' },
|
|
15
|
+
]);
|
|
16
|
+
expect(question.evaluate({ response: 'London' }, 0).isCorrect).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should be case-insensitive', () => {
|
|
20
|
+
const question = new SimpleTextQuestion([
|
|
21
|
+
{ questionText: 'What is the capital of France?', correctAnswer: 'Paris' },
|
|
22
|
+
]);
|
|
23
|
+
expect(question.evaluate({ response: 'paris' }, 0).isCorrect).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
|
|
2
|
+
import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
|
|
3
|
+
import SimpleTextQuestionView from './SimpleTextQuestionView.vue';
|
|
4
|
+
|
|
5
|
+
export class SimpleTextQuestion extends Question {
|
|
6
|
+
public static dataShapes: DataShape[] = [
|
|
7
|
+
{
|
|
8
|
+
name: 'SimpleTextQuestion' as DataShapeName,
|
|
9
|
+
fields: [
|
|
10
|
+
{ name: 'questionText', type: FieldType.STRING },
|
|
11
|
+
{ name: 'correctAnswer', type: FieldType.STRING },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
public static views = [{ name: 'SimpleTextQuestionView', component: SimpleTextQuestionView }];
|
|
17
|
+
|
|
18
|
+
// @ts-expect-error TS6133: Used in Vue template
|
|
19
|
+
private questionText: string;
|
|
20
|
+
private correctAnswer: string;
|
|
21
|
+
|
|
22
|
+
constructor(data: ViewData[]) {
|
|
23
|
+
super(data);
|
|
24
|
+
this.questionText = data[0].questionText as string;
|
|
25
|
+
this.correctAnswer = data[0].correctAnswer as string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public dataShapes(): DataShape[] {
|
|
29
|
+
return SimpleTextQuestion.dataShapes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public views() {
|
|
33
|
+
// This will be dynamically populated or imported
|
|
34
|
+
return SimpleTextQuestion.views;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected isCorrect(answer: Answer): boolean {
|
|
38
|
+
return (answer.response as string).toLowerCase() === this.correctAnswer.toLowerCase();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<p>{{ questionText }}</p>
|
|
4
|
+
<input v-model="userAnswer" @keyup.enter="submitAnswer" placeholder="Your answer" />
|
|
5
|
+
<button @click="submitAnswer">Submit</button>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { ref, onMounted, PropType } from 'vue';
|
|
11
|
+
import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
|
|
12
|
+
import { SimpleTextQuestion } from './SimpleTextQuestion';
|
|
13
|
+
import { ViewData } from '@vue-skuilder/common';
|
|
14
|
+
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
questionText: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
data: {
|
|
21
|
+
type: Array as PropType<ViewData[]>,
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const userAnswer = ref('');
|
|
27
|
+
|
|
28
|
+
const viewableUtils = useViewable(props, () => {}, 'SimpleTextQuestionView');
|
|
29
|
+
const questionUtils = useQuestionView<SimpleTextQuestion>(viewableUtils);
|
|
30
|
+
|
|
31
|
+
// Initialize question
|
|
32
|
+
questionUtils.question.value = new SimpleTextQuestion(props.data);
|
|
33
|
+
|
|
34
|
+
const submitAnswer = () => {
|
|
35
|
+
questionUtils.submitAnswer({ response: userAnswer.value });
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
// Optionally, you might want to focus the input field on mount
|
|
40
|
+
// This requires a ref on the input element
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
/* Add some basic styling if needed */
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CourseWare } from '@vue-skuilder/courseware';
|
|
2
|
+
import { SimpleTextQuestion } from './SimpleTextQuestion';
|
|
3
|
+
import { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
|
|
4
|
+
import { NumberRangeQuestion } from './NumberRangeQuestion';
|
|
5
|
+
|
|
6
|
+
export const exampleCourse = new CourseWare('ExampleCourse', [
|
|
7
|
+
SimpleTextQuestion,
|
|
8
|
+
MultipleChoiceQuestion,
|
|
9
|
+
NumberRangeQuestion,
|
|
10
|
+
]);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Library entry point for custom questions in standalone-ui
|
|
2
|
+
// This file exports question types and components for consumption by studio-ui
|
|
3
|
+
|
|
4
|
+
import { CourseWare } from '@vue-skuilder/courseware';
|
|
5
|
+
import { DataShape } from '@vue-skuilder/common';
|
|
6
|
+
import { ViewComponent } from '@vue-skuilder/common-ui';
|
|
7
|
+
|
|
8
|
+
// [ ] todo: simplify exports here. Only the final 'bundle' is strictly required.
|
|
9
|
+
|
|
10
|
+
// Export individual question classes
|
|
11
|
+
export { SimpleTextQuestion } from './SimpleTextQuestion';
|
|
12
|
+
export { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
|
|
13
|
+
export { NumberRangeQuestion } from './NumberRangeQuestion';
|
|
14
|
+
|
|
15
|
+
// Export example course
|
|
16
|
+
export { exampleCourse } from './exampleCourse';
|
|
17
|
+
|
|
18
|
+
// Import components for re-export
|
|
19
|
+
import SimpleTextQuestionView from './SimpleTextQuestionView.vue';
|
|
20
|
+
import MultipleChoiceQuestionView from './MultipleChoiceQuestionView.vue';
|
|
21
|
+
import NumberRangeQuestionView from './NumberRangeQuestionView.vue';
|
|
22
|
+
|
|
23
|
+
// Export Vue components
|
|
24
|
+
export { SimpleTextQuestionView, MultipleChoiceQuestionView, NumberRangeQuestionView };
|
|
25
|
+
|
|
26
|
+
// Import classes for analysis
|
|
27
|
+
import { SimpleTextQuestion } from './SimpleTextQuestion';
|
|
28
|
+
import { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
|
|
29
|
+
import { NumberRangeQuestion } from './NumberRangeQuestion';
|
|
30
|
+
import { exampleCourse } from './exampleCourse';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Main function to export all custom questions for studio-ui consumption
|
|
34
|
+
* This provides a standardized interface for the CLI to discover and integrate
|
|
35
|
+
* custom question types into studio-ui builds
|
|
36
|
+
*/
|
|
37
|
+
export function allCustomQuestions() {
|
|
38
|
+
// Collect all question classes
|
|
39
|
+
const questionClasses = [SimpleTextQuestion, MultipleChoiceQuestion, NumberRangeQuestion];
|
|
40
|
+
|
|
41
|
+
// Collect all data shapes from questions
|
|
42
|
+
const dataShapes: DataShape[] = [];
|
|
43
|
+
questionClasses.forEach((questionClass) => {
|
|
44
|
+
if (questionClass.dataShapes) {
|
|
45
|
+
questionClass.dataShapes.forEach((shape) => {
|
|
46
|
+
// Avoid duplicates
|
|
47
|
+
if (!dataShapes.find((existing) => existing.name === shape.name)) {
|
|
48
|
+
dataShapes.push(shape);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Collect all view components from questions
|
|
55
|
+
const views: ViewComponent[] = [];
|
|
56
|
+
questionClasses.forEach((questionClass) => {
|
|
57
|
+
if (questionClass.views) {
|
|
58
|
+
questionClass.views.forEach((view) => {
|
|
59
|
+
// Avoid duplicates by name
|
|
60
|
+
if (!views.find((existing) => existing.name === view.name)) {
|
|
61
|
+
views.push(view);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const courses = [exampleCourse];
|
|
68
|
+
|
|
69
|
+
// Return structured data for studio-ui integration
|
|
70
|
+
return {
|
|
71
|
+
// CourseWare instances with question instances
|
|
72
|
+
courses,
|
|
73
|
+
|
|
74
|
+
// Question class constructors for registration
|
|
75
|
+
questionClasses,
|
|
76
|
+
|
|
77
|
+
// Available data shapes for studio-ui CreateCardView
|
|
78
|
+
dataShapes,
|
|
79
|
+
|
|
80
|
+
// Vue components for runtime registration
|
|
81
|
+
views,
|
|
82
|
+
|
|
83
|
+
// Metadata for debugging and analysis
|
|
84
|
+
meta: {
|
|
85
|
+
questionCount: questionClasses.length,
|
|
86
|
+
dataShapeCount: dataShapes.length,
|
|
87
|
+
viewCount: views.length,
|
|
88
|
+
courseCount: courses.length,
|
|
89
|
+
packageName: '@vue-skuilder/standalone-ui',
|
|
90
|
+
sourceDirectory: 'src/questions',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Type definitions for the custom questions export structure
|
|
97
|
+
* This provides TypeScript support for CLI and studio-ui integration
|
|
98
|
+
*/
|
|
99
|
+
export interface CustomQuestionsExport {
|
|
100
|
+
courses: CourseWare[];
|
|
101
|
+
questionClasses: Array<
|
|
102
|
+
typeof SimpleTextQuestion | typeof MultipleChoiceQuestion | typeof NumberRangeQuestion
|
|
103
|
+
>;
|
|
104
|
+
dataShapes: DataShape[];
|
|
105
|
+
views: ViewComponent[];
|
|
106
|
+
meta: {
|
|
107
|
+
questionCount: number;
|
|
108
|
+
dataShapeCount: number;
|
|
109
|
+
viewCount: number;
|
|
110
|
+
courseCount: number;
|
|
111
|
+
packageName: string;
|
|
112
|
+
sourceDirectory: string;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Default export for convenience
|
|
117
|
+
export default allCustomQuestions;
|
|
@@ -0,0 +1,59 @@
|
|
|
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 BrowseView from '../views/BrowseView.vue';
|
|
6
|
+
import UserStatsView from '../views/UserStatsView.vue';
|
|
7
|
+
import UserSettingsView from '../views/UserSettingsView.vue';
|
|
8
|
+
import { UserLogin, UserRegistration } from '@vue-skuilder/common-ui';
|
|
9
|
+
|
|
10
|
+
const routes: Array<RouteRecordRaw> = [
|
|
11
|
+
{
|
|
12
|
+
path: '/',
|
|
13
|
+
name: 'home',
|
|
14
|
+
component: HomeView,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
path: '/study',
|
|
18
|
+
name: 'study',
|
|
19
|
+
component: StudyView,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: '/progress',
|
|
23
|
+
name: 'progress',
|
|
24
|
+
component: ProgressView,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: '/browse',
|
|
28
|
+
name: 'browse',
|
|
29
|
+
component: BrowseView,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: '/login',
|
|
33
|
+
name: 'login',
|
|
34
|
+
component: UserLogin,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: '/register',
|
|
38
|
+
alias: '/signup',
|
|
39
|
+
name: 'Register',
|
|
40
|
+
component: UserRegistration,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: '/u/:username',
|
|
44
|
+
name: 'UserSettings',
|
|
45
|
+
component: UserSettingsView,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: '/u/:username/stats',
|
|
49
|
+
name: 'UserStats',
|
|
50
|
+
component: UserStatsView,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const router = createRouter({
|
|
55
|
+
history: createWebHistory(),
|
|
56
|
+
routes,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default router;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container>
|
|
3
|
+
<div v-if="courseId">
|
|
4
|
+
<CourseInformation :course-id="courseId" :view-lookup-function="viewLookup" :edit-mode="editMode">
|
|
5
|
+
<template #actions="{ editMode }">
|
|
6
|
+
<!-- Standalone-ui specific actions - no registration/trial buttons -->
|
|
7
|
+
<div v-if="editMode !== 'none'">
|
|
8
|
+
<v-btn color="success" class="me-2" to="/study">Start Study Session</v-btn>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
</CourseInformation>
|
|
12
|
+
</div>
|
|
13
|
+
<div v-else class="text-center">
|
|
14
|
+
<v-alert type="error"> Course ID not found in configuration </v-alert>
|
|
15
|
+
</div>
|
|
16
|
+
</v-container>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { ref, onMounted } from 'vue';
|
|
21
|
+
import { CourseInformation } from '@vue-skuilder/common-ui';
|
|
22
|
+
import { allCourseWare } from '@vue-skuilder/courseware';
|
|
23
|
+
import { getDataLayer } from '@vue-skuilder/db';
|
|
24
|
+
import config from '../../skuilder.config.json';
|
|
25
|
+
|
|
26
|
+
const courseId = ref<string>('');
|
|
27
|
+
const editMode = ref<'none' | 'readonly' | 'full'>('full');
|
|
28
|
+
|
|
29
|
+
// Full view lookup using courses package (same as platform-ui)
|
|
30
|
+
const viewLookup = (x: unknown) => {
|
|
31
|
+
return allCourseWare.getView(x);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
courseId.value = config.course || '';
|
|
36
|
+
|
|
37
|
+
// Determine edit mode based on data layer capabilities
|
|
38
|
+
const dataLayer = getDataLayer();
|
|
39
|
+
editMode.value = dataLayer.isReadOnly() ? 'readonly' : 'full';
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
@@ -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,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container>
|
|
3
|
+
<v-row>
|
|
4
|
+
<v-col cols="12">
|
|
5
|
+
<div v-if="sessionPrepared">
|
|
6
|
+
<StudySession
|
|
7
|
+
:content-sources="sessionContentSources"
|
|
8
|
+
:session-time-limit="sessionTimeLimit"
|
|
9
|
+
:user="user"
|
|
10
|
+
:session-config="studySessionConfig"
|
|
11
|
+
:data-layer="dataLayer"
|
|
12
|
+
:get-view-component="getViewComponent"
|
|
13
|
+
@session-finished="handleSessionFinished"
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
<div v-else>
|
|
17
|
+
<v-card class="pa-4">
|
|
18
|
+
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
|
19
|
+
<p class="mt-4">Preparing study session...</p>
|
|
20
|
+
</v-card>
|
|
21
|
+
</div>
|
|
22
|
+
</v-col>
|
|
23
|
+
</v-row>
|
|
24
|
+
</v-container>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { ref, onMounted } from 'vue';
|
|
29
|
+
import { ContentSourceID, getDataLayer } from '@vue-skuilder/db';
|
|
30
|
+
import { StudySession, type StudySessionConfig, useConfigStore } from '@vue-skuilder/common-ui';
|
|
31
|
+
import { allCourseWare } from '@vue-skuilder/courseware';
|
|
32
|
+
import ENV from '../ENVIRONMENT_VARS';
|
|
33
|
+
|
|
34
|
+
const user = getDataLayer().getUserDB();
|
|
35
|
+
const dataLayer = getDataLayer();
|
|
36
|
+
const configStore = useConfigStore();
|
|
37
|
+
const sessionPrepared = ref(false);
|
|
38
|
+
const sessionTimeLimit = ref(configStore.config.sessionTimeLimit);
|
|
39
|
+
const sessionContentSources = ref<ContentSourceID[]>([]);
|
|
40
|
+
const studySessionConfig = ref<StudySessionConfig>({
|
|
41
|
+
likesConfetti: configStore.config.likesConfetti,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Function to get view component from courses
|
|
45
|
+
const getViewComponent = (view_id: string) => allCourseWare.getView(view_id);
|
|
46
|
+
|
|
47
|
+
// Initialize study session with course from environment vars
|
|
48
|
+
const initStudySession = async () => {
|
|
49
|
+
// Check if course ID is valid
|
|
50
|
+
if (!ENV.STATIC_COURSE_ID || ENV.STATIC_COURSE_ID === 'not_set') {
|
|
51
|
+
console.error('[StudyView] No course ID specified in environment vars!');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`[StudyView] Starting study session for course: ${ENV.STATIC_COURSE_ID}`);
|
|
56
|
+
|
|
57
|
+
// Set the content source to the course ID from environment vars
|
|
58
|
+
sessionContentSources.value = [
|
|
59
|
+
{
|
|
60
|
+
type: 'course',
|
|
61
|
+
id: ENV.STATIC_COURSE_ID,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Mark the session as prepared
|
|
66
|
+
sessionPrepared.value = true;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Handle session finished
|
|
70
|
+
const handleSessionFinished = () => {
|
|
71
|
+
// router.go(0); // Refresh the page
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Initialize on component mount
|
|
75
|
+
onMounted(async () => {
|
|
76
|
+
// user.value = await getCurrentUser();
|
|
77
|
+
|
|
78
|
+
// if (user.value) {
|
|
79
|
+
await initStudySession();
|
|
80
|
+
// } else {
|
|
81
|
+
// console.error('[StudyView] No user available!');
|
|
82
|
+
// }
|
|
83
|
+
});
|
|
84
|
+
</script>
|