Apply code changes from migration guide for Vue 3

This commit is contained in:
Ramon Wenger 2022-03-23 16:21:06 +01:00
parent 445f09e16a
commit 09d8d36678
30 changed files with 903 additions and 673 deletions

View File

@ -42,7 +42,7 @@ module.exports = {
'@': resolve('src'), '@': resolve('src'),
styles: resolve('src/styles'), styles: resolve('src/styles'),
gql: resolve('src/graphql/gql'), gql: resolve('src/graphql/gql'),
// vue: '@vue/compat', vue: '@vue/compat',
}, },
}, },
module: { module: {

View File

@ -9,38 +9,54 @@
</template> </template>
<script> <script>
import { defineAsyncComponent } from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import ScrollUp from '@/components/ScrollUp'; import ScrollUp from '@/components/ScrollUp';
import ReadOnlyBanner from '@/components/ReadOnlyBanner'; import ReadOnlyBanner from '@/components/ReadOnlyBanner';
import modals from '@/components/modals'; import modals from '@/components/modals';
const NewContentBlockWizard = () => const NewContentBlockWizard = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-forms" */ '@/components/content-block-form/NewContentBlockWizard'); import(/* webpackChunkName: "content-forms" */ '@/components/content-block-form/NewContentBlockWizard')
const EditContentBlockWizard = () => );
import(/* webpackChunkName: "content-forms" */ '@/components/content-block-form/EditContentBlockWizard'); const EditContentBlockWizard = defineAsyncComponent(() =>
const EditRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */ '@/components/content-block-form/EditContentBlockWizard')
import(/* webpackChunkName: "content-forms" */ '@/components/rooms/room-entries/EditRoomEntryWizard'); );
const NewProjectEntryWizard = () => const EditRoomEntryWizard = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-forms" */ '@/components/portfolio/NewProjectEntryWizard'); import(/* webpackChunkName: "content-forms" */ '@/components/rooms/room-entries/EditRoomEntryWizard')
const EditProjectEntryWizard = () => );
import(/* webpackChunkName: "content-forms" */ '@/components/portfolio/EditProjectEntryWizard'); const NewProjectEntryWizard = defineAsyncComponent(() =>
const NewObjectiveWizard = () => import(/* webpackChunkName: "content-forms" */ '@/components/portfolio/NewProjectEntryWizard')
import(/* webpackChunkName: "content-forms" */ '@/components/objective-groups/NewObjectiveWizard'); );
const NewNoteWizard = () => import(/* webpackChunkName: "content-forms" */ '@/components/notes/NewNoteWizard'); const EditProjectEntryWizard = defineAsyncComponent(() =>
const EditNoteWizard = () => import(/* webpackChunkName: "content-forms" */ '@/components/notes/EditNoteWizard'); import(/* webpackChunkName: "content-forms" */ '@/components/portfolio/EditProjectEntryWizard')
const EditClassNameWizard = () => );
import(/* webpackChunkName: "content-forms" */ '@/components/school-class/EditClassNameWizard'); const NewObjectiveWizard = defineAsyncComponent(() =>
const EditTeamNameWizard = () => import(/* webpackChunkName: "content-forms" */ '@/components/objective-groups/NewObjectiveWizard')
import(/* webpackChunkName: "content-forms" */ '@/components/profile/EditTeamNameWizard'); );
const EditSnapshotTitleWizard = () => const NewNoteWizard = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-forms" */ '@/components/snapshots/EditSnapshotTitleWizard'); import(/* webpackChunkName: "content-forms" */ '@/components/notes/NewNoteWizard')
const DefaultLayout = () => import(/* webpackChunkName: "layouts" */ '@/layouts/DefaultLayout'); );
const SimpleLayout = () => import(/* webpackChunkName: "layouts" */ '@/layouts/SimpleLayout'); const EditNoteWizard = defineAsyncComponent(() =>
const FullScreenLayout = () => import(/* webpackChunkName: "layouts" */ '@/layouts/FullScreenLayout'); import(/* webpackChunkName: "content-forms" */ '@/components/notes/EditNoteWizard')
const PublicLayout = () => import(/* webpackChunkName: "layouts" */ '@/layouts/PublicLayout'); );
const BlankLayout = () => import(/* webpackChunkName: "layouts" */ '@/layouts/BlankLayout'); const EditClassNameWizard = defineAsyncComponent(() =>
const SplitLayout = () => import(/* webpackChunkName: "layouts" */ '@/layouts/SplitLayout'); import(/* webpackChunkName: "content-forms" */ '@/components/school-class/EditClassNameWizard')
);
const EditTeamNameWizard = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-forms" */ '@/components/profile/EditTeamNameWizard')
);
const EditSnapshotTitleWizard = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-forms" */ '@/components/snapshots/EditSnapshotTitleWizard')
);
const DefaultLayout = defineAsyncComponent(() => import(/* webpackChunkName: "layouts" */ '@/layouts/DefaultLayout'));
const SimpleLayout = defineAsyncComponent(() => import(/* webpackChunkName: "layouts" */ '@/layouts/SimpleLayout'));
const FullScreenLayout = defineAsyncComponent(() =>
import(/* webpackChunkName: "layouts" */ '@/layouts/FullScreenLayout')
);
const PublicLayout = defineAsyncComponent(() => import(/* webpackChunkName: "layouts" */ '@/layouts/PublicLayout'));
const BlankLayout = defineAsyncComponent(() => import(/* webpackChunkName: "layouts" */ '@/layouts/BlankLayout'));
const SplitLayout = defineAsyncComponent(() => import(/* webpackChunkName: "layouts" */ '@/layouts/SplitLayout'));
export default { export default {
name: 'App', name: 'App',

View File

@ -1,15 +1,22 @@
<template>
<header class="header-bar">
<a class="header-bar__sidebar-link" data-cy="open-sidebar-link" @click.stop="openSidebar('navigation')"> <a class="header-bar__sidebar-link" data-cy="open-sidebar-link" @click.stop="openSidebar('navigation')">
<hamburger class="header-bar__sidebar-icon" /> <hamburger class="header-bar__sidebar-icon" />
</a> </a>
<content-navigation class="header-bar__content-navigation" /> <content-navigation class="header-bar__content-navigation" />
<div class="user-header"> <div class="user-header">
<a class="user-header__sidebar-link"> <a
<current-class class="user-header__current-class" @click.native.stop="openSidebar('profile')" /> class="user-header__sidebar-link"
>
<current-class
class="user-header__current-class"
@click.stop="openSidebar('profile')"
/>
</a> </a>
<user-widget v-bind="me" data-cy="header-user-widget" @click.native.stop="openSidebar('profile')" /> <user-widget
v-bind="me"
data-cy="header-user-widget"
@click.stop="openSidebar('profile')"
/>
</div> </div>
</header> </header>
</template> </template>

View File

@ -8,7 +8,7 @@
<logo /> <logo />
</router-link> </router-link>
<user-widget v-bind="me" @click.native.stop="openSidebar('profile')" /> <user-widget v-bind="me" @click.stop="openSidebar('profile')" />
</div> </div>
</template> </template>

View File

@ -27,7 +27,7 @@ export default {
}; };
}, },
destroyed() { unmounted() {
document.body.onscroll = null; document.body.onscroll = null;
}, },

View File

@ -4,14 +4,14 @@
{{ name }} {{ name }}
</div> </div>
<div class="student-submission__entry entry"> <div class="student-submission__entry entry">
<p>{{ submission.text | trimToLength(50) }}</p> <p>{{ text }}</p>
<p class="entry__document" v-if="submission.document && submission.document.length > 0"> <p class="entry__document" v-if="submission.document && submission.document.length > 0">
<student-submission-document :document="submission.document" class="entry-document" /> <student-submission-document :document="submission.document" class="entry-document" />
</p> </p>
</div> </div>
<div class="student-submission__feedback entry" v-if="submission.submissionFeedback"> <div class="student-submission__feedback entry" v-if="submission.submissionFeedback">
<p :class="{ 'entry__text--final': submission.submissionFeedback.final }" class="entry__text"> <p :class="{ 'entry__text--final': submission.submissionFeedback.final }" class="entry__text">
{{ submission.submissionFeedback.text | trimToLength(50) }} {{ feedback }}
</p> </p>
</div> </div>
</div> </div>
@ -25,7 +25,21 @@ export default {
components: { components: {
StudentSubmissionDocument, StudentSubmissionDocument,
}, },
filters: {
computed: {
text() {
return this.trimToLength(this.submission.text, 50);
},
feedback() {
return this.trimToLength(this.submission.submissionFeedback.text, 50);
},
name() {
return this.submission && this.submission.student
? `${this.submission.student.firstName} ${this.submission.student.lastName}`
: '';
},
},
methods: {
trimToLength: function (text, numberOfChars) { trimToLength: function (text, numberOfChars) {
if (!text) { if (!text) {
return ''; return '';
@ -40,14 +54,6 @@ export default {
return `${text.substring(0, index)}`; return `${text.substring(0, index)}`;
}, },
}, },
computed: {
name() {
return this.submission && this.submission.student
? `${this.submission.student.firstName} ${this.submission.student.lastName}`
: '';
},
},
}; };
</script> </script>

View File

@ -7,7 +7,7 @@
:to="topicRoute" :to="topicRoute"
active-class="content-navigation__link--active" active-class="content-navigation__link--active"
class="content-navigation__link" class="content-navigation__link"
@click.native="close" @click="close"
> >
{{ $flavor.textTopics }} {{ $flavor.textTopics }}
</router-link> </router-link>
@ -20,7 +20,7 @@
to="/instruments" to="/instruments"
active-class="content-navigation__link--active" active-class="content-navigation__link--active"
class="content-navigation__link" class="content-navigation__link"
@click.native="close" @click="close"
> >
{{ $flavor.textInstruments }} {{ $flavor.textInstruments }}
</router-link> </router-link>
@ -33,7 +33,7 @@
class="content-navigation__link" class="content-navigation__link"
data-cy="news-navigation-link" data-cy="news-navigation-link"
v-if="!me.readOnly" v-if="!me.readOnly"
@click.native="close" @click="close"
> >
News News
</router-link> </router-link>
@ -51,7 +51,7 @@
to="/rooms" to="/rooms"
active-class="content-navigation__link--active" active-class="content-navigation__link--active"
class="content-navigation__link content-navigation__link--secondary" class="content-navigation__link content-navigation__link--secondary"
@click.native="close" @click="close"
> >
Räume Räume
</router-link> </router-link>
@ -62,7 +62,7 @@
to="/portfolio" to="/portfolio"
active-class="content-navigation__link--active" active-class="content-navigation__link--active"
class="content-navigation__link content-navigation__link--secondary" class="content-navigation__link content-navigation__link--secondary"
@click.native="close" @click="close"
> >
Portfolio Portfolio
</router-link> </router-link>

View File

@ -8,7 +8,7 @@
class="topic-navigation__topic book-subnavigation__item" class="topic-navigation__topic book-subnavigation__item"
v-for="topic in topics" v-for="topic in topics"
:key="topic.id" :key="topic.id"
@click.native="closeSidebar('navigation')" @click="closeSidebar('navigation')"
> >
{{ topic.order }}. {{ topic.order }}.
{{ topic.title }} {{ topic.title }}

View File

@ -105,7 +105,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue, { PropType } from 'vue'; import { PropType, defineComponent } from 'vue';
import Toggle from '@/components/ui/Toggle.vue'; import Toggle from '@/components/ui/Toggle.vue';
import ContentFormSection from '@/components/content-block-form/ContentFormSection.vue'; import ContentFormSection from '@/components/content-block-form/ContentFormSection.vue';
import InputWithLabel from '@/components/ui/InputWithLabel.vue'; import InputWithLabel from '@/components/ui/InputWithLabel.vue';
@ -130,7 +130,7 @@ interface ContentBlockFormData {
localContentBlock: any; localContentBlock: any;
} }
export default Vue.extend({ export default defineComponent({
props: { props: {
title: { title: {
type: String, type: String,

View File

@ -55,7 +55,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import { defineComponent } from 'vue';
import WidgetPopover from '@/components/ui/WidgetPopover.vue'; import WidgetPopover from '@/components/ui/WidgetPopover.vue';
import Ellipses from '@/components/icons/Ellipses.vue'; import Ellipses from '@/components/icons/Ellipses.vue';
import ButtonWithIconAndText from '@/components/ui/ButtonWithIconAndText.vue'; import ButtonWithIconAndText from '@/components/ui/ButtonWithIconAndText.vue';
@ -65,7 +65,7 @@ interface Data {
show: boolean; show: boolean;
} }
export default Vue.extend({ export default defineComponent({
props: { props: {
actions: { actions: {
type: Object as () => ActionOptions, type: Object as () => ActionOptions,

View File

@ -7,6 +7,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
<<<<<<< HEAD
import Vue, { PropType } from 'vue'; import Vue, { PropType } from 'vue';
import { Editor, EditorContent } from '@tiptap/vue-2'; import { Editor, EditorContent } from '@tiptap/vue-2';
import Document from '@tiptap/extension-document'; import Document from '@tiptap/extension-document';
@ -15,6 +16,25 @@ import Text from '@tiptap/extension-text';
import BulletList from '@tiptap/extension-bullet-list'; import BulletList from '@tiptap/extension-bullet-list';
import ListItem from '@tiptap/extension-list-item'; import ListItem from '@tiptap/extension-list-item';
import Toggle from '@/components/ui/Toggle.vue'; import Toggle from '@/components/ui/Toggle.vue';
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
import Vue, {PropType} from 'vue';
import {Editor, EditorContent} from "@tiptap/vue-2";
import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text';
import BulletList from '@tiptap/extension-bullet-list';
import ListItem from '@tiptap/extension-list-item';
import Toggle from "@/components/ui/Toggle.vue";
=======
import {PropType, defineComponent} from 'vue';
import {Editor, EditorContent} from "@tiptap/vue-2";
import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text';
import BulletList from '@tiptap/extension-bullet-list';
import ListItem from '@tiptap/extension-list-item';
import Toggle from "@/components/ui/Toggle.vue";
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
interface Data { interface Data {
editor: Editor | undefined; editor: Editor | undefined;
@ -23,6 +43,7 @@ interface Value {
text: string; text: string;
} }
<<<<<<< HEAD
export default Vue.extend({ export default Vue.extend({
props: { props: {
value: { value: {
@ -71,6 +92,21 @@ export default Vue.extend({
editorProps: { editorProps: {
attributes: { attributes: {
class: 'tip-tap__editor', class: 'tip-tap__editor',
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
export default Vue.extend({
props: {
value: {
type: Object as PropType<Value>,
validator(value: Value) {
return Object.prototype.hasOwnProperty.call(value, 'text');
=======
export default defineComponent({
props: {
value: {
type: Object as PropType<Value>,
validator: (value: Value) => {
return Object.prototype.hasOwnProperty.call(value, 'text');
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
}, },
}, },
content: this.text, content: this.text,
@ -80,6 +116,7 @@ export default Vue.extend({
this.$emit('input', text); this.$emit('input', text);
this.$emit('change-text', text); this.$emit('change-text', text);
}, },
<<<<<<< HEAD
}); });
}, },
@ -91,6 +128,15 @@ export default Vue.extend({
toggleList() { toggleList() {
const editor = this.editor as Editor; const editor = this.editor as Editor;
editor.chain().selectAll().toggleBulletList().run(); editor.chain().selectAll().toggleBulletList().run();
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
text(): string {
return this.value.text;
}
=======
text(): string {
return this.value?.text || '';
}
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
}, },
}, },
}); });

View File

@ -3,10 +3,11 @@
<filter-entry <filter-entry
:text="title" :text="title"
v-bind="$attrs" v-bind="$attrs"
:type="category"
:category="category" :category="category"
:is-category="true" :is-category="true"
:id="category.id" :id="category.id"
@click.native="setCategoryFilter(category.id)" @click="setCategoryFilter(category.id)"
/> />
<div class="filter-group__children"> <div class="filter-group__children">
<filter-entry <filter-entry
@ -16,7 +17,7 @@
v-for="type in types" v-for="type in types"
:id="type.id" :id="type.id"
:key="type.id" :key="type.id"
@click.native="setFilter(`type:${type.id}`)" @click="setFilter(`type:${type.id}`)"
/> />
</div> </div>
</div> </div>

View File

@ -2,8 +2,8 @@
<div class="page-form-input"> <div class="page-form-input">
<label :for="id" class="page-form-input__label">{{ label }}</label> <label :for="id" class="page-form-input__label">{{ label }}</label>
<component <component
:value="value"
:class="classes" :class="classes"
:value.prop="value"
:data-cy="cyId" :data-cy="cyId"
:is="type" :is="type"
:id="id" :id="id"

View File

@ -18,7 +18,7 @@
icon="document-with-lines-icon" icon="document-with-lines-icon"
data-cy="use-template-button" data-cy="use-template-button"
text="Vorlage nutzen" text="Vorlage nutzen"
@click.native="useTemplate" @click="useTemplate"
/> />
<file-upload <file-upload
@ -30,12 +30,38 @@
</div> </div>
</div> </div>
</div> </div>
<<<<<<< HEAD
<div slot="footer"> <div slot="footer">
<a class="button button--primary" data-cy="modal-save-button" @click="$emit('save', localProjectEntry)" <a class="button button--primary" data-cy="modal-save-button" @click="$emit('save', localProjectEntry)"
>Speichern</a >Speichern</a
> >
<a class="button" @click="$emit('hide')">Abbrechen</a> <a class="button" @click="$emit('hide')">Abbrechen</a>
</div> </div>
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
<div slot="footer">
<a
class="button button--primary"
data-cy="modal-save-button"
@click="$emit('save', localProjectEntry)"
>Speichern</a>
<a
class="button"
@click="$emit('hide')"
>Abbrechen</a>
</div>
=======
<template #footer>
<a
class="button button--primary"
data-cy="modal-save-button"
@click="$emit('save', localProjectEntry)"
>Speichern</a>
<a
class="button"
@click="$emit('hide')"
>Abbrechen</a>
</template>
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
</modal> </modal>
</template> </template>
@ -43,8 +69,17 @@
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import ButtonWithIconAndText from '@/components/ui/ButtonWithIconAndText'; import ButtonWithIconAndText from '@/components/ui/ButtonWithIconAndText';
<<<<<<< HEAD
import { PROJECT_ENTRY_TEMPLATE } from '@/consts/strings.consts'; import { PROJECT_ENTRY_TEMPLATE } from '@/consts/strings.consts';
const FileUpload = () => import('@/components/ui/file-upload/FileUpload.vue'); const FileUpload = () => import('@/components/ui/file-upload/FileUpload.vue');
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
import {PROJECT_ENTRY_TEMPLATE} from '@/consts/strings.consts';
const FileUpload = () => import('@/components/ui/file-upload/FileUpload');
=======
import {PROJECT_ENTRY_TEMPLATE} from '@/consts/strings.consts';
const FileUpload = () => import('@/components/ui/file-upload/FileUpload');
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
export default { export default {
props: { props: {

View File

@ -1,8 +1,40 @@
<template> <template>
<<<<<<< HEAD
<page-form :title="title" @save="$emit('save', localProject)"> <page-form :title="title" @save="$emit('save', localProject)">
<page-form-input label="Titel" v-model="localProject.title" /> <page-form-input label="Titel" v-model="localProject.title" />
<page-form-input label="Beschreibung" type="textarea" v-model="localProject.description" /> <page-form-input label="Beschreibung" type="textarea" v-model="localProject.description" />
<template slot="footer"> <template slot="footer">
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
<page-form
:title="title"
@save="$emit('save', localProject)"
>
<page-form-input
label="Titel"
v-model="localProject.title"
/>
<page-form-input
label="Beschreibung"
type="textarea"
v-model="localProject.description"
/>
<template slot="footer">
=======
<page-form
:title="title"
@save="$emit('save', localProject)"
>
<page-form-input
label="Titel"
v-model="localProject.title"
/>
<page-form-input
label="Beschreibung"
type="textarea"
v-model="localProject.description"
/>
<template #footer>
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
<button <button
:class="{ 'button--disabled': !formValid }" :class="{ 'button--disabled': !formValid }"
:disabled="!formValid" :disabled="!formValid"

View File

@ -6,10 +6,34 @@
<modal-input :value="name" :placeholder="placeholder" data-cy="edit-name-input" @input="$emit('input', $event)" /> <modal-input :value="name" :placeholder="placeholder" data-cy="edit-name-input" @input="$emit('input', $event)" />
<template #footer> <template #footer>
<<<<<<< HEAD
<div slot="footer"> <div slot="footer">
<a class="button button--primary" data-cy="modal-save-button" @click="$emit('save')">Speichern</a> <a class="button button--primary" data-cy="modal-save-button" @click="$emit('save')">Speichern</a>
<a class="button" @click="$emit('cancel')">Abbrechen</a> <a class="button" @click="$emit('cancel')">Abbrechen</a>
</div> </div>
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
<div slot="footer">
<a
class="button button--primary"
data-cy="modal-save-button"
@click="$emit('save')"
>Speichern</a>
<a
class="button"
@click="$emit('cancel')"
>Abbrechen</a>
</div>
=======
<a
class="button button--primary"
data-cy="modal-save-button"
@click="$emit('save')"
>Speichern</a>
<a
class="button"
@click="$emit('cancel')"
>Abbrechen</a>
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
</template> </template>
</modal> </modal>
</template> </template>

View File

@ -1,6 +1,18 @@
<template> <template>
<div class="simple-file-upload"> <div class="simple-file-upload">
<<<<<<< HEAD
<component :is="button" @click.native="clickUploadCare" /> <component :is="button" @click.native="clickUploadCare" />
||||||| parent of a423cfde (Apply code changes from migration guide for Vue 3)
<component
:is="button"
@click.native="clickUploadCare"
/>
=======
<component
:is="button"
@click="clickUploadCare"
/>
>>>>>>> a423cfde (Apply code changes from migration guide for Vue 3)
<simple-file-upload-hidden-input @link-change-url="$emit('link-change-url', $event)" /> <simple-file-upload-hidden-input @link-change-url="$emit('link-change-url', $event)" />
</div> </div>
</template> </template>

View File

@ -4,7 +4,7 @@
icon="document-icon" icon="document-icon"
text="Dokument hochladen" text="Dokument hochladen"
v-if="!value" v-if="!value"
@click.native="clickUploadCare" @click="clickUploadCare"
/> />
<simple-file-upload-hidden-input @link-change-url="$emit('link-change-url', $event)" /> <simple-file-upload-hidden-input @link-change-url="$emit('link-change-url', $event)" />

View File

@ -1,4 +1,4 @@
const resizeElement = (el) => { const resizeElement = (el: HTMLElement) => {
el.style.height = `auto`; el.style.height = `auto`;
el.style.height = `${el.clientHeight - el.offsetHeight + el.scrollHeight}px`; el.style.height = `${el.clientHeight - el.offsetHeight + el.scrollHeight}px`;
}; };
@ -6,13 +6,13 @@ const resizeElement = (el) => {
export default { export default {
update: resizeElement, update: resizeElement,
inserted: resizeElement, inserted: resizeElement,
bind(el) { created(el: HTMLElement) {
el.classList.add('skillbox-auto-grow'); el.classList.add('skillbox-auto-grow');
el.addEventListener('input', () => { el.addEventListener('input', () => {
resizeElement(el); resizeElement(el);
}); });
}, },
unbind(el) { unmounted(el: HTMLElement) {
el.classList.remove('skillbox-auto-grow'); el.classList.remove('skillbox-auto-grow');
el.removeEventListener('input', () => { el.removeEventListener('input', () => {
resizeElement(el); resizeElement(el);

View File

@ -1,14 +0,0 @@
// taken from https://stackoverflow.com/questions/36170425/detect-click-outside-element
export default {
bind(el, binding, vnode) {
el.clickOutsideEvent = (event) => {
if (!(el === event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unbind(el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
},
};

View File

@ -0,0 +1,23 @@
// taken from https://stackoverflow.com/questions/36170425/detect-click-outside-element
import {DirectiveBinding, VNode} from "vue";
declare global {
interface HTMLElement {
clickOutsideEvent: (event: Event) => void
}
}
export default {
unmounted(el: HTMLElement) {
document.body.removeEventListener('click', el.clickOutsideEvent);
},
created: (el: HTMLElement, binding: DirectiveBinding) => {
el.clickOutsideEvent = (event: Event) => {
if (!(el === event.target || el.contains(event.target as Node))) {
const eventHandler = binding.value;
eventHandler(event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
}
};

View File

@ -1,131 +1,160 @@
<template> <template>
<footer class="default-footer" data-cy="page-footer"> <footer
class="default-footer"
data-cy="page-footer"
>
<div class="default-footer__section"> <div class="default-footer__section">
<div class="default-footer__info"> <div class="default-footer__info">
<div class="default-footer__who-are-we who-are-we"> <div class="default-footer__who-are-we who-are-we">
<h5 class="who-are-we__title">Wer sind wir?</h5> <h5 class="who-are-we__title">
Wer sind wir?
</h5>
<p class="who-are-we__text"> <p class="who-are-we__text">
mySkillbox ist ein Angebot des hep Verlags in Zusammenarbeit mit der Eidgenössischen Hochschule für mySkillbox ist ein Angebot des hep Verlags in
Berufsbildung (EHB). Zusammenarbeit mit der Eidgenössischen Hochschule für Berufsbildung (EHB).
</p> </p>
</div> </div>
<a href="https://www.hep-verlag.ch/" target="_blank"> <a
href="https://www.hep-verlag.ch/"
target="_blank"
>
<hep-logo class="default-footer__logo-hep" /> <hep-logo class="default-footer__logo-hep" />
</a> </a>
<a href="https://www.ehb.swiss/" target="_blank"> <a
href="https://www.ehb.swiss/"
target="_blank"
>
<ehb-logo class="default-footer__logo-ehb" /> <ehb-logo class="default-footer__logo-ehb" />
</a> </a>
</div> </div>
</div> </div>
<div class="default-footer__section"> <div class="default-footer__section">
<div class="default-footer__links"> <div class="default-footer__links">
<a href="https://myskillbox.ch/datenschutz" target="_blank" class="default-footer__link">Datenschutz</a> <a
<a href="https://myskillbox.ch/impressum" target="_blank" class="default-footer__link">Impressum</a> href="https://myskillbox.ch/datenschutz"
<a href="https://myskillbox.ch/agb" target="_blank" class="default-footer__link">AGB</a> target="_blank"
<a :href="$flavor.supportLink" target="_blank" class="default-footer__link">Support</a> class="default-footer__link"
>Datenschutz</a>
<a
href="https://myskillbox.ch/impressum"
target="_blank"
class="default-footer__link"
>Impressum</a>
<a
href="https://myskillbox.ch/agb"
target="_blank"
class="default-footer__link"
>AGB</a>
<a
:href="$flavor.supportLink"
target="_blank"
class="default-footer__link"
>Support</a>
</div> </div>
</div> </div>
</footer> </footer>
</template> </template>
<script> <script>
const HepLogo = () => import(/* webpackChunkName: "icons" */ '@/components/icons/HepLogo'); import {defineAsyncComponent} from 'vue';
const EhbLogo = () => import(/* webpackChunkName: "icons" */ '@/components/icons/EhbLogo');
export default { const HepLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/HepLogo'));
components: { const EhbLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EhbLogo'));
HepLogo,
EhbLogo, export default {
}, components: {
}; HepLogo,
EhbLogo
}
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/styles/_variables.scss'; @import "@/styles/_variables.scss";
@import '@/styles/_mixins.scss'; @import "@/styles/_mixins.scss";
.default-footer { .default-footer {
background-color: $color-silver-light; background-color: $color-silver-light;
max-width: 100vw; max-width: 100vw;
overflow: hidden; overflow: hidden;
&__section { &__section {
width: 100%; width: 100%;
border-bottom: $color-silver 1px solid; border-bottom: $color-silver 1px solid;
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
&__info { &__info {
width: 100%; width: 100%;
max-width: $footer-width; max-width: $footer-width;
padding: 2 * $large-spacing 0; padding: 2*$large-spacing 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@include desktop { @include desktop {
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
}
}
&__who-are-we {
width: 100%;
margin-bottom: $large-spacing;
@include desktop {
width: 330px;
margin-bottom: 0;
}
}
&__logo-hep {
width: auto;
height: 35px;
margin-bottom: $large-spacing;
@include desktop {
width: 147px;
margin-bottom: 0;
}
}
&__logo-ehb {
width: 100px;
height: 32px;
}
&__links {
width: 100%;
max-width: $footer-width;
padding: $large-spacing 0;
display: flex;
flex-direction: column;
@include desktop {
flex-direction: row;
}
}
&__link {
@include aside-with-cheese;
margin-right: $large-spacing;
margin-bottom: $small-spacing;
@include desktop {
margin-bottom: 0;
}
} }
} }
&__who-are-we { .who-are-we {
width: 100%; &__title {
margin-bottom: $large-spacing; @include heading-4;
}
@include desktop { &__text {
width: 330px; @include aside-text;
margin-bottom: 0;
} }
} }
&__logo-hep {
width: auto;
height: 35px;
margin-bottom: $large-spacing;
@include desktop {
width: 147px;
margin-bottom: 0;
}
}
&__logo-ehb {
width: 100px;
height: 32px;
}
&__links {
width: 100%;
max-width: $footer-width;
padding: $large-spacing 0;
display: flex;
flex-direction: column;
@include desktop {
flex-direction: row;
}
}
&__link {
@include aside-with-cheese;
margin-right: $large-spacing;
margin-bottom: $small-spacing;
@include desktop {
margin-bottom: 0;
}
}
}
.who-are-we {
&__title {
@include heading-4;
}
&__text {
@include aside-text;
}
}
</style> </style>

View File

@ -1,73 +1,80 @@
<template> <template>
<div class="layout layout--public public"> <div class="layout layout--public public">
<div class="public__logo"> <div class="public__logo">
<router-link :to="{ name: 'hello' }" class="hep-link"> <router-link
:to="{name: 'hello'}"
class="hep-link"
>
<logo /> <logo />
</router-link> </router-link>
</div> </div>
<router-view class="public__content layout__content" /> <router-view class="public__content layout__content" />
<default-footer class="skillbox__footer public__footer footer" v-if="$flavor.showFooter" /> <default-footer
class="skillbox__footer public__footer footer"
v-if="$flavor.showFooter"
/>
</div> </div>
</template> </template>
<script> <script>
import DefaultFooter from '@/layouts/DefaultFooter'; import {defineAsyncComponent} from 'vue';
import DefaultFooter from '@/layouts/DefaultFooter';
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
const Logo = () => import(/* webpackChunkName: "icons" */ '@/components/icons/Logo'); export default {
components: {
export default { Logo,
components: { DefaultFooter
Logo, },
DefaultFooter, };
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '@/styles/_variables.scss'; @import "@/styles/_variables.scss";
@import '@/styles/_mixins.scss'; @import "@/styles/_mixins.scss";
@import '@/styles/_default-layout.scss'; @import "@/styles/_default-layout.scss";
@mixin content-block { @mixin content-block {
padding-right: $medium-spacing; padding-right: $medium-spacing;
padding-left: $medium-spacing; padding-left: $medium-spacing;
max-width: 800px; max-width: 800px;
min-width: 320px; min-width: 320px;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
} }
.logo { .logo {
position: relative; position: relative;
width: auto;
height: 43px;
}
.public {
grid-template-areas: 'h' 'c' 'f';
&__content {
@include content-block();
margin-bottom: $large-spacing;
width: auto; width: auto;
height: 43px;
} }
&__logo { .public {
@include content-block(); grid-template-areas: "h" "c" "f";
margin-top: $medium-spacing;
&__content {
@include content-block();
margin-bottom: $large-spacing;
width: auto;
}
&__logo {
@include content-block();
margin-top: $medium-spacing
}
&__footer {
background-color: $color-silver-light;
display: block;
}
} }
&__footer { .footer {
background-color: $color-silver-light; padding: $large-spacing $medium-spacing 0;
display: block;
}
}
.footer { &__content {
padding: $large-spacing $medium-spacing 0; @include content-block();
}
&__content {
@include content-block();
} }
}
</style> </style>

View File

@ -1,5 +1,5 @@
import '@babel/polyfill'; import '@babel/polyfill';
import Vue from 'vue'; import {createApp, inject} from 'vue';
import VueVimeoPlayer from 'vue-vimeo-player'; import VueVimeoPlayer from 'vue-vimeo-player';
import apolloClientFactory from './graphql/client'; import apolloClientFactory from './graphql/client';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
@ -13,42 +13,9 @@ import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import VueModal from '@/plugins/modal'; import VueModal from '@/plugins/modal';
import VueRemoveEdges from '@/plugins/edges'; import VueRemoveEdges from '@/plugins/edges';
import VueMatomo from 'vue-matomo'; import VueMatomo from 'vue-matomo';
import VueLogger from 'vuejs3-logger';
import { joiningClass, loginRequired, unauthorizedAccess } from '@/router/guards'; import { joiningClass, loginRequired, unauthorizedAccess } from '@/router/guards';
import flavorPlugin from '@/plugins/flavor'; import flavorPlugin from '@/plugins/flavor';
import log from 'loglevel';
window.log = log; // make log available in app when built, to change log level: log.setLevel('debug')
Vue.config.productionTip = false;
const isProduction = process.env.NODE_ENV === 'production';
const logLevel = isProduction ? 'error' : 'warn';
log.setDefaultLevel(logLevel);
Vue.use(VueModal);
Vue.use(VueRemoveEdges);
Vue.use(VueApollo);
Vue.use(VueVimeoPlayer);
Vue.use(VueScrollTo, {
duration: 500,
easing: 'ease-out',
offset: -50,
});
Vue.use(flavorPlugin);
if (process.env.MATOMO_HOST) {
Vue.use(VueMatomo, {
host: process.env.MATOMO_HOST,
siteId: process.env.MATOMO_SITE_ID,
router: router,
});
}
Vue.directive('click-outside', clickOutside);
Vue.directive('auto-grow', autoGrow);
const publicApolloClient = apolloClientFactory('/api/graphql-public/', null); const publicApolloClient = apolloClientFactory('/api/graphql-public/', null);
const privateApolloClient = apolloClientFactory('/api/graphql/', networkErrorCallback); const privateApolloClient = apolloClientFactory('/api/graphql/', networkErrorCallback);
@ -60,56 +27,96 @@ const apolloProvider = new VueApollo({
defaultClient: privateApolloClient, defaultClient: privateApolloClient,
}); });
const app = createApp({
store,
router,
apolloProvider,
render: h => h(App),
});
const isProduction = process.env.NODE_ENV === 'production';
app.use(VueModal);
app.use(VueRemoveEdges);
app.use(VueApollo);
app.use(VueVimeoPlayer);
app.use(VueLogger, {
isEnabled: true,
logLevel: isProduction ? 'error' : 'debug',
stringifyArguments: false,
showConsoleColors: true,
});
// VueScrollTo.setDefaults({
// duration: 500,
// easing: 'ease-out',
// offset: -50,
// });
app.directive('scroll-to', VueScrollTo);
app.use(flavorPlugin);
if (process.env.MATOMO_HOST) {
app.use(VueMatomo, {
host: process.env.MATOMO_HOST,
siteId: process.env.MATOMO_SITE_ID,
router: router,
});
}
app.directive('click-outside', clickOutside);
app.directive('auto-grow', autoGrow);
/* guards */ /* guards */
function redirectUsersWithoutValidLicense() { function redirectUsersWithoutValidLicense() {
return privateApolloClient return privateApolloClient.query({
.query({ query: ME_QUERY,
query: ME_QUERY, }).then(({data}) => data.me.expiryDate == null);
})
.then(({ data }) => data.me.expiryDate == null);
} }
function redirectStudentsWithoutClass() { function redirectStudentsWithoutClass() {
return privateApolloClient return privateApolloClient.query({
.query({ query: ME_QUERY,
query: ME_QUERY, }).then(({data}) => data.me.schoolClasses.length === 0 && !data.me.isTeacher);
})
.then(({ data }) => data.me.schoolClasses.length === 0 && !data.me.isTeacher);
} }
function redirectUsersToOnboarding() { function redirectUsersToOnboarding() {
return privateApolloClient return privateApolloClient.query({
.query({ query: ME_QUERY,
query: ME_QUERY, }).then(({data}) => !data.me.onboardingVisited);
})
.then(({ data }) => !data.me.onboardingVisited);
} }
function networkErrorCallback(statusCode) { function networkErrorCallback(statusCode) {
if (statusCode === 402) { if (statusCode === 402) {
log.debug('status code 402, redirecting'); router.push({name: 'licenseActivation'});
router.push({ name: 'licenseActivation' });
} }
} }
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
log.debug('navigation guard called', to, from); // todo: make logger work outside vue app
// const logger = inject('vuejs3-logger');
// logger.$log.debug('navigation guard called', to, from);
if (to.path === '/logout') { if (to.path === '/logout') {
log.debug('logout', to); await publicApolloClient.resetStore();
publicApolloClient.resetStore();
if (process.env.LOGOUT_REDIRECT_URL) { if (process.env.LOGOUT_REDIRECT_URL) {
location.replace(`https://sso.hep-verlag.ch/logout?return_to=${process.env.LOGOUT_REDIRECT_URL}`); location.replace(`https://sso.hep-verlag.ch/logout?return_to=${process.env.LOGOUT_REDIRECT_URL}`);
next(false); next(false);
return; return;
} else { } else {
next({ name: 'hello' }); next({name: 'hello'});
return; return;
} }
} }
if (unauthorizedAccess(to)) { if (unauthorizedAccess(to)) {
log.debug('unauthorized', to); //logger.$log.debug('unauthorized', to);
const postLoginRedirectionUrl = to.path; const postLoginRedirectionUrl = to.path;
const redirectUrl = `/hello/`; const redirectUrl = `/hello/`;
@ -117,46 +124,33 @@ router.beforeEach(async (to, from, next) => {
localStorage.setItem(postLoginRedirectUrlKey, postLoginRedirectionUrl); localStorage.setItem(postLoginRedirectUrlKey, postLoginRedirectionUrl);
} }
log.debug('redirecting to hello', to); // logger.$log.debug('redirecting to hello', to);
next(redirectUrl); next(redirectUrl);
return; return;
} }
if (to.name && to.name !== 'licenseActivation' && loginRequired(to) && (await redirectUsersWithoutValidLicense())) { if (to.name && to.name !== 'licenseActivation' && loginRequired(to) && await redirectUsersWithoutValidLicense()) {
log.debug('redirecting to licenseActivation', to, null); // logger.$log.debug('redirecting to licenseActivation', to, null);
console.log('redirecting to licenseActivation', to, null); console.log('redirecting to licenseActivation', to, null);
next({ name: 'licenseActivation' }); next({name: 'licenseActivation'});
return; return;
} }
if (!joiningClass(to) && loginRequired(to) && (await redirectStudentsWithoutClass())) { if (!joiningClass(to) && loginRequired(to) && await redirectStudentsWithoutClass()) {
log.debug('redirecting to join-class', to); //logger.$log.debug('redirecting to join-class', to);
log.debug('await redirectStudentsWithoutClass()', await redirectStudentsWithoutClass()); //logger.$log.debug('await redirectStudentsWithoutClass()', await redirectStudentsWithoutClass());
next({ name: 'join-class' }); next({name: 'join-class'});
return; return;
} }
if ( if ((to.name && to.name.indexOf('onboarding') === -1) && !joiningClass(to) && loginRequired(to) && await redirectUsersToOnboarding()) {
to.name && //logger.$log.debug('redirecting to onboarding-start', to);
to.name.indexOf('onboarding') === -1 && next({name: 'onboarding-start'});
!joiningClass(to) &&
loginRequired(to) &&
(await redirectUsersToOnboarding())
) {
log.debug('redirecting to onboarding-start', to);
next({ name: 'onboarding-start' });
return; return;
} }
log.debug('End of Guard reached', to); //logger.$log.debug('End of Guard reached', to);
next(); next();
}); });
/* eslint-disable no-new */ app.mount('#app');
new Vue({
el: '#app',
store,
router,
apolloProvider,
render: (h) => h(App),
});

File diff suppressed because one or more lines are too long

View File

@ -1,82 +1,85 @@
<template> <template>
<content-block-form title="Inhaltsblock erfassen" :content-block="contentBlock" @back="goToModule" @save="save" /> <content-block-form
title="Inhaltsblock erfassen"
:content-block="contentBlock"
@back="goToModule"
@save="save"
/>
</template> </template>
<script> <script>
import Vue from 'vue'; import {defineComponent} from 'vue';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm'; import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import { setUserBlockType } from '@/helpers/content-block'; import {setUserBlockType} from '@/helpers/content-block';
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql'; import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql'; import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
import { cleanUpContents } from '@/components/content-block-form/helpers'; import {cleanUpContents} from '@/components/content-block-form/helpers';
export default Vue.extend({ export default defineComponent({
props: { props: {
parent: { parent: {
type: String, type: String,
default: '', default: ''
}, },
after: { after: {
type: String, type: String,
default: '', default: ''
},
},
components: {
ContentBlockForm,
},
data: () => ({
contentBlock: {
title: '',
isAssignment: false,
contents: [],
},
}),
methods: {
save({ title, contents, isAssignment }) {
let cleanedContents = cleanUpContents(contents);
const contentBlock = {
title: title,
contents: cleanedContents,
type: setUserBlockType(isAssignment),
};
let input;
const { parent, after, slug } = this.$route.params;
if (after) {
input = {
contentBlock,
after,
};
} else {
input = {
contentBlock,
parent,
};
} }
this.$apollo },
.mutate({
components: {
ContentBlockForm,
},
data: () => ({
contentBlock: {
title: '',
isAssignment: false,
contents: [
]},
}),
methods: {
save({title, contents, isAssignment}) {
let cleanedContents = cleanUpContents(contents);
const contentBlock = {
title: title,
contents: cleanedContents,
type: setUserBlockType(isAssignment),
};
let input;
const { parent, after, slug } = this.$route.params;
if(after) {
input = {
contentBlock,
after
};
} else {
input = {
contentBlock,
parent
};
}
this.$apollo.mutate({
mutation: NEW_CONTENT_BLOCK_MUTATION, mutation: NEW_CONTENT_BLOCK_MUTATION,
variables: { variables: {
input, input
}, },
refetchQueries: [ refetchQueries: [{
{ query: MODULE_DETAILS_QUERY,
query: MODULE_DETAILS_QUERY, variables: {
variables: { slug
slug, }
}, }]
}, }).then(this.goToModule);
], },
}) goToModule() {
.then(this.goToModule); // use the history, so the scroll position is preserved
}, this.$router.go(-1);
goToModule() { }
// use the history, so the scroll position is preserved }
this.$router.go(-1);
},
}, });
});
</script> </script>

View File

@ -8,7 +8,7 @@
:final="project.final" :final="project.final"
data-cy="project-share-link" data-cy="project-share-link"
class="project__share" class="project__share"
@click.native="updateProjectShareState(project.slug, !project.final)" @click="updateProjectShareState(project.slug, !project.final)"
/> />
<project-actions :share-buttons="false" class="project__more" :slug="project.slug" v-if="canEdit" /> <project-actions :share-buttons="false" class="project__more" :slug="project.slug" v-if="canEdit" />

View File

@ -5,14 +5,21 @@
</div> </div>
<div class="topic__content"> <div class="topic__content">
<h1 data-cy="topic-title" class="topic__title"> <h1
data-cy="topic-title"
class="topic__title"
>
{{ topic.title }} {{ topic.title }}
</h1> </h1>
<p class="topic__teaser"> <p class="topic__teaser">
{{ topic.teaser }} {{ topic.teaser }}
</p> </p>
<div class="topic__links"> <div class="topic__links">
<div class="topic__video-link topic__link" v-if="topic.vimeoId" @click="openVideo"> <div
class="topic__video-link topic__link"
v-if="topic.vimeoId"
@click="openVideo"
>
<play-icon class="topic__video-link-icon topic__link-icon" /> <play-icon class="topic__video-link-icon topic__link-icon" />
<span class="topic__link-description">Video schauen</span> <span class="topic__link-description">Video schauen</span>
</div> </div>
@ -27,187 +34,184 @@
</a> </a>
</div> </div>
<div class="topic__modules"> <div class="topic__modules">
<module-teaser v-for="module in modules" :key="module.slug" v-bind="module" /> <module-teaser
v-for="module in modules"
v-bind="module"
:key="module.slug"
/>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import ModuleTeaser from '@/components/modules/ModuleTeaser.vue'; import ModuleTeaser from '@/components/modules/ModuleTeaser.vue';
import TOPIC_QUERY from '@/graphql/gql/queries/topicQuery.gql'; import TOPIC_QUERY from '@/graphql/gql/queries/topicQuery.gql';
import me from '@/mixins/me'; import me from '@/mixins/me';
import TopicNavigation from '@/components/book-navigation/TopicNavigation'; import TopicNavigation from '@/components/book-navigation/TopicNavigation';
import UPDATE_LAST_TOPIC_MUTATION from '@/graphql/gql/mutations/updateLastTopic.gql'; import UPDATE_LAST_TOPIC_MUTATION from '@/graphql/gql/mutations/updateLastTopic.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql'; import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
const PlayIcon = () => import(/* webpackChunkName: "icons" */ '@/components/icons/Play'); const PlayIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/Play');
const BulbIcon = () => import(/* webpackChunkName: "icons" */ '@/components/icons/BulbIcon'); const BulbIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/BulbIcon');
export default { export default {
mixins: [me],
components: {
TopicNavigation,
ModuleTeaser,
PlayIcon,
BulbIcon,
},
apollo: { mixins: [me],
topic() { components: {
return { TopicNavigation,
query: TOPIC_QUERY, ModuleTeaser,
variables: { PlayIcon,
slug: this.$route.params.topicSlug, BulbIcon,
},
update(data) {
return this.$getRidOfEdges(data).topic || {};
},
result() {
if (this.saveMe) {
this.saveMe = false;
this.updateLastVisitedTopic(this.topic.id);
}
},
};
}, },
},
data() { apollo: {
return { topic() {
topic: { return {
modules: { query: TOPIC_QUERY,
edges: [], variables: {
}, slug: this.$route.params.topicSlug,
},
update(data) {
return this.$getRidOfEdges(data).topic || {};
},
result() {
if (this.saveMe) {
this.saveMe = false;
this.updateLastVisitedTopic(this.topic.id);
}
},
};
}, },
saveMe: false,
};
},
computed: {
modules() {
return this.topic.modules;
}, },
},
mounted() { data() {
if (!this.topic.id) { return {
// component was loaded before topic, apollo not ready yet topic: {
this.saveMe = true; // needs saving, apollo will do this modules: {
} else { edges: [],
this.updateLastVisitedTopic(this.topic.id);
}
},
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
},
updateLastVisitedTopic(topicId) {
if (!topicId) {
return;
}
this.$apollo.mutate({
mutation: UPDATE_LAST_TOPIC_MUTATION,
variables: {
input: {
id: topicId,
}, },
}, },
update( saveMe: false,
store, };
{
data: {
updateLastTopic: { topic },
},
}
) {
if (topic) {
const query = ME_QUERY;
const { me } = store.readQuery({ query });
if (me) {
const data = {
me: {
...me,
lastTopic: topic,
},
};
store.writeQuery({ query, data });
}
}
},
});
}, },
},
}; computed: {
modules() {
return this.topic.modules;
},
},
mounted() {
if (!this.topic.id) { // component was loaded before topic, apollo not ready yet
this.saveMe = true; // needs saving, apollo will do this
} else {
this.updateLastVisitedTopic(this.topic.id);
}
},
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
},
updateLastVisitedTopic(topicId) {
if (!topicId) {
return;
}
this.$apollo.mutate({
mutation: UPDATE_LAST_TOPIC_MUTATION,
variables: {
input: {
id: topicId,
},
},
update(store, {data: {updateLastTopic: {topic}}}) {
if (topic) {
const query = ME_QUERY;
const {me} = store.readQuery({query});
if (me) {
const data = {
me: {
...me,
lastTopic: topic,
},
};
store.writeQuery({query, data});
}
}
},
});
},
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/styles/_variables.scss'; @import "@/styles/_variables.scss";
@import '@/styles/_mixins.scss'; @import "@/styles/_mixins.scss";
.topic { .topic {
display: grid; display: grid;
padding: $large-spacing 0; padding: $large-spacing 0;
grid-template-columns: 1fr; grid-template-columns: 1fr;
@include desktop {
grid-template-columns: 300px 1fr;
}
&__navigation {
padding: 0 $medium-spacing;
display: none;
@include desktop { @include desktop {
display: block; grid-template-columns: 300px 1fr;
}
&__navigation {
padding: 0 $medium-spacing;
display: none;
@include desktop {
display: block;
}
}
&__teaser {
color: $color-charcoal-dark;
width: 90%;
@include lead-paragraph;
margin-bottom: $large-spacing;
}
&__links {
margin-bottom: $large-spacing;
display: flex;
}
&__link {
cursor: pointer;
display: flex;
align-items: center;
}
&__video-link {
margin-right: $large-spacing;
}
&__link-icon {
width: 40px;
height: 40px;
margin-right: $medium-spacing;
}
&__link-description {
@include heading-3;
}
&__modules {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
@supports (display: grid) {
display: grid;
}
grid-column-gap: $large-spacing;
grid-row-gap: $large-spacing;
@include desktop {
grid-template-columns: repeat(3, minmax(auto, 380px));
}
} }
} }
&__teaser {
color: $color-charcoal-dark;
width: 90%;
@include lead-paragraph;
margin-bottom: $large-spacing;
}
&__links {
margin-bottom: $large-spacing;
display: flex;
}
&__link {
cursor: pointer;
display: flex;
align-items: center;
}
&__video-link {
margin-right: $large-spacing;
}
&__link-icon {
width: 40px;
height: 40px;
margin-right: $medium-spacing;
}
&__link-description {
@include heading-3;
}
&__modules {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
@supports (display: grid) {
display: grid;
}
grid-column-gap: $large-spacing;
grid-row-gap: $large-spacing;
@include desktop {
grid-template-columns: repeat(3, minmax(auto, 380px));
}
}
}
</style> </style>

View File

@ -1,51 +1,51 @@
// adapted from // adapted from
// https://stackoverflow.com/questions/41791193/vuejs-reactive-binding-for-a-plugin-how-to/41801107#41801107 // https://stackoverflow.com/questions/41791193/vuejs-reactive-binding-for-a-plugin-how-to/41801107#41801107
import Vue, { VueConstructor } from 'vue'; import {reactive, App} from 'vue';
class ModalStore { class ModalStore {
vm: Vue; data: any;
constructor() { constructor() {
this.vm = new Vue({ this.data = reactive({
data: () => ({ component: '',
component: '', payload: {}
payload: {},
}),
}); });
} }
get state() { get state() {
return this.vm.$data; return this.data;
} }
} }
interface Modal { interface Modal {
state: any; state: any,
component: string; component: string,
payload?: any; payload?: any,
confirm: (res: any) => void; confirm: (res: any) => void,
open: (component: string, payload?: any) => Promise<(resolve: () => any, reject: () => any) => void>; open: (component: string, payload?: any) => Promise<(resolve: () => any, reject: () => any) => void>,
cancel: () => void; cancel: () => void,
_resolve: (r?: any) => any; _resolve: (r?: any) => any,
_reject: (r?: any) => any; _reject: (r?: any) => any
} }
declare module 'vue/types/vue' { declare module '@vue/runtime-core' {
interface Vue { interface ComponentCustomProperties {
$modal: Modal; $modal: Modal
} }
} }
const ModalPlugin = { const ModalPlugin = {
install(Vue: VueConstructor) { install(app: App) {
const store = new ModalStore(); const store = new ModalStore();
console.log('installing modal plugin');
const reset = () => { const reset = () => {
store.state.component = ''; store.state.component = '';
store.state.payload = {}; store.state.payload = {};
}; };
const modal: Modal = { app.config.globalProperties.$modal = {
state: store.state, state: store.state,
component: store.state.component, component: store.state.component,
payload: store.state.payload, payload: store.state.payload,
@ -65,12 +65,13 @@ const ModalPlugin = {
reset(); reset();
this._reject(); this._reject();
}, },
_resolve: () => {}, _resolve: () => {
_reject: () => {}, },
_reject: () => {
},
}; };
Vue.prototype.$modal = modal; }
},
}; };
export default ModalPlugin; export default ModalPlugin;

View File

@ -1,13 +1,13 @@
// from https://stackoverflow.com/questions/64213461/vuejs-typescript-cannot-find-module-components-navigation-or-its-correspon // from https://stackoverflow.com/questions/64213461/vuejs-typescript-cannot-find-module-components-navigation-or-its-correspon
declare module '*.vue' { // declare module "*.vue" {
import Vue from 'vue'; // import Vue from 'vue';
export default Vue; // export default Vue;
} // }
// for Vue 3 // for Vue 3
// declare module '*.vue' { declare module '*.vue' {
// import type { DefineComponent } from 'vue' import type { DefineComponent } from 'vue';
// const component: DefineComponent<{}, {}, any> const component: DefineComponent<{}, {}, any>;
// export default component export default component;
// } }
//