@vue-skuilder/cli 0.1.14-2 â 0.1.15
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/cypress/e2e/custom-questions-dev.cy.js +27 -0
- package/cypress/e2e/custom-questions-studio.cy.js +93 -0
- package/cypress/e2e/scaffolded-app.cy.js +32 -0
- package/cypress/support/commands.js +9 -0
- package/cypress/support/e2e.js +17 -0
- package/cypress.config.js +17 -0
- package/dist/commands/pack.d.ts.map +1 -1
- package/dist/commands/pack.js +13 -0
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/studio.js +52 -24
- package/dist/commands/studio.js.map +1 -1
- package/dist/mcp-server.js +9 -3
- package/dist/mcp-server.js.map +1 -1
- package/dist/standalone-ui-template/package.json +3 -3
- package/dist/standalone-ui-template/src/main.ts +11 -18
- package/dist/standalone-ui-template/src/questions/MultipleChoiceQuestion.ts +2 -3
- package/dist/standalone-ui-template/src/questions/MultipleChoiceQuestionView.vue +24 -10
- package/dist/standalone-ui-template/src/questions/NumberRangeQuestion.ts +2 -1
- package/dist/standalone-ui-template/src/questions/NumberRangeQuestionView.vue +19 -6
- package/dist/standalone-ui-template/src/questions/README.md +28 -14
- package/dist/standalone-ui-template/src/questions/SimpleTextQuestion.ts +2 -1
- package/dist/standalone-ui-template/src/questions/SimpleTextQuestionView.vue +20 -11
- package/dist/standalone-ui-template/src/questions/index.ts +9 -5
- package/dist/standalone-ui-template/src/views/StudyView.vue +1 -1
- package/dist/standalone-ui-template/vite.config.ts +4 -0
- package/dist/studio-ui-src/App.vue +4 -4
- package/dist/studio-ui-src/components/StudioFlush.vue +1 -1
- package/dist/studio-ui-src/main.ts +55 -20
- package/dist/studio-ui-src/package.json +2 -2
- package/dist/studio-ui-src/views/BrowseView.vue +3 -0
- package/dist/utils/pack-courses.d.ts.map +1 -1
- package/dist/utils/pack-courses.js +26 -0
- package/dist/utils/pack-courses.js.map +1 -1
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +35 -0
- package/dist/utils/template.js.map +1 -1
- package/eslint.config.mjs +1 -1
- package/package.json +21 -12
- package/src/commands/pack.ts +19 -0
- package/src/commands/studio.ts +35 -5
- package/src/mcp-server.ts +10 -3
- package/src/utils/pack-courses.ts +33 -0
- package/src/utils/template.ts +49 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.15",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./dist-lib/questions.cjs.js",
|
|
9
9
|
"module": "./dist-lib/questions.mjs",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"@types/cypress": "1.1.6",
|
|
52
52
|
"@types/events": "^3",
|
|
53
53
|
"@vitejs/plugin-vue": "^5.2.1",
|
|
54
|
-
"cypress": "
|
|
54
|
+
"cypress": "^15.6.0",
|
|
55
55
|
"typescript": "^5.7.2",
|
|
56
56
|
"vite": "^6.0.9",
|
|
57
57
|
"vite-plugin-dts": "^4.3.0",
|
|
58
58
|
"vue-tsc": "^1.8.0",
|
|
59
59
|
"wait-on": "8.0.2"
|
|
60
60
|
},
|
|
61
|
-
"stableVersion": "0.1.
|
|
61
|
+
"stableVersion": "0.1.15"
|
|
62
62
|
}
|
|
@@ -42,32 +42,25 @@ import config from '../skuilder.config.json';
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
if (config.dataLayerType === 'static') {
|
|
45
|
-
// Load manifest for static mode
|
|
46
|
-
const courseId = config.course;
|
|
47
|
-
if (!courseId) {
|
|
48
|
-
throw new Error('Course ID required for static data layer');
|
|
49
|
-
}
|
|
50
|
-
|
|
45
|
+
// Load root skuilder.json manifest for static mode
|
|
51
46
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
47
|
+
const rootManifestUrl = '/skuilder.json';
|
|
48
|
+
const rootManifestResponse = await fetch(rootManifestUrl);
|
|
49
|
+
if (!rootManifestResponse.ok) {
|
|
54
50
|
throw new Error(
|
|
55
|
-
`Failed to load manifest: ${
|
|
51
|
+
`Failed to load root manifest: ${rootManifestResponse.status} ${rootManifestResponse.statusText}`
|
|
56
52
|
);
|
|
57
53
|
}
|
|
58
|
-
const
|
|
59
|
-
console.log(
|
|
60
|
-
console.log(JSON.stringify(manifest));
|
|
54
|
+
const rootManifest = await rootManifestResponse.json();
|
|
55
|
+
console.log('[DEBUG] Loaded root manifest:', rootManifest);
|
|
61
56
|
|
|
62
57
|
dataLayerOptions = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
[courseId]: manifest,
|
|
66
|
-
},
|
|
58
|
+
rootManifest,
|
|
59
|
+
rootManifestUrl: new URL(rootManifestUrl, window.location.href).href,
|
|
67
60
|
};
|
|
68
61
|
} catch (error) {
|
|
69
|
-
console.error('[DEBUG] Failed to load
|
|
70
|
-
throw new Error(`Could not load
|
|
62
|
+
console.error('[DEBUG] Failed to load root manifest:', error);
|
|
63
|
+
throw new Error(`Could not load root skuilder.json manifest: ${error}`);
|
|
71
64
|
}
|
|
72
65
|
}
|
|
73
66
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
|
|
2
2
|
import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
|
|
3
|
+
import { markRaw } from 'vue';
|
|
3
4
|
import MultipleChoiceQuestionView from './MultipleChoiceQuestionView.vue';
|
|
4
5
|
|
|
5
6
|
export class MultipleChoiceQuestion extends Question {
|
|
@@ -14,9 +15,7 @@ export class MultipleChoiceQuestion extends Question {
|
|
|
14
15
|
},
|
|
15
16
|
];
|
|
16
17
|
|
|
17
|
-
public static views = [
|
|
18
|
-
{ name: 'MultipleChoiceQuestionView', component: MultipleChoiceQuestionView },
|
|
19
|
-
];
|
|
18
|
+
public static views = [markRaw(MultipleChoiceQuestionView)];
|
|
20
19
|
|
|
21
20
|
// @ts-expect-error TS6133: Used in Vue template
|
|
22
21
|
private _questionText: string;
|
|
@@ -10,34 +10,48 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
|
-
import { ref, PropType } from 'vue';
|
|
13
|
+
import { ref, computed, PropType, defineOptions } from 'vue';
|
|
14
14
|
import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
|
|
15
15
|
import { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
|
|
16
16
|
import { ViewData } from '@vue-skuilder/common';
|
|
17
17
|
|
|
18
|
+
defineOptions({
|
|
19
|
+
name: 'MultipleChoiceQuestionView'
|
|
20
|
+
});
|
|
21
|
+
|
|
18
22
|
const props = defineProps({
|
|
19
|
-
questionText: {
|
|
20
|
-
type: String,
|
|
21
|
-
required: true,
|
|
22
|
-
},
|
|
23
|
-
options: {
|
|
24
|
-
type: Array as () => string[],
|
|
25
|
-
required: true,
|
|
26
|
-
},
|
|
27
23
|
data: {
|
|
28
24
|
type: Array as PropType<ViewData[]>,
|
|
29
25
|
required: true,
|
|
30
26
|
},
|
|
27
|
+
modifyDifficulty: {
|
|
28
|
+
type: Number,
|
|
29
|
+
required: false,
|
|
30
|
+
default: 0,
|
|
31
|
+
},
|
|
31
32
|
});
|
|
32
33
|
|
|
34
|
+
const emit = defineEmits(['emit-response']);
|
|
35
|
+
|
|
33
36
|
const selectedAnswer = ref('');
|
|
34
37
|
|
|
35
|
-
const viewableUtils = useViewable(props,
|
|
38
|
+
const viewableUtils = useViewable(props, emit, 'MultipleChoiceQuestionView');
|
|
36
39
|
const questionUtils = useQuestionView<MultipleChoiceQuestion>(viewableUtils);
|
|
37
40
|
|
|
38
41
|
// Initialize question
|
|
39
42
|
questionUtils.question.value = new MultipleChoiceQuestion(props.data);
|
|
40
43
|
|
|
44
|
+
// Extract question text and options from the question instance
|
|
45
|
+
const questionText = computed(() => {
|
|
46
|
+
const question = new MultipleChoiceQuestion(props.data);
|
|
47
|
+
return (question as any)._questionText || '';
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const options = computed(() => {
|
|
51
|
+
const question = new MultipleChoiceQuestion(props.data);
|
|
52
|
+
return (question as any).options || [];
|
|
53
|
+
});
|
|
54
|
+
|
|
41
55
|
const submitAnswer = () => {
|
|
42
56
|
if (selectedAnswer.value) {
|
|
43
57
|
questionUtils.submitAnswer({ response: selectedAnswer.value });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
|
|
2
2
|
import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
|
|
3
|
+
import { markRaw } from 'vue';
|
|
3
4
|
import NumberRangeQuestionView from './NumberRangeQuestionView.vue';
|
|
4
5
|
|
|
5
6
|
export class NumberRangeQuestion extends Question {
|
|
@@ -14,7 +15,7 @@ export class NumberRangeQuestion extends Question {
|
|
|
14
15
|
},
|
|
15
16
|
];
|
|
16
17
|
|
|
17
|
-
public static views = [
|
|
18
|
+
public static views = [markRaw(NumberRangeQuestionView)];
|
|
18
19
|
|
|
19
20
|
// @ts-expect-error TS6133: Used in Vue template
|
|
20
21
|
private questionText: string;
|
|
@@ -7,30 +7,43 @@
|
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
import { ref, PropType } from 'vue';
|
|
10
|
+
import { ref, computed, PropType, defineOptions } from 'vue';
|
|
11
11
|
import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
|
|
12
12
|
import { NumberRangeQuestion } from './NumberRangeQuestion';
|
|
13
13
|
import { ViewData } from '@vue-skuilder/common';
|
|
14
14
|
|
|
15
|
+
defineOptions({
|
|
16
|
+
name: 'NumberRangeQuestionView'
|
|
17
|
+
});
|
|
18
|
+
|
|
15
19
|
const props = defineProps({
|
|
16
|
-
questionText: {
|
|
17
|
-
type: String,
|
|
18
|
-
required: true,
|
|
19
|
-
},
|
|
20
20
|
data: {
|
|
21
21
|
type: Array as PropType<ViewData[]>,
|
|
22
22
|
required: true,
|
|
23
23
|
},
|
|
24
|
+
modifyDifficulty: {
|
|
25
|
+
type: Number,
|
|
26
|
+
required: false,
|
|
27
|
+
default: 0,
|
|
28
|
+
},
|
|
24
29
|
});
|
|
25
30
|
|
|
31
|
+
const emit = defineEmits(['emit-response']);
|
|
32
|
+
|
|
26
33
|
const userAnswer = ref<number | null>(null);
|
|
27
34
|
|
|
28
|
-
const viewableUtils = useViewable(props,
|
|
35
|
+
const viewableUtils = useViewable(props, emit, 'NumberRangeQuestionView');
|
|
29
36
|
const questionUtils = useQuestionView<NumberRangeQuestion>(viewableUtils);
|
|
30
37
|
|
|
31
38
|
// Initialize question
|
|
32
39
|
questionUtils.question.value = new NumberRangeQuestion(props.data);
|
|
33
40
|
|
|
41
|
+
// Extract question text from the question instance
|
|
42
|
+
const questionText = computed(() => {
|
|
43
|
+
const question = new NumberRangeQuestion(props.data);
|
|
44
|
+
return (question as any).questionText || '';
|
|
45
|
+
});
|
|
46
|
+
|
|
34
47
|
const submitAnswer = () => {
|
|
35
48
|
if (userAnswer.value !== null) {
|
|
36
49
|
questionUtils.submitAnswer({ response: userAnswer.value });
|
|
@@ -24,19 +24,24 @@ To use your custom questions in a course, you need to:
|
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
26
|
// MyCustomQuestion.ts
|
|
27
|
+
import { markRaw } from 'vue';
|
|
27
28
|
import { Question, DataShape, ViewData, Answer } from '@vue-skuilder/courseware';
|
|
28
29
|
import { FieldType } from '@vue-skuilder/common';
|
|
29
30
|
import MyCustomQuestionView from './MyCustomQuestionView.vue';
|
|
30
31
|
|
|
31
32
|
export class MyCustomQuestion extends Question {
|
|
32
33
|
public static dataShapes: DataShape[] = [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
{
|
|
35
|
+
name: 'MyCustomQuestion' as DataShapeName,
|
|
36
|
+
fields: [
|
|
37
|
+
{ name: 'myField', type: FieldType.STRING },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
36
40
|
];
|
|
37
41
|
|
|
42
|
+
// Direct inline view registration - use markRaw() to prevent Vue reactivity
|
|
38
43
|
public static views = [
|
|
39
|
-
{ name: 'MyCustomQuestionView', component: MyCustomQuestionView },
|
|
44
|
+
{ name: 'MyCustomQuestionView', component: markRaw(MyCustomQuestionView) },
|
|
40
45
|
];
|
|
41
46
|
|
|
42
47
|
constructor(data: ViewData[]) {
|
|
@@ -59,6 +64,11 @@ To use your custom questions in a course, you need to:
|
|
|
59
64
|
}
|
|
60
65
|
```
|
|
61
66
|
|
|
67
|
+
**Important Notes:**
|
|
68
|
+
- Use `markRaw()` to wrap component imports (prevents unnecessary reactivity)
|
|
69
|
+
- Views must be `{ name: string, component: ViewComponent }` format for studio mode
|
|
70
|
+
- The `name` field must match your component's `defineOptions({ name: '...' })`
|
|
71
|
+
|
|
62
72
|
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
73
|
|
|
64
74
|
```vue
|
|
@@ -72,9 +82,14 @@ To use your custom questions in a course, you need to:
|
|
|
72
82
|
</template>
|
|
73
83
|
|
|
74
84
|
<script setup lang="ts">
|
|
75
|
-
import { ref } from 'vue';
|
|
85
|
+
import { ref, defineOptions } from 'vue';
|
|
76
86
|
import { useStudySessionStore } from '@vue-skuilder/common-ui';
|
|
77
87
|
|
|
88
|
+
// REQUIRED: Component name for runtime lookup
|
|
89
|
+
defineOptions({
|
|
90
|
+
name: 'MyCustomQuestionView'
|
|
91
|
+
});
|
|
92
|
+
|
|
78
93
|
const props = defineProps({
|
|
79
94
|
// Define props based on your question's data
|
|
80
95
|
questionData: { type: Object, required: true },
|
|
@@ -90,6 +105,8 @@ To use your custom questions in a course, you need to:
|
|
|
90
105
|
</script>
|
|
91
106
|
```
|
|
92
107
|
|
|
108
|
+
**Important:** Component name must match Question class `views` array name
|
|
109
|
+
|
|
93
110
|
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
111
|
|
|
95
112
|
```typescript
|
|
@@ -117,13 +134,10 @@ To use your custom questions in a course, you need to:
|
|
|
117
134
|
|
|
118
135
|
**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
136
|
|
|
120
|
-
##
|
|
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.
|
|
137
|
+
## Key Requirements
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
- **DataShape Definition**: Defines data structure passed to constructor and Vue component
|
|
140
|
+
- **Answer Evaluation**: Implement `isCorrect()` method in your Question subclass
|
|
141
|
+
- **Component Names**: Use `defineOptions({ name: '...' })` - must match Question class `views` array
|
|
142
|
+
- **View Registration**: Register views inline with `markRaw()` - no separate setup files needed
|
|
143
|
+
- **Format**: Use `{ name: string, component: ViewComponent }` format for studio mode compatibility
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
|
|
2
2
|
import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
|
|
3
|
+
import { markRaw } from 'vue';
|
|
3
4
|
import SimpleTextQuestionView from './SimpleTextQuestionView.vue';
|
|
4
5
|
|
|
5
6
|
export class SimpleTextQuestion extends Question {
|
|
@@ -13,7 +14,7 @@ export class SimpleTextQuestion extends Question {
|
|
|
13
14
|
},
|
|
14
15
|
];
|
|
15
16
|
|
|
16
|
-
public static views = [
|
|
17
|
+
public static views = [markRaw(SimpleTextQuestionView)];
|
|
17
18
|
|
|
18
19
|
// @ts-expect-error TS6133: Used in Vue template
|
|
19
20
|
private questionText: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
+
<p>HELLO WORLD</p>
|
|
3
4
|
<p>{{ questionText }}</p>
|
|
4
5
|
<input v-model="userAnswer" @keyup.enter="submitAnswer" placeholder="Your answer" />
|
|
5
6
|
<button @click="submitAnswer">Submit</button>
|
|
@@ -7,38 +8,46 @@
|
|
|
7
8
|
</template>
|
|
8
9
|
|
|
9
10
|
<script setup lang="ts">
|
|
10
|
-
import { ref,
|
|
11
|
+
import { ref, computed, PropType, defineOptions } from 'vue';
|
|
11
12
|
import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
|
|
12
13
|
import { SimpleTextQuestion } from './SimpleTextQuestion';
|
|
13
14
|
import { ViewData } from '@vue-skuilder/common';
|
|
14
15
|
|
|
16
|
+
defineOptions({
|
|
17
|
+
name: 'SimpleTextQuestionView',
|
|
18
|
+
});
|
|
19
|
+
|
|
15
20
|
const props = defineProps({
|
|
16
|
-
questionText: {
|
|
17
|
-
type: String,
|
|
18
|
-
required: true,
|
|
19
|
-
},
|
|
20
21
|
data: {
|
|
21
22
|
type: Array as PropType<ViewData[]>,
|
|
22
23
|
required: true,
|
|
23
24
|
},
|
|
25
|
+
modifyDifficulty: {
|
|
26
|
+
type: Number,
|
|
27
|
+
required: false,
|
|
28
|
+
default: 0,
|
|
29
|
+
},
|
|
24
30
|
});
|
|
25
31
|
|
|
32
|
+
const emit = defineEmits(['emit-response']);
|
|
33
|
+
|
|
26
34
|
const userAnswer = ref('');
|
|
27
35
|
|
|
28
|
-
const viewableUtils = useViewable(props,
|
|
36
|
+
const viewableUtils = useViewable(props, emit, 'SimpleTextQuestionView');
|
|
29
37
|
const questionUtils = useQuestionView<SimpleTextQuestion>(viewableUtils);
|
|
30
38
|
|
|
31
39
|
// Initialize question
|
|
32
40
|
questionUtils.question.value = new SimpleTextQuestion(props.data);
|
|
33
41
|
|
|
42
|
+
// Extract question text from the question instance
|
|
43
|
+
const questionText = computed(() => {
|
|
44
|
+
const question = new SimpleTextQuestion(props.data);
|
|
45
|
+
return (question as any).questionText || '';
|
|
46
|
+
});
|
|
47
|
+
|
|
34
48
|
const submitAnswer = () => {
|
|
35
49
|
questionUtils.submitAnswer({ response: userAnswer.value });
|
|
36
50
|
};
|
|
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
51
|
</script>
|
|
43
52
|
|
|
44
53
|
<style scoped>
|
|
@@ -52,13 +52,17 @@ export function allCustomQuestions() {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
// Collect all view components from questions
|
|
55
|
-
const views: ViewComponent
|
|
55
|
+
const views: Array<{ name: string; component: ViewComponent }> = [];
|
|
56
56
|
questionClasses.forEach((questionClass) => {
|
|
57
57
|
if (questionClass.views) {
|
|
58
58
|
questionClass.views.forEach((view) => {
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
views.
|
|
59
|
+
const viewName = (view as any).name || (view as any).__name;
|
|
60
|
+
if (viewName) {
|
|
61
|
+
if (!views.find((existing) => existing.name === viewName)) {
|
|
62
|
+
views.push({ name: viewName, component: view });
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
console.warn('[allCustomQuestions] View component missing name property:', view);
|
|
62
66
|
}
|
|
63
67
|
});
|
|
64
68
|
}
|
|
@@ -102,7 +106,7 @@ export interface CustomQuestionsExport {
|
|
|
102
106
|
typeof SimpleTextQuestion | typeof MultipleChoiceQuestion | typeof NumberRangeQuestion
|
|
103
107
|
>;
|
|
104
108
|
dataShapes: DataShape[];
|
|
105
|
-
views: ViewComponent
|
|
109
|
+
views: Array<{ name: string; component: ViewComponent }>;
|
|
106
110
|
meta: {
|
|
107
111
|
questionCount: number;
|
|
108
112
|
dataShapeCount: number;
|
|
@@ -35,7 +35,7 @@ const user = getDataLayer().getUserDB();
|
|
|
35
35
|
const dataLayer = getDataLayer();
|
|
36
36
|
const configStore = useConfigStore();
|
|
37
37
|
const sessionPrepared = ref(false);
|
|
38
|
-
const sessionTimeLimit = ref(configStore.config.sessionTimeLimit);
|
|
38
|
+
const sessionTimeLimit = ref(Number(configStore.config.sessionTimeLimit));
|
|
39
39
|
const sessionContentSources = ref<ContentSourceID[]>([]);
|
|
40
40
|
const studySessionConfig = ref<StudySessionConfig>({
|
|
41
41
|
likesConfetti: configStore.config.likesConfetti,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-app>
|
|
3
|
-
<v-app-bar
|
|
3
|
+
<v-app-bar>
|
|
4
4
|
<v-toolbar-title>
|
|
5
5
|
<v-icon start>mdi-palette</v-icon>
|
|
6
6
|
Skuilder Studio
|
|
@@ -9,19 +9,19 @@
|
|
|
9
9
|
|
|
10
10
|
<!-- Navigation buttons -->
|
|
11
11
|
<v-btn-group v-if="courseId" class="mr-4">
|
|
12
|
-
<v-btn :color="$route.name === 'browse' ? '
|
|
12
|
+
<v-btn :color="$route.name === 'browse' ? 'primary' : 'secondary'" @click="$router.push('/')">
|
|
13
13
|
<v-icon start>mdi-magnify</v-icon>
|
|
14
14
|
Browse Course
|
|
15
15
|
</v-btn>
|
|
16
16
|
<v-btn
|
|
17
|
-
:color="$route.name === 'create-card' ? '
|
|
17
|
+
:color="$route.name === 'create-card' ? 'primary' : 'secondary'"
|
|
18
18
|
@click="$router.push('/create-card')"
|
|
19
19
|
>
|
|
20
20
|
<v-icon start>mdi-card-plus</v-icon>
|
|
21
21
|
Create Card
|
|
22
22
|
</v-btn>
|
|
23
23
|
<v-btn
|
|
24
|
-
:color="$route.name === 'bulk-import' ? '
|
|
24
|
+
:color="$route.name === 'bulk-import' ? 'primary' : 'secondary'"
|
|
25
25
|
@click="$router.push('/bulk-import')"
|
|
26
26
|
>
|
|
27
27
|
<v-icon start>mdi-file-import</v-icon>
|
|
@@ -68,16 +68,25 @@ const vuetify = createVuetify({
|
|
|
68
68
|
try {
|
|
69
69
|
console.log(' đ Fetching custom-questions-config.json');
|
|
70
70
|
const configResponse = await fetch('/custom-questions-config.json');
|
|
71
|
+
|
|
72
|
+
console.log(` đ Config fetch response status: ${configResponse.status}`);
|
|
73
|
+
console.log(` đ Config fetch response URL: ${configResponse.url}`);
|
|
74
|
+
|
|
71
75
|
if (configResponse.ok) {
|
|
72
76
|
console.log(' â
Custom questions config file found');
|
|
73
77
|
const customConfig = await configResponse.json();
|
|
74
78
|
console.log(' đ Custom config parsed:', customConfig);
|
|
79
|
+
|
|
75
80
|
if (customConfig.hasCustomQuestions && customConfig.importPath) {
|
|
76
81
|
console.log(`đ¨ Studio Mode: Loading custom questions from ${customConfig.packageName}`);
|
|
77
82
|
console.log(` đĻ Import path: ${customConfig.importPath}`);
|
|
83
|
+
|
|
78
84
|
try {
|
|
79
|
-
|
|
85
|
+
console.log(' đ Attempting dynamic import...');
|
|
86
|
+
const customModule = await import(/* @vite-ignore */ customConfig.importPath);
|
|
80
87
|
console.log(' â
Custom module imported successfully');
|
|
88
|
+
console.log(' đ Module exports:', Object.keys(customModule));
|
|
89
|
+
|
|
81
90
|
customQuestions = customModule.allCustomQuestions?.();
|
|
82
91
|
if (customQuestions) {
|
|
83
92
|
console.log(
|
|
@@ -85,24 +94,27 @@ const vuetify = createVuetify({
|
|
|
85
94
|
);
|
|
86
95
|
console.log(' đ Custom questions object:', customQuestions);
|
|
87
96
|
} else {
|
|
88
|
-
console.
|
|
97
|
+
console.error(' â FATAL: Custom module did not return questions data!');
|
|
98
|
+
console.error(' đ allCustomQuestions result:', customQuestions);
|
|
99
|
+
console.error(' đ allCustomQuestions function:', customModule.allCustomQuestions);
|
|
89
100
|
}
|
|
90
101
|
} catch (importError) {
|
|
91
|
-
console.
|
|
92
|
-
|
|
93
|
-
);
|
|
102
|
+
console.error(' â FATAL: Failed to import custom questions module!');
|
|
103
|
+
console.error(' đ Import path attempted:', customConfig.importPath);
|
|
104
|
+
console.error(' đ Error:', importError);
|
|
105
|
+
throw importError; // Re-throw to make it visible
|
|
94
106
|
}
|
|
95
107
|
} else {
|
|
96
|
-
console.
|
|
97
|
-
|
|
98
|
-
);
|
|
108
|
+
console.warn(' â ī¸ Config exists but hasCustomQuestions is false or importPath is missing');
|
|
109
|
+
console.warn(' đ Config content:', customConfig);
|
|
99
110
|
}
|
|
100
111
|
} else {
|
|
101
|
-
console.
|
|
112
|
+
console.warn(` â ī¸ Custom questions config not found (HTTP ${configResponse.status})`);
|
|
113
|
+
console.warn(` đ Attempted URL: ${configResponse.url}`);
|
|
102
114
|
}
|
|
103
115
|
} catch (configError) {
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
console.error(' â Error fetching custom questions config:', configError);
|
|
117
|
+
// Don't throw - missing config is valid for non-custom courses
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
// Register custom question types in CourseConfig if available
|
|
@@ -164,17 +176,29 @@ const vuetify = createVuetify({
|
|
|
164
176
|
);
|
|
165
177
|
}
|
|
166
178
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
// Register custom courses with the allCourseWare singleton
|
|
180
|
+
console.log('đ¨ Studio Mode: Registering custom courses');
|
|
181
|
+
const { allCourseWare } = await import('@vue-skuilder/courseware');
|
|
182
|
+
console.log(` đ allCourseWare instance:`, allCourseWare);
|
|
183
|
+
console.log(` đ Current courses BEFORE registration:`, allCourseWare.courses.map(c => c.name));
|
|
184
|
+
|
|
185
|
+
if (customQuestions?.courses) {
|
|
186
|
+
console.log(` đĻ Registering ${customQuestions.courses.length} custom course(s)`);
|
|
187
|
+
console.log(` đĻ Custom courses to register:`, customQuestions.courses.map(c => c.name));
|
|
188
|
+
customQuestions.courses.forEach((course) => {
|
|
189
|
+
// Check if already registered to avoid duplicates
|
|
190
|
+
if (!allCourseWare.courses.find((c) => c.name === course.name)) {
|
|
191
|
+
allCourseWare.courses.push(course);
|
|
192
|
+
console.log(` â
Registered course: ${course.name}`);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(` âšī¸ Course ${course.name} already registered`);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
console.log(` đ Current courses AFTER registration:`, allCourseWare.courses.map(c => c.name));
|
|
198
|
+
}
|
|
175
199
|
|
|
176
200
|
console.log('đ¨ Studio Mode: Collecting view components');
|
|
177
|
-
const viewComponents =
|
|
201
|
+
const viewComponents = allCourseWare.allViewsRaw();
|
|
178
202
|
console.log(` â
Collected ${Object.keys(viewComponents).length} base view components`);
|
|
179
203
|
|
|
180
204
|
// Add custom question view components if available
|
|
@@ -199,6 +223,17 @@ const vuetify = createVuetify({
|
|
|
199
223
|
app.component(name, component);
|
|
200
224
|
});
|
|
201
225
|
|
|
226
|
+
// Provide inline markdown components if available
|
|
227
|
+
console.log('đ¨ Studio Mode: Checking for inline markdown components');
|
|
228
|
+
if (customQuestions?.inlineComponents) {
|
|
229
|
+
console.log(` â
Found ${Object.keys(customQuestions.inlineComponents).length} inline components`);
|
|
230
|
+
console.log(` đ Component names: ${Object.keys(customQuestions.inlineComponents).join(', ')}`);
|
|
231
|
+
app.provide('markdownComponents', customQuestions.inlineComponents);
|
|
232
|
+
} else {
|
|
233
|
+
console.log(' âšī¸ No inline markdown components available');
|
|
234
|
+
app.provide('markdownComponents', {});
|
|
235
|
+
}
|
|
236
|
+
|
|
202
237
|
app.use(pinia);
|
|
203
238
|
app.use(vuetify);
|
|
204
239
|
app.use(router);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.15",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"dev": "vite",
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"vite": "^6.0.9",
|
|
33
33
|
"vue-tsc": "^1.8.0"
|
|
34
34
|
},
|
|
35
|
-
"stableVersion": "0.1.
|
|
35
|
+
"stableVersion": "0.1.15"
|
|
36
36
|
}
|
|
@@ -38,6 +38,9 @@ const courseId = ref<string | null>(null);
|
|
|
38
38
|
|
|
39
39
|
// View lookup function with proper context binding
|
|
40
40
|
const viewLookupFunction = (viewDescription: any) => {
|
|
41
|
+
console.log('[BrowseView] Looking up view:', viewDescription);
|
|
42
|
+
console.log('[BrowseView] allCourseWare instance:', allCourseWare);
|
|
43
|
+
console.log('[BrowseView] Available courses:', allCourseWare.courses.map(c => c.name));
|
|
41
44
|
return allCourseWare.getView(viewDescription);
|
|
42
45
|
};
|
|
43
46
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pack-courses.d.ts","sourceRoot":"","sources":["../../src/utils/pack-courses.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pack-courses.d.ts","sourceRoot":"","sources":["../../src/utils/pack-courses.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoE5E"}
|