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) => {
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

@ -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"