@vue-skuilder/platform-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.
Files changed (108) hide show
  1. package/LICENCE +661 -0
  2. package/README.md +64 -0
  3. package/dist/assets/Roboto-Black-B0ZKieaB.woff +0 -0
  4. package/dist/assets/Roboto-Black-VhoA2qKx.woff2 +0 -0
  5. package/dist/assets/Roboto-BlackItalic-D0gSnuIb.woff +0 -0
  6. package/dist/assets/Roboto-BlackItalic-D4yie1YO.woff2 +0 -0
  7. package/dist/assets/Roboto-Bold-D9plYbeK.woff +0 -0
  8. package/dist/assets/Roboto-Bold-hN3duQhD.woff2 +0 -0
  9. package/dist/assets/Roboto-BoldItalic-BWDm51uc.woff2 +0 -0
  10. package/dist/assets/Roboto-BoldItalic-CyLKvOHD.woff +0 -0
  11. package/dist/assets/Roboto-Light-Cu-PAxXt.woff +0 -0
  12. package/dist/assets/Roboto-Light-DHTugVNA.woff2 +0 -0
  13. package/dist/assets/Roboto-LightItalic-CZg5kHIB.woff +0 -0
  14. package/dist/assets/Roboto-LightItalic-JQyp2Y3P.woff2 +0 -0
  15. package/dist/assets/Roboto-Medium-ByKogCTi.woff2 +0 -0
  16. package/dist/assets/Roboto-Medium-b81vv18W.woff +0 -0
  17. package/dist/assets/Roboto-MediumItalic-DFQ-RYa0.woff +0 -0
  18. package/dist/assets/Roboto-MediumItalic-i1eR0KbF.woff2 +0 -0
  19. package/dist/assets/Roboto-Regular-BX5l9hRW.woff +0 -0
  20. package/dist/assets/Roboto-Regular-C6rbFxYz.woff2 +0 -0
  21. package/dist/assets/Roboto-RegularItalic-BjnLZsam.woff +0 -0
  22. package/dist/assets/Roboto-RegularItalic-CvPUdkvM.woff2 +0 -0
  23. package/dist/assets/Roboto-Thin-BfJvJcog.woff +0 -0
  24. package/dist/assets/Roboto-Thin-NicBC1pN.woff2 +0 -0
  25. package/dist/assets/Roboto-ThinItalic-CKlCjrO_.woff2 +0 -0
  26. package/dist/assets/Roboto-ThinItalic-DnIWFxRE.woff +0 -0
  27. package/dist/assets/index-CQ-sNKGW.css +14 -0
  28. package/dist/assets/index-EbqpUgvM.js +161 -0
  29. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  30. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  31. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  32. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  33. package/dist/assets/workbox-window.prod.es5-p40uij6f.js +1 -0
  34. package/dist/favicon.ico +0 -0
  35. package/dist/img/icons/safari-pinned-tab.svg +149 -0
  36. package/dist/index.html +19 -0
  37. package/dist/manifest.json +20 -0
  38. package/dist/manifest.webmanifest +1 -0
  39. package/dist/robots.txt +2 -0
  40. package/dist/sw.js +1 -0
  41. package/dist/workbox-1be04862.js +1 -0
  42. package/package.json +105 -0
  43. package/src/App.vue +156 -0
  44. package/src/ENVIRONMENT_VARS.ts +79 -0
  45. package/src/components/Classrooms/ClassroomCtrlPanel.vue +206 -0
  46. package/src/components/Classrooms/CreateClassroom.vue +159 -0
  47. package/src/components/Classrooms/JoinCode.vue +83 -0
  48. package/src/components/Courses/CourseCardBrowser.vue +365 -0
  49. package/src/components/Courses/CourseEditor.vue +164 -0
  50. package/src/components/Courses/CourseInformation.vue +164 -0
  51. package/src/components/Courses/CourseRouter.vue +116 -0
  52. package/src/components/Courses/CourseStubCard.vue +76 -0
  53. package/src/components/Courses/EloModeration.vue +122 -0
  54. package/src/components/Courses/TagInformation.vue +209 -0
  55. package/src/components/Edit/BulkImport/CardPreviewList.vue +345 -0
  56. package/src/components/Edit/BulkImportView.vue +633 -0
  57. package/src/components/Edit/CardBrowser.vue +79 -0
  58. package/src/components/Edit/ComponentRegistration/ComponentRegistration.vue +235 -0
  59. package/src/components/Edit/ComponentRegistration/UnregisteredComponentsTable.vue +19 -0
  60. package/src/components/Edit/CourseEditor.vue +162 -0
  61. package/src/components/Edit/NavigationStrategy/NavigationStrategyEditor.vue +170 -0
  62. package/src/components/Edit/NavigationStrategy/NavigationStrategyList.vue +92 -0
  63. package/src/components/Edit/TagsInput.vue +247 -0
  64. package/src/components/Edit/ViewableDataInputForm/DataInputForm.vue +524 -0
  65. package/src/components/Edit/ViewableDataInputForm/FieldInput.types.ts +33 -0
  66. package/src/components/Edit/ViewableDataInputForm/FieldInputs/AudioInput.vue +188 -0
  67. package/src/components/Edit/ViewableDataInputForm/FieldInputs/ChessPuzzleInput.vue +79 -0
  68. package/src/components/Edit/ViewableDataInputForm/FieldInputs/FieldInput.css +12 -0
  69. package/src/components/Edit/ViewableDataInputForm/FieldInputs/ImageInput.vue +231 -0
  70. package/src/components/Edit/ViewableDataInputForm/FieldInputs/IntegerInput.vue +49 -0
  71. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MarkdownInput.vue +34 -0
  72. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MediaDragDropUploader.vue +246 -0
  73. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MidiInput.vue +113 -0
  74. package/src/components/Edit/ViewableDataInputForm/FieldInputs/NumberInput.vue +49 -0
  75. package/src/components/Edit/ViewableDataInputForm/FieldInputs/StringInput.vue +49 -0
  76. package/src/components/Edit/ViewableDataInputForm/FieldInputs/typeValidators.ts +49 -0
  77. package/src/components/Edit/ViewableDataInputForm/OptionsFieldInput.ts +161 -0
  78. package/src/components/Study/SessionConfiguration.vue +371 -0
  79. package/src/components/TextSwap.vue +65 -0
  80. package/src/components/User/UserStats.vue +30 -0
  81. package/src/dev/DataInputFormTester.vue +117 -0
  82. package/src/dev/readme.md +3 -0
  83. package/src/enums.ts +0 -0
  84. package/src/glyphs.txt +933 -0
  85. package/src/main.ts +45 -0
  86. package/src/plugins/vuetify.ts +41 -0
  87. package/src/registerServiceWorker.ts +18 -0
  88. package/src/router.ts +184 -0
  89. package/src/server/index.spec.ts +192 -0
  90. package/src/server/index.ts +71 -0
  91. package/src/shims-vue.d.ts +5 -0
  92. package/src/store.mock.ts +122 -0
  93. package/src/stores/useDataInputFormStore.ts +49 -0
  94. package/src/stores/useFieldInputStore.ts +191 -0
  95. package/src/types/shims-vuetify.d.ts +12 -0
  96. package/src/types/svg.d.ts +4 -0
  97. package/src/utils/bulkImport/index.ts +94 -0
  98. package/src/views/About.vue +29 -0
  99. package/src/views/Admin.vue +128 -0
  100. package/src/views/Classrooms.vue +258 -0
  101. package/src/views/Courses.vue +265 -0
  102. package/src/views/Home.vue +154 -0
  103. package/src/views/Login.vue +75 -0
  104. package/src/views/ReleaseNotes.vue +20 -0
  105. package/src/views/SignUp.vue +32 -0
  106. package/src/views/Study.vue +261 -0
  107. package/src/views/User.vue +109 -0
  108. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,235 @@
1
+ <template>
2
+ <div>
3
+ <h3>DataShapes</h3>
4
+ <ul>
5
+ <li v-for="dataShape in dataShapes" :key="dataShape.name" class="ma-2">
6
+ <v-btn v-if="!dataShape.registered" size="small" @click="registerShape(dataShape.name)"> Register </v-btn>
7
+ <span v-else class="inset"> (Registered) </span>
8
+ {{ dataShape.name }}
9
+ <ul>
10
+ <div v-for="view in dataShape.dataShape.views" :key="view.name">
11
+ <li v-if="view">
12
+ {{ view.name }}
13
+ </li>
14
+ </div>
15
+ </ul>
16
+ </li>
17
+ </ul>
18
+
19
+ <h3>Questions</h3>
20
+ <ul>
21
+ <li v-for="question in questions" :key="question.name" class="ma-2">
22
+ <v-btn v-if="!question.registered" size="small" @click="registerQuestionView(question.name)"> Register </v-btn>
23
+ <span v-else class="inset"> (Registered) </span>
24
+ {{ question.name }}
25
+ </li>
26
+ </ul>
27
+ </div>
28
+ </template>
29
+
30
+ <script lang="ts">
31
+ import { defineComponent } from 'vue';
32
+ import { Displayable } from '@vue-skuilder/common-ui';
33
+ import { allCourses } from '@vue-skuilder/courses';
34
+ import { getDataLayer, CourseDBInterface } from '@vue-skuilder/db';
35
+ import {
36
+ NameSpacer,
37
+ QuestionDescriptor,
38
+ CourseConfig,
39
+ DataShape55,
40
+ QuestionType55,
41
+ DataShape,
42
+ } from '@vue-skuilder/common';
43
+ import * as _ from 'lodash';
44
+ import { getCurrentUser } from '@vue-skuilder/common-ui';
45
+
46
+ export interface DataShapeRegistrationStatus {
47
+ name: string;
48
+ course: string;
49
+ dataShape: DataShape;
50
+ registered: boolean;
51
+ }
52
+
53
+ export interface QuestionRegistrationStatus {
54
+ name: string;
55
+ course: string;
56
+ question: typeof Displayable;
57
+ registered: boolean;
58
+ }
59
+
60
+ export default defineComponent({
61
+ name: 'ComponentRegistration',
62
+
63
+ props: {
64
+ course: {
65
+ type: String,
66
+ required: true,
67
+ },
68
+ },
69
+
70
+ data() {
71
+ return {
72
+ dataShapes: [] as (DataShapeRegistrationStatus & { displayable: typeof Displayable })[],
73
+ questions: [] as QuestionRegistrationStatus[],
74
+ courseDatashapes: [] as DataShape55[],
75
+ courseQuestionTypes: [] as QuestionType55[],
76
+ courseConfig: undefined as CourseConfig | undefined,
77
+ courseDB: null as CourseDBInterface | null,
78
+ };
79
+ },
80
+
81
+ async created() {
82
+ this.courseDB = getDataLayer().getCourseDB(this.course);
83
+ this.courseConfig = await this.courseDB.getCourseConfig();
84
+ this.courseDatashapes = this.courseConfig.dataShapes;
85
+ this.courseQuestionTypes = this.courseConfig.questionTypes;
86
+
87
+ const dataShapeData = allCourses.allDataShapes();
88
+
89
+ allCourses.allDataShapesRaw().forEach((ds) => {
90
+ console.log(`[ComponentRegistration] Datashape:\n${JSON.stringify(ds)}`);
91
+ });
92
+
93
+ dataShapeData.forEach((shape) => {
94
+ const index = this.courseDatashapes.find((test) => {
95
+ return test.name === NameSpacer.getDataShapeString(shape);
96
+ });
97
+
98
+ this.dataShapes.push({
99
+ name: shape.dataShape,
100
+ course: shape.course,
101
+ dataShape: allCourses.getDataShape(shape),
102
+ registered: index !== undefined,
103
+ displayable: shape.displayable,
104
+ });
105
+ });
106
+
107
+ this.dataShapes = _.sortBy(this.dataShapes, ['registered', 'name']);
108
+
109
+ const courseNameList = allCourses.courses.map((course) => course.name);
110
+ const questionData: Array<[QuestionDescriptor, typeof Displayable]> = [];
111
+
112
+ courseNameList.forEach((course) => {
113
+ const courseQs = allCourses.getCourse(course)!.questions;
114
+
115
+ courseQs.forEach((courseQ) => {
116
+ questionData.push([
117
+ {
118
+ course,
119
+ questionType: courseQ.name,
120
+ },
121
+ courseQ,
122
+ ]);
123
+ });
124
+ });
125
+
126
+ questionData.forEach((question) => {
127
+ const index = this.courseQuestionTypes.find((test) => {
128
+ return NameSpacer.getQuestionString(question[0]) === test.name;
129
+ });
130
+
131
+ this.questions.push({
132
+ course: question[0].course,
133
+ name: question[1].name,
134
+ registered: index !== undefined,
135
+ question: question[1],
136
+ });
137
+ });
138
+ },
139
+
140
+ methods: {
141
+ async registerShape(shapeName: string) {
142
+ const shape = this.dataShapes.find((findShape) => {
143
+ return findShape.name === shapeName;
144
+ })!;
145
+
146
+ this.courseConfig!.dataShapes.push({
147
+ name: NameSpacer.getDataShapeString({
148
+ dataShape: shape.name,
149
+ course: shape.course,
150
+ }),
151
+ questionTypes: [],
152
+ });
153
+
154
+ const update = await this.courseDB!.updateCourseConfig(this.courseConfig!);
155
+
156
+ if (update.ok) {
157
+ shape.registered = true;
158
+ }
159
+ },
160
+
161
+ async registerQuestionView(questionName: string) {
162
+ const question = this.questions.find((q) => {
163
+ return q.name === questionName;
164
+ })!;
165
+
166
+ const nsQuestionName = NameSpacer.getQuestionString({
167
+ course: question.course,
168
+ questionType: question.name,
169
+ });
170
+
171
+ this.courseConfig!.questionTypes.push({
172
+ name: nsQuestionName,
173
+ viewList: question.question.views.map((v) => {
174
+ if (v.name) {
175
+ return v.name;
176
+ } else {
177
+ return 'unnamedComponent';
178
+ }
179
+ }),
180
+ dataShapeList: question.question.dataShapes.map((d) =>
181
+ NameSpacer.getDataShapeString({
182
+ course: question.course,
183
+ dataShape: d.name,
184
+ })
185
+ ),
186
+ });
187
+
188
+ question.question.dataShapes.forEach((ds) => {
189
+ const nsDatashapeName = NameSpacer.getDataShapeString({
190
+ course: question.course,
191
+ dataShape: ds.name,
192
+ });
193
+
194
+ for (const db of this.courseConfig!.dataShapes) {
195
+ if (db.name === nsDatashapeName) {
196
+ db.questionTypes.push(nsQuestionName);
197
+ }
198
+ }
199
+ });
200
+
201
+ const update = await this.courseDB!.updateCourseConfig(this.courseConfig!);
202
+ const u = await getCurrentUser();
203
+
204
+ if (update.ok) {
205
+ question.registered = true;
206
+ console.log(`[ComponentRegistration]
207
+ Question: ${JSON.stringify(question)}
208
+ CourseID: ${this.course}
209
+ `);
210
+ if (question.question.seedData) {
211
+ console.log(`[ComponentRegistration] Question has seed data!`);
212
+ question.question.seedData.forEach((d) => {
213
+ this.courseDB!.addNote(question.course, question.question.dataShapes[0], d, u.getUsername(), []);
214
+ });
215
+ } else {
216
+ console.log(`[ComponentRegistration] Question has NO seed data!`);
217
+ }
218
+ }
219
+ },
220
+ },
221
+ });
222
+ </script>
223
+
224
+ <style scoped>
225
+ div {
226
+ margin-top: 15px;
227
+ }
228
+
229
+ .inset {
230
+ background-color: rgb(240, 240, 243);
231
+ font-size: smaller;
232
+ padding: 2px;
233
+ border-radius: 2px;
234
+ }
235
+ </style>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <div>
3
+ <table>
4
+ <thead>
5
+ <td></td>
6
+ </thead>
7
+ </table>
8
+ </div>
9
+ </template>
10
+
11
+ <script lang="ts">
12
+ import { defineComponent } from 'vue';
13
+
14
+ // [ ] delete this file
15
+
16
+ export default defineComponent({
17
+ name: 'UnregisteredComponentsTable',
18
+ });
19
+ </script>
@@ -0,0 +1,162 @@
1
+ <template>
2
+ <div v-if="course" class="courseEditor">
3
+ <div v-if="loading">
4
+ <v-progress-circular indeterminate color="secondary"></v-progress-circular>
5
+ </div>
6
+ <div v-else>
7
+ <h1 class="text-h4">
8
+ <router-link to="/q">Quilts</router-link> /
9
+ <router-link :to="`/q/${courseConfig ? courseConfig.name : course}`">{{ courseConfig?.name }}</router-link>
10
+ </h1>
11
+
12
+ <v-tabs v-model="currentTab" bg-color="primary" grow>
13
+ <v-tab value="single">Single Card Input</v-tab>
14
+ <v-tab value="bulk">Bulk Import</v-tab>
15
+ <v-tab value="registration">Navigation</v-tab>
16
+ <v-tab value="registration">Component Registration</v-tab>
17
+ </v-tabs>
18
+
19
+ <v-window v-model="currentTab">
20
+ <v-window-item value="single">
21
+ <v-container fluid>
22
+ <v-select
23
+ v-model="selectedShape"
24
+ label="What kind of content are you adding?"
25
+ :items="registeredDataShapes.map((shape) => shape.name)"
26
+ class="mt-4"
27
+ />
28
+ <data-input-form
29
+ v-if="selectedShape !== '' && courseConfig && dataShape"
30
+ :data-shape="dataShape"
31
+ :course-cfg="courseConfig"
32
+ />
33
+ </v-container>
34
+ </v-window-item>
35
+
36
+ <v-window-item value="bulk">
37
+ <v-container fluid>
38
+ <bulk-import-view v-if="courseConfig" :course-cfg="courseConfig" class="mt-4" />
39
+ </v-container>
40
+ </v-window-item>
41
+
42
+ <v-window-item value="registration">
43
+ <v-container fluid>
44
+ <component-registration :course="course" class="mt-4" />
45
+ </v-container>
46
+ </v-window-item>
47
+
48
+ <v-window-item value="navigation">
49
+ <v-container fluid>
50
+ <navigation-strategy-editor :course-id="course" />
51
+ </v-container>
52
+ </v-window-item>
53
+
54
+ </v-window>
55
+ </div>
56
+ </div>
57
+ </template>
58
+
59
+ <script lang="ts">
60
+ import { defineComponent } from 'vue';
61
+ import ComponentRegistration from '@/components/Edit/ComponentRegistration/ComponentRegistration.vue';
62
+ import NavigationStrategyEditor from '@/components/Edit/NavigationStrategy/NavigationStrategyEditor.vue';
63
+ import { allCourses } from '@vue-skuilder/courses';
64
+ import { BlanksCard, BlanksCardDataShapes } from '@vue-skuilder/courses';
65
+ import { CourseConfig, NameSpacer, DataShape } from '@vue-skuilder/common';
66
+ import DataInputForm from './ViewableDataInputForm/DataInputForm.vue';
67
+ import BulkImportView from './BulkImportView.vue'; // Added import
68
+ import { getDataLayer } from '@vue-skuilder/db';
69
+ import { useDataInputFormStore } from '@/stores/useDataInputFormStore';
70
+
71
+ export default defineComponent({
72
+ name: 'CourseEditor',
73
+
74
+ components: {
75
+ DataInputForm,
76
+ ComponentRegistration,
77
+ NavigationStrategyEditor,
78
+ BulkImportView,
79
+ },
80
+
81
+ props: {
82
+ course: {
83
+ type: String,
84
+ required: true,
85
+ },
86
+ },
87
+
88
+ data() {
89
+ return {
90
+ registeredDataShapes: [] as DataShape[],
91
+ dataShapes: [] as DataShape[],
92
+ selectedShape: BlanksCard.dataShapes[0].name,
93
+ courseConfig: null as CourseConfig | null,
94
+ dataShape: BlanksCardDataShapes[0] as DataShape,
95
+ loading: true,
96
+ currentTab: 'single',
97
+ dataInputFormStore: useDataInputFormStore(),
98
+ };
99
+ },
100
+
101
+ watch: {
102
+ selectedShape: {
103
+ handler(value?: string) {
104
+ if (value) {
105
+ this.dataShape = this.getDataShape(value);
106
+ this.dataInputFormStore.setDataShape(this.dataShape);
107
+
108
+ this.dataInputFormStore.dataInputForm.course = this.courseConfig;
109
+ }
110
+ },
111
+ },
112
+ },
113
+
114
+ async created() {
115
+ this.courseConfig = await getDataLayer().getCoursesDB().getCourseConfig(this.course);
116
+
117
+ // for testing getCourseTagStubs...
118
+ // log(JSON.stringify(await getCourseTagStubs(this.course)));
119
+
120
+ // this.dataShapes = BaseCards.dataShapes;
121
+ // this.registeredDataShapes = BaseCards.dataShapes;
122
+ // BaseCards.dataShapes.forEach((shape) => {
123
+ // this.dataShapes.push(shape);
124
+ // this.registeredDataShapes.push(shape);
125
+ // });
126
+
127
+ // #55 make all 'programmed' datashapes available, rather than
128
+ // the previous code-based name scoping
129
+ allCourses.courses.forEach((course) => {
130
+ course.questions.forEach((question) => {
131
+ question.dataShapes.forEach((ds) => {
132
+ this.dataShapes.push(ds);
133
+ });
134
+ });
135
+ });
136
+
137
+ this.courseConfig.dataShapes.forEach((ds) => {
138
+ this.registeredDataShapes.push(
139
+ this.dataShapes.find((shape) => {
140
+ return shape.name === NameSpacer.getDataShapeDescriptor(ds.name).dataShape;
141
+ })!
142
+ );
143
+ });
144
+
145
+ this.loading = false;
146
+ },
147
+
148
+ methods: {
149
+ getDataShape(shapeName: string): DataShape {
150
+ return this.dataShapes.find((shape) => {
151
+ return shape.name === shapeName;
152
+ })!;
153
+ },
154
+ },
155
+ });
156
+ </script>
157
+
158
+ <style scoped>
159
+ div {
160
+ margin-top: 15px;
161
+ }
162
+ </style>
@@ -0,0 +1,170 @@
1
+ <template>
2
+ <div class="navigation-strategy-editor">
3
+ <div v-if="loading">
4
+ <v-progress-circular indeterminate color="secondary"></v-progress-circular>
5
+ </div>
6
+ <div v-else>
7
+ <h2 class="text-h5 mb-4">Navigation Strategies</h2>
8
+
9
+ <div v-if="strategies.length === 0" class="no-strategies">
10
+ <p>No navigation strategies defined for this course.</p>
11
+ </div>
12
+
13
+ <navigation-strategy-list
14
+ v-else
15
+ :strategies="strategies"
16
+ @edit="editStrategy"
17
+ @delete="confirmDeleteStrategy"
18
+ />
19
+
20
+ <v-btn color="primary" class="mt-4" disabled title="New strategy types coming soon">
21
+ <v-icon start>mdi-plus</v-icon>
22
+ Add New Strategy
23
+ </v-btn>
24
+
25
+
26
+
27
+ <v-dialog v-model="showDeleteConfirm" max-width="400px">
28
+ <v-card>
29
+ <v-card-title class="text-h5">Delete Strategy</v-card-title>
30
+ <v-card-text> Are you sure you want to delete the strategy "{{ strategyToDelete?.name }}"? </v-card-text>
31
+ <v-card-actions>
32
+ <v-spacer></v-spacer>
33
+ <v-btn color="error" @click="deleteStrategy">Delete</v-btn>
34
+ <v-btn @click="showDeleteConfirm = false">Cancel</v-btn>
35
+ </v-card-actions>
36
+ </v-card>
37
+ </v-dialog>
38
+ </div>
39
+ </div>
40
+ </template>
41
+
42
+ <script lang="ts">
43
+ import { defineComponent } from 'vue';
44
+ import type { ContentNavigationStrategyData } from '@vue-skuilder/db/src/core/types/contentNavigationStrategy';
45
+ import NavigationStrategyList from './NavigationStrategyList.vue';
46
+ import { getDataLayer, DocType, Navigators } from '@vue-skuilder/db';
47
+
48
+ export default defineComponent({
49
+ name: 'NavigationStrategyEditor',
50
+
51
+ components: {
52
+ NavigationStrategyList,
53
+ },
54
+
55
+ props: {
56
+ courseId: {
57
+ type: String,
58
+ required: true,
59
+ },
60
+ },
61
+
62
+ data() {
63
+ return {
64
+ strategies: [] as ContentNavigationStrategyData[],
65
+ loading: true,
66
+ showDeleteConfirm: false,
67
+ strategyToDelete: null as ContentNavigationStrategyData | null
68
+ };
69
+ },
70
+
71
+ async created() {
72
+ await this.loadStrategies();
73
+ },
74
+
75
+ methods: {
76
+ async loadStrategies() {
77
+ this.loading = true;
78
+ try {
79
+ const dataLayer = getDataLayer();
80
+ const courseDB = dataLayer.getCourseDB(this.courseId);
81
+
82
+ // Get all navigation strategies
83
+ this.strategies = await courseDB.getAllNavigationStrategies();
84
+ } catch (error) {
85
+ console.error('Failed to load navigation strategies:', error);
86
+ // In case of error, use a placeholder
87
+ this.strategies = [
88
+ {
89
+ id: 'ELO',
90
+ docType: DocType.NAVIGATION_STRATEGY,
91
+ name: 'ELO',
92
+ description: 'Default ELO-based navigation strategy',
93
+ implementingClass: Navigators.ELO,
94
+ course: this.courseId,
95
+ serializedData: '',
96
+ },
97
+ ];
98
+ }
99
+ this.loading = false;
100
+ },
101
+
102
+ createNewStrategy() {
103
+ // Disabled for now - new strategy types will be implemented in the future
104
+ console.log('Creating new strategies is not yet implemented');
105
+ },
106
+
107
+ editStrategy(strategy: ContentNavigationStrategyData) {
108
+ // Strategy editing is not yet implemented
109
+ console.log(`Editing strategy ${strategy.id} is not yet implemented`);
110
+ },
111
+
112
+
113
+
114
+ confirmDeleteStrategy(strategy: ContentNavigationStrategyData) {
115
+ this.strategyToDelete = strategy;
116
+ this.showDeleteConfirm = true;
117
+ },
118
+
119
+ async deleteStrategy() {
120
+ if (!this.strategyToDelete) return;
121
+
122
+ this.loading = true;
123
+ try {
124
+ const dataLayer = getDataLayer();
125
+ const courseDB = dataLayer.getCourseDB(this.courseId);
126
+
127
+ // Since deleteNavigationStrategy doesn't exist in the interface yet,
128
+ // we'll use updateNavigationStrategy with an empty/invalid strategy that
129
+ // will be ignored by the system
130
+ const emptyStrategy: ContentNavigationStrategyData = {
131
+ id: this.strategyToDelete!.id,
132
+ docType: DocType.NAVIGATION_STRATEGY,
133
+ name: "DELETED",
134
+ description: "This strategy has been deleted",
135
+ implementingClass: "",
136
+ course: this.courseId,
137
+ serializedData: ""
138
+ };
139
+
140
+ // Update with empty strategy
141
+ await courseDB.updateNavigationStrategy(this.strategyToDelete!.id, emptyStrategy);
142
+ console.log(`Strategy ${this.strategyToDelete!.id} marked as deleted`);
143
+
144
+ // Remove from our local array
145
+ this.strategies = this.strategies.filter((s) => s.id !== this.strategyToDelete?.id);
146
+
147
+ this.showDeleteConfirm = false;
148
+ this.strategyToDelete = null;
149
+ } catch (error) {
150
+ console.error('Failed to delete navigation strategy:', error);
151
+ }
152
+ this.loading = false;
153
+ },
154
+ },
155
+ });
156
+ </script>
157
+
158
+ <style scoped>
159
+ .navigation-strategy-editor {
160
+ padding: 16px;
161
+ }
162
+
163
+ .no-strategies {
164
+ margin: 20px 0;
165
+ padding: 20px;
166
+ background-color: #f5f5f5;
167
+ border-radius: 4px;
168
+ text-align: center;
169
+ }
170
+ </style>
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <div class="navigation-strategy-list">
3
+ <v-list>
4
+ <v-list-item
5
+ v-for="strategy in strategies"
6
+ :key="strategy.id"
7
+ lines="three"
8
+ >
9
+ <template #prepend>
10
+ <v-icon> mdi-navigation </v-icon>
11
+ </template>
12
+
13
+ <v-list-item-title class="text-h6">
14
+ {{ strategy.name }}
15
+ </v-list-item-title>
16
+
17
+ <v-list-item-subtitle>{{ strategy.description }}</v-list-item-subtitle>
18
+
19
+ <v-list-item-subtitle class="strategy-details mt-2">
20
+ <div><strong>Type:</strong> {{ strategy.implementingClass }}</div>
21
+ <div v-if="strategy.serializedData"><strong>Configuration:</strong> {{ getDisplayConfig(strategy) }}</div>
22
+ </v-list-item-subtitle>
23
+
24
+ <template #append>
25
+ <div class="d-flex">
26
+ <v-btn icon size="small" title="Edit Strategy (coming soon)" class="mr-1" disabled>
27
+ <v-icon>mdi-pencil</v-icon>
28
+ </v-btn>
29
+
30
+ <v-btn
31
+ icon
32
+ size="small"
33
+ title="Delete Strategy"
34
+ class="mr-1"
35
+ @click="$emit('delete', strategy)"
36
+ >
37
+ <v-icon>mdi-delete</v-icon>
38
+ </v-btn>
39
+ </div>
40
+ </template>
41
+ </v-list-item>
42
+ </v-list>
43
+ </div>
44
+ </template>
45
+
46
+ <script lang="ts">
47
+ import { defineComponent, PropType } from 'vue';
48
+ import type { ContentNavigationStrategyData } from '@vue-skuilder/db/src/core/types/contentNavigationStrategy';
49
+
50
+ export default defineComponent({
51
+ name: 'NavigationStrategyList',
52
+
53
+ props: {
54
+ strategies: {
55
+ type: Array as PropType<ContentNavigationStrategyData[]>,
56
+ required: true,
57
+ },
58
+ },
59
+
60
+ emits: ['edit', 'delete'],
61
+
62
+ methods: {
63
+ getDisplayConfig(strategy: ContentNavigationStrategyData): string {
64
+ if (!strategy.serializedData) return 'No configuration';
65
+
66
+ try {
67
+ // Try to parse the serialized data to show a more user-friendly display
68
+ const config = JSON.parse(strategy.serializedData);
69
+ return Object.keys(config)
70
+ .map((key) => `${key}: ${config[key]}`)
71
+ .join(', ');
72
+ } catch {
73
+ // If it's not valid JSON, just return as is
74
+ return strategy.serializedData;
75
+ }
76
+ },
77
+ },
78
+ });
79
+ </script>
80
+
81
+ <style scoped>
82
+ .navigation-strategy-list {
83
+ margin: 16px 0;
84
+ }
85
+
86
+
87
+
88
+ .strategy-details {
89
+ font-size: 0.9em;
90
+ color: rgba(0, 0, 0, 0.6);
91
+ }
92
+ </style>