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"> <script setup lang="ts">
import type { LearningContentType } from "@/types"; import type { LearningContentType } from "@/types";
import { learningContentTypesToName } from "@/utils/typeMaps"; import { learningContentTypeData } from "@/utils/typeMaps";
const props = defineProps<{ const props = defineProps<{
learningContentType: LearningContentType; learningContentType: LearningContentType;
@ -11,39 +11,12 @@ const props = defineProps<{
<div <div
class="flex bg-gray-200 rounded-full px-2.5 py-0.5 gap-2 items-center w-min h-min" 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 <component
v-if="props.learningContentType === 'assignment'" :is="learningContentTypeData(props.learningContentType).icon"
class="w-6 h-6" class="w-6 h-6"
/> ></component>
<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" />
<p class="whitespace-nowrap"> <p class="whitespace-nowrap">
{{ learningContentTypesToName.get(props.learningContentType) }} {{ learningContentTypeData(props.learningContentType).title }}
</p> </p>
</div> </div>
</template> </template>

View File

@ -4,7 +4,8 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCompetenceStore } from "@/stores/competence"; import { useCompetenceStore } from "@/stores/competence";
import type { CourseCompletionStatus } from "@/types"; import type { CourseCompletionStatus } from "@/types";
import * as log from "loglevel"; 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"); log.debug("CompetencesMainView created");

View File

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

View File

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

View File

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

View File

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

View File

@ -3,105 +3,133 @@ import type { Component } from "vue";
export type CourseCompletionStatus = "unknown" | "fail" | "success"; export type CourseCompletionStatus = "unknown" | "fail" | "success";
export type LearningContentType =
| "assignment"
| "book"
| "document"
| "exercise"
| "media_library"
| "online_training"
| "resource"
| "test"
| "video"
| "placeholder";
export interface LearningContentBlock { export interface BaseCourseWagtailPage {
type: LearningContentType; readonly id: number;
value: { readonly title: string;
description: string; readonly slug: string;
}; readonly type: string;
id: string; readonly translation_key: string;
readonly frontend_url: string;
completion_status?: CourseCompletionStatus;
completion_status_updated_at?: string;
} }
export interface AssignmentBlock { export interface CircleLight {
type: "assignment"; readonly id: number;
value: { readonly title: string;
description: string; readonly translation_key: string;
url: string;
};
id: string;
} }
export interface BookBlock { export interface BaseLearningContentBlock {
type: "book"; readonly type: string;
value: { readonly id: string;
readonly value: {
description: string; description: string;
url: string; url: string;
text?: string;
}; };
id: string;
} }
export interface DocumentBlock { export interface AssignmentBlock extends BaseLearningContentBlock {
type: "document"; readonly type: "assignment";
value: {
description: string;
url: string;
};
id: string;
} }
export interface ExerciseBlock { export interface BookBlock extends BaseLearningContentBlock {
type: "exercise"; readonly type: "book";
value: {
description: string;
url: string;
};
id: string;
} }
export interface MediaLibraryBlock { export interface DocumentBlock extends BaseLearningContentBlock {
type: "media_library"; readonly type: "document";
value: {
description: string;
url: string;
};
id: string;
} }
export interface OnlineTrainingBlock { export interface ExerciseBlock extends BaseLearningContentBlock {
type: "online_training"; readonly type: "exercise";
value: {
description: string;
url: string;
};
id: string;
} }
export interface ResourceBlock { export interface MediaLibraryBlock extends BaseLearningContentBlock {
type: "resource"; readonly type: "media_library";
value: {
description: string;
url: string;
};
id: string;
} }
export interface TestBlock { export interface OnlineTrainingBlock extends BaseLearningContentBlock {
type: "test"; readonly type: "online_training";
value: {
description: string;
url: string;
};
id: string;
} }
export interface VideoBlock { export interface ResourceBlock extends BaseLearningContentBlock {
type: "video"; readonly type: "resource";
value: { }
description: string;
url: string; export interface TestBlock extends BaseLearningContentBlock {
}; readonly type: "test";
id: string; }
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 { export interface CircleGoal {
@ -116,83 +144,19 @@ export interface CircleJobSituation {
id: string; id: string;
} }
export interface CourseWagtailPage { export interface WagtailCircle extends BaseCourseWagtailPage {
readonly id: number; readonly type: "learnpath.Circle";
readonly title: string; readonly description: string;
readonly slug: string; readonly goal_description: string;
readonly translation_key: string; readonly goals: CircleGoal[];
readonly frontend_url: string; readonly job_situation_description: string;
completion_status: CourseCompletionStatus; readonly job_situations: CircleJobSituation[];
completion_status_updated_at?: string; readonly children: CircleChild[];
} }
export interface CircleLight { export interface Topic extends BaseCourseWagtailPage {
readonly id: number; readonly type: "learnpath.Topic";
readonly title: string; readonly is_visible: boolean;
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;
circles: Circle[]; circles: Circle[];
} }
@ -208,7 +172,7 @@ export interface CourseCompletion {
page_slug: string; page_slug: string;
course: number; course: number;
completion_status: CourseCompletionStatus; completion_status: CourseCompletionStatus;
additional_json_data: any; additional_json_data: unknown;
} }
export interface CircleDiagramData { export interface CircleDiagramData {
@ -224,7 +188,7 @@ export interface CircleDiagramData {
export interface Course { export interface Course {
id: number; id: number;
name: string; title: string;
category_name: 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"; type: "media_library.MediaCategoryPage";
overview_icon: string; overview_icon: string;
introduction_text: string; introduction_text: string;
@ -295,31 +259,31 @@ export interface MediaCategoryPage extends CourseWagtailPage {
body: MediaContentCollection[]; body: MediaContentCollection[];
} }
export interface MediaLibraryPage extends CourseWagtailPage { export interface MediaLibraryPage extends BaseCourseWagtailPage {
type: "media_library.MediaLibraryPage"; readonly type: "media_library.MediaLibraryPage";
course: Course; readonly course: Course;
children: MediaCategoryPage[]; readonly children: MediaCategoryPage[];
} }
export interface PerformanceCriteria extends CourseWagtailPage { export interface PerformanceCriteria extends BaseCourseWagtailPage {
type: "competence.PerformanceCriteria"; readonly type: "competence.PerformanceCriteria";
competence_id: string; readonly competence_id: string;
circle: CircleLight; readonly circle: CircleLight;
course_category: CourseCategory; readonly course_category: CourseCategory;
learning_unit: CourseWagtailPage; readonly learning_unit: BaseCourseWagtailPage;
} }
export interface CompetencePage extends CourseWagtailPage { export interface CompetencePage extends BaseCourseWagtailPage {
type: "competence.CompetencePage"; readonly type: "competence.CompetencePage";
competence_id: string; readonly competence_id: string;
children: PerformanceCriteria[]; readonly children: PerformanceCriteria[];
} }
export interface CompetenceProfilePage extends CourseWagtailPage { export interface CompetenceProfilePage extends BaseCourseWagtailPage {
type: "competence.CompetenceProfilePage"; readonly type: "competence.CompetenceProfilePage";
course: Course; readonly course: Course;
circles: CircleLight[]; readonly circles: CircleLight[];
children: CompetencePage[]; readonly children: CompetencePage[];
} }
// dropdown // dropdown

View File

@ -1,14 +1,32 @@
import type { LearningContentType } from "@/types"; import type { LearningContentType } from "@/types";
import { assertUnreachable } from "@/utils/utils";
export const learningContentTypesToName = new Map<LearningContentType, string>([ export function learningContentTypeData(t: LearningContentType): {
["assignment", "Transferauftrag"], title: string;
["book", "Buch"], icon: string;
["document", "Dokument"], } {
["exercise", "Übung"], switch (t) {
["media_library", "Mediathek"], case "assignment":
["online_training", "Online-Training"], return { title: "Transferauftrag", icon: "it-icon-lc-assignment" };
["video", "Video"], case "book":
["test", "Test"], return { title: "Buch", icon: "it-icon-lc-book" };
["resource", "Seite"], case "document":
["placeholder", "In Umsetzung"], 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" icon = "media"
class PlaceholderBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.TextBlock()
class Meta:
icon = "media"
class ExerciseBlock(blocks.StructBlock): class ExerciseBlock(blocks.StructBlock):
description = blocks.TextBlock() description = blocks.TextBlock()
url = blocks.TextBlock() url = blocks.TextBlock()
@ -82,3 +74,11 @@ class VideoBlock(blocks.StructBlock):
class Meta: class Meta:
icon = "media" icon = "media"
class PlaceholderBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.TextBlock()
class Meta:
icon = "media"