diff --git a/client/src/components/circle/LearningContent.vue b/client/src/components/circle/LearningContent.vue index 6644ba26..bbc0515a 100644 --- a/client/src/components/circle/LearningContent.vue +++ b/client/src/components/circle/LearningContent.vue @@ -1,32 +1,29 @@ + diff --git a/client/src/main.ts b/client/src/main.ts index 977fb61c..c2777dd9 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -1,4 +1,4 @@ -import { createApp } from 'vue' +import { createApp, markRaw } from 'vue' import { createPinia } from 'pinia' import * as log from 'loglevel' @@ -7,6 +7,7 @@ import App from './App.vue' import router from './router' import '../tailwind.css' +import type { Router } from 'vue-router' if (window.location.href.indexOf('localhost') >= 0) { log.setLevel('trace') @@ -21,8 +22,19 @@ const app = createApp(App) // todo: define lang setup // await loadLocaleMessages(i18n, 'de') -app.use(createPinia()) app.use(router) + +declare module 'pinia' { + export interface PiniaCustomProperties { + router: Router + } +} +const pinia = createPinia(); +pinia.use(({ store }) => { + store.router = markRaw(router) +}) +app.use(pinia) // app.use(i18n) + app.mount('#app') diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 764f3508..af8feacc 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -46,6 +46,16 @@ const router = createRouter({ component: () => import('../views/CircleView.vue'), props: true, }, + { + path: '/learn/:learningPathSlug/:circleSlug/evaluate/:learningUnitSlug', + component: () => import('../views/LearningUnitSelfEvaluationView.vue'), + props: true, + }, + { + path: '/learn/:learningPathSlug/:circleSlug/:contentSlug', + component: () => import('../views/LearningContentView.vue'), + props: true, + }, { path: '/styleguide', component: () => import('../views/StyleGuideView.vue'), diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts index 87c0e7c4..abb767cb 100644 --- a/client/src/services/circle.ts +++ b/client/src/services/circle.ts @@ -174,6 +174,16 @@ export class Circle implements LearningWagtailPage { 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) => { @@ -215,4 +225,9 @@ export class Circle implements LearningWagtailPage { this.parentLearningPath.calcNextLearningContent(completionData); } } + + public getUrl(): string { + const shortSlug = this.slug.replace(`${this.parentLearningPath?.slug}-circle-`, '') + return `/learn/${this.parentLearningPath?.slug}/${shortSlug}`; + } } diff --git a/client/src/stores/app.ts b/client/src/stores/app.ts index 8e4bec05..7dcead06 100644 --- a/client/src/stores/app.ts +++ b/client/src/stores/app.ts @@ -6,10 +6,28 @@ export type AppState = { showMainNavigationBar: boolean } +const showMainNavigationBarInitialState = () => { + let path = window.location.pathname; + + // remove dangling slash + if (path.endsWith('/')) { + path = path.slice(0, -1); + } + + const numberOfSlashes = (path.match(/\//g) || []).length; + + // it should hide main navigation bar when on learning content page + if (path.startsWith('/learn/') && numberOfSlashes >= 4) { + return false + } + + return true; +} + export const useAppStore = defineStore({ id: 'app', state: () => ({ - showMainNavigationBar: true, + showMainNavigationBar: showMainNavigationBarInitialState(), userLoaded: false, routingFinished: false, } as AppState), diff --git a/client/src/stores/circle.ts b/client/src/stores/circle.ts index 387966fc..9fff1755 100644 --- a/client/src/stores/circle.ts +++ b/client/src/stores/circle.ts @@ -5,14 +5,10 @@ import { defineStore } from 'pinia' import type { LearningContent, LearningUnit, LearningUnitQuestion } from '@/types' import type { Circle } from '@/services/circle' import { itPost } from '@/fetchHelpers' -import { useAppStore } from '@/stores/app' import { useLearningPathStore } from '@/stores/learningPath' export type CircleStoreState = { circle: Circle | undefined - currentLearningContent: LearningContent | undefined - currentSelfEvaluation: LearningUnit | undefined - page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION' } export const useCircleStore = defineStore({ @@ -20,15 +16,12 @@ export const useCircleStore = defineStore({ state: () => { return { circle: undefined, - currentLearningContent: undefined, - currentSelfEvaluation: undefined, - page: 'INDEX', } as CircleStoreState; }, getters: { }, actions: { - async loadCircle(learningPathSlug: string, circleSlug: string) { + async loadCircle(learningPathSlug: string, circleSlug: string): Promise { this.circle = undefined; const learningPathStore = useLearningPathStore(); await learningPathStore.loadLearningPath(learningPathSlug); @@ -44,6 +37,30 @@ export const useCircleStore = defineStore({ return this.circle }, + async loadLearningContent(learningPathSlug: string, circleSlug: string, learningContentSlug: string) { + const circle = await this.loadCircle(learningPathSlug, circleSlug); + const result = circle.flatLearningContents.find((learningContent) => { + return learningContent.slug.endsWith(learningContentSlug); + }); + + if (!result) { + throw `No learning content found with slug: ${learningContentSlug}`; + } + + return result + }, + async loadSelfEvaluation(learningPathSlug: string, circleSlug: string, learningUnitSlug: string) { + const circle = await this.loadCircle(learningPathSlug, circleSlug); + const learningUnit = circle.flatLearningUnits.find((child) => { + return child.slug.endsWith(learningUnitSlug) + }); + + if (!learningUnit) { + throw `No self evaluation found with slug: ${learningUnitSlug}`; + } + + return learningUnit + }, async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) { try { page.completed = flag; @@ -60,28 +77,26 @@ export const useCircleStore = defineStore({ } }, openLearningContent(learningContent: LearningContent) { - this.currentLearningContent = learningContent; - const appStore = useAppStore(); - appStore.showMainNavigationBar = false; - this.page = 'LEARNING_CONTENT'; + const shortSlug = learningContent.slug.replace(`${this.circle?.slug}-lc-`, ''); + this.router.push({ + path: `${this.circle?.getUrl()}/${shortSlug}`, + }); }, closeLearningContent() { - this.currentLearningContent = undefined; - const appStore = useAppStore(); - appStore.showMainNavigationBar = true; - this.page = 'INDEX'; + this.router.push({ + path: `${this.circle?.getUrl()}` + }); }, openSelfEvaluation(learningUnit: LearningUnit) { - this.page = 'SELF_EVALUATION'; - const appStore = useAppStore(); - appStore.showMainNavigationBar = false; - this.currentSelfEvaluation = learningUnit; + const shortSlug = learningUnit.slug.replace(`${this.circle?.slug}-lu-`, ''); + this.router.push({ + path: `${this.circle?.getUrl()}/evaluate/${shortSlug}`, + }); }, closeSelfEvaluation() { - this.currentSelfEvaluation = undefined; - const appStore = useAppStore(); - appStore.showMainNavigationBar = true; - this.page = 'INDEX'; + this.router.push({ + path: `${this.circle?.getUrl()}` + }); }, calcSelfEvaluationStatus(learningUnit: LearningUnit) { if (learningUnit.children.length > 0) { @@ -94,12 +109,12 @@ export const useCircleStore = defineStore({ } return undefined; }, - continueFromLearningContent() { - if (this.currentLearningContent) { - this.markCompletion(this.currentLearningContent, true); + continueFromLearningContent(currentLearningContent: LearningContent) { + if (currentLearningContent) { + this.markCompletion(currentLearningContent, true); - const nextLearningContent = this.currentLearningContent.nextLearningContent; - const currentParent = this.currentLearningContent.parentLearningUnit; + const nextLearningContent = currentLearningContent.nextLearningContent; + const currentParent = currentLearningContent.parentLearningUnit; const nextParent = nextLearningContent?.parentLearningUnit; if ( @@ -108,13 +123,14 @@ export const useCircleStore = defineStore({ currentParent.children.length > 0 ) { // go to self evaluation - this.openSelfEvaluation(currentParent); - } else if (this.currentLearningContent.nextLearningContent) { + // this.openSelfEvaluation(currentParent); + this.closeLearningContent(); + } else if (currentLearningContent.nextLearningContent) { if ( - this.currentLearningContent.parentLearningSequence && - this.currentLearningContent.parentLearningSequence.id === nextLearningContent?.parentLearningSequence?.id + currentLearningContent.parentLearningSequence && + currentLearningContent.parentLearningSequence.id === nextLearningContent?.parentLearningSequence?.id ) { - this.openLearningContent(this.currentLearningContent.nextLearningContent); + this.openLearningContent(currentLearningContent.nextLearningContent); } else { this.closeLearningContent(); } @@ -126,21 +142,22 @@ export const useCircleStore = defineStore({ } }, continueFromSelfEvaluation() { - if (this.currentSelfEvaluation) { - const nextContent = this.currentSelfEvaluation.learningContents[this.currentSelfEvaluation.learningContents.length - 1].nextLearningContent; - - if (nextContent) { - if (this.currentSelfEvaluation?.parentLearningSequence?.id === nextContent?.parentLearningSequence?.id) { - this.openLearningContent(nextContent); - } else { - this.closeSelfEvaluation(); - } - } else { - this.closeSelfEvaluation(); - } - } else { - log.error('currentSelfEvaluation is undefined'); - } + this.closeSelfEvaluation() + // if (this.currentSelfEvaluation) { + // const nextContent = this.currentSelfEvaluation.learningContents[this.currentSelfEvaluation.learningContents.length - 1].nextLearningContent; + // + // if (nextContent) { + // if (this.currentSelfEvaluation?.parentLearningSequence?.id === nextContent?.parentLearningSequence?.id) { + // this.openLearningContent(nextContent); + // } else { + // this.closeSelfEvaluation(); + // } + // } else { + // this.closeSelfEvaluation(); + // } + // } else { + // log.error('currentSelfEvaluation is undefined'); + // } } } }) diff --git a/client/src/views/CircleView.vue b/client/src/views/CircleView.vue index 358301c0..6881dfec 100644 --- a/client/src/views/CircleView.vue +++ b/client/src/views/CircleView.vue @@ -7,7 +7,7 @@ import LearningContent from '@/components/circle/LearningContent.vue' import { onMounted } from 'vue' import { useCircleStore } from '@/stores/circle' -import SelfEvaluation from '@/components/circle/SelfEvaluation.vue' +import { useAppStore } from '@/stores/app' log.debug('CircleView.vue created') @@ -16,6 +16,9 @@ const props = defineProps<{ circleSlug: string }>() +const appStore = useAppStore() +appStore.showMainNavigationBar = true + const circleStore = useCircleStore() onMounted(async () => { @@ -42,9 +45,6 @@ onMounted(async () => {
-
- -
diff --git a/client/src/views/LearningContentView.vue b/client/src/views/LearningContentView.vue new file mode 100644 index 00000000..17af144b --- /dev/null +++ b/client/src/views/LearningContentView.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/client/src/views/LearningUnitSelfEvaluationView.vue b/client/src/views/LearningUnitSelfEvaluationView.vue new file mode 100644 index 00000000..dfcc08f6 --- /dev/null +++ b/client/src/views/LearningUnitSelfEvaluationView.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/client/vite.config.ts b/client/vite.config.ts index 36825c0d..5edc7109 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,9 +1,9 @@ -import {fileURLToPath, URL} from 'url' +import { fileURLToPath, URL } from "url"; -import {defineConfig, loadEnv} from 'vite' -import vue from '@vitejs/plugin-vue' +import { defineConfig, loadEnv } from "vite"; +import vue from "@vitejs/plugin-vue"; // import vueI18n from '@intlify/vite-plugin-vue-i18n' -import alias from '@rollup/plugin-alias' +import alias from "@rollup/plugin-alias"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { @@ -33,6 +33,10 @@ export default defineConfig(({ mode }) => { // ] }), ], + server: { + port: 5173, + hmr: { port: 5173 } + }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), diff --git a/server/requirements/requirements-dev.txt b/server/requirements/requirements-dev.txt index 2fc1305d..d8dcb9d5 100644 --- a/server/requirements/requirements-dev.txt +++ b/server/requirements/requirements-dev.txt @@ -109,7 +109,7 @@ django-csp==3.7 # via -r requirements.in django-debug-toolbar==3.2.4 # via -r requirements-dev.in -django-extensions==3.1.5 +django-extensions==3.2.0 # via -r requirements-dev.in django-filter==21.1 # via wagtail @@ -428,9 +428,9 @@ wagtail-factories==2.0.1 # via -r requirements.in wagtail-localize==1.2.1 # via -r requirements.in -watchdog==2.1.7 +watchdog==2.1.9 # via werkzeug -watchgod==0.8.1 +watchgod==0.8.2 # via # -r requirements-dev.in # uvicorn @@ -440,7 +440,7 @@ webencodings==0.5.1 # via html5lib websockets==10.2 # via uvicorn -werkzeug[watchdog]==2.1.0 +werkzeug[watchdog]==2.2.0 # via -r requirements-dev.in wheel==0.37.1 # via pip-tools