Refactor LearningContent to its own route
This commit is contained in:
parent
8dbec8b699
commit
5d6e94ebd6
|
|
@ -1,32 +1,24 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel'
|
||||||
import {computed} from 'vue';
|
import { computed } from 'vue'
|
||||||
import {useCircleStore} from '@/stores/circle';
|
import { useCircleStore } from '@/stores/circle'
|
||||||
|
|
||||||
log.debug('LearningContent.vue setup');
|
log.debug('LearningContent.vue setup')
|
||||||
|
|
||||||
const circleStore = useCircleStore();
|
const circleStore = useCircleStore()
|
||||||
|
|
||||||
const learningContent = computed(() => circleStore.currentLearningContent);
|
const learningContent = computed(() => circleStore.currentLearningContent)
|
||||||
|
|
||||||
const block = computed(() => {
|
const block = computed(() => {
|
||||||
if (learningContent.value) {
|
if (learningContent.value) {
|
||||||
return learningContent.value.contents[0];
|
return learningContent.value.contents[0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<nav
|
<nav class="px-4 lg:px-8 py-4 flex justify-between items-center border-b border-gray-500">
|
||||||
class="
|
|
||||||
px-4 lg:px-8
|
|
||||||
py-4
|
|
||||||
flex justify-between items-center
|
|
||||||
border-b border-gray-500
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-text inline-flex items-center px-3 py-2 font-normal"
|
class="btn-text inline-flex items-center px-3 py-2 font-normal"
|
||||||
|
|
@ -50,15 +42,9 @@ const block = computed(() => {
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div v-if="block.type === 'exercise'" class="h-screen">
|
<div v-if="block.type === 'exercise'" class="h-screen">
|
||||||
<iframe
|
<iframe width="100%" height="100%" scrolling="no" :src="block.value.url" />
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
scrolling="no"
|
|
||||||
:src="block.value.url"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div v-else class="mx-auto max-w-5xl px-4 lg:px-8 py-4">
|
<div v-else class="mx-auto max-w-5xl px-4 lg:px-8 py-4">
|
||||||
<p>{{ block.value.description }}</p>
|
<p>{{ block.value.description }}</p>
|
||||||
|
|
||||||
|
|
@ -69,24 +55,19 @@ const block = computed(() => {
|
||||||
:title="learningContent.title"
|
:title="learningContent.title"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
|
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
|
||||||
allowfullscreen>
|
allowfullscreen
|
||||||
|
>
|
||||||
</iframe>
|
</iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-if="block.type === 'podcast'">
|
||||||
v-if="block.type === 'podcast'"
|
|
||||||
>
|
|
||||||
<iframe width="100%" height="300" scrolling="no" frameborder="no" allow="" :src="block.value.url"></iframe>
|
<iframe width="100%" height="300" scrolling="no" frameborder="no" allow="" :src="block.value.url"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
$header-height: 77px;
|
$header-height: 77px;
|
||||||
$footer-height: 57px;
|
$footer-height: 57px;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp, markRaw } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import * as log from 'loglevel'
|
import * as log from 'loglevel'
|
||||||
|
|
||||||
|
|
@ -7,6 +7,7 @@ import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
import '../tailwind.css'
|
import '../tailwind.css'
|
||||||
|
import type { Router } from 'vue-router'
|
||||||
|
|
||||||
if (window.location.href.indexOf('localhost') >= 0) {
|
if (window.location.href.indexOf('localhost') >= 0) {
|
||||||
log.setLevel('trace')
|
log.setLevel('trace')
|
||||||
|
|
@ -21,8 +22,19 @@ const app = createApp(App)
|
||||||
// todo: define lang setup
|
// todo: define lang setup
|
||||||
// await loadLocaleMessages(i18n, 'de')
|
// await loadLocaleMessages(i18n, 'de')
|
||||||
|
|
||||||
app.use(createPinia())
|
|
||||||
app.use(router)
|
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.use(i18n)
|
||||||
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@ const router = createRouter({
|
||||||
component: () => import('../views/CircleView.vue'),
|
component: () => import('../views/CircleView.vue'),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/learn/:learningPathSlug/:circleSlug/:contentSlug',
|
||||||
|
component: () => import('../views/LearningContentView.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/styleguide',
|
path: '/styleguide',
|
||||||
component: () => import('../views/StyleGuideView.vue'),
|
component: () => import('../views/StyleGuideView.vue'),
|
||||||
|
|
|
||||||
|
|
@ -215,4 +215,9 @@ export class Circle implements LearningWagtailPage {
|
||||||
this.parentLearningPath.calcNextLearningContent(completionData);
|
this.parentLearningPath.calcNextLearningContent(completionData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getUrl(): string {
|
||||||
|
const shortSlug = this.slug.replace(`${this.parentLearningPath?.slug}-circle-`, '')
|
||||||
|
return `/learn/${this.parentLearningPath?.slug}/${shortSlug}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,28 @@ export type AppState = {
|
||||||
showMainNavigationBar: boolean
|
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({
|
export const useAppStore = defineStore({
|
||||||
id: 'app',
|
id: 'app',
|
||||||
state: () => ({
|
state: () => ({
|
||||||
showMainNavigationBar: true,
|
showMainNavigationBar: showMainNavigationBarInitialState(),
|
||||||
userLoaded: false,
|
userLoaded: false,
|
||||||
routingFinished: false,
|
routingFinished: false,
|
||||||
} as AppState),
|
} as AppState),
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const useCircleStore = defineStore({
|
||||||
getters: {
|
getters: {
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCircle(learningPathSlug: string, circleSlug: string) {
|
async loadCircle(learningPathSlug: string, circleSlug: string): Promise<Circle> {
|
||||||
this.circle = undefined;
|
this.circle = undefined;
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
await learningPathStore.loadLearningPath(learningPathSlug);
|
await learningPathStore.loadLearningPath(learningPathSlug);
|
||||||
|
|
@ -44,6 +44,20 @@ export const useCircleStore = defineStore({
|
||||||
|
|
||||||
return this.circle
|
return this.circle
|
||||||
},
|
},
|
||||||
|
async loadLearningContent(learningPathSlug: string, circleSlug: string, learningContentSlug: string) {
|
||||||
|
const circle = await this.loadCircle(learningPathSlug, circleSlug);
|
||||||
|
if (circle) {
|
||||||
|
this.currentLearningContent = circle.flatLearningContents.find((learningContent) => {
|
||||||
|
return learningContent.slug.endsWith(learningContentSlug);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.currentLearningContent) {
|
||||||
|
throw `No learning content found with slug: ${learningContentSlug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.currentLearningContent;
|
||||||
|
},
|
||||||
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
|
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
|
||||||
try {
|
try {
|
||||||
page.completed = flag;
|
page.completed = flag;
|
||||||
|
|
@ -61,15 +75,15 @@ export const useCircleStore = defineStore({
|
||||||
},
|
},
|
||||||
openLearningContent(learningContent: LearningContent) {
|
openLearningContent(learningContent: LearningContent) {
|
||||||
this.currentLearningContent = learningContent;
|
this.currentLearningContent = learningContent;
|
||||||
const appStore = useAppStore();
|
const shortSlug = learningContent.slug.replace(`${this.circle?.slug}-lc-`, '');
|
||||||
appStore.showMainNavigationBar = false;
|
this.router.push({
|
||||||
this.page = 'LEARNING_CONTENT';
|
path: `${this.circle?.getUrl()}/${shortSlug}`,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
closeLearningContent() {
|
closeLearningContent() {
|
||||||
this.currentLearningContent = undefined;
|
this.router.push({
|
||||||
const appStore = useAppStore();
|
path: `${this.circle?.getUrl()}`
|
||||||
appStore.showMainNavigationBar = true;
|
});
|
||||||
this.page = 'INDEX';
|
|
||||||
},
|
},
|
||||||
openSelfEvaluation(learningUnit: LearningUnit) {
|
openSelfEvaluation(learningUnit: LearningUnit) {
|
||||||
this.page = 'SELF_EVALUATION';
|
this.page = 'SELF_EVALUATION';
|
||||||
|
|
@ -108,7 +122,8 @@ export const useCircleStore = defineStore({
|
||||||
currentParent.children.length > 0
|
currentParent.children.length > 0
|
||||||
) {
|
) {
|
||||||
// go to self evaluation
|
// go to self evaluation
|
||||||
this.openSelfEvaluation(currentParent);
|
// this.openSelfEvaluation(currentParent);
|
||||||
|
this.closeLearningContent();
|
||||||
} else if (this.currentLearningContent.nextLearningContent) {
|
} else if (this.currentLearningContent.nextLearningContent) {
|
||||||
if (
|
if (
|
||||||
this.currentLearningContent.parentLearningSequence &&
|
this.currentLearningContent.parentLearningSequence &&
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import LearningContent from '@/components/circle/LearningContent.vue'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useCircleStore } from '@/stores/circle'
|
import { useCircleStore } from '@/stores/circle'
|
||||||
import SelfEvaluation from '@/components/circle/SelfEvaluation.vue'
|
import SelfEvaluation from '@/components/circle/SelfEvaluation.vue'
|
||||||
|
import { useAppStore } from '@/stores/app'
|
||||||
|
|
||||||
log.debug('CircleView.vue created')
|
log.debug('CircleView.vue created')
|
||||||
|
|
||||||
|
|
@ -16,6 +17,9 @@ const props = defineProps<{
|
||||||
circleSlug: string
|
circleSlug: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
appStore.showMainNavigationBar = true
|
||||||
|
|
||||||
const circleStore = useCircleStore()
|
const circleStore = useCircleStore()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as log from 'loglevel'
|
||||||
|
import LearningContent from '@/components/circle/LearningContent.vue'
|
||||||
|
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useCircleStore } from '@/stores/circle'
|
||||||
|
import { useAppStore } from '@/stores/app'
|
||||||
|
|
||||||
|
log.debug('LearningContentView created')
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
learningPathSlug: string
|
||||||
|
circleSlug: string
|
||||||
|
contentSlug: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
appStore.showMainNavigationBar = false
|
||||||
|
|
||||||
|
const circleStore = useCircleStore()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug('LearningContentView mounted', props.learningPathSlug, props.circleSlug, props.contentSlug)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await circleStore.loadLearningContent(props.learningPathSlug, props.circleSlug, props.contentSlug)
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LearningContent
|
||||||
|
v-if="circleStore.currentLearningContent"
|
||||||
|
:key="circleStore.currentLearningContent.translation_key"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.circle-container {
|
||||||
|
background: linear-gradient(to right, white 0%, white 50%, theme(colors.gray.200) 50%, theme(colors.gray.200) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
max-width: 1440px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-enter-active,
|
||||||
|
.v-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-enter-from,
|
||||||
|
.v-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import {fileURLToPath, URL} from 'url'
|
import { fileURLToPath, URL } from "url";
|
||||||
|
|
||||||
import {defineConfig, loadEnv} from 'vite'
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from "@vitejs/plugin-vue";
|
||||||
// import vueI18n from '@intlify/vite-plugin-vue-i18n'
|
// import vueI18n from '@intlify/vite-plugin-vue-i18n'
|
||||||
import alias from '@rollup/plugin-alias'
|
import alias from "@rollup/plugin-alias";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
|
|
@ -33,6 +33,10 @@ export default defineConfig(({ mode }) => {
|
||||||
// ]
|
// ]
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
hmr: { port: 5173 }
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue