Refactor learningPath loading

This commit is contained in:
Daniel Egger 2022-12-06 10:12:55 +01:00
parent 00c2217ad1
commit 2c17012686
11 changed files with 100 additions and 79 deletions

View File

@ -12,6 +12,10 @@ export default {
type: String, type: String,
default: "horizontal", default: "horizontal",
}, },
postfix: {
type: String,
default: "",
},
learningPath: { learningPath: {
required: true, required: true,
type: Object, type: Object,
@ -25,7 +29,7 @@ export default {
}, },
computed: { computed: {
svgId() { svgId() {
return `learningpath-diagram-${this.learningPath.slug}-${this.diagramType}`; return `learningpath-diagram-${this.learningPath.slug}-${this.diagramType}${this.postfix}`;
}, },
viewBox() { viewBox() {
return `0 0 ${this.width} ${this.height}`; return `0 0 ${this.width} ${this.height}`;

View File

@ -9,7 +9,7 @@ import { ref } from "vue";
log.debug("LearningPathDiagramSmall created"); log.debug("LearningPathDiagramSmall created");
const props = defineProps<{ const props = defineProps<{
learningPathUrl: string; courseSlug: string;
}>(); }>();
const learningPathData = ref<LearningPath | undefined>(undefined); const learningPathData = ref<LearningPath | undefined>(undefined);
@ -17,11 +17,7 @@ const learningPathData = ref<LearningPath | undefined>(undefined);
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
learningPathStore learningPathStore
.loadLearningPath( .loadLearningPath(props.courseSlug + "-lp", undefined, false, false)
props.learningPathUrl.replace(/\/learn$/, "-lp").replace(/^\/course\//, ""),
false,
false
)
.then((data) => { .then((data) => {
learningPathData.value = data; learningPathData.value = data;
}); });

View File

@ -1,31 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { useLearningPathStore } from "@/stores/learningPath";
import * as log from "loglevel"; import * as log from "loglevel";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue"; import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue"; import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
import type { LearningPath } from "@/services/learningPath";
log.debug("LearningPathView created"); log.debug("LearningPathView created");
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string; learningPath: LearningPath | undefined;
show: boolean; show: boolean;
}>(); }>();
const learningPathStore = useLearningPathStore();
const emits = defineEmits(["closemodal"]); const emits = defineEmits(["closemodal"]);
</script> </script>
<template> <template>
<ItFullScreenModal :show="show" @closemodal="$emit('closemodal')"> <ItFullScreenModal :show="show" @closemodal="$emit('closemodal')">
<div v-if="learningPathStore.learningPath" class="container-medium"> <div v-if="learningPath" class="container-medium">
<h1>{{ learningPathStore.learningPath.title }}</h1> <h1>{{ learningPath.title }}</h1>
<div class="learningpath flex flex-col"> <div class="learningpath flex flex-col">
<div class="flex flex-col h-max"> <div class="flex flex-col h-max">
<LearningPathDiagram <LearningPathDiagram
v-if="learningPathStore.learningPath" v-if="learningPath"
class="w-full" class="w-full"
:learning-path="learningPathStore.learningPath" :learning-path="learningPath"
diagram-type="vertical" diagram-type="vertical"
></LearningPathDiagram> ></LearningPathDiagram>
</div> </div>

View File

@ -42,7 +42,7 @@ onMounted(async () => {
<div> <div>
<LearningPathDiagramSmall <LearningPathDiagramSmall
class="mb-4" class="mb-4"
:learning-path-url="courseSession.learning_path_url" :course-slug="courseSession.course.slug"
></LearningPathDiagramSmall> ></LearningPathDiagramSmall>
</div> </div>
<div> <div>

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCockpitStore } from "@/stores/cockpit"; import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence"; import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath";
import * as log from "loglevel"; import * as log from "loglevel";
import { onMounted } from "vue"; import { onMounted } from "vue";
@ -12,6 +13,7 @@ const props = defineProps<{
const cockpitStore = useCockpitStore(); const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore(); const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();
onMounted(async () => { onMounted(async () => {
log.debug("CockpitParentPage mounted", props.courseSlug); log.debug("CockpitParentPage mounted", props.courseSlug);
@ -23,6 +25,8 @@ onMounted(async () => {
props.courseSlug + "-competence", props.courseSlug + "-competence",
csu.user_id csu.user_id
); );
learningPathStore.loadLearningPath(props.courseSlug + "-lp", csu.user_id);
}); });
} catch (error) { } catch (error) {
log.error(error); log.error(error);

View File

@ -1,9 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCockpitStore } from "@/stores/cockpit"; import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath"; import { useLearningPathStore } from "@/stores/learningPath";
import * as log from "loglevel"; import * as log from "loglevel";
import { onMounted } from "vue"; import { computed, onMounted } from "vue";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue"; import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
@ -15,23 +14,19 @@ const props = defineProps<{
log.debug("CockpitUserProfilePage created", props.userId); log.debug("CockpitUserProfilePage created", props.userId);
const cockpitStore = useCockpitStore(); const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
function userCountStatus(userId: number) {
return competenceStore.calcStatusCount(
competenceStore.flatPerformanceCriteria(userId)
);
}
onMounted(async () => { onMounted(async () => {
log.debug("CockpitUserProfilePage mounted"); log.debug("CockpitUserProfilePage mounted");
});
try { const learningPath = computed(() => {
await learningPathStore.loadLearningPath(props.courseSlug + "-lp"); if (learningPathStore.learningPaths.size > 0) {
} catch (error) { const learningPathKey = `${props.courseSlug}-lp-${props.userId}`;
log.error(error); return learningPathStore.learningPaths.get(learningPathKey);
} }
return undefined;
}); });
function courseSessionUser() { function courseSessionUser() {
@ -50,11 +45,12 @@ function courseSessionUser() {
:src="courseSessionUser()?.avatar_url" :src="courseSessionUser()?.avatar_url"
/> />
</div> </div>
<div v-if="learningPathStore.learningPath"> <div v-if="learningPath">
<LearningPathDiagram <LearningPathDiagram
class="mx-auto max-w-[1920px] max-h-[90px] lg:max-h-[380px] w-full px-4" class="mx-auto max-w-[1920px] max-h-[90px] lg:max-h-[380px] w-full px-4"
diagram-type="horizontal" diagram-type="horizontal"
:learning-path="learningPathStore.learningPath" :learning-path="learningPath"
:postfix="userId"
></LearningPathDiagram> ></LearningPathDiagram>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import * as log from "loglevel";
import { useLearningPathStore } from "@/stores/learningPath"; import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import { onMounted } from "vue"; import { computed, onMounted } from "vue";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue"; import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
import LearningPathViewVertical from "@/components/learningPath/LearningPathViewVertical.vue"; import LearningPathViewVertical from "@/components/learningPath/LearningPathViewVertical.vue";
@ -18,6 +18,15 @@ const props = defineProps<{
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
const userStore = useUserStore(); const userStore = useUserStore();
const learningPath = computed(() => {
if (userStore.loggedIn && learningPathStore.learningPaths.size > 0) {
const learningPathKey = `${props.courseSlug}-lp-${userStore.id}`;
return learningPathStore.learningPaths.get(learningPathKey);
}
return undefined;
});
onMounted(async () => { onMounted(async () => {
log.debug("LearningPathView mounted"); log.debug("LearningPathView mounted");
@ -29,7 +38,7 @@ onMounted(async () => {
}); });
const createContinueUrl = (learningPath: LearningPath): [string, boolean] => { const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
if (learningPath.nextLearningContent) { if (learningPath?.nextLearningContent) {
const circle = learningPath.nextLearningContent.parentCircle; const circle = learningPath.nextLearningContent.parentCircle;
const url = const url =
learningPath.nextLearningContent.parentLearningSequence?.frontend_url || learningPath.nextLearningContent.parentLearningSequence?.frontend_url ||
@ -45,11 +54,11 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
</script> </script>
<template> <template>
<div v-if="learningPathStore.learningPath" class="bg-gray-200"> <div v-if="learningPath" class="bg-gray-200">
<Teleport to="body"> <Teleport to="body">
<LearningPathViewVertical <LearningPathViewVertical
:show="learningPathStore.page === 'OVERVIEW'" :show="learningPathStore.page === 'OVERVIEW'"
:learning-path-slug="props.courseSlug + '-lp'" :learning-path="learningPath"
@closemodal="learningPathStore.page = 'INDEX'" @closemodal="learningPathStore.page = 'INDEX'"
/> />
</Teleport> </Teleport>
@ -70,13 +79,13 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
<LearningPathDiagram <LearningPathDiagram
class="mx-auto max-w-[1920px] max-h-[90px] lg:max-h-[380px] w-full px-4" class="mx-auto max-w-[1920px] max-h-[90px] lg:max-h-[380px] w-full px-4"
diagram-type="horizontal" diagram-type="horizontal"
:learning-path="learningPathStore.learningPath" :learning-path="learningPath"
></LearningPathDiagram> ></LearningPathDiagram>
</div> </div>
<div class="container-large pt-0 lg:pt-4"> <div class="container-large pt-0 lg:pt-4">
<h1 data-cy="learning-path-title" class="mt-6 lg:mt-12 mb-6"> <h1 data-cy="learning-path-title" class="mt-6 lg:mt-12 mb-6">
{{ learningPathStore.learningPath.title }} {{ learningPath.title }}
</h1> </h1>
<div <div
class="bg-white p-4 lg:mb-16 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start" class="bg-white p-4 lg:mb-16 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start"
@ -87,27 +96,19 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
</h2> </h2>
<p class="mt-4 text-xl"></p> <p class="mt-4 text-xl"></p>
</div> </div>
<div <div v-if="learningPath.nextLearningContent" class="p-4 lg:p-8 flex-2">
v-if="learningPathStore.learningPath.nextLearningContent"
class="p-4 lg:p-8 flex-2"
>
{{ $t("learningPathPage.nextStep") }} {{ $t("learningPathPage.nextStep") }}
<h3> <h3>
{{ {{ learningPath.nextLearningContent.parentCircle.title }}:
learningPathStore.learningPath.nextLearningContent.parentCircle.title {{ learningPath.nextLearningContent.parentLearningSequence?.title }}
}}:
{{
learningPathStore.learningPath.nextLearningContent
.parentLearningSequence?.title
}}
</h3> </h3>
<router-link <router-link
class="mt-4 btn-blue" class="mt-4 btn-blue"
:to="createContinueUrl(learningPathStore.learningPath)[0]" :to="createContinueUrl(learningPath)[0]"
data-cy="lp-continue-button" data-cy="lp-continue-button"
translate translate
> >
<span v-if="createContinueUrl(learningPathStore.learningPath)[1]"> <span v-if="createContinueUrl(learningPath)[1]">
{{ $t("general.start") }} {{ $t("general.start") }}
</span> </span>
<span v-else>{{ $t("general.nextStep") }}</span> <span v-else>{{ $t("general.nextStep") }}</span>

View File

@ -3,6 +3,7 @@ import * as log from "loglevel";
import type { Circle } from "@/services/circle"; import type { Circle } from "@/services/circle";
import { useCompletionStore } from "@/stores/completion"; import { useCompletionStore } from "@/stores/completion";
import { useLearningPathStore } from "@/stores/learningPath"; import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import type { import type {
CourseCompletionStatus, CourseCompletionStatus,
LearningContent, LearningContent,
@ -37,13 +38,25 @@ export const useCircleStore = defineStore({
}, },
getters: {}, getters: {},
actions: { actions: {
async loadCircle(courseSlug: string, circleSlug: string): Promise<Circle> { async loadCircle(
courseSlug: string,
circleSlug: string,
userId: number | undefined = undefined
): Promise<Circle> {
if (!userId) {
const userStore = useUserStore();
userId = userStore.id;
}
this.circle = undefined; this.circle = undefined;
const learningPathSlug = courseSlug + "-lp"; const learningPathSlug = courseSlug + "-lp";
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
await learningPathStore.loadLearningPath(learningPathSlug); const learningPath = await learningPathStore.loadLearningPath(
if (learningPathStore.learningPath) { learningPathSlug,
this.circle = learningPathStore.learningPath.circles.find((circle) => { userId
);
if (learningPath) {
this.circle = learningPath.circles.find((circle) => {
return circle.slug.endsWith(circleSlug); return circle.slug.endsWith(circleSlug);
}); });
} }

View File

@ -124,7 +124,7 @@ export const useCompetenceStore = defineStore({
userId = userStore.id; userId = userStore.id;
} }
if (this.competenceProfilePages.get(userId) && !reload) { if (this.competenceProfilePages.has(userId) && !reload) {
const competenceProfilePage = this.competenceProfilePages.get(userId); const competenceProfilePage = this.competenceProfilePages.get(userId);
await this.parseCompletionData(userId); await this.parseCompletionData(userId);
return competenceProfilePage; return competenceProfilePage;

View File

@ -2,52 +2,60 @@ import { itGetCached } from "@/fetchHelpers";
import { LearningPath } from "@/services/learningPath"; import { LearningPath } from "@/services/learningPath";
import { useCompletionStore } from "@/stores/completion"; import { useCompletionStore } from "@/stores/completion";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import _ from "lodash";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
export type LearningPathStoreState = { export type LearningPathStoreState = {
learningPath: LearningPath | undefined; learningPaths: Map<string, LearningPath>;
page: "INDEX" | "OVERVIEW";
loading: boolean;
};
let lastSlug = ""; page: "INDEX" | "OVERVIEW";
};
export const useLearningPathStore = defineStore({ export const useLearningPathStore = defineStore({
id: "learningPath", id: "learningPath",
state: () => { state: () => {
return { return {
learningPath: undefined, learningPaths: new Map<string, LearningPath>(),
page: "INDEX", page: "INDEX",
loading: false, loading: false,
} as LearningPathStoreState; } as LearningPathStoreState;
}, },
getters: {}, getters: {},
actions: { actions: {
async loadLearningPath(slug: string, reload = false, fail = true) { async loadLearningPath(
this.loading = true; slug: string,
const completionStore = useCompletionStore(); userId: number | undefined = undefined,
if (this.learningPath && !reload && slug === lastSlug) { reload = false,
return this.learningPath; fail = true
) {
if (!userId) {
const userStore = useUserStore();
userId = userStore.id;
} }
this.learningPath = undefined;
const learningPathData = await itGetCached(`/api/course/page/${slug}/`);
const key = `${slug}-${userId}`;
if (this.learningPaths.has(key) && !reload) {
return this.learningPaths.get(key);
}
const learningPathData = await itGetCached(`/api/course/page/${slug}/`);
if (!learningPathData && fail) { if (!learningPathData && fail) {
throw `No learning path found with: ${slug}`; throw `No learning path found with: ${slug}`;
} }
const userStore = useUserStore(); const completionStore = useCompletionStore();
if (learningPathData && userStore.loggedIn) { const completionData = await completionStore.loadCompletionData(
lastSlug = slug; learningPathData.course.id,
const completionData = await completionStore.loadCompletionData( userId
learningPathData.course.id, );
userStore.id
);
this.learningPath = LearningPath.fromJson(learningPathData, completionData); const learningPath = LearningPath.fromJson(
this.loading = false; _.cloneDeep(learningPathData),
return this.learningPath; completionData
} );
this.learningPaths.set(key, learningPath);
return learningPath;
}, },
}, },
}); });

View File

@ -194,6 +194,7 @@ export interface Course {
id: number; id: number;
title: string; title: string;
category_name: string; category_name: string;
slug: string;
} }
export interface CourseCategory { export interface CourseCategory {