Upgrade code according to migration guide for Vue 3

Update npm dependencies

Update vue router version

Disable validation temporarily

Specify property

Update dependencies

Update store to v4

Update async component definitions

Update some event emitters

Update tiptap vue version

Implement some router changes for v4

Remove obsolete tag attributes

Update dependencies

Fix some cypress tests

Fix most jest tests

Fix some more cypress tests

Fix school class cypress test

Fix another cypress test

Disable failing test temporarily

Fix validation

Fix error messages for validation

Fix e2e test for beta login page

Apply prettier
This commit is contained in:
Ramon Wenger 2022-03-23 16:21:06 +01:00
parent 4b55f8952c
commit a52671fd40
106 changed files with 4604 additions and 5195 deletions

View File

@ -1 +1,3 @@
bundle-analysis
dist
node_modules

View File

@ -1,6 +1,6 @@
import module from '../../../fixtures/module.minimal';
import {getMinimalMe} from '../../../support/helpers';
import {hasOperationName} from '../../../support/graphql';
import { getMinimalMe } from '../../../support/helpers';
import { hasOperationName } from '../../../support/graphql';
let snapshotTitle;
let deleteSuccess;
@ -133,7 +133,7 @@ describe('Snapshot', () => {
},
},
},
MeQuery: getMinimalMe({isTeacher}),
MeQuery: getMinimalMe({ isTeacher }),
ModuleDetailsQuery: {
module,
},
@ -219,7 +219,9 @@ describe('Snapshot', () => {
cy.getByDataCy('module-snapshots-button').click();
cy.getByDataCy('create-snapshot-button').click();
cy.getByDataCy('show-all-snapshots-button').click();
cy.getByDataCy('snapshot-list').should('exist').within(() => {
cy.getByDataCy('snapshot-list')
.should('exist')
.within(() => {
cy.get('.snapshots__snapshot').should('have.length', 1);
});
waitNTimes(7);

View File

@ -1,7 +1,7 @@
describe('Room Team Management - Read only', () => {
const SELECTED_CLASS_ID = 'selectedClassId';
const getOperations = ({readOnly, classReadOnly}) => ({
const getOperations = ({ readOnly, classReadOnly }) => ({
MeQuery: {
me: {
readOnly,
@ -13,7 +13,8 @@ describe('Room Team Management - Read only', () => {
},
},
RoomsQuery: {
rooms: [{
rooms: [
{
id: '',
slug: 'some-room',
title: 'some room',
@ -24,12 +25,13 @@ describe('Room Team Management - Read only', () => {
id: SELECTED_CLASS_ID,
name: 'bla',
},
}],
},
],
},
});
const checkRoomsReadOnly = ({editable, readOnly, classReadOnly = false}) => {
const operations = getOperations({readOnly, classReadOnly});
const checkRoomsReadOnly = ({ editable, readOnly, classReadOnly = false }) => {
const operations = getOperations({ readOnly, classReadOnly });
cy.mockGraphqlOps({
operations,
@ -48,14 +50,14 @@ describe('Room Team Management - Read only', () => {
});
it('can edit room', () => {
checkRoomsReadOnly({editable: true, readOnly: false});
checkRoomsReadOnly({ editable: true, readOnly: false });
});
it('can not edit room', () => {
checkRoomsReadOnly({editable: false, readOnly: true});
checkRoomsReadOnly({ editable: false, readOnly: true });
});
it('can not edit room of inactive class', () => {
checkRoomsReadOnly({editable: false, readOnly: false, classReadOnly: true});
checkRoomsReadOnly({ editable: false, readOnly: false, classReadOnly: true });
});
});

View File

@ -1,11 +1,5 @@
module.exports = {
moduleFileExtensions: [
'js',
'jsx',
'ts',
'json',
'vue',
],
moduleFileExtensions: ['js', 'jsx', 'ts', 'json', 'vue'],
transform: {
'\\.(gql|graphql)$': '@graphql-tools/jest-transform',
'^.+\\.js$': 'babel-jest',
@ -13,20 +7,13 @@ module.exports = {
'^.+\\.vue$': '@vue/vue3-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
},
modulePaths: [
'<rootDir>/src',
'<rootDir>/node_modules',
],
transformIgnorePatterns: [
'/node_modules/',
],
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
transformIgnorePatterns: ['/node_modules/'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^gql/(.*)$': '<rootDir>/src/graphql/gql/$1',
},
snapshotSerializers: [
'<rootDir>/node_modules/jest-serializer-vue',
],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
testEnvironment: 'jsdom',
testEnvironmentOptions: {
url: 'http://localhost/',

View File

@ -3,6 +3,9 @@
"version": "1.0.0",
"description": "skillbox vue client",
"author": "ramon / chrigu",
"prettier": {
"singleQuote": true
},
"private": true,
"prettier": {
"singleQuote": true

View File

@ -1,9 +1,6 @@
<template>
<div class="add-content">
<a
class="add-content__button"
@click="addContent"
>
<a class="add-content__button" @click="addContent">
<add-pointer class="add-content__icon" />
</a>
</div>
@ -18,19 +15,20 @@
const AddPointer = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddPointer'));
export default {
export default {
props: {
where: {
type: Object,
validator(prop) {
return Object.prototype.hasOwnProperty.call(prop, 'after' )
|| Object.prototype.hasOwnProperty.call(prop, 'parent');
}
return (
Object.prototype.hasOwnProperty.call(prop, 'after') || Object.prototype.hasOwnProperty.call(prop, 'parent')
);
},
},
},
components: {
AddPointer
AddPointer,
},
computed: {
@ -45,14 +43,13 @@
},
slug() {
return this.$route.params.slug;
}
},
},
methods: {
addContent() {
if (this.isObjectiveGroup) {
this.$modal.open('new-objective-wizard', {parent: this.parent.id});
this.$modal.open('new-objective-wizard', { parent: this.parent.id });
} else {
let route;
if (this.after && this.after.id) {
@ -60,28 +57,28 @@
name: CREATE_CONTENT_BLOCK_AFTER_PAGE,
params: {
after: this.after.id,
slug: this.slug
}
slug: this.slug,
},
};
} else {
route = {
name: CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE,
params: {
parent: this.parent.id
}
parent: this.parent.id,
},
};
}
this.$router.push(route);
}
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.add-content {
.add-content {
display: none;
position: relative;
@include desktop {
@ -101,5 +98,5 @@
width: 40px;
fill: $color-silver-dark;
}
}
}
</style>

View File

@ -1,8 +1,5 @@
<template>
<div
class="add-content-element"
@click="$emit('add-element', index)"
>
<div class="add-content-element" @click="$emit('add-element', index)">
<add-icon class="add-content-element__icon" />
</div>
</template>
@ -11,19 +8,19 @@
import {defineAsyncComponent} from 'vue';
const AddIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon'));
export default {
export default {
props: ['index'],
components: {
AddIcon
}
};
AddIcon,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.add-content-element {
.add-content-element {
display: flex;
justify-content: center;
border-bottom: 2px solid $color-silver-dark;
@ -38,5 +35,5 @@
background-color: $color-white;
border-radius: 50px;
}
}
}
</style>

View File

@ -14,24 +14,25 @@
import {defineAsyncComponent} from 'vue';
const AddIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon.vue'));
export default {
export default {
props: {
route: {
type: String,
default: null
default: null,
},
reverse: { // use reverse colors
reverse: {
// use reverse colors
type: Boolean,
default: false
default: false,
},
click: {
type: Function,
default: null
}
default: null,
},
},
components: {
AddIcon
AddIcon,
},
computed: {
@ -40,19 +41,21 @@
return this.route ? 'router-link' : 'a';
},
properties() {
return this.route ? {
return this.route
? {
to: this.route,
tag: 'div'
} : {};
tag: 'div',
}
: {};
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.add-widget {
.add-widget {
display: none;
align-items: center;
justify-content: center;
@ -75,5 +78,5 @@
&--reverse &__add {
fill: white;
}
}
}
</style>

View File

@ -1,8 +1,5 @@
<template>
<router-link
:to="to"
class="sub-navigation-item back-link"
>
<router-link :to="to" class="sub-navigation-item back-link">
<chevron-left class="back-link__icon sub-navigation-item__icon" />
{{ fullTitle }}
</router-link>
@ -16,7 +13,7 @@
const ChevronLeft = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronLeft'));
export default {
export default {
props: {
title: {
type: String,
@ -68,9 +65,9 @@
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.back-link {
.back-link {
@include regular-text;
}
}
</style>

View File

@ -1,21 +1,15 @@
<template>
<div class="color-chooser">
<div
:class="{'color-chooser__color-wrapper--selected': selectedColor === color.name}"
:class="{ 'color-chooser__color-wrapper--selected': selectedColor === color.name }"
class="color-chooser__color-wrapper"
data-cy="color-select"
v-for="(color, index) in colors"
:key="index"
@click="$emit('input', color.name)"
>
<div
:class="'color-chooser__color--' + color.name"
class="color-chooser__color"
>
<tick
class="color-chooser__selected-icon"
v-if="selectedColor === color.name"
/>
<div :class="'color-chooser__color--' + color.name" class="color-chooser__color">
<tick class="color-chooser__selected-icon" v-if="selectedColor === color.name" />
</div>
</div>
</div>
@ -25,39 +19,39 @@
import {defineAsyncComponent} from 'vue';
const Tick = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Tick'));
export default {
export default {
props: ['selectedColor'],
components: {
Tick
Tick,
},
data() {
return {
colors: [
{
name: 'yellow'
name: 'yellow',
},
{
name: 'blue'
name: 'blue',
},
{
name: 'red'
name: 'red',
},
{
name: 'green'
}
]
name: 'green',
},
],
};
},
};
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.color-chooser {
.color-chooser {
display: flex;
&__color-wrapper {
@ -82,12 +76,12 @@
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid
display: grid;
}
justify-items: center;
align-items: center;
@include skillbox-colors;
}
}
}
</style>

View File

@ -1,23 +1,24 @@
<template>
<modal :fullscreen="true">
<component
:value="value"
:is="type"
/>
<component :value="value" :is="type" />
</modal>
</template>
<script>
import Modal from '@/components/Modal';
import {defineAsyncComponent} from 'vue';
const InfogramBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/InfogramBlock'));
const GeniallyBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/GeniallyBlock'));
import Modal from '@/components/Modal';
import { defineAsyncComponent } from 'vue';
const InfogramBlock = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-components" */ '@/components/content-blocks/InfogramBlock')
);
const GeniallyBlock = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-components" */ '@/components/content-blocks/GeniallyBlock')
);
export default {
export default {
components: {
Modal,
InfogramBlock,
GeniallyBlock
GeniallyBlock,
},
computed: {
@ -29,9 +30,9 @@
},
value() {
return {
id: this.id
};
}
}
id: this.id,
};
},
},
};
</script>

View File

@ -1,44 +1,31 @@
<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" />
</a>
<content-navigation class="header-bar__content-navigation" />
<div class="user-header">
<a
class="user-header__sidebar-link"
>
<current-class
class="user-header__current-class"
@click="openSidebar('profile')"
/>
<a class="user-header__sidebar-link">
<current-class class="user-header__current-class" @click="openSidebar('profile')" />
</a>
<user-widget
:avatar-url="me.avatarUrl"
data-cy="header-user-widget"
@click="openSidebar('profile')"
/>
<user-widget :avatar-url="me.avatarUrl" data-cy="header-user-widget" @click="openSidebar('profile')" />
</div>
</header>
</template>
<script>
import ContentNavigation from '@/components/book-navigation/ContentNavigation.vue';
import UserWidget from '@/components/UserWidget.vue';
import CurrentClass from '@/components/school-class/CurrentClass';
import ContentNavigation from '@/components/book-navigation/ContentNavigation.vue';
import UserWidget from '@/components/UserWidget.vue';
import CurrentClass from '@/components/school-class/CurrentClass';
import openSidebar from '@/mixins/open-sidebar';
import me from '@/mixins/me';
import {defineAsyncComponent} from 'vue';
import openSidebar from '@/mixins/open-sidebar';
import me from '@/mixins/me';
import { defineAsyncComponent } from 'vue';
const Hamburger = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Hamburger'));
const Hamburger = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Hamburger'));
export default {
export default {
mixins: [openSidebar, me],
components: {
@ -47,13 +34,13 @@
CurrentClass,
Hamburger,
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.header-bar {
.header-bar {
display: flex;
flex-direction: row;
@supports (display: grid) {
@ -119,9 +106,9 @@
-ms-grid-column: 1;
-ms-grid-column-span: 3;
}
}
}
.user-header {
.user-header {
display: flex;
&__current-class {
@ -136,5 +123,5 @@
display: flex;
}
}
}
}
</style>

View File

@ -10,23 +10,23 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon'));
import { defineAsyncComponent } from 'vue';
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/InfoIcon'));
export default {
export default {
props: ['text'],
components: {
InfoIcon
}
};
InfoIcon,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.helpful-tooltip {
.helpful-tooltip {
position: relative;
&__icon {
@ -66,11 +66,10 @@
height: 10px;
transform: rotate(-45deg) translateY(-50%);
}
}
&:hover &__tooltip {
visibility: visible;
}
}
}
</style>

View File

@ -1,47 +1,43 @@
<template>
<button
:disabled="loading || disabled"
class="loading-button button button--primary button--big"
>
<button :disabled="loading || disabled" class="loading-button button button--primary button--big">
<template v-if="!loading">
{{ label }}
</template>
<loading-icon
class="loading-button__icon"
v-else
/>
<loading-icon class="loading-button__icon" v-else />
</button>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const LoadingIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/LoadingIcon'));
import { defineAsyncComponent } from 'vue';
const LoadingIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/LoadingIcon')
);
export default {
export default {
props: {
loading: {
type: Boolean,
default: false
default: false,
},
disabled: {
type: Boolean,
default: false
default: false,
},
label: {
type: String,
default: ''
}
default: '',
},
},
components: {
LoadingIcon
}
};
LoadingIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.loading-button {
.loading-button {
height: 52px;
min-width: 100px;
display: inline-flex;
@ -54,5 +50,5 @@
@include spin;
fill: $color-brand;
}
}
}
</style>

View File

@ -4,31 +4,25 @@
<hamburger class="mobile-header__hamburger" />
</a>
<router-link
to="/"
data-cy="mobile-home-link"
>
<router-link to="/" data-cy="mobile-home-link">
<logo />
</router-link>
<user-widget
v-bind="me"
@click.stop="openSidebar('profile')"
/>
<user-widget v-bind="me" @click.stop="openSidebar('profile')" />
</div>
</template>
<script>
import UserWidget from '@/components/UserWidget';
import UserWidget from '@/components/UserWidget';
import me from '@/mixins/me';
import openSidebar from '@/mixins/open-sidebar';
import {defineAsyncComponent} from 'vue';
import me from '@/mixins/me';
import openSidebar from '@/mixins/open-sidebar';
import { defineAsyncComponent } from 'vue';
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
const Hamburger = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Hamburger'));
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Logo'));
const Hamburger = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Hamburger'));
export default {
export default {
mixins: [me, openSidebar],
components: {
@ -42,13 +36,13 @@
this.$store.dispatch('showMobileNavigation', true);
},
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.mobile-header {
.mobile-header {
justify-content: space-between;
align-items: center;
@ -65,5 +59,5 @@
height: 30px;
fill: $color-silver-dark;
}
}
}
</style>

View File

@ -1,7 +1,11 @@
<template>
<div class="modal__backdrop">
<div
:class="{'modal--hide-header': hideHeader || fullscreen, 'modal--fullscreen': fullscreen, 'modal--small': small}"
:class="{
'modal--hide-header': hideHeader || fullscreen,
'modal--fullscreen': fullscreen,
'modal--small': small,
}"
class="modal"
>
<div class="modal__header">
@ -9,20 +13,14 @@
</div>
<div class="modal__body">
<slot />
<div
class="modal__close-button"
@click="hideModal"
>
<div class="modal__close-button" @click="hideModal">
<cross class="modal__close-icon" />
</div>
</div>
<div class="modal__footer">
<slot name="footer">
<!--<a class="button button&#45;&#45;active">Speichern</a>-->
<a
class="button"
@click="hideModal"
>Abbrechen</a>
<a class="button" @click="hideModal">Abbrechen</a>
</slot>
</div>
</div>
@ -30,41 +28,41 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/CrossIcon'));
import { defineAsyncComponent } from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/CrossIcon'));
export default {
export default {
props: {
hideHeader: {
type: Boolean,
default: false
default: false,
},
fullscreen: {
type: Boolean,
default: false
default: false,
},
small: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
Cross
Cross,
},
methods: {
hideModal() {
this.$store.dispatch('hideModal');
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.modal {
.modal {
align-self: center;
justify-self: center;
width: 700px;
@ -78,7 +76,7 @@
display: grid;
}
grid-template-rows: auto 1fr 65px;
grid-template-areas: "header" "body" "footer";
grid-template-areas: 'header' 'body' 'footer';
-ms-grid-rows: auto 1fr 65px;
position: relative;
@ -136,7 +134,7 @@
&--hide-header {
grid-template-rows: 1fr 65px;
grid-template-areas: "body" "footer";
grid-template-areas: 'body' 'footer';
#{$parent}__header {
display: none;
@ -145,7 +143,6 @@
#{$parent}__body {
padding: $default-padding;
}
}
&--fullscreen {
@ -153,7 +150,7 @@
height: auto;
grid-template-rows: 1fr;
-ms-grid-rows: 1fr;
grid-template-areas: "body";
grid-template-areas: 'body';
overflow: hidden;
#{$parent}__footer {
@ -185,5 +182,5 @@
min-height: 0;
}
}
}
}
</style>

View File

@ -1,46 +1,38 @@
<template>
<div class="more-options">
<a
class="more-options__more-link"
data-cy="more-options-link"
@click.stop="showMenu = !showMenu"
>
<a class="more-options__more-link" data-cy="more-options-link" @click.stop="showMenu = !showMenu">
<ellipses class="more-options__ellipses" />
</a>
<widget-popover
class="more-options__popover"
v-if="showMenu"
@hide-me="showMenu = false"
>
<widget-popover class="more-options__popover" v-if="showMenu" @hide-me="showMenu = false">
<slot />
</widget-popover>
</div>
</template>
<script>
import WidgetPopover from '@/components/ui/WidgetPopover';
import {defineAsyncComponent} from 'vue';
import WidgetPopover from '@/components/ui/WidgetPopover';
import { defineAsyncComponent } from 'vue';
const Ellipses = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Ellipses.vue'));
const Ellipses = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Ellipses.vue'));
export default {
export default {
components: {
WidgetPopover,
Ellipses
Ellipses,
},
data() {
return {
showMenu: false
};
}
showMenu: false,
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.more-options {
.more-options {
display: flex;
justify-content: flex-end;
@ -64,5 +56,5 @@
min-width: 200px;
@include popover-defaults();
}
}
}
</style>

View File

@ -1,27 +1,23 @@
<template>
<transition name="fade">
<a
class="scroll-up"
v-if="scroll>200"
@click="scrollTop"
>
<a class="scroll-up" v-if="scroll > 200" @click="scrollTop">
<arrow-up class="scroll-up__icon" />
</a>
</transition>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const ArrowUp = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ArrowUp'));
import { defineAsyncComponent } from 'vue';
const ArrowUp = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/ArrowUp'));
export default {
export default {
components: {
ArrowUp
ArrowUp,
},
data() {
return {
scroll: 0
scroll: 0,
};
},
@ -39,16 +35,16 @@
methods: {
scrollTop() {
document.scrollingElement.scrollTop = 0;
}
},
};
},
};
</script>
<style scoped lang="scss">
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.scroll-up {
.scroll-up {
position: fixed;
right: $large-spacing;
bottom: $large-spacing;
@ -65,15 +61,14 @@
height: 50px;
fill: $color-brand;
}
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .3s;
}
.fade-enter-from, .fade-leave-to /* .fade-leave-active below version 2.1.8 */
{
.fade-enter-from, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
}
</style>

View File

@ -1,20 +1,19 @@
<template>
<div class="submission-document">
<p
class="submission-document__content content"
v-if="document && document.length > 0"
>
<p class="submission-document__content content" v-if="document && document.length > 0">
<document-icon class="content__icon" /><span class="content__text">{{ filename }}</span>
</p>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue';
import filenameFromUrl from '@/helpers/urls';
const DocumentIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon'));
import { defineAsyncComponent } from 'vue';
import filenameFromUrl from '@/helpers/urls';
const DocumentIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/DocumentIcon')
);
export default {
export default {
name: 'StudentSubmissionDocument',
props: ['document'],
components: { DocumentIcon },
@ -22,13 +21,13 @@
computed: {
filename() {
return filenameFromUrl(this.document);
}
},
};
},
};
</script>
<style scoped lang="scss">
.content {
.content {
display: flex;
&__icon {
@ -40,5 +39,5 @@
align-self: center;
padding-left: 5px;
}
}
}
</style>

View File

@ -1,48 +1,38 @@
<template>
<div
:class="{'user-widget--is-profile': isProfile}"
class="user-widget"
@click.stop="$emit('click', $event)"
>
<div
class="user-widget__avatar"
data-cy="user-widget-avatar"
>
<avatar
:avatar-url="avatarUrl"
:icon-highlighted="isProfile"
/>
<div :class="{ 'user-widget--is-profile': isProfile }" class="user-widget" @click.stop="$emit('click', $event)">
<div class="user-widget__avatar" data-cy="user-widget-avatar">
<avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile" />
</div>
</div>
</template>
<script>
import Avatar from '@/components/profile/Avatar';
import Avatar from '@/components/profile/Avatar';
export default {
export default {
props: {
avatarUrl: {
type: String
}
type: String,
},
},
emits: ['click'],
components: {
Avatar
emits: ['click'],components: {
Avatar,
},
computed: {
isProfile() {
return this.$route.meta.isProfile;
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.user-widget {
.user-widget {
color: $color-silver-dark;
display: flex;
justify-content: space-between;
@ -78,5 +68,5 @@
color: $color-brand;
}
}
}
}
</style>

View File

@ -1,12 +1,9 @@
<template>
<nav
:class="{'content-navigation--sidebar': isSidebar}"
class="content-navigation"
>
<nav :class="{ 'content-navigation--sidebar': isSidebar }" class="content-navigation">
<div class="content-navigation__primary">
<div class="content-navigation__item">
<router-link
:class="{'content-navigation__link--active': isActive('book')}"
:class="{ 'content-navigation__link--active': isActive('book') }"
:to="topicRoute"
active-class="content-navigation__link--active"
class="content-navigation__link"
@ -15,9 +12,7 @@
{{ $flavor.textTopics }}
</router-link>
<topic-navigation
v-if="isSidebar"
/>
<topic-navigation v-if="isSidebar" />
</div>
<div class="content-navigation__item">
@ -33,7 +28,7 @@
<div class="content-navigation__item">
<router-link
:to="{name: 'news'}"
:to="{ name: 'news' }"
active-class="content-navigation__link--active"
class="content-navigation__link"
data-cy="news-navigation-link"
@ -45,19 +40,14 @@
</div>
</div>
<router-link
to="/"
class="content-navigation__logo"
data-cy="home-link"
v-if="!isSidebar"
>
<router-link to="/" class="content-navigation__logo" data-cy="home-link" v-if="!isSidebar">
<logo class="content-navigation__logo-icon" />
</router-link>
<div class="content-navigation__secondary">
<div class="content-navigation__item content-navigation__item--secondary">
<router-link
:class="{'content-navigation__link--active': isRoomUrl()}"
:class="{ 'content-navigation__link--active': isRoomUrl() }"
to="/rooms"
active-class="content-navigation__link--active"
class="content-navigation__link content-navigation__link--secondary"
@ -67,10 +57,7 @@
</router-link>
</div>
<div
class="content-navigation__item content-navigation__item--secondary"
v-if="showPortfolio"
>
<div class="content-navigation__item content-navigation__item--secondary" v-if="showPortfolio">
<router-link
to="/portfolio"
active-class="content-navigation__link--active"
@ -80,10 +67,7 @@
Portfolio
</router-link>
</div>
<div
class="content-navigation__item content-navigation__item--secondary"
v-if="isSidebar"
>
<div class="content-navigation__item content-navigation__item--secondary" v-if="isSidebar">
<a
:href="$flavor.supportLink"
target="_blank"
@ -97,7 +81,7 @@
</template>
<script>
import TopicNavigation from '@/components/book-navigation/TopicNavigation';
import TopicNavigation from '@/components/book-navigation/TopicNavigation';
import sidebarMixin from '@/mixins/sidebar';
import meMixin from '@/mixins/me';
@ -105,24 +89,24 @@
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
export default {
export default {
props: {
isSidebar: {
default: false
}
default: false,
},
},
mixins: [sidebarMixin, meMixin],
components: {
TopicNavigation,
Logo
Logo,
},
computed: {
showPortfolio() {
return this.$flavor.showPortfolio;
}
},
},
methods: {
@ -134,16 +118,16 @@
},
close() {
this.closeSidebar('navigation');
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.content-navigation {
.content-navigation {
display: flex;
align-items: center;
@ -152,7 +136,8 @@
@include navigation-link;
}
&__primary, &__secondary {
&__primary,
&__secondary {
display: none;
flex-direction: row;
@ -162,7 +147,7 @@
}
&__logo {
color: #17A887;
color: #17a887;
font-size: 36px;
font-weight: 800;
font-family: $sans-serif-font-family;
@ -197,7 +182,8 @@
&--sidebar {
flex-direction: column;
#{$parent}__primary, #{$parent}__secondary {
#{$parent}__primary,
#{$parent}__secondary {
display: flex;
flex-direction: column;
width: 100%;
@ -208,12 +194,11 @@
line-height: 2.5em;
padding: 0;
display: block;
margin-bottom: 0.5*$small-spacing;
margin-bottom: 0.5 * $small-spacing;
&:only-child {
margin-bottom: 0;
}
}
#{$parent}__item {
@ -234,5 +219,5 @@
/*}*/
}
}
}
}
</style>

View File

@ -1,18 +1,8 @@
<template>
<transition name="slide">
<div
class="navigation-sidebar"
v-if="sidebar.navigation"
v-click-outside="close"
>
<content-navigation
:is-sidebar="true"
class="navigation-sidebar__main"
/>
<div
class="navigation-sidebar__close-button"
@click="close"
>
<div class="navigation-sidebar" v-if="sidebar.navigation" v-click-outside="close">
<content-navigation :is-sidebar="true" class="navigation-sidebar__main" />
<div class="navigation-sidebar__close-button" @click="close">
<cross class="navigation-sidebar__close-icon" />
</div>
</div>
@ -20,37 +10,36 @@
</template>
<script>
import ContentNavigation from '@/components/book-navigation/ContentNavigation';
import ContentNavigation from '@/components/book-navigation/ContentNavigation';
import sidebarMixin from '@/mixins/sidebar';
import {defineAsyncComponent} from 'vue';
import sidebarMixin from '@/mixins/sidebar';
import { defineAsyncComponent } from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/CrossIcon'));
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/CrossIcon'));
export default {
export default {
mixins: [sidebarMixin],
components: {
ContentNavigation,
Cross
Cross,
},
methods: {
close() {
this.closeSidebar('navigation');
}
},
};
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
$desktop-width: 285px;
$desktop-width: 285px;
.navigation-sidebar {
.navigation-sidebar {
position: fixed;
left: 0;
right: 0;
@ -68,10 +57,10 @@
grid-template-columns: 1fr 50px;
grid-template-rows: 50px max-content auto 100px;
grid-template-areas: "m m" "m m" "s s" "s s";
grid-template-areas: 'm m' 'm m' 's s' 's s';
&--with-subnavigation {
grid-template-areas: "m m" "m m" "sub sub" "s s";
grid-template-areas: 'm m' 'm m' 'sub sub' 's s';
}
height: 100vh;
@ -96,18 +85,20 @@
justify-self: center;
cursor: pointer;
}
}
}
.slide {
&-enter-active, &-leave-active {
.slide {
&-enter-active,
&-leave-active {
transition: left 0.2s;
}
&-enter-from, &-leave-to {
&-enter-from,
&-leave-to {
left: -100vw;
@include desktop {
left: -$desktop-width;
}
}
}
}
</style>

View File

@ -1,21 +1,11 @@
<template>
<div
:class="{ 'sub-navigation-item--active': show}"
class="sub-navigation-item"
v-click-outside="close"
>
<div
class="sub-navigation-item__title"
@click="show = !show"
>
<div :class="{ 'sub-navigation-item--active': show }" class="sub-navigation-item" v-click-outside="close">
<div class="sub-navigation-item__title" @click="show = !show">
{{ title }}
<chevron-down class="sub-navigation-item__icon sub-navigation-item__chevron-down" />
<chevron-up class="sub-navigation-item__icon sub-navigation-item__chevron-up" />
</div>
<div
class="sub-navigation-item__nav-items book-subnavigation"
v-if="show"
>
<div class="sub-navigation-item__nav-items book-subnavigation" v-if="show">
<slot />
</div>
</div>
@ -26,30 +16,30 @@
const ChevronDown = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronDown'));
const ChevronUp = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronUp'));
export default {
export default {
props: ['title'],
components: {
ChevronDown,
ChevronUp
ChevronUp,
},
data() {
return {
show: false
show: false,
};
},
watch: {
$route() {
this.show = false;
}
},
},
methods: {
close() {
this.show = false;
}
}
};
},
},
};
</script>

View File

@ -1,8 +1,8 @@
<template>
<nav class="topic-navigation">
<router-link
:to="{name: 'topic', params: {topicSlug: topic.slug}}"
:class="{'topic-navigation__topic--active': topic.active, 'book-subnavigation__item--mobile': mobile}"
:to="{ name: 'topic', params: { topicSlug: topic.slug } }"
:class="{ 'topic-navigation__topic--active': topic.active, 'book-subnavigation__item--mobile': mobile }"
tag="div"
active-class="book-subnavigation__item--active"
class="topic-navigation__topic book-subnavigation__item"
@ -17,8 +17,8 @@
</template>
<script>
import ALL_TOPICS_QUERY from '@/graphql/gql/queries/allTopicsQuery.gql';
import sidebarMixin from '@/mixins/sidebar';
import ALL_TOPICS_QUERY from '@/graphql/gql/queries/allTopicsQuery.gql';
import sidebarMixin from '@/mixins/sidebar';
export default {
props: {
@ -26,6 +26,7 @@
default: false,
},
},
},
mixins: [sidebarMixin],
@ -40,26 +41,27 @@
return atob(id);
},
},
},
apollo: {
topics: {
query: ALL_TOPICS_QUERY,
manual: true,
result({data, loading}) {
result({ data, loading }) {
if (!loading) {
this.topics = this.$getRidOfEdges(data).topics;
}
},
},
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
.topic-navigation {
.topic-navigation {
&__topic {
}
}
}
</style>

View File

@ -26,14 +26,10 @@
:class="['content-element__component']"
v-bind="element"
:is="component"
@change-text="changeText"
@link-change-url="changeUrl"
@change-url="changeUrl"
@switch-to-document="switchToDocument"
@assignment-change-title="changeAssignmentTitle"
@assignment-change-assignment="changeAssignmentAssignment"
/>
@ -67,9 +63,9 @@
const ThinglinkBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-forms" */'@/components/content-blocks/ThinglinkBlock'));
const InfogramBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-forms" */'@/components/content-blocks/InfogramBlock'));
const CHOOSER = 'content-block-element-chooser-widget';
const CHOOSER = 'content-block-element-chooser-widget';
export default {
export default {
props: {
element: {
type: Object,
@ -110,7 +106,7 @@
CmsDocumentBlock,
InfogramBlock,
ThinglinkBlock,
Assignment
Assignment,
},
computed: {
@ -216,12 +212,12 @@
case 'thinglink_block':
return {
component: 'thinglink-block',
title: 'Interaktive Grafik'
title: 'Interaktive Grafik',
};
case 'infogram_block':
return {
component: 'infogram-block',
title: 'Interaktive Grafik'
title: 'Interaktive Grafik',
};
}
return {
@ -253,7 +249,7 @@
changeAssignmentAssignment(value) {
this._updateProperty(value, 'assignment');
},
changeType({type, convertToList}, value) {
changeType({ type, convertToList }, value) {
let el = {
type: type,
value: Object.assign({}, value),
@ -295,9 +291,12 @@
case 'document_block':
el = {
...el,
value: Object.assign({
value: Object.assign(
{
url: '',
}, value),
},
value
),
};
break;
case 'image_url_block':
@ -325,13 +324,13 @@
this.changeType('document_block', value);
},
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.content-element {
.content-element {
display: flex;
flex-direction: column;
@ -351,5 +350,5 @@
&__chooser {
grid-column: 1 / span 2;
}
}
}
</style>

View File

@ -1,13 +1,8 @@
<template>
<div class="content-form-section">
<h2 class="content-form-section__heading">
<component
class="content-form-section__icon"
:is="icon"
/> <span
class="content-form-section__title"
data-cy="content-form-section-title"
>{{ title }}</span>
<component class="content-form-section__icon" :is="icon" />
<span class="content-form-section__title" data-cy="content-form-section-title">{{ title }}</span>
</h2>
<content-element-actions
@ -55,9 +50,9 @@
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.content-form-section {
.content-form-section {
@include default-box-shadow;
border-radius: $default-border-radius;
padding: $small-spacing $medium-spacing;
@ -96,5 +91,5 @@
&__content {
grid-area: c;
}
}
}
</style>

View File

@ -18,41 +18,23 @@
/>
</template>
<add-content-element
:index="-1"
class="contents-form__add"
@add-element="addElement"
/>
<div
class="contents-form__element"
v-for="(element, index) in localContentBlock.contents"
:key="index"
>
<content-element
:element="element"
@update="update(index, $event)"
@remove="remove(index)"
/>
<add-content-element :index="-1" class="contents-form__add" @add-element="addElement" />
<div class="contents-form__element" v-for="(element, index) in localContentBlock.contents" :key="index">
<content-element :element="element" @update="update(index, $event)" @remove="remove(index)" />
<add-content-element
:index="index"
class="contents-form__add"
@add-element="addElement"
/>
<add-content-element :index="index" class="contents-form__add" @add-element="addElement" />
</div>
<template #footer>
<div>
<a
:class="{'button--disabled': disableSave}"
:class="{ 'button--disabled': disableSave }"
class="button button--primary"
data-cy="modal-save-button"
@click="save"
>Speichern</a>
<a
class="button"
@click="$emit('hide')"
>Abbrechen</a>
>Speichern</a
>
<a class="button" @click="$emit('hide')">Abbrechen</a>
</div>
</template>
</modal>
@ -69,7 +51,7 @@
const Modal = defineAsyncComponent(() => import('@/components/Modal'));
const Checkbox = defineAsyncComponent(() => import('@/components/ui/Checkbox'));
export default {
export default {
props: {
contentBlock: Object,
blockType: {
@ -97,12 +79,15 @@
data() {
return {
error: false,
localContentBlock: Object.assign({}, {
localContentBlock: Object.assign(
{},
{
title: this.contentBlock.title,
contents: [...this.contentBlock.contents],
id: this.contentBlock.id || undefined,
isAssignment: this.contentBlock.type && this.contentBlock.type.toLowerCase() === 'task',
}),
}
),
me: {},
};
},
@ -148,19 +133,17 @@
remove(index) {
this.localContentBlock.contents.splice(index, 1);
},
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.contents-form {
.contents-form {
/* top level does not exist, because of the modal */
&__element {
}
&__element-component {
@ -180,5 +163,5 @@
&__task {
margin: 15px 0 10px;
}
}
}
</style>

View File

@ -1,14 +1,9 @@
<template>
<contents-form
:content-block="contentBlock"
:show-task-selection="true"
@save="saveContentBlock"
@hide="hideModal"
/>
<contents-form :content-block="contentBlock" :show-task-selection="true" @save="saveContentBlock" @hide="hideModal" />
</template>
<script>
import ContentsForm from '@/components/content-block-form/ContentsForm';
import ContentsForm from '@/components/content-block-form/ContentsForm';
import {store} from '@/store';
@ -38,7 +33,8 @@
this.$store.dispatch('hideModal');
},
saveContentBlock(contentBlock) {
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: EDIT_CONTENT_BLOCK_MUTATION,
variables: {
input: {
@ -61,6 +57,7 @@
});
},
},
},
apollo: {
contentBlock() {

View File

@ -1,9 +1,5 @@
<template>
<div
:class="componentClass"
:data-scrollto="component.id"
data-cy="content-component"
>
<div :class="componentClass" :data-scrollto="component.id" data-cy="content-component">
<bookmark-actions
:bookmarked="bookmarked"
:note="note"
@ -13,11 +9,7 @@
@edit-note="editNote"
@bookmark="bookmarkContent(component.id, !bookmarked)"
/>
<component
v-bind="component"
:parent="parent"
:is="component.type"
/>
<component v-bind="component" :parent="parent" :is="component.type" />
</div>
</template>
@ -50,60 +42,60 @@ export default {
props: {
component: {
type: Object,
default: () => ({})
default: () => ({}),
},
parent: {
type: Object,
default: () => ({})
default: () => ({}),
},
bookmarks: {
type: Array,
default: () => ([])
default: () => [],
},
notes: {
type: Array,
default: () => ([])
default: () => [],
},
root: {
type: String,
default: ''
default: '',
},
editMode: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
'text_block': TextBlock,
'basic_knowledge': InstrumentWidget, // for legacy
'instrument': InstrumentWidget,
'image_block': ImageBlock,
'image_url_block': ImageUrlBlock,
'video_block': VideoBlock,
'link_block': LinkBlock,
'document_block': DocumentBlock,
'infogram_block': InfogramBlock,
'genially_block': GeniallyBlock,
'subtitle': SubtitleBlock,
'section_title': SectionTitleBlock,
'content_list': ContentListBlock,
'module_room_slug': ModuleRoomSlug,
'thinglink_block': ThinglinkBlock,
'cms_document_block': CmsDocumentBlock,
text_block: TextBlock,
basic_knowledge: InstrumentWidget, // for legacy
instrument: InstrumentWidget,
image_block: ImageBlock,
image_url_block: ImageUrlBlock,
video_block: VideoBlock,
link_block: LinkBlock,
document_block: DocumentBlock,
infogram_block: InfogramBlock,
genially_block: GeniallyBlock,
subtitle: SubtitleBlock,
section_title: SectionTitleBlock,
content_list: ContentListBlock,
module_room_slug: ModuleRoomSlug,
thinglink_block: ThinglinkBlock,
cms_document_block: CmsDocumentBlock,
Survey,
Solution,
Instruction,
Assignment,
BookmarkActions
BookmarkActions,
},
computed: {
bookmarked() {
return this.bookmarks && !!this.bookmarks.find(bookmark => bookmark.uuid === this.component.id);
return this.bookmarks && !!this.bookmarks.find((bookmark) => bookmark.uuid === this.component.id);
},
note() {
const bookmark = this.bookmarks && this.bookmarks.find(bookmark => bookmark.uuid === this.component.id);
const bookmark = this.bookmarks && this.bookmarks.find((bookmark) => bookmark.uuid === this.component.id);
return bookmark && bookmark.note;
},
showBookmarkActions() {
@ -115,18 +107,19 @@ export default {
classes.push('content-component--bookmarked');
}
return classes;
}
},
},
methods: {
addNote(id) {
const type = Object.prototype.hasOwnProperty.call(this.parent, '__typename')
? this.parent.__typename : 'ContentBlockNode';
? this.parent.__typename
: 'ContentBlockNode';
this.$store.dispatch('addNote', {
content: id,
type,
block: this.root
block: this.root,
});
},
editNote() {
@ -134,19 +127,18 @@ export default {
},
bookmarkContent(uuid, bookmarked) {
this.$apollo.mutate(constructContentComponentBookmarkMutation(uuid, bookmarked, this.parent, this.root));
}
}
},
},
};
</script>
<style lang="scss" scoped>
@import "~styles/helpers";
@import '~styles/helpers';
.content-component {
.content-component {
position: relative;
&--bookmarked {
}
&--subtitle {
@ -166,5 +158,5 @@ export default {
&--document_block {
margin-bottom: $large-spacing;
}
}
}
</style>

View File

@ -1,12 +1,7 @@
<template>
<content-list
:items="contentBlocks"
>
<content-list :items="contentBlocks">
<template #default="{ item }">
<content-block
:content-block="item"
:parent="parent"
/>
<content-block :content-block="item" :parent="parent" />
</template>
</content-list>
</template>

View File

@ -1,16 +1,8 @@
<template>
<div class="document-block">
<document-icon class="document-block__icon" />
<a
:href="value.url"
class="document-block__link"
target="_blank"
>{{ urlName }}</a>
<a
class="document-block__remove"
v-if="showTrashIcon"
@click="$emit('trash')"
>
<a :href="value.url" class="document-block__link" target="_blank">{{ urlName }}</a>
<a class="document-block__remove" v-if="showTrashIcon" @click="$emit('trash')">
<trash-icon class="document-block__trash-icon" />
</a>
</div>
@ -21,7 +13,7 @@
const DocumentIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon'));
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon'));
export default {
export default {
props: {
value: Object,
showTrashIcon: Boolean,
@ -33,21 +25,21 @@
},
computed: {
urlName: function() {
urlName: function () {
if (this.value && this.value.url) {
const parts = this.value.url.split('/');
return parts[parts.length - 1];
}
return null;
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.document-block {
.document-block {
display: grid;
grid-template-columns: 50px 1fr 50px;
align-items: center;
@ -76,5 +68,5 @@
cursor: pointer;
justify-self: center;
}
}
}
</style>

View File

@ -1,28 +1,22 @@
<template>
<div
class="instruction"
v-if="me.isTeacher"
>
<div class="instruction" v-if="me.isTeacher">
<bulb-icon class="instruction__icon" />
<a
:href="url"
class="instruction__link"
>{{ text }}</a>
<a :href="url" class="instruction__link">{{ text }}</a>
</div>
</template>
<script>
import me from '@/mixins/me';
import {defineAsyncComponent} from 'vue';
const BulbIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/BulbIcon'));
import me from '@/mixins/me';
import { defineAsyncComponent } from 'vue';
const BulbIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/BulbIcon'));
export default {
export default {
props: ['value'],
mixins: [me],
components: {
BulbIcon
BulbIcon,
},
computed: {
@ -31,15 +25,15 @@
},
url() {
return this.value.document ? this.value.document.url : this.value.url;
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_mixins.scss";
@import '@/styles/_mixins.scss';
.instruction {
.instruction {
margin-bottom: 1rem;
display: flex;
align-items: center;
@ -53,5 +47,5 @@
&__link {
@include heading-3;
}
}
}
</style>

View File

@ -1,44 +1,37 @@
<template>
<div
:class="{ 'link-block--no-margin': noMargin}"
class="link-block"
>
<div :class="{ 'link-block--no-margin': noMargin }" class="link-block">
<link-icon class="link-block__icon" />
<a
:href="href"
class="link-block__link"
target="_blank"
>{{ value.text }}</a>
<a :href="href" class="link-block__link" target="_blank">{{ value.text }}</a>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const LinkIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/LinkIcon'));
import { defineAsyncComponent } from 'vue';
const LinkIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/LinkIcon'));
export default {
export default {
props: {
value: Object,
noMargin: {
default: false
}
default: false,
},
},
components: {
LinkIcon
LinkIcon,
},
computed: {
href() {
const url = this.value.url;
return url.startsWith('http') ? this.value.url : `http://${this.value.url}`;
}
}
};
},
},
};
</script>
<style scoped lang="scss">
.link-block {
.link-block {
margin-bottom: 30px;
display: grid;
grid-template-columns: 50px 1fr;
@ -56,5 +49,5 @@
&__link {
text-decoration: underline;
}
}
}
</style>

View File

@ -1,47 +1,39 @@
<template>
<div
class="final-submission"
data-cy="final-submission"
>
<document-block
:value="{url: userInput.document}"
class="final-submission__document"
v-if="userInput.document"
/>
<div class="final-submission" data-cy="final-submission">
<document-block :value="{ url: userInput.document }" class="final-submission__document" v-if="userInput.document" />
<div class="final-submission__explanation">
<info-icon class="final-submission__explanation-icon" />
<span class="final-submission__explanation-text">{{ sharedMsg }}</span>
<a
class="final-submission__reopen"
data-cy="final-submission-reopen"
v-if="showReopen"
@click="$emit('reopen')"
>Bearbeiten</a>
<a class="final-submission__reopen" data-cy="final-submission-reopen" v-if="showReopen" @click="$emit('reopen')"
>Bearbeiten</a
>
</div>
</div>
</template>
<script>
import {newLineToParagraph} from '@/helpers/text';
import {defineAsyncComponent} from 'vue';
const DocumentBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock'));
import { newLineToParagraph } from '@/helpers/text';
import { defineAsyncComponent } from 'vue';
const DocumentBlock = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-components" */ '@/components/content-blocks/DocumentBlock')
);
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon'));
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/InfoIcon'));
export default {
export default {
props: {
userInput: {
type: Object,
default: () => ({})
default: () => ({}),
},
showReopen: {
type: Boolean,
default: true
default: true,
},
sharedMsg: {
type: String,
default: ''
}
default: '',
},
},
components: {
@ -52,15 +44,15 @@
computed: {
text() {
return newLineToParagraph(this.userInput.text);
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.final-submission {
.final-submission {
&__text {
background-color: $color-white;
@include input-box-shadow;
@ -105,5 +97,5 @@
cursor: pointer;
color: $color-charcoal-light;
}
}
}
</style>

View File

@ -10,10 +10,7 @@
/>
</div>
<div
class="submission-form-container__actions"
v-if="!isFinalOrReadOnly"
>
<div class="submission-form-container__actions" v-if="!isFinalOrReadOnly">
<button
class="submission-form-container__submit button button--primary button--white-bg"
data-cy="submission-form-submit"
@ -29,11 +26,7 @@
>
{{ spellcheckText }}
</button>
<file-upload
:document="userInput.document"
v-if="allowsDocuments"
@change-document-url="changeDocumentUrl"
/>
<file-upload :document="userInput.document" v-if="allowsDocuments" @change-document-url="changeDocumentUrl" />
<slot />
</div>
@ -48,13 +41,12 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const SubmissionInput = defineAsyncComponent(() => import('@/components/content-blocks/assignment/SubmissionInput'));
const FinalSubmission = defineAsyncComponent(() => import('@/components/content-blocks/assignment/FinalSubmission'));
const FileUpload = defineAsyncComponent(() => import('@/components/ui/file-upload/FileUpload'));
import { defineAsyncComponent } from 'vue';
const SubmissionInput = defineAsyncComponent(() => import('@/components/content-blocks/assignment/SubmissionInput'));
const FinalSubmission = defineAsyncComponent(() => import('@/components/content-blocks/assignment/FinalSubmission'));
const FileUpload = defineAsyncComponent(() => import('@/components/ui/file-upload/FileUpload'));
export default {
export default {
props: {
userInput: Object,
saved: Boolean,
@ -116,14 +108,13 @@
this.$emit('changeDocumentUrl', documentUrl);
},
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.submission-form-container {
.submission-form-container {
@include form-with-border;
margin-bottom: $medium-spacing;
@ -158,6 +149,5 @@
text-align: center;
display: inline-block;
}
}
}
</style>

View File

@ -4,54 +4,52 @@
:placeholder="placeholder"
:readonly="readonly"
:value="inputText"
:class="{'submission-form__textarea--readonly': readonly}"
:class="{ 'submission-form__textarea--readonly': readonly }"
data-cy="submission-textarea"
rows="1"
class="submission-form__textarea"
v-auto-grow
@input="$emit('input', $event.target.value)"
/>
<div
class="submission-form__save-status submission-form__save-status--saved"
v-if="saved"
>
<div class="submission-form__save-status submission-form__save-status--saved" v-if="saved">
<tick-circle-icon class="submission-form__save-status-icon" />
</div>
<div
class="submission-form__save-status submission-form__save-status--unsaved"
v-if="!saved"
>
<div class="submission-form__save-status submission-form__save-status--unsaved" v-if="!saved">
<loading-icon class="submission-form__save-status-icon submission-form__saving-icon" />
</div>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const TickCircleIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/TickCircleIcon'));
const LoadingIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/LoadingIcon'));
import { defineAsyncComponent } from 'vue';
const TickCircleIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/TickCircleIcon')
);
const LoadingIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/LoadingIcon')
);
export default {
export default {
props: {
inputText: String,
saved: Boolean,
readonly: Boolean,
placeholder: {
type: String,
default: 'Ergebnis erfassen'
}
default: 'Ergebnis erfassen',
},
},
components: {
TickCircleIcon,
LoadingIcon
}
};
LoadingIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.submission-form {
.submission-form {
display: flex;
flex-direction: row;
justify-content: space-between;
@ -74,5 +72,5 @@
&__saving-icon {
@include spin;
}
}
}
</style>

View File

@ -5,7 +5,7 @@
class="assignment-form__title skillbox-input"
placeholder="Aufgabentitel"
@input="$emit('assignment-change-title', $event.target.value, index)"
>
/>
<textarea
:value="value.assignment"
class="assignment-form__exercise-text skillbox-textarea"
@ -23,18 +23,18 @@
import {defineAsyncComponent} from 'vue';
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon'));
export default {
export default {
props: ['value', 'index'],
components: {
InfoIcon
}
};
InfoIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.assignment-form {
.assignment-form {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 13px;
@ -59,5 +59,5 @@
&__help-description {
}
}
}
</style>

View File

@ -1,28 +1,12 @@
<template>
<div
class="document-form"
ref="documentform"
>
<div
v-if="!value.url"
ref="uploadcare-panel"
/>
<div
class="document-form__spinner"
v-if="loading"
>
<div class="document-form" ref="documentform">
<div v-if="!value.url" ref="uploadcare-panel" />
<div class="document-form__spinner" v-if="loading">
<loading-icon class="document-form__loading-icon" />
</div>
<div
class="document-form__uploaded"
v-if="value.url"
>
<div class="document-form__uploaded" v-if="value.url">
<document-icon class="document-form__icon" />
<a
:href="previewUrl"
class="document-form__link"
target="_blank"
>{{ previewLink }}</a>
<a :href="previewUrl" class="document-form__link" target="_blank">{{ previewLink }}</a>
</div>
</div>
</template>
@ -34,7 +18,7 @@
const DocumentIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon'));
export default {
export default {
props: ['value', 'index'],
components: {
@ -65,21 +49,24 @@
},
mounted() {
uploadcare(this, url => {
uploadcare(
this,
(url) => {
this.$emit('change-url', url, this.index);
this.loading = false;
}, () => {
this.loading = true;
});
},
};
() => {
this.loading = true;
}
);
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.document-form {
.document-form {
&__uploaded {
display: flex;
align-items: center;
@ -129,5 +116,5 @@
text-decoration: underline;
}
}
}
}
</style>

View File

@ -1,21 +1,11 @@
<template>
<div>
<div
class="video-form"
v-if="!isVimeo && !isYoutube && !isSrf"
>
<div class="video-form" v-if="!isVimeo && !isYoutube && !isSrf">
<info-icon class="video-form__help-icon help-text__icon" />
<p class="video-form__help-description help-text__description">
Sie können Videos auf <a
class="video-form__platform-link help-text__link"
href="https://youtube.com/"
target="_blank"
>Youtube</a>
oder <a
class="video-form__platform-link help-text__link"
href="https://vimeo.com/"
target="_blank"
>Vimeo</a>
Sie können Videos auf
<a class="video-form__platform-link help-text__link" href="https://youtube.com/" target="_blank">Youtube</a>
oder <a class="video-form__platform-link help-text__link" href="https://vimeo.com/" target="_blank">Vimeo</a>
hochladen und anschliessen einen Link hier einfügen.
</p>
@ -24,7 +14,7 @@
class="video-form__video-link skillbox-input"
placeholder="Bsp: https://www.youtube.com/watch?v=dQw4w9WgXcQ"
@input="$emit('change-url', $event.target.value, index)"
>
/>
</div>
<div v-if="isYoutube">
@ -40,21 +30,21 @@
</template>
<script>
import YoutubeEmbed from '@/components/videos/YoutubeEmbed';
import VimeoEmbed from '@/components/videos/VimeoEmbed';
import SrfEmbed from '@/components/videos/SrfEmbed';
import {isVimeoUrl, isYoutubeUrl, isSrfUrl} from '@/helpers/video';
import {defineAsyncComponent} from 'vue';
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon'));
import YoutubeEmbed from '@/components/videos/YoutubeEmbed';
import VimeoEmbed from '@/components/videos/VimeoEmbed';
import SrfEmbed from '@/components/videos/SrfEmbed';
import { isVimeoUrl, isYoutubeUrl, isSrfUrl } from '@/helpers/video';
import { defineAsyncComponent } from 'vue';
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/InfoIcon'));
export default {
export default {
props: ['value', 'index'],
components: {
InfoIcon,
YoutubeEmbed,
VimeoEmbed,
SrfEmbed
SrfEmbed,
},
computed: {
@ -66,16 +56,16 @@
},
isSrf() {
return isSrfUrl(this.value.url);
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_functions.scss';
.video-form {
.video-form {
display: grid;
grid-auto-rows: auto;
grid-template-columns: 40px 1fr;
@ -84,11 +74,9 @@
align-items: center;
&__help-icon {
}
&__help-description {
}
&__platform-link {
@ -100,7 +88,7 @@
&__video-link {
grid-column: 1 / span 2;
width: $modal-input-width
}
width: $modal-input-width;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 378 505">
<svg xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 378 505">
<defs>
<clipPath id="b">
<path
@ -284,14 +284,9 @@
/>
<path
d="M339.92,387.48c0-.82-.33-1.61-.91-2.19-.58-.58-1.37-.91-2.19-.91H23.71c-.82,0-1.61,.33-2.19,.91-.58,.58-.91,1.37-.91,2.19h0c0,.82,.33,1.61,.91,2.19,.58,.58,1.37,.91,2.19,.91H336.83c.82,0,1.61-.33,2.19-.91,.58-.58,.91-1.37,.91-2.19h0Z"
fill="#fff"
fill-rule="evenodd"
/>
fill="#fff" fill-rule="evenodd" />
<polygon
points="291.56 386.68 286.32 386.68 286.32 505.02 291.56 505.02 291.56 386.68 291.56 386.68"
fill="#fff"
fill-rule="evenodd"
/>
points="291.56 386.68 286.32 386.68 286.32 505.02 291.56 505.02 291.56 386.68 291.56 386.68" fill="#fff" fill-rule="evenodd" />
<polygon
points="72.67 387.59 67.43 387.59 67.43 505.02 72.67 505.02 72.67 387.59 72.67 387.59"
fill="#fff"

View File

@ -1,31 +1,21 @@
<template>
<a
:class="typeClass"
class="filter-entry"
data-cy="filter-entry"
:style="categoryStyle"
@click="$emit('filter')"
<a :class="typeClass" class="filter-entry" data-cy="filter-entry" :style="categoryStyle" @click="$emit('filter')"
>
<span class="filter-entry__text">{{ text }}</span>
<span
:style="activeStyle"
class="filter-entry__icon-wrapper"
>
<chevron-right
:style="{fill: category.foreground}"
class="filter-entry__icon"
/>
<span :style="activeStyle" class="filter-entry__icon-wrapper">
<chevron-right :style="{ fill: category.foreground }" class="filter-entry__icon" />
</span>
</a>
</template>
<script>
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFilter.gql';
import {defineAsyncComponent} from 'vue';
const ChevronRight = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronRight'));
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFilter.gql';
import { defineAsyncComponent } from 'vue';
const ChevronRight = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/ChevronRight')
);
export default {
export default {
props: {
text: {
type: String,
@ -100,13 +90,13 @@
};
},
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.filter-entry {
.filter-entry {
display: flex;
justify-content: space-between;
align-items: center;
@ -175,5 +165,5 @@
fill: white;
}
}
}
}
</style>

View File

@ -24,13 +24,13 @@
</template>
<script>
import {defineComponent} from 'vue';
import FilterEntry from '@/components/instruments/FilterEntry';
import { defineComponent } from 'vue';
import FilterEntry from '@/components/instruments/FilterEntry';
import SET_FILTER_MUTATION from 'gql/local/mutations/setInstrumentFilter.gql';
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFilter.gql';
import SET_FILTER_MUTATION from 'gql/local/mutations/setInstrumentFilter.gql';
import INSTRUMENT_FILTER_QUERY from 'gql/local/instrumentFilter.gql';
export default defineComponent({
export default defineComponent({
props: {
title: {
type: String,
@ -51,15 +51,15 @@
apollo: {
instrumentFilter: {
query: INSTRUMENT_FILTER_QUERY
}
query: INSTRUMENT_FILTER_QUERY,
},
},
data() {
return {
instrumentFilter: {
currentFilter: ''
}
currentFilter: '',
},
};
},
inheritAttrs: false,
@ -76,18 +76,17 @@
this.$apollo.mutate({
mutation: SET_FILTER_MUTATION,
variables: {
filter
}
filter,
},
});
}
},
});
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.filter-group {
.filter-group {
border-bottom: 1px solid $color-silver;
padding: $medium-spacing 0;
display: flex;
@ -96,5 +95,5 @@
&__children {
padding-left: $medium-spacing;
}
}
}
</style>

View File

@ -1,12 +1,6 @@
<template>
<router-link
:to="moduleLink"
:class="['module-teaser', {'module-teaser--small': !teaser}]"
>
<div
:style="{backgroundImage: 'url('+heroImage+')'}"
class="module-teaser__image"
/>
<router-link :to="moduleLink" :class="['module-teaser', { 'module-teaser--small': !teaser }]">
<div :style="{ backgroundImage: 'url(' + heroImage + ')' }" class="module-teaser__image" />
<div class="module-teaser__body">
<h3 class="module-teaser__meta-title">
{{ metaTitle }}
@ -22,7 +16,7 @@
</template>
<script>
export default {
export default {
props: ['metaTitle', 'title', 'teaser', 'id', 'slug', 'heroImage'],
computed: {
@ -38,16 +32,17 @@
return {};
}
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
.module-teaser {
.module-teaser {
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
border: 1px solid #E2E2E2;
border: 1px solid #e2e2e2;
height: 330px;
max-width: 380px;
width: 100%;
@ -88,5 +83,5 @@
line-height: $default-line-height;
font-size: 1.2rem;
}
}
}
</style>

View File

@ -1,10 +1,7 @@
<template>
<div
class="bookmark-actions"
v-if="!editMode"
>
<div class="bookmark-actions" v-if="!editMode">
<a
:class="{'bookmark-actions__action--bookmarked': bookmarked}"
:class="{ 'bookmark-actions__action--bookmarked': bookmarked }"
class="bookmark-actions__action bookmark-actions__bookmark"
data-cy="bookmark-action"
@click="$emit('bookmark')"
@ -37,39 +34,39 @@
const AddNoteIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddNoteIcon'));
const NoteIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/NoteIcon'));
export default {
export default {
props: {
bookmarked: {
type: Boolean,
default: false
default: false,
},
note: {
type: [Object, Boolean],
default: false
default: false,
},
editMode: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
BookmarkIcon,
AddNoteIcon,
NoteIcon
NoteIcon,
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.bookmark-actions {
.bookmark-actions {
height: 100%;
min-height: 60px;
padding: 0 2*$large-spacing;
padding: 0 2 * $large-spacing;
position: absolute;
right: -5*$large-spacing;
right: -5 * $large-spacing;
display: none;
@ -88,7 +85,8 @@
display: flex;
justify-content: center;
&--bookmarked, &--noted {
&--bookmarked,
&--noted {
opacity: 1;
}
}
@ -100,6 +98,5 @@
opacity: 1;
}
}
}
}
</style>

View File

@ -6,34 +6,31 @@
placeholder="Lernziel erfassen..."
@input="$emit('input', $event)"
/>
<a
class="icon-button"
@click="$emit('delete')"
>
<a class="icon-button" @click="$emit('delete')">
<trash-icon class="icon-button__icon icon-button__icon--subtle" />
</a>
</div>
</template>
<script>
import ModalInput from '@/components/ModalInput';
import {defineAsyncComponent} from 'vue';
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon'));
import ModalInput from '@/components/ModalInput';
import { defineAsyncComponent } from 'vue';
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/TrashIcon'));
export default {
export default {
props: ['objective'],
components: {
ModalInput,
TrashIcon
}
};
TrashIcon,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.objective-form {
.objective-form {
display: grid;
grid-template-columns: 1fr 50px;
margin-bottom: 10px;
@ -41,5 +38,5 @@
&__input {
width: $modal-input-width;
}
}
}
</style>

View File

@ -1,8 +1,5 @@
<template>
<a
class="add-project-entry"
@click="addProjectEntry"
>
<a class="add-project-entry" @click="addProjectEntry">
<plus-icon class="add-project-entry__icon" />
<span class="add-project-entry__text">Beitrag erfassen</span>
</a>
@ -18,15 +15,15 @@
methods: {
addProjectEntry() {
this.$store.dispatch('addProjectEntry', this.project);
}
}
};
},
},
};
</script>
<style lang="scss" scoped>
@import "~styles/helpers";
@import '~styles/helpers';
.add-project-entry {
.add-project-entry {
display: flex;
align-items: center;
justify-content: center;
@ -45,5 +42,5 @@
@include navigation-link;
color: $color-brand;
}
}
}
</style>

View File

@ -1,25 +1,9 @@
<template>
<div class="portfolio-onboarding">
<h1
class="portfolio-onboarding__heading"
data-cy="page-title"
>
Portfolio
</h1>
<portfolio-illustration
data-cy="portfolio-onboarding-illustration"
class="portfolio-onboarding__illustration"
/>
<h2
class="portfolio-onboarding__subheading"
data-cy="portfolio-onboarding-subtitle"
>
Woran denken Sie gerade?
</h2>
<p
class="portfolio-onboarding__text"
data-cy="portfolio-onboarding-text"
>
<h1 class="portfolio-onboarding__heading" data-cy="page-title">Portfolio</h1>
<portfolio-illustration data-cy="portfolio-onboarding-illustration" class="portfolio-onboarding__illustration" />
<h2 class="portfolio-onboarding__subheading" data-cy="portfolio-onboarding-subtitle">Woran denken Sie gerade?</h2>
<p class="portfolio-onboarding__text" data-cy="portfolio-onboarding-text">
Hier können Sie Projekte erstellen, um Ihre Gedanken festzuhalten oder Ihre Arbeit zu dokumentieren.
</p>
@ -37,9 +21,9 @@
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.portfolio-onboarding {
.portfolio-onboarding {
@include onboarding-page;
&__heading {
@ -57,5 +41,5 @@
&__text {
@include onboarding-text;
}
}
}
</style>

View File

@ -1,48 +1,20 @@
<template>
<div
class="project-actions"
data-cy="project-actions"
>
<a
class="project-actions__more-link"
@click.stop="toggleMenu"
>
<div class="project-actions" data-cy="project-actions">
<a class="project-actions__more-link" @click.stop="toggleMenu">
<ellipses />
</a>
<widget-popover
class="project-actions__popover"
v-if="showMenu"
@hide-me="showMenu = false"
>
<widget-popover class="project-actions__popover" v-if="showMenu" @hide-me="showMenu = false">
<li class="popover-links__link">
<a
data-cy="delete-project"
@click="deleteProject(slug)"
>Projekt löschen</a>
<a data-cy="delete-project" @click="deleteProject(slug)">Projekt löschen</a>
</li>
<li class="popover-links__link">
<a
data-cy="edit-project"
@click="editProject(slug)"
>Projekt bearbeiten</a>
<a data-cy="edit-project" @click="editProject(slug)">Projekt bearbeiten</a>
</li>
<li
class="popover-links__link"
v-if="!final && shareButtons"
>
<a
data-cy="share-project"
@click="updateProjectShareState(slug, true)"
>Projekt teilen</a>
<li class="popover-links__link" v-if="!final && shareButtons">
<a data-cy="share-project" @click="updateProjectShareState(slug, true)">Projekt teilen</a>
</li>
<li
class="popover-links__link"
v-if="final && shareButtons"
>
<a
data-cy="unshare-project"
@click="updateProjectShareState(slug, false)"
>Projekt nicht mehr teilen</a>
<li class="popover-links__link" v-if="final && shareButtons">
<a data-cy="unshare-project" @click="updateProjectShareState(slug, false)">Projekt nicht mehr teilen</a>
</li>
</widget-popover>
</div>
@ -55,9 +27,9 @@ import DELETE_PROJECT_MUTATION from '@/graphql/gql/mutations/deleteProject.gql';
import PROJECTS_QUERY from '@/graphql/gql/queries/allProjects.gql';
import updateProjectShareState from '@/mixins/update-project-share-state';
import {removeAtIndex} from '@/graphql/immutable-operations.ts';
import {defineAsyncComponent} from 'vue';
const Ellipses = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Ellipses.vue'));
import { removeAtIndex } from '@/graphql/immutable-operations.ts';
import { defineAsyncComponent } from 'vue';
const Ellipses = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Ellipses.vue'));
export default {
props: {
@ -93,33 +65,41 @@ export default {
this.showMenu = !this.showMenu;
},
editProject(slug) {
this.$router.push({name: 'edit-project', params: {slug}});
this.$router.push({ name: 'edit-project', params: { slug } });
},
deleteProject(slug) {
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: DELETE_PROJECT_MUTATION,
variables: {
input: {
slug,
},
},
update(store, {data: {deleteProject: {success}}}) {
update(
store,
{
data: {
deleteProject: { success },
},
}
) {
if (success) {
const {projects: prevProjects} = store.readQuery({query: PROJECTS_QUERY});
const { projects: prevProjects } = store.readQuery({ query: PROJECTS_QUERY });
if (prevProjects) {
let index = prevProjects.findIndex(project => project.slug === slug);
let index = prevProjects.findIndex((project) => project.slug === slug);
const projects = removeAtIndex(prevProjects, index);
const data = {
projects
projects,
};
store.writeQuery({query: PROJECTS_QUERY, data});
store.writeQuery({ query: PROJECTS_QUERY, data });
}
}
},
}).then(() => {
})
.then(() => {
this.$router.push('/portfolio');
});
},
@ -128,7 +108,7 @@ export default {
</script>
<style scoped lang="scss">
@import "~styles/_helpers.scss";
@import '~styles/_helpers.scss';
.project-actions {
position: relative;

View File

@ -1,44 +1,22 @@
<template>
<div
class="project-entry"
data-cy="project-entry"
>
<more-options-widget
class="project-entry__more"
data-cy="project-entry-more"
v-if="!readOnly"
>
<div class="project-entry" data-cy="project-entry">
<more-options-widget class="project-entry__more" data-cy="project-entry-more" v-if="!readOnly">
<li class="popover-links__link">
<a
data-cy="edit-project-entry"
@click="editProjectEntry()"
>Eintrag bearbeiten</a>
<a data-cy="edit-project-entry" @click="editProjectEntry()">Eintrag bearbeiten</a>
</li>
<li class="popover-links__link">
<a
data-cy="delete-project-entry"
@click="deleteProjectEntry()"
>Eintrag löschen</a>
<a data-cy="delete-project-entry" @click="deleteProjectEntry()">Eintrag löschen</a>
</li>
</more-options-widget>
<h3
class="project-entry__heading"
data-cy="project-entry-date"
>
<h3 class="project-entry__heading" data-cy="project-entry-date">
{{ createdDateTime }}
</h3>
<p
class="project-entry__paragraph"
data-cy="project-entry-activity"
>
<p class="project-entry__paragraph" data-cy="project-entry-activity">
{{ description }}
</p>
<p
class="project-entry__paragraph"
v-if="documentUrl"
>
<document-block :value="{url: documentUrl}" />
<p class="project-entry__paragraph" v-if="documentUrl">
<document-block :value="{ url: documentUrl }" />
</p>
<div class="project-entry__date">
{{ createdDate }}
@ -47,7 +25,7 @@
</template>
<script>
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import DELETE_PROJECT_ENTRY_MUTATION from '@/graphql/gql/mutations/deleteProjectEntry.gql';
import PROJECT_QUERY from '@/graphql/gql/queries/projectQuery.gql';
@ -57,7 +35,7 @@
const DocumentBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock'));
export default {
export default {
props: ['description', 'documentUrl', 'created', 'id', 'readOnly'],
components: {
DocumentBlock,
@ -86,15 +64,22 @@
id: this.id,
},
},
update(store, {data: {deleteProjectEntry: {success}}}) {
update(
store,
{
data: {
deleteProjectEntry: { success },
},
}
) {
if (success) {
const query = PROJECT_QUERY;
const variables = {
slug: projectEntry.$route.params.slug,
};
const {project} = store.readQuery({query, variables});
const { project } = store.readQuery({ query, variables });
if (project) {
const index = project.entries.findIndex(entry => entry.id === projectEntry.id);
const index = project.entries.findIndex((entry) => entry.id === projectEntry.id);
const entries = removeAtIndex(project.entries, index);
const data = {
project: {
@ -102,20 +87,20 @@
entries,
},
};
store.writeQuery({query, variables, data});
store.writeQuery({ query, variables, data });
}
}
},
});
},
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.project-entry {
.project-entry {
background-color: $color-white;
border-radius: $default-border-radius;
padding: 30px 20px;
@ -151,6 +136,5 @@
display: block;
}
}
}
}
</style>

View File

@ -1,12 +1,7 @@
<template>
<modal :hide-header="false">
<template #header>
<h2
class="project-entry-modal__heading"
data-cy="modal-title"
>
Beitrag erfassen
</h2>
<h2 class="project-entry-modal__heading" data-cy="modal-title">Beitrag erfassen</h2>
</template>
<div class="project-entry-modal">
@ -36,29 +31,24 @@
</div>
</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>
<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>
</modal>
</template>
<script>
import Modal from '@/components/Modal';
import ButtonWithIconAndText from '@/components/ui/ButtonWithIconAndText';
import Modal from '@/components/Modal';
import ButtonWithIconAndText from '@/components/ui/ButtonWithIconAndText';
import {PROJECT_ENTRY_TEMPLATE} from '@/consts/strings.consts';
import { PROJECT_ENTRY_TEMPLATE } from '@/consts/strings.consts';
import {defineAsyncComponent} from 'vue';
const FileUpload = defineAsyncComponent(() => import('@/components/ui/file-upload/FileUpload'));
import { defineAsyncComponent } from 'vue';
const FileUpload = defineAsyncComponent(() => import('@/components/ui/file-upload/FileUpload'));
export default {
export default {
props: {
projectEntry: {
type: Object,
@ -74,9 +64,12 @@
data() {
return {
localProjectEntry: Object.assign({}, {
localProjectEntry: Object.assign(
{},
{
...this.projectEntry,
}),
}
),
};
},
@ -88,13 +81,13 @@
this.localProjectEntry.description = `${this.localProjectEntry.description}${PROJECT_ENTRY_TEMPLATE}`;
},
},
};
};
</script>
<style lang="scss" scoped>
@import "~styles/helpers";
@import '~styles/helpers';
.project-entry-modal {
.project-entry-modal {
display: flex;
flex-direction: column;
@ -135,7 +128,5 @@
@include heading-3;
margin-bottom: 0;
}
}
}
</style>

View File

@ -1,17 +1,7 @@
<template>
<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"
/>
<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>
<button
:class="{ 'button--disabled': !formValid }"
@ -22,12 +12,7 @@
>
Speichern
</button>
<router-link
to="/portfolio"
class="button"
>
Abbrechen
</router-link>
<router-link to="/portfolio" class="button"> Abbrechen </router-link>
</template>
</page-form>
</template>

View File

@ -5,7 +5,7 @@
<owner-widget :owner="project.student" class="project__owner" />
<entry-count-widget class="project__entry-count" :verbose="false" :entry-count="project.entriesCount" />
<entry-count-widgetclass="project__entry-count" :verbose="false" :entry-count="project.entriesCount" />
</router-link>
<project-actions
:final="project.final"
@ -70,10 +70,8 @@ export default {
@include desktop {
display: flex;
flex: 80%;
flex-direction: row;
align-items: center;
}
}
flex-direction: row;align-items: center;
}}
&__title {
flex: 50%;
@ -96,7 +94,6 @@ export default {
&__entry-count {
justify-self: flex-end;
grid-column: 2 / span 1;
}
grid-column: 2 / span 1;}
}
</style>

View File

@ -1,7 +1,5 @@
<template>
<a
class="share-icon"
@click="$emit('share')"
<a class="share-icon" @click="$emit('share')"
>
<share-icon class="share-icon__icon" />
<span class="share-icon__text">
@ -12,10 +10,10 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const ShareIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ShareIcon'));
import { defineAsyncComponent } from 'vue';
const ShareIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/ShareIcon'));
export default {
export default {
props: {
final: {
type: Boolean,
@ -23,14 +21,14 @@
},
},
emits: ['share'],
components: {ShareIcon},
};
components: { ShareIcon },
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.share-icon {
.share-icon {
display: flex;
align-items: center;
cursor: pointer;
@ -44,5 +42,5 @@
&__text {
@include large-link;
}
}
}
</style>

View File

@ -2,31 +2,23 @@
<div class="avatar">
<transition name="fade">
<default-avatar
:class="{'avatar__placeholder--highlighted': iconHighlighted}"
:class="{ 'avatar__placeholder--highlighted': iconHighlighted }"
class="avatar__placeholder"
v-show="!isAvatarLoaded"
/>
</transition>
<transition name="show">
<div
:style="{'background-image': `url(${avatarUrl})`}"
:style="{ 'background-image': `url(${avatarUrl})` }"
class="avatar__image"
v-show="isAvatarLoaded"
ref="avatarImage"
/>
</transition>
<img
:src="avatarUrl"
class="avatar__fake-image"
ref="fakeImage"
>
<img :src="avatarUrl" class="avatar__fake-image" ref="fakeImage" />
<div
class="avatar__edit"
v-if="editable"
@click="closeSidebar"
>
<router-link :to="{name: 'profile'}">
<div class="avatar__edit" v-if="editable" @click="closeSidebar">
<router-link :to="{ name: 'profile' }">
<pen-icon />
</router-link>
</div>
@ -40,23 +32,23 @@
const DefaultAvatar = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DefaultAvatar'));
const PenIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/PenIcon'));
export default {
export default {
props: {
avatarUrl: {
type: String
type: String,
},
iconHighlighted: {},
editable: {
default: false
}
default: false,
},
},
components: {
DefaultAvatar,
PenIcon
PenIcon,
},
data() {
return {
isAvatarLoaded: false
isAvatarLoaded: false,
};
},
mounted() {
@ -75,21 +67,21 @@
mutation: TOGGLE_SIDEBAR,
variables: {
sidebar: {
profile: false
}
}
profile: false,
},
},
});
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
$max-width: 100%;
$max-width: 100%;
.avatar {
.avatar {
height: $max-width;
width: $max-width;
overflow: hidden;
@ -149,5 +141,5 @@
.show-enter-to {
opacity: 1;
}
}
}
</style>

View File

@ -1,15 +1,9 @@
<template>
<div class="content-bookmark module-activity-entry">
<!-- eslint-disable vue/no-v-html -->
<div
v-if="content.type === 'text_block'"
v-html="text"
/>
<div v-if="content.type === 'text_block'" v-html="text" />
<div v-else-if="content.type === 'link_block'">
<link-block
:value="content.value"
:no-margin="true"
/>
<link-block :value="content.value" :no-margin="true" />
</div>
<p v-else>
{{ type }}
@ -21,14 +15,14 @@
import {defineAsyncComponent} from 'vue';
const LinkBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/LinkBlock'));
export default {
export default {
props: ['bookmark'],
components: {LinkBlock},
components: { LinkBlock },
computed: {
content() {
return this.bookmark.contentBlock
? this.bookmark.contentBlock.contents.find(e => e.id === this.bookmark.uuid)
: this.bookmark.instrument.contents.find(e => e.id === this.bookmark.uuid);
? this.bookmark.contentBlock.contents.find((e) => e.id === this.bookmark.uuid)
: this.bookmark.instrument.contents.find((e) => e.id === this.bookmark.uuid);
},
text() {
return this.content.value.text ? this.content.value.text : 'TO BE DEFINED';
@ -46,7 +40,7 @@
default:
return this.content.type;
}
}
}
};
},
},
};
</script>

View File

@ -1,9 +1,5 @@
<template>
<a
class="edit-group-name"
data-cy="edit-group-name-link"
@click="$emit('edit')"
>
<a class="edit-group-name" data-cy="edit-group-name-link" @click="$emit('edit')">
<pen-icon class="edit-group-name__icon" />
</a>
</template>
@ -12,21 +8,21 @@
import {defineAsyncComponent} from 'vue';
const PenIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/PenIcon'));
export default {
export default {
components: {
PenIcon
}
};
PenIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/_variables.scss";
@import '~styles/_variables.scss';
.edit-group-name {
.edit-group-name {
&__icon {
width: 20px;
height: 20px;
fill: $color-brand;
}
}
}
</style>

View File

@ -181,17 +181,13 @@ export default {
}
&__role {
@include desktop {
flex: 0 1 110px;
@include desktop {flex: 0 1 110px;
text-align: right;
}
}
}}
&__action {
@include desktop {
flex: 0 1 110px;
@include desktop {flex: 0 1 110px;
padding-left: $large-spacing;
}
}
}
}}
</style>

View File

@ -1,49 +1,38 @@
<template>
<div class="profile">
<h1 class="profile__header">
Profilbild
</h1>
<div
class="profile-avatar"
v-if="me.avatarUrl"
>
<h1 class="profile__header">Profilbild</h1>
<div class="profile-avatar" v-if="me.avatarUrl">
<div class="profile-avatar__image">
<avatar :avatar-url="me.avatarUrl" />
</div>
<a
class="profile-avatar__remove icon-button"
@click="deleteAvatar()"
>
<a class="profile-avatar__remove icon-button" @click="deleteAvatar()">
<trash-icon class="profile-avatar__remove-icon icon-button__icon icon-button__icon--subtle" />
</a>
</div>
<avatar-upload-form
v-else
@avatarUpdate="updateAvatar"
/>
<avatar-upload-form v-else @avatarUpdate="updateAvatar" />
</div>
</template>
<script>
import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/updateAvatarUrl.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import AvatarUploadForm from '@/components/profile/AvatarUploadForm';
import Avatar from '@/components/profile/Avatar';
import {defineAsyncComponent} from 'vue';
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon'));
import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/updateAvatarUrl.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import AvatarUploadForm from '@/components/profile/AvatarUploadForm';
import Avatar from '@/components/profile/Avatar';
import { defineAsyncComponent } from 'vue';
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/TrashIcon'));
export default {
export default {
components: {
AvatarUploadForm,
Avatar,
TrashIcon
TrashIcon,
},
data() {
return {
me: {
avatarUrl: ''
}
avatarUrl: '',
},
};
},
apollo: {
@ -52,43 +41,52 @@
},
},
methods: {
deleteAvatar () {
deleteAvatar() {
this.updateAvatar('');
},
updateAvatar (url) {
this.$apollo.mutate({
updateAvatar(url) {
this.$apollo
.mutate({
mutation: UPDATE_AVATAR_QUERY,
variables: {
input: {
avatarUrl: url
}
avatarUrl: url,
},
update(store, {data: {updateAvatar: {success}}}) {
},
update(
store,
{
data: {
updateAvatar: { success },
},
}
) {
if (success) {
const {me} = store.readQuery({query: ME_QUERY});
const { me } = store.readQuery({ query: ME_QUERY });
if (me) {
const data = {
me: {
...me,
avatarUrl: url
}
avatarUrl: url,
},
};
store.writeQuery({query: ME_QUERY, data});
store.writeQuery({ query: ME_QUERY, data });
}
}
}
}).catch((error) => {
},
})
.catch((error) => {
console.warn('UploadError', error);
});
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.profile-avatar {
.profile-avatar {
display: flex;
flex-direction: row;
@ -96,10 +94,9 @@
height: 230px;
width: 230px;
}
}
}
.profile-avatar {
.profile-avatar {
margin-bottom: $large-spacing;
}
}
</style>

View File

@ -1,69 +1,31 @@
<template>
<transition name="slide">
<div
class="profile-sidebar"
data-cy="sidebar"
v-if="sidebar.profile"
v-click-outside="close"
>
<a
class="profile-sidebar__close-link"
data-cy="close-profile-sidebar-link"
@click="close"
>
<div class="profile-sidebar" data-cy="sidebar" v-if="sidebar.profile" v-click-outside="close">
<a class="profile-sidebar__close-link" data-cy="close-profile-sidebar-link" @click="close">
<cross class="profile-sidebar__close-icon" />
</a>
<div class="profile-sidebar__section">
<profile-widget class="profile-sidebar__item" />
<div
class="profile-sidebar__item"
@click="close"
>
<router-link
to="/me/activity"
class="profile-sidebar__link"
>
Meine Aktivitäten
</router-link>
<div class="profile-sidebar__item" @click="close">
<router-link to="/me/activity" class="profile-sidebar__link"> Meine Aktivitäten </router-link>
</div>
<div
class="profile-sidebar__item"
v-if="me.isTeacher && !me.readOnly"
@click="close"
>
<router-link
:to="myTeamPage"
data-cy="my-team-link"
class="profile-sidebar__link"
>
Mein Team
</router-link>
<div class="profile-sidebar__item" v-if="me.isTeacher && !me.readOnly" @click="close">
<router-link :to="myTeamPage" data-cy="my-team-link" class="profile-sidebar__link"> Mein Team </router-link>
</div>
</div>
<div class="profile-sidebar__section">
<div class="profile-sidebar__item">
<class-selection-widget />
<div @click="close">
<router-link
:to="{name: 'my-class'}"
data-cy="class-list-link"
class="profile-sidebar__link"
>
<router-link :to="{ name: 'my-class' }" data-cy="class-list-link" class="profile-sidebar__link">
Klassenliste
</router-link>
</div>
</div>
</div>
<div class="profile-sidebar__section">
<div
class="profile-sidebar__item"
@click="close"
>
<router-link
:to="{name:'join-class'}"
data-cy="join-class-link"
class="profile-sidebar__link"
>
<div class="profile-sidebar__item" @click="close">
<router-link :to="{ name: 'join-class' }" data-cy="join-class-link" class="profile-sidebar__link">
Zugangscode
</router-link>
</div>
@ -76,19 +38,18 @@
</template>
<script>
import ProfileWidget from '@/components/profile/ProfileWidget';
import ProfileWidget from '@/components/profile/ProfileWidget';
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
import sidebar from '@/mixins/sidebar';
import me from '@/mixins/me';
import LogoutWidget from '@/components/LogoutWidget';
import {MY_TEAM} from '@/router/me.names';
import {defineAsyncComponent} from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/CrossIcon'));
export default {
import sidebar from '@/mixins/sidebar';
import me from '@/mixins/me';
import LogoutWidget from '@/components/LogoutWidget';
import { MY_TEAM } from '@/router/me.names';
import { defineAsyncComponent } from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/CrossIcon'));
export default {
mixins: [sidebar, me],
components: {
@ -111,15 +72,15 @@
this.closeSidebar('profile');
},
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
$desktop-width: 333px;
$desktop-width: 333px;
.profile-sidebar {
.profile-sidebar {
padding: $large-spacing 0;
box-sizing: border-box;
position: fixed;
@ -170,18 +131,20 @@
top: $small-spacing;
cursor: pointer;
}
}
}
.slide {
&-enter-active, &-leave-active {
.slide {
&-enter-active,
&-leave-active {
transition: right 0.2s;
}
&-enter-from, &-leave-to {
&-enter-from,
&-leave-to {
right: -100vw;
@include desktop {
right: -$desktop-width;
}
}
}
}
</style>

View File

@ -32,12 +32,9 @@ export default {
.show-code {
&__title {
@include regular-text;
margin-bottom: $large-spacing;
@include desktop {
margin-bottom: 2 * $large-spacing;
}
}
margin-bottom: $large-spacing;
@include desktop { margin-bottom: 2 * $large-spacing;
}}
&__code {
font-size: toRem(60px);

View File

@ -1,9 +1,5 @@
<template>
<router-link
class="add-room-entry-button"
data-cy="add-room-entry-button"
:to="addRoomEntryRoute"
>
<router-link class="add-room-entry-button" data-cy="add-room-entry-button" :to="addRoomEntryRoute">
<plus-icon class="add-room-entry-button__icon" />
<span class="add-room-entry-button__text">Beitrag erfassen</span>
</router-link>
@ -14,7 +10,7 @@
const PlusIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/PlusIcon'));
import { ADD_ROOM_ENTRY_PAGE } from '@/router/room.names';
export default {
export default {
props: ['parent'],
components: {
@ -28,13 +24,13 @@
},
};
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.add-room-entry-button {
.add-room-entry-button {
border: 2px solid $color-white;
border-radius: 12px;
height: 150px;
@ -61,5 +57,5 @@
@include regular-text;
color: $color-white;
}
}
}
</style>

View File

@ -1,16 +1,18 @@
<template>
<div class="entry-count-widget">
<component :is="icon" />
<span data-cy="entry-count">{{ entryCount }} <template v-if="verbose">{{ entryCount === 1 ? 'Beitrag' : 'Beiträge' }}</template></span>
<span data-cy="entry-count"
>{{ entryCount }} <template v-if="verbose">{{ entryCount === 1 ? 'Beitrag' : 'Beiträge' }}</template></span
>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue';
import SpeechBubbleIcon from '@/components/icons/SpeechBubbleIcon';
const Cards = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Cards.vue'));
import { defineAsyncComponent } from 'vue';
import SpeechBubbleIcon from '@/components/icons/SpeechBubbleIcon';
const Cards = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Cards.vue'));
export default {
export default {
props: {
entryCount: {
type: Number,
@ -21,8 +23,8 @@
},
icon: {
type: String,
default: 'cards'
}
default: 'cards',
},
},
components: {
@ -30,13 +32,13 @@
SpeechBubbleIcon,
Cards,
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.entry-count-widget {
.entry-count-widget {
display: flex;
align-items: center;
opacity: 0.6;
@ -51,6 +53,5 @@
& > span {
@include room-widget-text-style;
}
}
}
</style>

View File

@ -1,34 +1,30 @@
<template>
<div class="more-actions">
<a
:class="{'more-actions__toggle--background': background}"
:class="{ 'more-actions__toggle--background': background }"
class="more-actions__toggle"
data-cy="toggle-more-actions-menu"
@click.stop="toggleMenu"
>
<ellipses />
</a>
<widget-popover
class="more-actions__popover"
v-if="showMenu"
@hide-me="showMenu = false"
>
<widget-popover class="more-actions__popover" v-if="showMenu" @hide-me="showMenu = false">
<slot :toggle="toggleMenu" />
</widget-popover>
</div>
</template>
<script>
import WidgetPopover from '@/components/ui/WidgetPopover';
import {defineAsyncComponent} from 'vue';
const Ellipses = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Ellipses'));
import WidgetPopover from '@/components/ui/WidgetPopover';
import { defineAsyncComponent } from 'vue';
const Ellipses = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Ellipses'));
export default {
export default {
props: {
background: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
@ -46,13 +42,13 @@
this.showMenu = !this.showMenu;
},
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.more-actions {
.more-actions {
svg {
width: 30px;
fill: $color-charcoal-dark;
@ -67,5 +63,5 @@
background: white;
}
}
}
}
</style>

View File

@ -1,65 +1,40 @@
<template>
<page-form
class="room-form"
title="Neuer Raum"
@save="$emit('save', localRoom)"
>
<page-form-input
label="Titel"
v-model="localRoom.title"
/>
<page-form class="room-form" title="Neuer Raum" @save="$emit('save', localRoom)">
<page-form-input label="Titel" v-model="localRoom.title" />
<page-form-input
label="Beschreibung"
type="textarea"
v-model="localRoom.description"
/>
<page-form-input label="Beschreibung" type="textarea" v-model="localRoom.description" />
<h2 class="room-form__property-heading">
Farbe
</h2>
<color-chooser
:selected-color="localRoom.appearance"
@input="updateColor"
/>
<h2 class="room-form__property-heading">Farbe</h2>
<color-chooser :selected-color="localRoom.appearance" @input="updateColor" />
<template #footer>
<button
type="submit"
data-cy="room-form-save"
class="button button--primary room-form__save-button"
>
<button type="submit" data-cy="room-form-save" class="button button--primary room-form__save-button">
Speichern
</button>
<router-link
to="/rooms"
class="button"
>
Abbrechen
</router-link>
<router-link to="/rooms" class="button"> Abbrechen </router-link>
</template>
</page-form>
</template>
<script>
import ColorChooser from '@/components/ColorChooser';
import PageForm from '@/components/page-form/PageForm';
import PageFormInput from '@/components/page-form/PageFormInput';
import ColorChooser from '@/components/ColorChooser';
import PageForm from '@/components/page-form/PageForm';
import PageFormInput from '@/components/page-form/PageFormInput';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
export default {
export default {
props: ['room'],
components: {
ColorChooser,
PageForm,
PageFormInput
PageFormInput,
},
data() {
return {
localRoom: Object.assign({}, this.room),
me: {}
me: {},
};
},
@ -75,21 +50,21 @@
updateColor(newColor) {
this.localRoom.appearance = newColor;
this.$store.dispatch('setSpecialContainerClass', newColor);
}
},
},
apollo: {
me: {
query: ME_QUERY,
}
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.room-form {
.room-form {
&__property-heading {
@include page-form-input-heading;
}
@ -103,5 +78,5 @@
&__save-button {
margin-right: 15px;
}
}
}
</style>

View File

@ -8,22 +8,22 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const Group = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Group.vue'));
import { defineAsyncComponent } from 'vue';
const Group = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Group.vue'));
export default {
export default {
props: ['name'],
components: {
Group
}
};
Group,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.room-group-widget {
.room-group-widget {
display: flex;
align-items: center;
opacity: 0.6;
@ -35,7 +35,7 @@
}
& > span {
@include room-widget-text-style;;
}
@include room-widget-text-style;
}
}
</style>

View File

@ -13,29 +13,31 @@
<script>
import {defineAsyncComponent} from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EyeIcon'));
const ClosedEyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ClosedEyeIcon'));
import { defineAsyncComponent } from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/EyeIcon'));
const ClosedEyeIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/ClosedEyeIcon')
);
export default {
export default {
props: {
restricted: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
ClosedEyeIcon,
EyeIcon
}
};
EyeIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.room-visibility-widget {
.room-visibility-widget {
display: flex;
align-items: center;
opacity: 0.6;
@ -49,6 +51,5 @@
& > span {
@include room-widget-text-style;
}
}
}
</style>

View File

@ -1,12 +1,6 @@
<template>
<div
:class="roomClass"
class="room-widget"
>
<router-link
:to="{name: 'room', params: {slug: slug}}"
class="room-widget__content"
>
<div :class="roomClass" class="room-widget">
<router-link :to="{ name: 'room', params: { slug: slug } }" class="room-widget__content">
<h2 class="room-widget__title">
{{ title }}
</h2>

View File

@ -1,25 +1,12 @@
<template>
<div class="rooms-onboarding">
<h1
class="rooms-onboarding__heading"
data-cy="page-title"
>
Räume
</h1>
<h1 class="rooms-onboarding__heading" data-cy="page-title">Räume</h1>
<rooms-illustration class="rooms-onboarding__illustration" />
<p
data-cy="rooms-onboarding-text"
class="rooms-onboarding__text"
>
<p data-cy="rooms-onboarding-text" class="rooms-onboarding__text">
Hier können Sie Räume erstellen, damit SchülerInnen zusammenarbeiten und Beiträge teilen können.
</p>
<div class="rooms-onboarding__button">
<router-link
:to="newRoomRoute"
class="button button--primary"
data-cy="create-room-button"
v-if="isTeacher"
>
<router-link :to="newRoomRoute" class="button button--primary" data-cy="create-room-button" v-if="isTeacher">
Raum erstellen
</router-link>
</div>
@ -27,31 +14,33 @@
</template>
<script>
import {NEW_ROOM_PAGE} from '@/router/room.names';
import {defineAsyncComponent} from 'vue';
const RoomsIllustration = defineAsyncComponent(() => import(/* webpackChunkName: "illustrations" */'@/components/illustrations/RoomsIllustration'));
import { NEW_ROOM_PAGE } from '@/router/room.names';
import { defineAsyncComponent } from 'vue';
const RoomsIllustration = defineAsyncComponent(() =>
import(/* webpackChunkName: "illustrations" */ '@/components/illustrations/RoomsIllustration')
);
export default {
export default {
props: {
isTeacher: {
type: Boolean,
default: false,
},
},
components: {RoomsIllustration},
components: { RoomsIllustration },
data() {
return {
newRoomRoute: NEW_ROOM_PAGE,
};
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.rooms-onboarding {
.rooms-onboarding {
@include onboarding-page;
&__heading {
@ -65,5 +54,5 @@
&__text {
@include onboarding-text;
}
}
}
</style>

View File

@ -13,12 +13,7 @@
/>
<chevron-down class="selected-class__dropdown-icon" />
</div>
<widget-popover
:mobile="mobile"
class="class-selection__popover"
v-if="showPopover"
@hide-me="showPopover = false"
>
<widget-popover :mobile="mobile" class="class-selection__popover" v-if="showPopover" @hide-me="showPopover = false">
<li
:label="schoolClass.name"
:item="schoolClass"
@ -59,8 +54,8 @@
</template>
<script>
import WidgetPopover from '@/components/ui/WidgetPopover';
import CurrentClass from '@/components/school-class/CurrentClass';
import WidgetPopover from '@/components/ui/WidgetPopover';
import CurrentClass from '@/components/school-class/CurrentClass';
import updateSelectedClassMixin from '@/mixins/update-selected-class';
import sidebarMixin from '@/mixins/sidebar';
@ -69,13 +64,12 @@
const ChevronDown = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronDown'));
const AddIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon'));
export default {
export default {
props: {
mobile: {
type: Boolean,
default: false
}
default: false,
},
},
mixins: [updateSelectedClassMixin, sidebarMixin, meMixin],
@ -83,22 +77,22 @@
WidgetPopover,
ChevronDown,
CurrentClass,
AddIcon
AddIcon,
},
data() {
return {
showPopover: false
showPopover: false,
};
},
computed: {
currentClassSelection() {
let currentClass = this.me.schoolClasses.find(schoolClass => {
let currentClass = this.me.schoolClasses.find((schoolClass) => {
return schoolClass.id === this.me.selectedClass.id;
});
return currentClass || this.me.schoolClasses[0];
}
},
},
methods: {
@ -111,14 +105,14 @@
this.closeSidebar('profile');
}
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.class-selection {
.class-selection {
position: relative;
cursor: pointer;
margin-bottom: $medium-spacing;
@ -130,10 +124,9 @@
left: 0;
transform: translateY($small-spacing);
}
}
}
.selected-class {
.selected-class {
width: 100%;
box-sizing: border-box;
padding: $small-spacing 0;
@ -153,5 +146,5 @@
height: 20px;
fill: $color-charcoal-dark;
}
}
}
</style>

View File

@ -1,27 +1,24 @@
<template>
<span
class="current-class"
data-cy="current-class-name"
>{{ currentClassName }}</span>
<span class="current-class" data-cy="current-class-name">{{ currentClassName }}</span>
</template>
<script>
import me from '@/mixins/me';
import me from '@/mixins/me';
export default {
export default {
mixins: [me],
};
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.current-class {
.current-class {
display: flex;
flex-direction: column;
align-self: center;
line-height: 1;
@include regular-text;
}
}
</style>

View File

@ -6,26 +6,19 @@
class="base-input-container__input"
data-cy="base-input-input"
@change.prevent="$emit('input', $event.target.checked, item)"
>
/>
<span
:class="{'base-input-container__checkbox': type==='checkbox', 'base-input-container__radiobutton': type === 'radiobutton'}"
:class="{
'base-input-container__checkbox': type === 'checkbox',
'base-input-container__radiobutton': type === 'radiobutton',
}"
class="base-input-container__icon checkbox"
>
<tick v-if="type === 'checkbox'" />
<circle-icon
data-cy="circle-icon"
v-if="type === 'radiobutton'"
/>
<circle-icon data-cy="circle-icon" v-if="type === 'radiobutton'" />
</span>
<span
class="base-input-container__label"
data-cy="base-input-label"
v-if="label"
>{{ label }}</span>
<slot
class="base-input-container__label"
v-if="!label"
/>
<span class="base-input-container__label" data-cy="base-input-label" v-if="label">{{ label }}</span>
<slot class="base-input-container__label" v-if="!label" />
</label>
</template>
@ -34,19 +27,19 @@
const Tick = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Tick'));
const CircleIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/CircleIcon'));
export default {
export default {
props: {
label: String,
checked: {
type: Boolean
type: Boolean,
},
item: Object,
type: String
type: String,
},
components: {
Tick,
CircleIcon
}
};
CircleIcon,
},
};
</script>

View File

@ -1,27 +1,19 @@
<template>
<li
class="popover-links__link"
>
<a
class="popover-link"
@click="$emit('link-action')"
>
<component
class="popover-link__icon"
:is="icon"
/>
<li class="popover-links__link">
<a class="popover-link" @click="$emit('link-action')">
<component class="popover-link__icon" :is="icon" />
<span class="popover-link__text">{{ text }}</span>
</a>
</li>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EyeIcon'));
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon'));
const PenIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/PenIcon'));
import { defineAsyncComponent } from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/EyeIcon'));
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/TrashIcon'));
const PenIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/PenIcon'));
export default {
export default {
props: {
icon: {
type: String,
@ -37,13 +29,13 @@
TrashIcon,
PenIcon,
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.popover-link {
.popover-link {
@include popover-link;
&__icon {
@ -61,5 +53,5 @@
flex-basis: auto;
flex-shrink: 0;
}
}
}
</style>

View File

@ -1,11 +1,7 @@
<template>
<div class="file-upload">
<template v-if="document">
<document-block
:value="{url: document}"
show-trash-icon
@trash="$emit('change-document-url', '')"
/>
<document-block :value="{ url: document }" show-trash-icon @trash="$emit('change-document-url', '')" />
</template>
<template v-else>
<simple-file-upload
@ -18,11 +14,13 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const SimpleFileUpload = defineAsyncComponent(() => import('@/components/ui/file-upload/SimpleFileUpload'));
const DocumentBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock'));
import { defineAsyncComponent } from 'vue';
const SimpleFileUpload = defineAsyncComponent(() => import('@/components/ui/file-upload/SimpleFileUpload'));
const DocumentBlock = defineAsyncComponent(() =>
import(/* webpackChunkName: "content-components" */ '@/components/content-blocks/DocumentBlock')
);
export default {
export default {
props: {
document: {
type: String,
@ -30,14 +28,13 @@
},
withText: {
type: Boolean,
default: false
}
default: false,
},
components: {SimpleFileUpload, DocumentBlock},
};
},
components: { SimpleFileUpload, DocumentBlock },
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
</style>

View File

@ -5,21 +5,23 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const DocumentIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon'));
import { defineAsyncComponent } from 'vue';
const DocumentIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/DocumentIcon')
);
export default {
components: {DocumentIcon},
};
export default {
components: { DocumentIcon },
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.simple-file-upload-icon {
.simple-file-upload-icon {
&__icon {
width: 25px;
fill: $color-silver-dark;
}
}
}
</style>

View File

@ -30,5 +30,5 @@ export default {
ArrowThinDown,
ArrowThinTop,
ArrowThinUp,
TrashIcon
TrashIcon,
};

View File

@ -1,11 +1,6 @@
<template>
<div
class="skillboxform-input"
>
<label
:for="id"
class="skillboxform-input__label"
>
<div class="skillboxform-input">
<label :for="id" class="skillboxform-input__label">
{{ label }}
</label>
<slot :id="id" />
@ -13,8 +8,8 @@
</template>
<script setup>
defineProps({
defineProps({
id: String,
label: String
}) ;
label: String,
});
</script>

View File

@ -1,59 +1,51 @@
<template>
<div class="visibility-action">
<a
class="visibility-action__action-button"
v-if="canManageContent"
@click="toggleVisibility()"
>
<closed-eye-icon
class="visibility-action__action-icon action-icon"
v-if="hidden"
/>
<eye-icon
class="visibility-action__action-icon action-icon"
v-else
/>
<a class="visibility-action__action-button" v-if="canManageContent" @click="toggleVisibility()">
<closed-eye-icon class="visibility-action__action-icon action-icon" v-if="hidden" />
<eye-icon class="visibility-action__action-icon action-icon" v-else />
</a>
</div>
</template>
<script>
import me from '@/mixins/me';
import me from '@/mixins/me';
import {TYPES, CONTENT_TYPE} from '@/consts/types';
import {createVisibilityMutation, hidden} from '@/helpers/visibility';
import { TYPES, CONTENT_TYPE } from '@/consts/types';
import { createVisibilityMutation, hidden } from '@/helpers/visibility';
import {defineAsyncComponent} from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EyeIcon'));
const ClosedEyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ClosedEyeIcon'));
import { defineAsyncComponent } from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/EyeIcon'));
const ClosedEyeIcon = defineAsyncComponent(() =>
import(/* webpackChunkName: "icons" */ '@/components/icons/ClosedEyeIcon')
);
export default {
export default {
props: {
block: {
type: Object,
default: () => ({})
default: () => ({}),
},
type: {
type: String,
default: CONTENT_TYPE,
validator: value => {
validator: (value) => {
// value must be one of TYPES
return TYPES.indexOf(value) !== -1;
}
}
},
},
},
mixins: [me],
components: {
EyeIcon,
ClosedEyeIcon
ClosedEyeIcon,
},
computed: {
hidden() {
return hidden({type: this.type, block: this.block, schoolClass: this.schoolClass});
}
return hidden({ type: this.type, block: this.block, schoolClass: this.schoolClass });
},
},
methods: {
@ -61,24 +53,26 @@
const hidden = !this.hidden;
const schoolClassId = this.schoolClass.id;
const visibility = [{
const visibility = [
{
schoolClassId,
hidden
}];
hidden,
},
];
const {mutation, variables} = createVisibilityMutation(this.type, this.block.id, visibility);
const { mutation, variables } = createVisibilityMutation(this.type, this.block.id, visibility);
this.$apollo.mutate({
mutation,
variables
variables,
});
},
},
};
};
</script>
<style scoped lang="scss">
.visibility-action {
.visibility-action {
margin-top: 9px;
position: absolute;
@ -89,5 +83,5 @@
&__visibility-menu {
top: 40px;
}
}
}
</style>

View File

@ -1,56 +1,29 @@
<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__info">
<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">
mySkillbox ist ein Angebot des hep Verlags in
Zusammenarbeit mit der Eidgenössischen Hochschule für Berufsbildung (EHB).
mySkillbox ist ein Angebot des hep Verlags in Zusammenarbeit mit der Eidgenössischen Hochschule für
Berufsbildung (EHB).
</p>
</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" />
</a>
<a
href="https://www.ehb.swiss/"
target="_blank"
>
<a href="https://www.ehb.swiss/" target="_blank">
<ehb-logo class="default-footer__logo-ehb" />
</a>
</div>
</div>
<div class="default-footer__section">
<div class="default-footer__links">
<a
href="https://myskillbox.ch/datenschutz"
target="_blank"
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>
<a href="https://myskillbox.ch/datenschutz" target="_blank" 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>
</footer>
@ -62,19 +35,19 @@
const HepLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/HepLogo'));
const EhbLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EhbLogo'));
export default {
export default {
components: {
HepLogo,
EhbLogo
}
};
EhbLogo,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.default-footer {
.default-footer {
background-color: $color-silver-light;
max-width: 100vw;
overflow: hidden;
@ -89,7 +62,7 @@
&__info {
width: 100%;
max-width: $footer-width;
padding: 2*$large-spacing 0;
padding: 2 * $large-spacing 0;
display: flex;
flex-direction: column;
@ -146,9 +119,9 @@
margin-bottom: 0;
}
}
}
}
.who-are-we {
.who-are-we {
&__title {
@include heading-4;
}
@ -156,5 +129,5 @@
&__text {
@include aside-text;
}
}
}
</style>

View File

@ -1,12 +1,6 @@
<template>
<div
:class="specialContainerClass"
class="container layout layout--fullscreen"
>
<div
class="close-button"
@click="back"
>
<div :class="specialContainerClass" class="container layout layout--fullscreen">
<div class="close-button" @click="back">
<cross class="close-button__icon" />
</div>
@ -15,39 +9,38 @@
</template>
<script>
import {defineAsyncComponent} from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/CrossIcon'));
import { defineAsyncComponent } from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/CrossIcon'));
export default {
export default {
components: {
Cross
Cross,
},
computed: {
specialContainerClass() {
let cls = this.$store.state.specialContainerClass;
return [cls ? `skillbox--${cls}` : ''];
}
},
},
methods: {
back() {
this.$router.go(-1);
}
}
};
},
},
};
</script>
<style lang="scss" scoped>
@import "@/styles/_default-layout.scss";
@import '@/styles/_default-layout.scss';
.close-button {
.close-button {
margin-top: $medium-spacing;
margin-right: $medium-spacing;
justify-self: end;
cursor: pointer;
display:flex;
justify-content:flex-end;
}
display: flex;
justify-content: flex-end;
}
</style>

View File

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

View File

@ -1,31 +1,22 @@
<template>
<div
:class="{'layout--full-width': $route.meta.fullWidth}"
class="skillbox layout layout--simple"
>
<div
class="close-button"
@click="back"
>
<div :class="{ 'layout--full-width': $route.meta.fullWidth }" class="skillbox layout layout--simple">
<div class="close-button" @click="back">
<cross class="close-button__icon" />
</div>
<router-view class="layout__content" />
<simple-footer
class="layout__footer"
v-if="enableFooter"
/>
<simple-footer class="layout__footer" v-if="enableFooter" />
</div>
</template>
<script>
import SimpleFooter from '@/layouts/SimpleFooter';
import {defineAsyncComponent} from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/CrossIcon'));
import SimpleFooter from '@/layouts/SimpleFooter';
import { defineAsyncComponent } from 'vue';
const Cross = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/CrossIcon'));
export default {
export default {
components: {
Cross,
SimpleFooter
SimpleFooter,
},
computed: {
@ -34,21 +25,21 @@
return false;
}
return this.$flavor.showFooter;
}
},
},
methods: {
back() {
this.$router.go(-1);
}
}
};
},
},
};
</script>
<style lang="scss" scoped>
@import "~styles/helpers";
@import '~styles/helpers';
.layout {
.layout {
&--simple {
display: -ms-grid;
@supports (display: grid) {
@ -83,14 +74,14 @@
grid-column: 1 / span 3;
}
}
}
}
.close-button {
.close-button {
justify-self: end;
cursor: pointer;
display:flex;
justify-content:flex-end;
display: flex;
justify-content: flex-end;
margin-right: $small-spacing;
margin-top: $small-spacing;
@ -103,5 +94,5 @@
margin-right: $medium-spacing;
margin-top: $medium-spacing;
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div :class="['split-view', {'split-view--illustration': illustration}]">
<div :class="['split-view', { 'split-view--illustration': illustration }]">
<div :class="['split-view__illustration', illustrationAlignment]">
<component :is="illustration" />
</div>
@ -19,12 +19,12 @@
const HelloMyKVIllustration = defineAsyncComponent(() => import(/* webpackChunkName: "illustrations" */'@/components/illustrations/HelloMyKVIllustration'));
const Hello = flavorValues.appFlavor === 'my-kv' ? HelloMyKVIllustration : HelloIllustration;
export default {
export default {
components: {
contents: ContentsIllustration,
portfolio: PortfolioIllustration,
rooms: RoomsIllustration,
hello: Hello
hello: Hello,
},
computed: {
@ -32,16 +32,18 @@
return this.$route.meta.illustration;
},
illustrationAlignment() {
return this.$route.meta.illustrationAlign ? `split-view__illustration--${this.$route.meta.illustrationAlign}` : '';
}
return this.$route.meta.illustrationAlign
? `split-view__illustration--${this.$route.meta.illustrationAlign}`
: '';
},
};
},
};
</script>
<style lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.split-view {
.split-view {
background-color: $color-brand;
display: flex;
@ -94,7 +96,7 @@
width: 100%;
@include desktop {
padding: 2*$large-spacing;
padding: 2 * $large-spacing;
}
}
@ -117,7 +119,7 @@
&__page-heading {
@include heading-2;
color: $color-brand;
margin-bottom: 2*$large-spacing;
margin-bottom: 2 * $large-spacing;
}
&__heading {
@ -135,7 +137,7 @@
margin-bottom: $medium-spacing;
&:last-of-type {
margin-bottom: 2*$large-spacing;
margin-bottom: 2 * $large-spacing;
}
}
@ -159,5 +161,5 @@
margin-top: auto;
}
}
}
}
</style>

View File

@ -126,11 +126,12 @@
const redirectUrl = this.$route.query.redirect ? this.$route.query.redirect : '/';
this.$apollo.mutate({
this.$apollo
.mutate({
client: 'publicClient',
mutation: BETA_LOGIN_MUTATION,
variables,
update: (store, {data: {betaLogin}}) => {
update: (store, { data: { betaLogin } }) => {
try {
if (betaLogin.success) {
console.log(this);
@ -141,7 +142,8 @@
this.loginError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
}
},
}).catch(error => {
})
.catch((error) => {
const firstError = error.graphQLErrors[0];
switch (firstError.message) {
case 'invalid_credentials':
@ -158,15 +160,14 @@
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.text-link {
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
.actions {
}
.actions {
display: flex;
justify-content: space-between;
@ -176,6 +177,5 @@
padding: 15px;
line-height: 19px;
}
}
}
</style>

View File

@ -1,20 +1,15 @@
<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>
<script>
import {defineComponent} from 'vue';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import {setUserBlockType} from '@/helpers/content-block';
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
import {cleanUpContents} from '@/components/content-block-form/helpers';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import { setUserBlockType } from '@/helpers/content-block';
import NEW_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/addContentBlock.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
import { cleanUpContents } from '@/components/content-block-form/helpers';
export default defineComponent({
props: {
@ -27,6 +22,7 @@
default: ''
}
},
},
components: {
ContentBlockForm,
@ -36,12 +32,12 @@
contentBlock: {
title: '',
isAssignment: false,
contents: [
]},
contents: [],
},
}),
methods: {
save({title, contents, isAssignment}) {
save({ title, contents, isAssignment }) {
let cleanedContents = cleanUpContents(contents);
const contentBlock = {
title: title,
@ -50,36 +46,38 @@
};
let input;
const { parent, after, slug } = this.$route.params;
if(after) {
if (after) {
input = {
contentBlock,
after
after,
};
} else {
input = {
contentBlock,
parent
parent,
};
}
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: NEW_CONTENT_BLOCK_MUTATION,
variables: {
input
input,
},
refetchQueries: [{
refetchQueries: [
{
query: MODULE_DETAILS_QUERY,
variables: {
slug
}
}]
}).then(this.goToModule);
slug,
},
},
],
})
.then(this.goToModule);
},
goToModule() {
// use the history, so the scroll position is preserved
this.$router.go(-1);
}
}
});
},
},
});
</script>

View File

@ -1,64 +1,39 @@
<template>
<div
class="hello"
data-cy="hello-page"
>
<div class="hello" data-cy="hello-page">
<div class="about">
<div class="about__logos logos">
<a
href="https://www.hep-verlag.ch/"
target="_blank"
>
<a href="https://www.hep-verlag.ch/" target="_blank">
<hep-logo-no-claim class="logos__logo" />
</a>
<a
href="https://www.ehb.swiss/"
target="_blank"
v-if="$flavor.showEHB"
>
<a href="https://www.ehb.swiss/" target="_blank" v-if="$flavor.showEHB">
<ehb-logo class="logos__logo" />
</a>
</div>
<p class="about__text">
<template v-if="$flavor.showEHB">
{{ $flavor.textAppName }} ist ein Angebot des hep Verlags in
Zusammenarbeit mit der Eidgenössischen Hochschule für Berufsbildung (EHB).
</template>
<template v-else>
{{ $flavor.textAppName }} ist ein Angebot des hep Verlags.
{{ $flavor.textAppName }} ist ein Angebot des hep Verlags in Zusammenarbeit mit der Eidgenössischen Hochschule
für Berufsbildung (EHB).
</template>
<template v-else> {{ $flavor.textAppName }} ist ein Angebot des hep Verlags. </template>
</p>
</div>
<logo class="logo" />
<div class="login-actions">
<h2
class="login-actions__title"
data-cy="hello-title"
>
<h2 class="login-actions__title" data-cy="hello-title">
Wollen Sie {{ $flavor.textAppName }} im Unterricht verwenden?
</h2>
<a
class="button button--primary button--big actions__submit"
href="/api/oauth/login/"
data-cy="oauth-login"
>Mit hep Konto anmelden</a>
<a class="button button--primary button--big actions__submit" href="/api/oauth/login/" data-cy="oauth-login"
>Mit hep Konto anmelden</a
>
<div class="login-actions__register register">
<p>Haben Sie noch kein hep Konto?</p>
<a
class="hep-link"
href="/api/oauth/login/"
data-cy="oauth-login"
>Jetzt registrieren</a>
<a class="hep-link" href="/api/oauth/login/" data-cy="oauth-login">Jetzt registrieren</a>
</div>
</div>
<div class="information">
<p>Was ist ein hep Konto und wie kann ich mich dafür registrieren?</p>
<a
class="hep-link"
href="https://myskillbox.ch/anleitung"
data-cy="oauth-login"
>Anleitung anschauen</a>
<a class="hep-link" href="https://myskillbox.ch/anleitung" data-cy="oauth-login">Anleitung anschauen</a>
</div>
<div class="links">
<ul class="links__list">
@ -82,39 +57,38 @@
const EhbLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EhbLogo'));
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
export default {
export default {
components: {
HepLogoNoClaim,
EhbLogo,
Logo
Logo,
},
data() {
return {
email: '',
submitted: false,
loading: false
loading: false,
};
},
};
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
$hello-block-margin: 2*$medium-spacing;
$hello-block-margin: 2 * $medium-spacing;
.hello {
.hello {
max-width: 600px;
margin: 0 auto;
@include desktop {
max-width: 600px;
margin: 0 auto;
}
}
}
.logo {
.logo {
display: block;
width: 300px;
margin: $small-spacing auto $hello-block-margin;
@ -122,9 +96,9 @@
@include desktop {
display: none;
}
}
}
.about {
.about {
display: none;
margin-bottom: $hello-block-margin;
@ -142,15 +116,15 @@
margin-right: $large-spacing;
}
}
}
}
.logos {
.logos {
&__logo {
height: 30px;
}
}
}
.login-actions {
.login-actions {
@include widget-shadow;
padding: $medium-spacing;
margin-bottom: $hello-block-margin;
@ -163,25 +137,26 @@
&__register {
margin-top: $large-spacing;
> p, a {
> p,
a {
@include regular-text;
}
}
}
}
.information {
.information {
margin-top: $hello-block-margin;
> p, a {
> p,
a {
@include regular-text;
}
}
}
.links {
.links {
margin-top: $hello-block-margin;
display: flex;
&__list-item {
color: $color-silver-dark;
> a {
@ -204,6 +179,5 @@
}
}
}
}
}
</style>

View File

@ -88,9 +88,10 @@ export default {
display: grid;
@include desktop {
grid-template-columns: 300px auto;
}
grid-column-gap: $small-spacing;
}grid-column-gap: $small-spacing;
padding: 0 $small-spacing;
box-sizing: border-box;
&__list {

View File

@ -2,8 +2,7 @@
<div class="license-activation public-page">
<header class="info-header">
<p class="info-header__text small-emph">
Für <span class="info-header__emph">{{ me.email }}</span> haben wir keine
gültige Lizenz gefunden
Für <span class="info-header__emph">{{ me.email }}</span> haben wir keine gültige Lizenz gefunden
</p>
</header>
<section class="coupon">
@ -67,16 +66,10 @@
<h2>Oder, kaufen Sie eine Lizenz</h2>
<ul class="license-links">
<li class="license-links__item">
<a
:href="teacherEditionUrl"
class="hep-link"
>{{ $flavor.textAppName }} für Lehrpersonen</a>
<a :href="teacherEditionUrl" class="hep-link">{{ $flavor.textAppName }} für Lehrpersonen</a>
</li>
<li class="license-links__item">
<a
:href="studentEditionUrl"
class="hep-link"
>{{ $flavor.textAppName }} für Lernende</a>
<a :href="studentEditionUrl" class="hep-link">{{ $flavor.textAppName }} für Lernende</a>
</li>
</ul>
</section>
@ -84,16 +77,16 @@
</template>
<script>
import {defineComponent} from 'vue';
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import LoadingButton from '@/components/LoadingButton';
import { defineComponent } from 'vue';
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import LoadingButton from '@/components/LoadingButton';
import me from '@/mixins/me';
import logout from '@/mixins/logout';
import me from '@/mixins/me';
import logout from '@/mixins/logout';
import {Form, Field, ErrorMessage} from 'vee-validate';
import InputWrapper from '@/components/validation/InputWrapper';
import { Form, Field, ErrorMessage } from 'vee-validate';
import InputWrapper from '@/components/validation/InputWrapper';
export default defineComponent({
mixins: [me, logout],
@ -120,42 +113,43 @@
};
},
methods: {
required(value, {field}) {
required(value, { field }) {
if (value && value.trim()) {
return true;
}
return `${field} ist ein Pflichtfeld`;
},
validateBeforeSubmit({coupon: couponCode}) {
validateBeforeSubmit({ coupon: couponCode }) {
console.log('coupon', couponCode);
this.submitted = true;
this.loading = true;
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: REDEEM_COUPON,
variables: {
input: {
couponCode
},
},
update: (
store,
{
data: {coupon},
},
) => {
update: (store, { data: { coupon } }) => {
if (coupon.success) {
this.couponErrors = [];
this.$apollo.query({
this.$apollo
.query({
query: ME_QUERY,
fetchPolicy: 'network-only',
}).then(() => this.$router.push('/'));
})
.then(() => this.$router.push('/'));
}
},
}).catch(({message}) => {
})
.catch(({ message }) => {
if (message.indexOf('invalid_coupon') > -1) {
this.couponErrors = ['Der angegebene Coupon-Code ist ungültig.'];
} else {
this.couponErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.'];
this.couponErrors = [
'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.',
];
}
})
.finally(() => {
@ -163,32 +157,31 @@
});
},
},
});
});
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.text-link {
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
}
.actions {
.actions {
&__reset {
display: inline-block;
margin-left: $large-spacing;
}
}
}
.get-license {
margin-top: $large-spacing
}
.get-license {
margin-top: $large-spacing;
}
.license-links {
.license-links {
&__item {
margin-bottom: $medium-spacing;
}
}
}
</style>

View File

@ -1,8 +1,6 @@
<template>
<div class="module-visibility">
<h1 class="module-visibility__page-title">
Sichtbarkeit
</h1>
<h1 class="module-visibility__page-title">Sichtbarkeit</h1>
<div class="module-visibility__section">
<p class="module-visibility__paragraph">
Wollen Sie die angepasste Sichtbarkeit (
@ -18,44 +16,30 @@
class="skillbox-input skillbox-dropdown module-visibility__dropdown"
@change="select($event.target.value)"
>
<option
value=""
selected
>
-
</option>
<option
:value="schoolClass.id"
v-for="schoolClass in schoolClasses"
:key="schoolClass.id"
>
<option value="" selected>-</option>
<option :value="schoolClass.id" v-for="schoolClass in schoolClasses" :key="schoolClass.id">
{{ schoolClass.name }}
</option>
</select>
für {{ currentClassName }} übernehmen.
</div>
<div class="module-visibility__section">
<a
class="button button--primary"
data-cy="save-visibility-button"
@click="sync"
>Anpassungen übernehmen</a>
<a class="button button--primary" data-cy="save-visibility-button" @click="sync">Anpassungen übernehmen</a>
</div>
</div>
</template>
<script>
import me from '@/mixins/me';
import me from '@/mixins/me';
import SYNC_VISIBILITY_MUTATION from '@/graphql/gql/mutations/syncModuleVisibility.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery';
import {MODULE_PAGE} from '@/router/module.names';
import SYNC_VISIBILITY_MUTATION from '@/graphql/gql/mutations/syncModuleVisibility.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery';
import { MODULE_PAGE } from '@/router/module.names';
import {defineAsyncComponent} from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EyeIcon'));
export default {
import { defineAsyncComponent } from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/EyeIcon'));
export default {
mixins: [me],
components: {
EyeIcon,
@ -69,11 +53,11 @@
computed: {
schoolClasses() {
return this.me.schoolClasses.filter(schoolClass => schoolClass.id !== this.me.selectedClass.id);
return this.me.schoolClasses.filter((schoolClass) => schoolClass.id !== this.me.selectedClass.id);
},
slug() {
return this.$route.params.slug;
}
},
},
methods: {
@ -83,7 +67,8 @@
sync() {
if (this.selectedClassId) {
const slug = this.slug;
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: SYNC_VISIBILITY_MUTATION,
variables: {
input: {
@ -100,25 +85,25 @@
},
},
],
},
).then(() => {
})
.then(() => {
this.$router.push({
name: MODULE_PAGE,
params: {
slug
}
slug,
},
});
});
}
},
},
};
};
</script>
<style scoped lang="scss">
@import '~styles/_helpers';
@import '~styles/_helpers';
.module-visibility {
.module-visibility {
@include settings-page;
margin: 0 auto;
@ -140,5 +125,5 @@
@include regular-text;
font-weight: 600;
}
}
}
</style>

View File

@ -1,23 +1,19 @@
<template>
<div>
<logo class="onboarding__logo" />
<h1 class="onboarding__heading">
Herzlich willkommen!
</h1>
<h1 class="onboarding__heading">Herzlich willkommen!</h1>
<p class="onboarding__claim">
Schauen Sie sich die Einführung an und lernen Sie {{ $flavor.textAppName }} kennen.
</p>
<p class="onboarding__claim">Schauen Sie sich die Einführung an und lernen Sie {{ $flavor.textAppName }} kennen.</p>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue';
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
import { defineAsyncComponent } from 'vue';
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Logo'));
export default {
export default {
components: {
Logo
Logo,
},
};
};
</script>

View File

@ -140,8 +140,7 @@ export default {
't t'
'd d'
'm m';
}
}
}}
&__back {
grid-area: b;
@ -155,10 +154,8 @@ export default {
grid-area: a;
display: flex;
@include desktop {
justify-self: end;
}
}
@include desktop {justify-self: end;
}}
&__more {
display: none;
@ -198,17 +195,15 @@ export default {
display: flex;
flex-direction: column;
margin-bottom: $medium-spacing;
@include desktop {
flex-direction: row-reverse;
align-items: center;
margin-bottom: $medium-spacing;@include desktop {
flex-direction: row-reverse;align-items: center;
margin-bottom: 0;
}
justify-content: flex-start;
position: relative;
& > :first-child {
margin-bottom: $medium-spacing;
& > :first-child {margin-bottom: $medium-spacing;
@include desktop {
margin-left: $large-spacing;
margin-bottom: 0;
@ -216,11 +211,9 @@ export default {
}
& > :nth-child(2) {
@include desktop {
margin-left: $large-spacing;
}
}
@include desktop {margin-left: $large-spacing;
}
}}
&__content {
display: flex;

View File

@ -1,23 +1,17 @@
<template>
<content-block-form
:content-block="roomEntry"
:features="features"
v-if="roomEntry.id"
@save="save"
@back="goBack"
/>
<content-block-form :content-block="roomEntry" :features="features" v-if="roomEntry.id" @save="save" @back="goBack" />
</template>
<script>
import {defineComponent} from 'vue';
import ROOM_ENTRY_QUERY from 'gql/queries/roomEntryQuery.gql';
import ROOM_ENTRY_FRAGMENT from 'gql/fragments/roomEntryParts.gql';
import UPDATE_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/updateRoomEntry.gql';
import ROOM_ENTRY_QUERY from 'gql/queries/roomEntryQuery.gql';
import ROOM_ENTRY_FRAGMENT from 'gql/fragments/roomEntryParts.gql';
import UPDATE_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/updateRoomEntry.gql';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import {ROOMS_FEATURE_SET} from '@/consts/features.consts';
import {ROOM_PAGE} from '@/router/room.names';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import { ROOMS_FEATURE_SET } from '@/consts/features.consts';
import { ROOM_PAGE } from '@/router/room.names';
export default defineComponent( {
props: {
@ -30,6 +24,7 @@
required: true
}
},
},
components: {
ContentBlockForm,
@ -50,10 +45,10 @@
query: ROOM_ENTRY_QUERY,
variables() {
return {
slug: this.entrySlug
slug: this.entrySlug,
};
}
}
},
},
},
methods: {
@ -61,50 +56,55 @@
this.$router.push({
name: ROOM_PAGE,
params: {
slug: this.slug
}
slug: this.slug,
},
});
},
save({title, contents}) {
save({ title, contents }) {
const entry = {
slug: this.roomEntry.slug,
title,
contents,
};
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: UPDATE_ROOM_ENTRY_MUTATION,
variables: {
input: {
roomEntry: entry,
}
},
update: (store, {data: {updateRoomEntry: {roomEntry}}}) => {
},
update: (
store,
{
data: {
updateRoomEntry: { roomEntry },
},
}
) => {
try {
const fragment = ROOM_ENTRY_FRAGMENT;
const id = store.identify(roomEntry);
const cachedEntry = store.readQuery({fragment, id});
const cachedEntry = store.readQuery({ fragment, id });
const data = Object.assign({}, cachedEntry, roomEntry);
store.writeFragment({
id,
fragment,
data
data,
});
} catch (e) {
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
}
}).then(() => {
},
})
.then(() => {
this.goBack();
});
}
},
} );
},
});
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
</style>

View File

@ -1,28 +1,23 @@
<template>
<content-block-form
:content-block="roomEntry"
:features="features"
@save="save"
@back="goBack"
/>
<content-block-form :content-block="roomEntry" :features="features" @save="save" @back="goBack" />
</template>
<script>
import {defineComponent} from 'vue';
import { defineComponent } from 'vue';
import NEW_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/addRoomEntry.gql';
import ROOM_ENTRIES_QUERY from '@/graphql/gql/queries/roomEntriesQuery.gql';
import NEW_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/addRoomEntry.gql';
import ROOM_ENTRIES_QUERY from '@/graphql/gql/queries/roomEntriesQuery.gql';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import {ROOMS_FEATURE_SET} from '@/consts/features.consts';
import {ROOM_PAGE} from '@/router/room.names';
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
import { ROOMS_FEATURE_SET } from '@/consts/features.consts';
import { ROOM_PAGE } from '@/router/room.names';
export default defineComponent( {
export default defineComponent({
props: {
slug: {
type: String,
required: true
}
required: true,
},
},
components: {
@ -44,64 +39,66 @@
this.$router.push({
name: ROOM_PAGE,
params: {
slug: this.slug
}
slug: this.slug,
},
});
},
save({title, contents}) {
save({ title, contents }) {
const entry = {
title,
contents,
roomSlug: this.slug
roomSlug: this.slug,
};
this.$apollo.mutate({
this.$apollo
.mutate({
mutation: NEW_ROOM_ENTRY_MUTATION,
variables: {
input: {
roomEntry: entry,
}
},
update: (store, {data: {addRoomEntry: {roomEntry}}}) => {
},
update: (
store,
{
data: {
addRoomEntry: { roomEntry },
},
}
) => {
try {
const query = ROOM_ENTRIES_QUERY;
const variables = {slug: this.slug};
const {room} = store.readQuery({query, variables});
const variables = { slug: this.slug };
const { room } = store.readQuery({ query, variables });
if (room && room.roomEntries) {
const newEdge ={
const newEdge = {
node: roomEntry,
__typename: 'RoomEntryNodeEdge'
__typename: 'RoomEntryNodeEdge',
};
const edges = [
newEdge,
...room.roomEntries.edges
];
const edges = [newEdge, ...room.roomEntries.edges];
const data = {
room: {
...room,
roomEntries: {
...room.roomEntries,
edges
}
}
edges,
},
},
};
store.writeQuery({query, variables, data});
store.writeQuery({ query, variables, data });
}
} catch (e) {
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
}
}).then(() => {
},
})
.then(() => {
this.goBack();
});
}
},
} );
},
});
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
</style>

View File

@ -17,12 +17,7 @@
:key="index"
/>
</div>
<router-link
:to="topicRoute"
class="button"
>
Alle {{ $flavor.textModules }} anzeigen
</router-link>
<router-link :to="topicRoute" class="button"> Alle {{ $flavor.textModules }} anzeigen </router-link>
</div>
<div class="start-page__news news" data-cy="news-teasers" v-if="!me.readOnly">
<h2 class="start-page__heading">News</h2>

View File

@ -14,10 +14,9 @@
<script>
import '@/styles/survey.modern.css';
import '@/styles/survey.reset.css';
import { css } from '@/survey.config';
import '@/styles/survey.reset.css';import { css } from '@/survey.config';
import gql from 'graphql-tag';
import { Model, StylesManager } from 'survey-knockout';
import { Model , StylesManager } from 'survey-knockout';
// we are switching to the knockout version because the Vue version only works with Vue 2 (as of July 2022)
import SURVEY_QUERY from '@/graphql/gql/queries/surveyQuery.gql';
@ -35,7 +34,8 @@ const Solution = defineAsyncComponent(() =>
*/ '@/components/content-blocks/Solution'
)
);
StylesManager.applyTheme('modern');
StylesManager.applyTheme('modern')
);
const MODULE_QUERY = gql`
query ModuleSolutions($slug: String) {
@ -56,8 +56,7 @@ export default {
return {
survey: this.initSurvey(),
currentPage: null,
surveyData: null,
title: '',
surveyData: null,title: '',
module: {},
completed: false,
me: {
@ -115,15 +114,14 @@ export default {
}
},
destroyed() {},
methods: {
destroyed() {},methods: {
initSurvey(data, answers) {
let survey = new Model(data);
const flatAnswers = {};
for (let k in answers) {
flatAnswers[k] = answers[k].answer;
}
if (Object.keys(flatAnswers).length > 0) {
// answers are not empty
survey.data = flatAnswers;
@ -215,6 +213,7 @@ export default {
survey.locale = 'de';
survey.showProgressBar = 'bottom';
survey.pageNextText = 'Speichern & Weiter';
survey.render('survey');
return survey;
},

View File

@ -5,21 +5,14 @@
</div>
<div class="topic__content">
<h1
data-cy="topic-title"
class="topic__title"
>
<h1 data-cy="topic-title" class="topic__title">
{{ topic.title }}
</h1>
<p class="topic__teaser">
{{ topic.teaser }}
</p>
<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" />
<span class="topic__link-description">Video schauen</span>
</div>
@ -34,10 +27,7 @@
</a>
</div>
<div class="topic__modules">
<module-teaser
v-for="module in modules"
v-bind="module"
:key="module.slug"
<module-teaser v-for="module in modules" v-bind="module" :key="module.slug"
/>
</div>
</div>
@ -45,19 +35,18 @@
</template>
<script>
import ModuleTeaser from '@/components/modules/ModuleTeaser.vue';
import {defineAsyncComponent} from 'vue';
import TOPIC_QUERY from '@/graphql/gql/queries/topicQuery.gql';
import me from '@/mixins/me';
import TopicNavigation from '@/components/book-navigation/TopicNavigation';
import ModuleTeaser from '@/components/modules/ModuleTeaser.vue';
import { defineAsyncComponent } from 'vue';
import TOPIC_QUERY from '@/graphql/gql/queries/topicQuery.gql';
import me from '@/mixins/me';
import TopicNavigation from '@/components/book-navigation/TopicNavigation';
import UPDATE_LAST_TOPIC_MUTATION from '@/graphql/gql/mutations/updateLastTopic.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
const PlayIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Play'));
const BulbIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/BulbIcon'));
export default {
import UPDATE_LAST_TOPIC_MUTATION from '@/graphql/gql/mutations/updateLastTopic.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
const PlayIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/Play'));
const BulbIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/BulbIcon'));
export default {
mixins: [me],
components: {
TopicNavigation,
@ -104,7 +93,8 @@
},
mounted() {
if (!this.topic.id) { // component was loaded before topic, apollo not ready yet
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);
@ -126,10 +116,17 @@
id: topicId,
},
},
update(store, {data: {updateLastTopic: {topic}}}) {
update(
store,
{
data: {
updateLastTopic: { topic },
},
}
) {
if (topic) {
const query = ME_QUERY;
const {me} = store.readQuery({query});
const { me } = store.readQuery({ query });
if (me) {
const data = {
me: {
@ -137,21 +134,21 @@
lastTopic: topic,
},
};
store.writeQuery({query, data});
store.writeQuery({ query, data });
}
}
},
});
},
},
};
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.topic {
.topic {
display: grid;
padding: $large-spacing 0;
grid-template-columns: 1fr;
@ -213,5 +210,5 @@
grid-template-columns: repeat(3, minmax(auto, 380px));
}
}
}
}
</style>

View File

@ -1,6 +1,6 @@
// adapted from
// https://stackoverflow.com/questions/41791193/vuejs-reactive-binding-for-a-plugin-how-to/41801107#41801107
import {reactive, App} from 'vue';
import { reactive, App } from 'vue';
class ModalStore {
data: any;
@ -18,19 +18,19 @@ class ModalStore {
}
interface Modal {
state: any,
component: string,
payload?: any,
confirm: (res: any) => void,
open: (component: string, payload?: any) => Promise<(resolve: () => any, reject: () => any) => void>,
cancel: () => void,
_resolve: (r?: any) => any,
_reject: (r?: any) => any
state: any;
component: string;
payload?: any;
confirm: (res: any) => void;
open: (component: string, payload?: any) => Promise<(resolve: () => any, reject: () => any) => void>;
cancel: () => void;
_resolve: (r?: any) => any;
_reject: (r?: any) => any;
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$modal: Modal
$modal: Modal;
}
}

View File

@ -1,4 +1,4 @@
import {RouteLocationNormalized} from "vue-router";
import { RouteLocationNormalized } from 'vue-router';
function getCookieValue(cookieName: string) {
// https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript

View File

@ -1,4 +1,4 @@
import {createRouter, createWebHistory} from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router';
import moduleRoutes from './module.routes';
import portfolioRoutes from './portfolio.routes';
@ -7,19 +7,19 @@ import meRoutes from './me.routes';
import authRoutes from './auth.routes';
import roomRoutes from './room.routes';
import {store} from '@/store';
import {LAYOUT_SIMPLE} from '@/router/core.constants';
import {EMAIL_NOT_VERIFIED_STATE, NO_VALID_LICENSE_STATE, SUCCESS_STATE} from './oauth.names';
import { store } from '@/store';
import { LAYOUT_SIMPLE } from '@/router/core.constants';
import { EMAIL_NOT_VERIFIED_STATE, NO_VALID_LICENSE_STATE, SUCCESS_STATE } from './oauth.names';
import start from '@/pages/start';
const instrument = () => import(/* webpackChunkName: "instruments" */'@/pages/instrument');
const instrumentOverview = () => import(/* webpackChunkName: "instruments" */'@/pages/instrumentOverview');
const instrument = () => import(/* webpackChunkName: "instruments" */ '@/pages/instrument');
const instrumentOverview = () => import(/* webpackChunkName: "instruments" */ '@/pages/instrumentOverview');
const article = () => import(/* webpackChunkName: "news" */'@/pages/article');
const news = () => import(/* webpackChunkName: "news" */'@/pages/news');
const article = () => import(/* webpackChunkName: "news" */ '@/pages/article');
const news = () => import(/* webpackChunkName: "news" */ '@/pages/news');
const surveyPage = () => import(/* webpackChunkName: "survey" */'@/pages/survey');
const surveyPage = () => import(/* webpackChunkName: "survey" */ '@/pages/survey');
const styleGuidePage = () => import('@/pages/styleguide');
const joinClass = () => import('@/pages/joinClass');
@ -50,22 +50,22 @@ const routes = [
...onboardingRoutes,
...portfolioRoutes,
...meRoutes,
{path: '/article/:slug', name: 'article', component: article, meta: {layout: LAYOUT_SIMPLE}},
{ path: '/article/:slug', name: 'article', component: article, meta: { layout: LAYOUT_SIMPLE } },
{
path: '/instruments/',
name: 'instrument-overview',
component: instrumentOverview,
},
{path: '/instrument/:slug', name: 'instrument', component: instrument, meta: {layout: LAYOUT_SIMPLE}},
{path: '/submission/:id', name: 'submission', component: submission, meta: {layout: LAYOUT_SIMPLE}},
{path: '/topic/:topicSlug', name: 'topic', component: topic, alias: '/book/topic/:topicSlug'},
{path: '/join-class', name: 'join-class', component: joinClass, meta: {layout: LAYOUT_SIMPLE}},
{ path: '/instrument/:slug', name: 'instrument', component: instrument, meta: { layout: LAYOUT_SIMPLE } },
{ path: '/submission/:id', name: 'submission', component: submission, meta: { layout: LAYOUT_SIMPLE } },
{ path: '/topic/:topicSlug', name: 'topic', component: topic, alias: '/book/topic/:topicSlug' },
{ path: '/join-class', name: 'join-class', component: joinClass, meta: { layout: LAYOUT_SIMPLE } },
{
path: '/survey/:id',
component: surveyPage,
name: 'survey',
props: true,
meta: {layout: LAYOUT_SIMPLE},
meta: { layout: LAYOUT_SIMPLE },
},
{
path: '/news',
@ -74,7 +74,7 @@ const routes = [
},
{
path: '/oauth-redirect',
redirect: to => {
redirect: (to) => {
switch (to.query.state) {
case EMAIL_NOT_VERIFIED_STATE:
return '/verify-email';
@ -92,15 +92,15 @@ const routes = [
}
},
},
{path: '/styleguide', component: styleGuidePage},
{ path: '/styleguide', component: styleGuidePage },
{
path: '/not-found',
name: 'not-found',
...notFoundRoute
...notFoundRoute,
},
{
path: '/:pathMatch(.*)*',
...notFoundRoute
...notFoundRoute,
},
];
@ -113,7 +113,7 @@ const router = createRouter({
if (savedPosition) {
return savedPosition;
}
return {left: 0, top: 0};
return { left: 0, top: 0 };
},
});
@ -122,4 +122,4 @@ router.afterEach(() => {
store.dispatch('showMobileNavigation', false);
});
export {router, postLoginRedirectUrlKey};
export { router, postLoginRedirectUrlKey };

View File

@ -1,7 +1,7 @@
import { ONBOARDING_STEP_1, ONBOARDING_STEP_2, ONBOARDING_STEP_3 } from '@/router/onboarding.names';
const onboarding = () => import('@/pages/onboarding.vue');
const onboardingStart = () => import('@/pages/onboarding/start.vue');
const onboarding = () => import( '@/pages/onboarding.vue');
const onboardingStart = () => import( '@/pages/onboarding/start.vue');
const onboardingStep1 = () => import(/* webpackChunkName: "onboarding" */ '@/pages/onboarding/step1.vue');
const onboardingStep2 = () => import(/* webpackChunkName: "onboarding" */ '@/pages/onboarding/step2.vue');
const onboardingStep3 = () => import(/* webpackChunkName: "onboarding" */ '@/pages/onboarding/step3.vue');

View File

@ -10,4 +10,3 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>;
export default component;
}

Some files were not shown because too many files have changed in this diff Show More