254 lines
7.5 KiB
TypeScript
254 lines
7.5 KiB
TypeScript
import type { LearningPath } from "@/services/learningPath";
|
|
import type {
|
|
CircleChild,
|
|
CircleGoal,
|
|
CircleJobSituation,
|
|
CourseCompletion,
|
|
CourseCompletionStatus,
|
|
CourseWagtailPage,
|
|
LearningContent,
|
|
LearningSequence,
|
|
LearningUnit,
|
|
LearningUnitPerformanceCriteria,
|
|
} 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[]
|
|
): LearningSequence[] {
|
|
let learningSequence: LearningSequence | undefined;
|
|
let learningUnit: LearningUnit | undefined;
|
|
let learningContent: LearningContent | undefined;
|
|
let previousLearningContent: LearningContent | undefined;
|
|
const result: LearningSequence[] = [];
|
|
|
|
children.forEach((child) => {
|
|
if (child.type === "learnpath.LearningSequence") {
|
|
if (learningSequence) {
|
|
if (learningUnit) {
|
|
learningUnit.last = true;
|
|
learningSequence.learningUnits.push(learningUnit);
|
|
}
|
|
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");
|
|
}
|
|
|
|
if (learningUnit && learningUnit.learningContents.length) {
|
|
learningSequence.learningUnits.push(learningUnit);
|
|
}
|
|
|
|
learningUnit = Object.assign(child, {
|
|
learningContents: [],
|
|
parentLearningSequence: learningSequence,
|
|
parentCircle: circle,
|
|
children: child.children.map((c) => {
|
|
c.parentLearningUnit = learningUnit;
|
|
c.parentLearningSequence = learningSequence;
|
|
return c;
|
|
}),
|
|
});
|
|
} else if (child.type === "learnpath.LearningContent") {
|
|
if (!learningUnit) {
|
|
throw new Error("LearningContent found before LearningUnit");
|
|
}
|
|
previousLearningContent = learningContent;
|
|
|
|
learningContent = Object.assign(child, {
|
|
parentCircle: circle,
|
|
parentLearningSequence: learningSequence,
|
|
parentLearningUnit: learningUnit,
|
|
previousLearningContent: previousLearningContent,
|
|
});
|
|
|
|
if (previousLearningContent) {
|
|
previousLearningContent.nextLearningContent = learningContent;
|
|
}
|
|
|
|
learningUnit.learningContents.push(child);
|
|
}
|
|
});
|
|
|
|
if (learningUnit && learningSequence) {
|
|
// TypeScript does not get it here...
|
|
learningUnit.last = true;
|
|
(learningSequence as LearningSequence).learningUnits.push(learningUnit);
|
|
result.push(learningSequence);
|
|
} else {
|
|
throw new Error(
|
|
"Finished with LearningContent but there is no LearningSequence and LearningUnit"
|
|
);
|
|
}
|
|
|
|
// sum minutes
|
|
result.forEach((learningSequence) => {
|
|
learningSequence.minutes = 0;
|
|
learningSequence.learningUnits.forEach((learningUnit) => {
|
|
learningUnit.minutes = 0;
|
|
learningUnit.learningContents.forEach((learningContent) => {
|
|
learningUnit.minutes += learningContent.minutes;
|
|
});
|
|
learningSequence.minutes += learningUnit.minutes;
|
|
});
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
export class Circle implements CourseWagtailPage {
|
|
readonly type = "learnpath.Circle";
|
|
readonly learningSequences: LearningSequence[];
|
|
completion_status: CourseCompletionStatus = "unknown";
|
|
|
|
nextCircle?: Circle;
|
|
previousCircle?: Circle;
|
|
|
|
constructor(
|
|
public readonly id: number,
|
|
public readonly slug: string,
|
|
public readonly title: string,
|
|
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 parentLearningPath?: LearningPath
|
|
) {
|
|
this.learningSequences = parseLearningSequences(this, this.children);
|
|
}
|
|
|
|
public static fromJson(json: any, 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,
|
|
learningPath
|
|
);
|
|
}
|
|
|
|
public get flatChildren(): (LearningContent | LearningUnitPerformanceCriteria)[] {
|
|
const result: (LearningContent | LearningUnitPerformanceCriteria)[] = [];
|
|
this.learningSequences.forEach((learningSequence) => {
|
|
learningSequence.learningUnits.forEach((learningUnit) => {
|
|
learningUnit.children.forEach((performanceCriteria) => {
|
|
result.push(performanceCriteria);
|
|
});
|
|
learningUnit.learningContents.forEach((learningContent) => {
|
|
result.push(learningContent);
|
|
});
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public get flatLearningContents(): LearningContent[] {
|
|
const result: LearningContent[] = [];
|
|
this.learningSequences.forEach((learningSequence) => {
|
|
learningSequence.learningUnits.forEach((learningUnit) => {
|
|
learningUnit.learningContents.forEach((learningContent) => {
|
|
result.push(learningContent);
|
|
});
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public get flatLearningUnits(): LearningUnit[] {
|
|
const result: LearningUnit[] = [];
|
|
this.learningSequences.forEach((learningSequence) => {
|
|
learningSequence.learningUnits.forEach((learningUnit) => {
|
|
result.push(learningUnit);
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public someFinishedInLearningSequence(translationKey: string): boolean {
|
|
if (translationKey) {
|
|
return (
|
|
this.flatChildren.filter((lc) => {
|
|
return (
|
|
lc.completion_status === "success" &&
|
|
lc.parentLearningSequence?.translation_key === translationKey
|
|
);
|
|
}).length > 0
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public allFinishedInLearningSequence(translationKey: string): boolean {
|
|
if (translationKey) {
|
|
const finishedContents = this.flatChildren.filter((lc) => {
|
|
return (
|
|
lc.completion_status === "success" &&
|
|
lc.parentLearningSequence?.translation_key === translationKey
|
|
);
|
|
}).length;
|
|
|
|
const totalContents = this.flatChildren.filter((lc) => {
|
|
return lc.parentLearningSequence?.translation_key === translationKey;
|
|
}).length;
|
|
return finishedContents === totalContents;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public parseCompletionData(completionData: CourseCompletion[]) {
|
|
this.flatChildren.forEach((page) => {
|
|
const pageIndex = completionData.findIndex((e) => {
|
|
return e.page_key === page.translation_key;
|
|
});
|
|
if (pageIndex >= 0) {
|
|
page.completion_status = completionData[pageIndex].completion_status;
|
|
} else {
|
|
page.completion_status = "unknown";
|
|
}
|
|
});
|
|
|
|
if (this.parentLearningPath) {
|
|
this.parentLearningPath.calcNextLearningContent(completionData);
|
|
}
|
|
}
|
|
}
|