Refactor circle code to Circle class

This commit is contained in:
Daniel Egger 2022-07-04 10:45:00 +02:00
parent cc293400b4
commit 7022827cf3
7 changed files with 130 additions and 74 deletions

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import * as d3 from "d3"; import * as d3 from "d3";
import {computed, onMounted} from "vue"; import {computed} from "vue";
import * as _ from 'underscore' import * as _ from 'underscore'
import {useCircleStore} from '@/stores/circle';
const props = defineProps<{ const props = defineProps<{
circleStore: {},
width: { width: {
default: 500, default: 500,
type: number, type: number,
@ -18,28 +18,26 @@ const props = defineProps<{
}, },
}>() }>()
const circleStore = useCircleStore();
function someFinished(learningSequence) { function someFinished(learningSequence) {
return props.circleStore.flatChildren.filter((lc) => { if (circleStore.circle) {
return lc.completed && lc.parentLearningSequence?.translation_key === learningSequence.translation_key; return circleStore.circle.someFinishedInLearningSequence(learningSequence.translation_key);
}).length > 0; }
return false;
} }
const pieData = computed(() => { const pieData = computed(() => {
const circle = props.circleStore.circleData, completionData = props.circleStore.completionData const circle = circleStore.circle
console.log('initial of compute pie data ', circle, completionData) console.log('initial of compute pie data ', circle)
if (circle && completionData) { if (circle) {
console.log('initial of compute pie data ', circle, completionData) console.log('initial of compute pie data ', circle)
let learningSequences = _.filter(circle.children, (child) => { let learningSequences = _.filter(circle.children, (child) => {
return child.type === 'learnpath.LearningSequence'; return child.type === 'learnpath.LearningSequence';
}) })
const completionDataByPageId = _.object(_.map(completionData, function (item) {
return [item.page_key, item]
}))
let pieWeights = new Array(Math.max(learningSequences.length, 1)).fill(1) let pieWeights = new Array(Math.max(learningSequences.length, 1)).fill(1)
let pieGenerator = d3.pie() let pieGenerator = d3.pie()
let angles = pieGenerator(pieWeights) let angles = pieGenerator(pieWeights)

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type {Circle} from '@/types'; import {Circle} from '@/services/circle';
const props = defineProps<{ const props = defineProps<{
circleData: Circle circle: Circle
}>() }>()
</script> </script>
@ -17,7 +17,7 @@ const props = defineProps<{
</div> </div>
<h1 class="">Überblick: Circle "{{ circleData.title }}"</h1> <h1 class="">Überblick: Circle "{{ circle.title }}"</h1>
<p class="mt-8 text-xl">Hier zeigen wir dir, was du in diesem Circle lernen wirst.</p> <p class="mt-8 text-xl">Hier zeigen wir dir, was du in diesem Circle lernen wirst.</p>
@ -25,7 +25,7 @@ const props = defineProps<{
<h3>Du wirst in der Lage sein, ... </h3> <h3>Du wirst in der Lage sein, ... </h3>
<ul class="mt-4"> <ul class="mt-4">
<li class="text-xl flex items-center" v-for="goal in circleData.goals" :key="goal.id"> <li class="text-xl flex items-center" v-for="goal in circle.goals" :key="goal.id">
<it-icon-check class="mt-4 hidden lg:block w-12 h-12 text-sky-500 flex-none"></it-icon-check> <it-icon-check class="mt-4 hidden lg:block w-12 h-12 text-sky-500 flex-none"></it-icon-check>
<div class="mt-4">{{ goal.value }}</div> <div class="mt-4">{{ goal.value }}</div>
</li> </li>
@ -38,7 +38,7 @@ const props = defineProps<{
<ul class="grid grid-cols-1 lg:grid-cols-3 auto-rows-fr gap-6 mt-8"> <ul class="grid grid-cols-1 lg:grid-cols-3 auto-rows-fr gap-6 mt-8">
<li <li
v-for="jobSituation in circleData.job_situations" v-for="jobSituation in circle.job_situations"
:key="jobSituation.id" :key="jobSituation.id"
class="job-situation border border-gray-500 p-4 text-xl flex items-center" class="job-situation border border-gray-500 p-4 text-xl flex items-center"
> >

View File

@ -15,10 +15,8 @@ function toggleCompleted(learningContent: LearningContent) {
} }
const someFinished = computed(() => { const someFinished = computed(() => {
if (props.learningSequence) { if (props.learningSequence && circleStore.circle) {
return circleStore.flatChildren.filter((lc) => { return circleStore.circle.someFinishedInLearningSequence(props.learningSequence.translation_key);
return lc.completed && lc.parentLearningSequence?.translation_key === props.learningSequence.translation_key;
}).length > 0;
} }
return false; return false;

View File

@ -1,4 +1,13 @@
import type {CircleChild, LearningContent, LearningSequence, LearningUnit} from '@/types'; import type {
CircleChild,
CircleCompletion,
CircleGoal,
CircleInterface,
CircleJobSituation,
LearningContent,
LearningSequence,
LearningUnit
} from '@/types';
function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit { function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
@ -98,3 +107,74 @@ export function parseLearningSequences (children: CircleChild[]): LearningSequen
return result; return result;
} }
export class Circle implements CircleInterface {
readonly type = 'learnpath.Circle';
readonly learningSequences: LearningSequence[];
readonly completed: boolean;
constructor(
public readonly id: number,
public readonly slug: string,
public readonly title: string,
public readonly translation_key: string,
public description: string,
public children: CircleChild[],
public goals: CircleGoal[],
public job_situations: CircleJobSituation[],
) {
this.learningSequences = parseLearningSequences(this.children);
this.completed = false;
}
public static fromJson(json: any): 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.description,
json.children,
json.goals,
json.job_situations,
)
}
public get flatChildren(): CircleChild[] {
const result: CircleChild[] = [];
this.learningSequences.forEach((learningSequence) => {
learningSequence.learningUnits.forEach((learningUnit) => {
learningUnit.children.forEach((learningUnitQuestion) => {
result.push(learningUnitQuestion);
})
learningUnit.learningContents.forEach((learningContent) => {
result.push(learningContent);
});
});
});
return result;
}
public someFinishedInLearningSequence(translationKey: string): boolean {
if (translationKey) {
return this.flatChildren.filter((lc) => {
return lc.completed && lc.parentLearningSequence?.translation_key === translationKey;
}).length > 0;
}
return false;
}
public parseCompletionData(completionData: CircleCompletion[]) {
this.flatChildren.forEach((page) => {
const pageIndex = completionData.findIndex((e) => {
return e.page_key === page.translation_key;
});
if (pageIndex >= 0) {
page.completed = completionData[pageIndex].completed;
} else {
page.completed = undefined;
}
});
}
}

View File

@ -2,14 +2,13 @@ import * as log from 'loglevel';
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import type {Circle, CircleChild, CircleCompletion, LearningContent, LearningUnit, LearningUnitQuestion} from '@/types' import type {LearningContent, LearningUnit, LearningUnitQuestion} from '@/types'
import {Circle} from '@/services/circle'
import {itGet, itPost} from '@/fetchHelpers'; import {itGet, itPost} from '@/fetchHelpers';
import {parseLearningSequences} from '@/services/circle';
import {useAppStore} from '@/stores/app'; import {useAppStore} from '@/stores/app';
export type CircleStoreState = { export type CircleStoreState = {
circleData: Circle; circle: Circle | undefined;
completionData: CircleCompletion[];
currentLearningContent: LearningContent | undefined; currentLearningContent: LearningContent | undefined;
currentSelfEvaluation: LearningUnit | undefined; currentSelfEvaluation: LearningUnit | undefined;
page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION'; page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION';
@ -19,7 +18,7 @@ export const useCircleStore = defineStore({
id: 'circle', id: 'circle',
state: () => { state: () => {
return { return {
circleData: {}, circle: undefined,
completionData: {}, completionData: {},
currentLearningContent: undefined, currentLearningContent: undefined,
currentSelfEvaluation: undefined, currentSelfEvaluation: undefined,
@ -28,27 +27,15 @@ export const useCircleStore = defineStore({
}, },
getters: { getters: {
flatChildren: (state) => { flatChildren: (state) => {
const result:CircleChild[] = [];
state.circleData.learningSequences.forEach((learningSequence) => {
learningSequence.learningUnits.forEach((learningUnit) => {
learningUnit.children.forEach((learningUnitQuestion) => {
result.push(learningUnitQuestion);
})
learningUnit.learningContents.forEach((learningContent) => {
result.push(learningContent);
});
});
});
return result;
}, },
}, },
actions: { actions: {
async loadCircle(slug: string) { async loadCircle(slug: string) {
try { try {
this.circleData = await itGet(`/learnpath/api/circle/${slug}/`); const circleData = await itGet(`/learnpath/api/circle/${slug}/`);
this.circleData.learningSequences = parseLearningSequences(this.circleData.children); this.circle = Circle.fromJson(circleData);
this.completionData = await itGet(`/api/completion/circle/${this.circleData.translation_key}/`); const completionData = await itGet(`/api/completion/circle/${this.circle.translation_key}/`);
this.parseCompletionData(); this.circle.parseCompletionData(completionData);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
return error return error
@ -57,28 +44,18 @@ export const useCircleStore = defineStore({
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) { async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
try { try {
page.completed = flag; page.completed = flag;
this.completionData = await itPost('/api/completion/circle/mark/', { const completionData = await itPost('/api/completion/circle/mark/', {
page_key: page.translation_key, page_key: page.translation_key,
completed: page.completed, completed: page.completed,
}); });
this.parseCompletionData(); if (this.circle) {
this.circle.parseCompletionData(completionData);
}
} catch (error) { } catch (error) {
log.error(error); log.error(error);
return error return error
} }
}, },
parseCompletionData() {
this.flatChildren.forEach((page) => {
const pageIndex = this.completionData.findIndex((e) => {
return e.page_key === page.translation_key;
});
if (pageIndex >= 0) {
page.completed = this.completionData[pageIndex].completed;
} else {
page.completed = undefined;
}
});
},
openLearningContent(learningContent: LearningContent) { openLearningContent(learningContent: LearningContent) {
this.currentLearningContent = learningContent; this.currentLearningContent = learningContent;
const appStore = useAppStore(); const appStore = useAppStore();

View File

@ -1,3 +1,5 @@
import {parseLearningSequences} from '@/services/circle';
export interface LearningContentBlock { export interface LearningContentBlock {
type: 'web-based-training' | 'competence' | 'exercise' | 'knowledge'; type: 'web-based-training' | 'competence' | 'exercise' | 'knowledge';
value: { value: {
@ -46,10 +48,10 @@ export interface CircleJobSituation {
} }
export interface LearningWagtailPage { export interface LearningWagtailPage {
id: number; readonly id: number;
title: string; readonly title: string;
slug: string; readonly slug: string;
translation_key: string; readonly translation_key: string;
completed?: boolean; completed?: boolean;
} }
@ -105,15 +107,17 @@ export interface CircleCompletion {
json_data: any; json_data: any;
} }
export interface Circle extends LearningWagtailPage { export interface CircleInterface extends LearningWagtailPage {
type: 'learnpath.Circle'; readonly type: 'learnpath.Circle';
children: CircleChild[]; readonly children: CircleChild[];
description: string; readonly description: string;
readonly goals: CircleGoal[];
readonly job_situations: CircleJobSituation[];
learningSequences: LearningSequence[]; learningSequences: LearningSequence[];
goals: CircleGoal[];
job_situations: CircleJobSituation[];
} }
export interface CircleDiagramData { export interface CircleDiagramData {
index: number index: number
title: string title: string

View File

@ -35,15 +35,15 @@ onMounted(async () => {
<div v-else-if="circleStore.page === 'SELF_EVALUATION'"> <div v-else-if="circleStore.page === 'SELF_EVALUATION'">
<SelfEvaluation :key="circleStore.currentSelfEvaluation.translation_key"/> <SelfEvaluation :key="circleStore.currentSelfEvaluation.translation_key"/>
</div> </div>
<div v-else> <div v-else-if="circleStore.circle">
<div class="circle"> <div class="circle">
<div class="flex flex-col lg:flex-row"> <div class="flex flex-col lg:flex-row">
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:py-8"> <div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:py-8">
<h1 class="text-blue-dark text-7xl"> <h1 class="text-blue-dark text-7xl">
{{ circleStore.circleData.title }} {{ circleStore.circle.title }}
</h1> </h1>
<div v-if="circleStore.circleData.learningSequences && circleStore.flatChildren" class="w-full mt-8"> <div v-if="circleStore.circle.learningSequences && circleStore.circle.flatChildren" class="w-full mt-8">
<CircleDiagram :circle-store="circleStore"></CircleDiagram> <CircleDiagram :circle-store="circleStore"></CircleDiagram>
</div> </div>
@ -65,7 +65,7 @@ onMounted(async () => {
<div class="block border border-gray-500 mt-8 p-6"> <div class="block border border-gray-500 mt-8 p-6">
<h3 class="text-blue-dark">Das lernst du in diesem Circle.</h3> <h3 class="text-blue-dark">Das lernst du in diesem Circle.</h3>
<div class="prose mt-4"> <div class="prose mt-4">
{{ circleStore.circleData.description }} {{ circleStore.circle.description }}
</div> </div>
<button class="btn-primary mt-4" @click="circleStore.page = 'OVERVIEW'">Erfahre mehr dazu</button> <button class="btn-primary mt-4" @click="circleStore.page = 'OVERVIEW'">Erfahre mehr dazu</button>
@ -83,12 +83,11 @@ onMounted(async () => {
<div class="flex-auto bg-gray-100 px-4 py-8 lg:px-24"> <div class="flex-auto bg-gray-100 px-4 py-8 lg:px-24">
<div <div
v-for="learningSequence in circleStore.circleData.learningSequences" v-for="learningSequence in circleStore.circle.learningSequences"
:key="learningSequence.translation_key" :key="learningSequence.translation_key"
> >
<LearningSequence <LearningSequence
:learning-sequence="learningSequence" :learning-sequence="learningSequence"
:completion-data="circleStore.completionData"
></LearningSequence> ></LearningSequence>
</div> </div>