Refactor types

This commit is contained in:
Daniel Egger 2022-10-21 09:50:25 +02:00
parent bbc67a8526
commit c8824763f6
11 changed files with 230 additions and 287 deletions

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { LearningContentType } from "@/types";
import { learningContentTypesToName } from "@/utils/typeMaps";
import { learningContentTypeData } from "@/utils/typeMaps";
const props = defineProps<{
learningContentType: LearningContentType;
@ -11,39 +11,12 @@ const props = defineProps<{
<div
class="flex bg-gray-200 rounded-full px-2.5 py-0.5 gap-2 items-center w-min h-min"
>
<it-icon-lc-assignment
v-if="props.learningContentType === 'assignment'"
<component
:is="learningContentTypeData(props.learningContentType).icon"
class="w-6 h-6"
/>
<it-icon-lc-exercise
v-else-if="props.learningContentType === 'exercise'"
class="w-6 h-6"
/>
<it-icon-lc-book v-else-if="props.learningContentType === 'book'" class="w-6 h-6" />
<it-icon-lc-video
v-else-if="props.learningContentType === 'video'"
class="w-6 h-6"
/>
<it-icon-lc-media-library
v-else-if="props.learningContentType === 'media_library'"
class="w-6 h-6"
/>
<it-icon-lc-test v-else-if="props.learningContentType === 'test'" class="w-6 h-6" />
<it-icon-lc-online-training
v-else-if="props.learningContentType === 'online_training'"
class="w-6 h-6"
/>
<it-icon-lc-resource
v-else-if="props.learningContentType === 'resource'"
class="w-6 h-6"
/>
<it-icon-lc-resource
v-else-if="props.learningContentType === 'document'"
class="w-6 h-6"
/>
<it-icon-lc-document v-else class="w-6 h-6" />
></component>
<p class="whitespace-nowrap">
{{ learningContentTypesToName.get(props.learningContentType) }}
{{ learningContentTypeData(props.learningContentType).title }}
</p>
</div>
</template>

View File

@ -4,7 +4,8 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCompetenceStore } from "@/stores/competence";
import type { CourseCompletionStatus } from "@/types";
import * as log from "loglevel";
import { computed, Ref, ref } from "vue";
import type { Ref } from "vue";
import { computed, ref } from "vue";
log.debug("CompetencesMainView created");

View File

@ -4,34 +4,13 @@ import type {
CircleGoal,
CircleJobSituation,
CourseCompletion,
CourseCompletionStatus,
CourseWagtailPage,
LearningContent,
LearningSequence,
LearningUnit,
LearningUnitPerformanceCriteria,
WagtailCircle,
} from "@/types";
function _createEmptyLearningUnit(
parentLearningSequence: LearningSequence
): LearningUnit {
return {
id: 0,
title: "",
slug: "",
translation_key: "",
type: "learnpath.LearningUnit",
frontend_url: "",
learningContents: [],
minutes: 0,
parentLearningSequence: parentLearningSequence,
children: [],
last: true,
completion_status: "unknown",
evaluate_url: "",
};
}
export function parseLearningSequences(
circle: Circle,
children: CircleChild[]
@ -52,9 +31,6 @@ export function parseLearningSequences(
result.push(learningSequence);
}
learningSequence = Object.assign(child, { learningUnits: [] });
// initialize empty learning unit if there will not come a learning unit next
learningUnit = _createEmptyLearningUnit(learningSequence);
} else if (child.type === "learnpath.LearningUnit") {
if (!learningSequence) {
throw new Error("LearningUnit found before LearningSequence");
@ -92,13 +68,15 @@ export function parseLearningSequences(
}
learningUnit.learningContents.push(child);
} else {
throw new Error("Unknown CircleChild found...");
}
});
if (learningUnit && learningSequence) {
// TypeScript does not get it here...
learningUnit.last = true;
(learningSequence as LearningSequence).learningUnits.push(learningUnit);
learningSequence.learningUnits.push(learningUnit);
result.push(learningSequence);
} else {
throw new Error(
@ -121,10 +99,9 @@ export function parseLearningSequences(
return result;
}
export class Circle implements CourseWagtailPage {
export class Circle implements WagtailCircle {
readonly type = "learnpath.Circle";
readonly learningSequences: LearningSequence[];
completion_status: CourseCompletionStatus = "unknown";
nextCircle?: Circle;
previousCircle?: Circle;
@ -136,30 +113,33 @@ export class Circle implements CourseWagtailPage {
public readonly translation_key: string,
public readonly frontend_url: string,
public readonly description: string,
public children: CircleChild[],
public goal_description: string,
public goals: CircleGoal[],
public job_situation_description: string,
public job_situations: CircleJobSituation[],
public readonly children: CircleChild[],
public readonly goal_description: string,
public readonly goals: CircleGoal[],
public readonly job_situation_description: string,
public readonly job_situations: CircleJobSituation[],
public readonly parentLearningPath?: LearningPath
) {
this.learningSequences = parseLearningSequences(this, this.children);
}
public static fromJson(json: any, learningPath?: LearningPath): Circle {
public static fromJson(
wagtailCircle: WagtailCircle,
learningPath?: LearningPath
): Circle {
// TODO add error checking when the data does not conform to the schema
return new Circle(
json.id,
json.slug,
json.title,
json.translation_key,
json.frontend_url,
json.description,
json.children,
json.goal_description,
json.goals,
json.job_situation_description,
json.job_situations,
wagtailCircle.id,
wagtailCircle.slug,
wagtailCircle.title,
wagtailCircle.translation_key,
wagtailCircle.frontend_url,
wagtailCircle.description,
wagtailCircle.children,
wagtailCircle.goal_description,
wagtailCircle.goals,
wagtailCircle.job_situation_description,
wagtailCircle.job_situations,
learningPath
);
}

View File

@ -2,12 +2,12 @@ import * as _ from "lodash";
import { Circle } from "@/services/circle";
import type {
Course,
CourseCompletion,
CourseCompletionStatus,
CourseWagtailPage,
LearningContent,
LearningPathChild,
Topic,
WagtailLearningPath,
} from "@/types";
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
@ -22,21 +22,23 @@ function getLastCompleted(courseId: number, completionData: CourseCompletion[])
);
}
export class LearningPath implements CourseWagtailPage {
export class LearningPath implements WagtailLearningPath {
readonly type = "learnpath.LearningPath";
public topics: Topic[];
public circles: Circle[];
public nextLearningContent?: LearningContent;
readonly completion_status: CourseCompletionStatus = "unknown";
public static fromJson(json: any, completionData: CourseCompletion[]): LearningPath {
public static fromJson(
json: WagtailLearningPath,
completionData: CourseCompletion[]
): LearningPath {
return new LearningPath(
json.id,
json.slug,
json.course.title,
json.translation_key,
json.frontend_url,
json.course.id,
json.course,
json.children,
completionData
);
@ -48,7 +50,7 @@ export class LearningPath implements CourseWagtailPage {
public readonly title: string,
public readonly translation_key: string,
public readonly frontend_url: string,
public readonly courseId: number,
public readonly course: Course,
public children: LearningPathChild[],
completionData?: CourseCompletion[]
) {
@ -95,7 +97,7 @@ export class LearningPath implements CourseWagtailPage {
this.nextLearningContent = undefined;
const lastCompletedLearningContent = getLastCompleted(
this.courseId,
this.course.id,
completionData
);

View File

@ -1,9 +1,9 @@
import { itGet } from "@/fetchHelpers";
import { useCompletionStore } from "@/stores/completion";
import type {
BaseCourseWagtailPage,
CompetencePage,
CompetenceProfilePage,
CourseWagtailPage,
PerformanceCriteria,
} from "@/types";
import _ from "lodash";
@ -103,9 +103,11 @@ export const useCompetenceStore = defineStore({
this.competenceProfilePage = competenceProfilePageData;
const circles = competenceProfilePageData.circles.map((c: CourseWagtailPage) => {
return { id: c.translation_key, name: `Circle: ${c.title}` };
});
const circles = competenceProfilePageData.circles.map(
(c: BaseCourseWagtailPage) => {
return { id: c.translation_key, name: `Circle: ${c.title}` };
}
);
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
await this.parseCompletionData();

View File

@ -1,5 +1,5 @@
import { itGet, itPost } from "@/fetchHelpers";
import type { CourseCompletion, CourseWagtailPage } from "@/types";
import type { BaseCourseWagtailPage, CourseCompletion } from "@/types";
import { defineStore } from "pinia";
export type CompletionStoreState = {
@ -28,7 +28,7 @@ export const useCompletionStore = defineStore({
this.completionData = completionData;
return this.completionData || [];
},
async markPage(page: CourseWagtailPage) {
async markPage(page: BaseCourseWagtailPage) {
const completionData = await itPost("/api/course/completion/mark/", {
page_key: page.translation_key,
completion_status: page.completion_status,

View File

@ -3,105 +3,133 @@ import type { Component } from "vue";
export type CourseCompletionStatus = "unknown" | "fail" | "success";
export type LearningContentType =
| "assignment"
| "book"
| "document"
| "exercise"
| "media_library"
| "online_training"
| "resource"
| "test"
| "video"
| "placeholder";
export interface LearningContentBlock {
type: LearningContentType;
value: {
description: string;
};
id: string;
export interface BaseCourseWagtailPage {
readonly id: number;
readonly title: string;
readonly slug: string;
readonly type: string;
readonly translation_key: string;
readonly frontend_url: string;
completion_status?: CourseCompletionStatus;
completion_status_updated_at?: string;
}
export interface AssignmentBlock {
type: "assignment";
value: {
description: string;
url: string;
};
id: string;
export interface CircleLight {
readonly id: number;
readonly title: string;
readonly translation_key: string;
}
export interface BookBlock {
type: "book";
value: {
export interface BaseLearningContentBlock {
readonly type: string;
readonly id: string;
readonly value: {
description: string;
url: string;
text?: string;
};
id: string;
}
export interface DocumentBlock {
type: "document";
value: {
description: string;
url: string;
};
id: string;
export interface AssignmentBlock extends BaseLearningContentBlock {
readonly type: "assignment";
}
export interface ExerciseBlock {
type: "exercise";
value: {
description: string;
url: string;
};
id: string;
export interface BookBlock extends BaseLearningContentBlock {
readonly type: "book";
}
export interface MediaLibraryBlock {
type: "media_library";
value: {
description: string;
url: string;
};
id: string;
export interface DocumentBlock extends BaseLearningContentBlock {
readonly type: "document";
}
export interface OnlineTrainingBlock {
type: "online_training";
value: {
description: string;
url: string;
};
id: string;
export interface ExerciseBlock extends BaseLearningContentBlock {
readonly type: "exercise";
}
export interface ResourceBlock {
type: "resource";
value: {
description: string;
url: string;
};
id: string;
export interface MediaLibraryBlock extends BaseLearningContentBlock {
readonly type: "media_library";
}
export interface TestBlock {
type: "test";
value: {
description: string;
url: string;
};
id: string;
export interface OnlineTrainingBlock extends BaseLearningContentBlock {
readonly type: "online_training";
}
export interface VideoBlock {
type: "video";
value: {
description: string;
url: string;
};
id: string;
export interface ResourceBlock extends BaseLearningContentBlock {
readonly type: "resource";
}
export interface TestBlock extends BaseLearningContentBlock {
readonly type: "test";
}
export interface VideoBlock extends BaseLearningContentBlock {
readonly type: "video";
}
export interface PlaceholderBlock extends BaseLearningContentBlock {
readonly type: "placeholder";
}
export type LearningContentBlock =
| AssignmentBlock
| BookBlock
| DocumentBlock
| ExerciseBlock
| MediaLibraryBlock
| OnlineTrainingBlock
| ResourceBlock
| TestBlock
| VideoBlock
| PlaceholderBlock;
export type LearningContentType = LearningContentBlock["type"];
export interface LearningContent extends BaseCourseWagtailPage {
readonly type: "learnpath.LearningContent";
minutes: number;
contents: LearningContentBlock[];
parentCircle: Circle;
parentLearningSequence?: LearningSequence;
parentLearningUnit?: LearningUnit;
nextLearningContent?: LearningContent;
previousLearningContent?: LearningContent;
}
export interface LearningUnitPerformanceCriteria extends BaseCourseWagtailPage {
readonly type: "competence.PerformanceCriteria";
readonly competence_id: string;
parentLearningSequence?: LearningSequence;
parentLearningUnit?: LearningUnit;
}
export interface LearningUnit extends BaseCourseWagtailPage {
readonly type: "learnpath.LearningUnit";
readonly evaluate_url: string;
readonly course_category: CourseCategory;
children: LearningUnitPerformanceCriteria[];
// additional frontend fields
learningContents: LearningContent[];
minutes: number;
parentLearningSequence?: LearningSequence;
parentCircle?: Circle;
last?: boolean;
}
export interface LearningSequence extends BaseCourseWagtailPage {
readonly type: "learnpath.LearningSequence";
icon: string;
learningUnits: LearningUnit[];
minutes: number;
}
export type CircleChild = LearningContent | LearningUnit | LearningSequence;
export interface WagtailLearningPath extends BaseCourseWagtailPage {
readonly type: "learnpath.LearningPath";
course: Course;
children: LearningPathChild[];
}
export interface CircleGoal {
@ -116,83 +144,19 @@ export interface CircleJobSituation {
id: string;
}
export interface CourseWagtailPage {
readonly id: number;
readonly title: string;
readonly slug: string;
readonly translation_key: string;
readonly frontend_url: string;
completion_status: CourseCompletionStatus;
completion_status_updated_at?: string;
export interface WagtailCircle extends BaseCourseWagtailPage {
readonly type: "learnpath.Circle";
readonly description: string;
readonly goal_description: string;
readonly goals: CircleGoal[];
readonly job_situation_description: string;
readonly job_situations: CircleJobSituation[];
readonly children: CircleChild[];
}
export interface CircleLight {
readonly id: number;
readonly title: string;
readonly slug: string;
readonly translation_key: string;
}
export interface LearningContent extends CourseWagtailPage {
type: "learnpath.LearningContent";
minutes: number;
contents: (
| AssignmentBlock
| BookBlock
| DocumentBlock
| ExerciseBlock
| MediaLibraryBlock
| OnlineTrainingBlock
| ResourceBlock
| TestBlock
| VideoBlock
)[];
parentCircle: Circle;
parentLearningSequence?: LearningSequence;
parentLearningUnit?: LearningUnit;
nextLearningContent?: LearningContent;
previousLearningContent?: LearningContent;
}
export interface LearningUnitPerformanceCriteria extends CourseWagtailPage {
type: "competence.PerformanceCriteria";
parentLearningSequence?: LearningSequence;
parentLearningUnit?: LearningUnit;
}
export interface LearningUnit extends CourseWagtailPage {
type: "learnpath.LearningUnit";
learningContents: LearningContent[];
evaluate_url: string;
minutes: number;
parentLearningSequence?: LearningSequence;
parentCircle?: Circle;
children: LearningUnitPerformanceCriteria[];
last?: boolean;
}
export interface LearningSequence extends CourseWagtailPage {
type: "learnpath.LearningSequence";
icon: string;
learningUnits: LearningUnit[];
minutes: number;
}
export type CircleChild =
| LearningContent
| LearningUnit
| LearningSequence
| LearningUnitPerformanceCriteria;
export interface WagtailCircle extends CourseWagtailPage {
type: "learnpath.Circle";
children: CircleChild[];
description: string;
}
export interface Topic extends CourseWagtailPage {
type: "learnpath.Topic";
is_visible: boolean;
export interface Topic extends BaseCourseWagtailPage {
readonly type: "learnpath.Topic";
readonly is_visible: boolean;
circles: Circle[];
}
@ -208,7 +172,7 @@ export interface CourseCompletion {
page_slug: string;
course: number;
completion_status: CourseCompletionStatus;
additional_json_data: any;
additional_json_data: unknown;
}
export interface CircleDiagramData {
@ -224,7 +188,7 @@ export interface CircleDiagramData {
export interface Course {
id: number;
name: string;
title: string;
category_name: string;
}
@ -280,7 +244,7 @@ export interface MediaContentCollection {
};
}
export interface MediaCategoryPage extends CourseWagtailPage {
export interface MediaCategoryPage extends BaseCourseWagtailPage {
type: "media_library.MediaCategoryPage";
overview_icon: string;
introduction_text: string;
@ -295,31 +259,31 @@ export interface MediaCategoryPage extends CourseWagtailPage {
body: MediaContentCollection[];
}
export interface MediaLibraryPage extends CourseWagtailPage {
type: "media_library.MediaLibraryPage";
course: Course;
children: MediaCategoryPage[];
export interface MediaLibraryPage extends BaseCourseWagtailPage {
readonly type: "media_library.MediaLibraryPage";
readonly course: Course;
readonly children: MediaCategoryPage[];
}
export interface PerformanceCriteria extends CourseWagtailPage {
type: "competence.PerformanceCriteria";
competence_id: string;
circle: CircleLight;
course_category: CourseCategory;
learning_unit: CourseWagtailPage;
export interface PerformanceCriteria extends BaseCourseWagtailPage {
readonly type: "competence.PerformanceCriteria";
readonly competence_id: string;
readonly circle: CircleLight;
readonly course_category: CourseCategory;
readonly learning_unit: BaseCourseWagtailPage;
}
export interface CompetencePage extends CourseWagtailPage {
type: "competence.CompetencePage";
competence_id: string;
children: PerformanceCriteria[];
export interface CompetencePage extends BaseCourseWagtailPage {
readonly type: "competence.CompetencePage";
readonly competence_id: string;
readonly children: PerformanceCriteria[];
}
export interface CompetenceProfilePage extends CourseWagtailPage {
type: "competence.CompetenceProfilePage";
course: Course;
circles: CircleLight[];
children: CompetencePage[];
export interface CompetenceProfilePage extends BaseCourseWagtailPage {
readonly type: "competence.CompetenceProfilePage";
readonly course: Course;
readonly circles: CircleLight[];
readonly children: CompetencePage[];
}
// dropdown

View File

@ -1,14 +1,32 @@
import type { LearningContentType } from "@/types";
import { assertUnreachable } from "@/utils/utils";
export const learningContentTypesToName = new Map<LearningContentType, string>([
["assignment", "Transferauftrag"],
["book", "Buch"],
["document", "Dokument"],
["exercise", "Übung"],
["media_library", "Mediathek"],
["online_training", "Online-Training"],
["video", "Video"],
["test", "Test"],
["resource", "Seite"],
["placeholder", "In Umsetzung"],
]);
export function learningContentTypeData(t: LearningContentType): {
title: string;
icon: string;
} {
switch (t) {
case "assignment":
return { title: "Transferauftrag", icon: "it-icon-lc-assignment" };
case "book":
return { title: "Buch", icon: "it-icon-lc-book" };
case "document":
return { title: "Dokument", icon: "it-icon-lc-document" };
case "exercise":
return { title: "Übung", icon: "it-icon-lc-exercise" };
case "media_library":
return { title: "Mediathek", icon: "it-icon-lc-media-library" };
case "online_training":
return { title: "Online-Training", icon: "it-icon-lc-online-training" };
case "video":
return { title: "Video", icon: "it-icon-lc-video" };
case "test":
return { title: "Test", icon: "it-icon-lc-test" };
case "resource":
return { title: "Ressource", icon: "it-icon-lc-resource" };
case "placeholder":
return { title: "In Umsetzung", icon: "it-icon-lc-document" };
}
return assertUnreachable(t);
}

View File

@ -0,0 +1,3 @@
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}

View File

@ -273,7 +273,7 @@ wichtige Grundlage für eine erfolgreiche Beziehung.
VideoBlockFactory(
url="https://onedrive.live.com/embed?cid=26E4A934B79DCE5E&resid=26E4A934B79DCE5E%2153350&authkey=AId6i7z_X8l2fHw",
description="In dieser Circle zeigt dir ein Fachexperte anhand von Kundensituationen, wie du erfolgreich"
"den Kundenbedarf ermitteln, analysieren, priorisieren und anschliessend zusammenfassen kannst.",
"den Kundenbedarf ermitteln, analysieren, priorisieren und anschliessend zusammenfassen kannst.",
),
)
],

View File

@ -26,14 +26,6 @@ class DocumentBlock(blocks.StructBlock):
icon = "media"
class PlaceholderBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.TextBlock()
class Meta:
icon = "media"
class ExerciseBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.TextBlock()
@ -82,3 +74,11 @@ class VideoBlock(blocks.StructBlock):
class Meta:
icon = "media"
class PlaceholderBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.TextBlock()
class Meta:
icon = "media"