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,9 +219,11 @@ 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.get('.snapshots__snapshot').should('have.length', 1);
});
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,23 +13,25 @@ describe('Room Team Management - Read only', () => {
},
},
RoomsQuery: {
rooms: [{
id: '',
slug: 'some-room',
title: 'some room',
entryCount: 3,
appearance: 'red',
description: 'some description',
schoolClass: {
id: SELECTED_CLASS_ID,
name: 'bla',
rooms: [
{
id: '',
slug: 'some-room',
title: 'some room',
entryCount: 3,
appearance: 'red',
description: 'some description',
schoolClass: {
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,88 +15,88 @@
const AddPointer = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddPointer'));
export default {
props: {
where: {
type: Object,
validator(prop) {
return Object.prototype.hasOwnProperty.call(prop, 'after' )
|| Object.prototype.hasOwnProperty.call(prop, 'parent');
}
export default {
props: {
where: {
type: Object,
validator(prop) {
return (
Object.prototype.hasOwnProperty.call(prop, 'after') || Object.prototype.hasOwnProperty.call(prop, 'parent')
);
},
},
},
components: {
AddPointer
components: {
AddPointer,
},
computed: {
parent() {
return this.where.parent;
},
computed: {
parent() {
return this.where.parent;
},
after() {
return this.where.after;
},
isObjectiveGroup() {
return this.parent && this.parent.__typename === 'ObjectiveGroupNode';
},
slug() {
return this.$route.params.slug;
}
after() {
return this.where.after;
},
isObjectiveGroup() {
return this.parent && this.parent.__typename === 'ObjectiveGroupNode';
},
slug() {
return this.$route.params.slug;
},
},
methods: {
addContent() {
if (this.isObjectiveGroup) {
this.$modal.open('new-objective-wizard', {parent: this.parent.id});
methods: {
addContent() {
if (this.isObjectiveGroup) {
this.$modal.open('new-objective-wizard', { parent: this.parent.id });
} else {
let route;
if (this.after && this.after.id) {
route = {
name: CREATE_CONTENT_BLOCK_AFTER_PAGE,
params: {
after: this.after.id,
slug: this.slug,
},
};
} else {
let route;
if (this.after && this.after.id) {
route = {
name: CREATE_CONTENT_BLOCK_AFTER_PAGE,
params: {
after: this.after.id,
slug: this.slug
}
};
} else {
route = {
name: CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE,
params: {
parent: this.parent.id
}
};
}
this.$router.push(route);
route = {
name: CREATE_CONTENT_BLOCK_UNDER_PARENT_PAGE,
params: {
parent: this.parent.id,
},
};
}
this.$router.push(route);
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.add-content {
display: none;
position: relative;
@include desktop {
display: flex;
}
z-index: 1;
justify-content: flex-end;
&__button {
margin-right: -85px;
cursor: pointer;
display: inline-grid;
}
&__icon {
width: 40px;
fill: $color-silver-dark;
}
.add-content {
display: none;
position: relative;
@include desktop {
display: flex;
}
z-index: 1;
justify-content: flex-end;
&__button {
margin-right: -85px;
cursor: pointer;
display: inline-grid;
}
&__icon {
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,32 +8,32 @@
import {defineAsyncComponent} from 'vue';
const AddIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon'));
export default {
props: ['index'],
export default {
props: ['index'],
components: {
AddIcon
}
};
components: {
AddIcon,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.add-content-element {
display: flex;
justify-content: center;
border-bottom: 2px solid $color-silver-dark;
margin-bottom: 21px + 25px;
cursor: pointer;
.add-content-element {
display: flex;
justify-content: center;
border-bottom: 2px solid $color-silver-dark;
margin-bottom: 21px + 25px;
cursor: pointer;
&__icon {
width: 40px;
height: 40px;
fill: $color-silver-dark;
margin-bottom: -21px;
background-color: $color-white;
border-radius: 50px;
}
&__icon {
width: 40px;
height: 40px;
fill: $color-silver-dark;
margin-bottom: -21px;
background-color: $color-white;
border-radius: 50px;
}
}
</style>

View File

@ -14,66 +14,69 @@
import {defineAsyncComponent} from 'vue';
const AddIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon.vue'));
export default {
props: {
route: {
type: String,
default: null
},
reverse: { // use reverse colors
type: Boolean,
default: false
},
click: {
type: Function,
default: null
}
export default {
props: {
route: {
type: String,
default: null,
},
reverse: {
// use reverse colors
type: Boolean,
default: false,
},
click: {
type: Function,
default: null,
},
},
components: {
AddIcon
},
components: {
AddIcon,
},
computed: {
component() {
// only use the router link if the route prop is provided, otherwise render a normal anchor tag
return this.route ? 'router-link' : 'a';
},
properties() {
return this.route ? {
to: this.route,
tag: 'div'
} : {};
}
computed: {
component() {
// only use the router link if the route prop is provided, otherwise render a normal anchor tag
return this.route ? 'router-link' : 'a';
},
};
properties() {
return this.route
? {
to: this.route,
tag: 'div',
}
: {};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.add-widget {
display: none;
align-items: center;
justify-content: center;
@include widget-shadow;
cursor: pointer;
.add-widget {
display: none;
align-items: center;
justify-content: center;
@include widget-shadow;
cursor: pointer;
@include desktop {
display: flex;
}
&__add {
width: 80px;
fill: $color-silver-dark;
}
&--reverse {
@include widget-shadow-reverse;
}
&--reverse &__add {
fill: white;
}
@include desktop {
display: flex;
}
&__add {
width: 80px;
fill: $color-silver-dark;
}
&--reverse {
@include widget-shadow-reverse;
}
&--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,25 +13,25 @@
const ChevronLeft = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronLeft'));
export default {
props: {
title: {
type: String,
default: '',
},
type: {
type: String,
default: 'topic',
},
slug: {
type: String,
default: '',
},
export default {
props: {
title: {
type: String,
default: '',
},
type: {
type: String,
default: 'topic',
},
slug: {
type: String,
default: '',
},
},
components: {
ChevronLeft,
},
components: {
ChevronLeft,
},
computed: {
to() {
@ -68,9 +65,9 @@
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.back-link {
@include regular-text;
}
.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,69 +19,69 @@
import {defineAsyncComponent} from 'vue';
const Tick = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Tick'));
export default {
props: ['selectedColor'],
export default {
props: ['selectedColor'],
components: {
Tick
},
components: {
Tick,
},
data() {
return {
colors: [
{
name: 'yellow'
},
{
name: 'blue'
},
{
name: 'red'
},
{
name: 'green'
}
]
};
},
};
data() {
return {
colors: [
{
name: 'yellow',
},
{
name: 'blue',
},
{
name: 'red',
},
{
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 {
display: flex;
.color-chooser {
display: flex;
&__color-wrapper {
margin-right: 10px;
border-radius: 50px;
padding: 10px;
&__color-wrapper {
margin-right: 10px;
border-radius: 50px;
padding: 10px;
&--selected {
border: 1px solid $color-charcoal-dark;
}
}
&__selected-icon {
width: 17px;
fill: $color-charcoal-dark;
}
&__color {
width: 46px;
height: 46px;
border-radius: 23px;
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid
}
justify-items: center;
align-items: center;
@include skillbox-colors;
&--selected {
border: 1px solid $color-charcoal-dark;
}
}
&__selected-icon {
width: 17px;
fill: $color-charcoal-dark;
}
&__color {
width: 46px;
height: 46px;
border-radius: 23px;
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid;
}
justify-items: center;
align-items: center;
@include skillbox-colors;
}
}
</style>

View File

@ -1,37 +1,38 @@
<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 {
components: {
Modal,
InfogramBlock,
GeniallyBlock
export default {
components: {
Modal,
InfogramBlock,
GeniallyBlock,
},
computed: {
id() {
return this.$store.state.infographic.id;
},
computed: {
id() {
return this.$store.state.infographic.id;
},
type() {
return this.$store.state.infographic.type;
},
value() {
return {
id: this.id
};
}
}
};
type() {
return this.$store.state.infographic.type;
},
value() {
return {
id: this.id,
};
},
},
};
</script>

View File

@ -1,140 +1,127 @@
<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 {
mixins: [openSidebar, me],
export default {
mixins: [openSidebar, me],
components: {
ContentNavigation,
UserWidget,
CurrentClass,
Hamburger,
},
};
components: {
ContentNavigation,
UserWidget,
CurrentClass,
Hamburger,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.header-bar {
display: flex;
flex-direction: row;
@supports (display: grid) {
display: grid;
}
align-items: center;
justify-content: space-between;
background-color: $color-white;
grid-auto-rows: 50px;
width: 100%;
max-width: 100vw;
.header-bar {
display: flex;
flex-direction: row;
@supports (display: grid) {
display: grid;
}
align-items: center;
justify-content: space-between;
background-color: $color-white;
grid-auto-rows: 50px;
width: 100%;
max-width: 100vw;
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
@include desktop {
grid-template-columns: 50px 1fr auto;
grid-template-rows: 50px;
grid-auto-rows: auto;
}
@include desktop {
grid-template-columns: 50px 1fr auto;
grid-template-rows: 50px;
grid-auto-rows: auto;
}
/*
/*
* For IE10+
*/
-ms-grid-columns: 1fr 1fr 1fr;
-ms-grid-rows: 50px 50px;
-ms-grid-columns: 1fr 1fr 1fr;
-ms-grid-rows: 50px 50px;
/*
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-column: 1;
-ms-grid-row-align: center;
}
& > :nth-child(1) {
-ms-grid-column: 1;
-ms-grid-row-align: center;
}
&__content-navigation {
grid-column: 2;
justify-content: space-between;
}
&__content-navigation {
grid-column: 2;
justify-content: space-between;
}
&__sidebar-link {
padding: $small-spacing;
cursor: pointer;
}
&__sidebar-link {
padding: $small-spacing;
cursor: pointer;
}
&__sidebar-icon {
width: 30px;
height: 30px;
}
&__sidebar-icon {
width: 30px;
height: 30px;
}
/*
/*
* For IE10+
*/
& > :nth-child(3) {
-ms-grid-column: 3;
-ms-grid-row-align: center;
-ms-grid-column-align: end;
& > :nth-child(3) {
-ms-grid-column: 3;
-ms-grid-row-align: center;
-ms-grid-column-align: end;
justify-self: end;
}
& > :nth-child(4) {
-ms-grid-row: 2;
-ms-grid-column: 1;
-ms-grid-column-span: 3;
}
justify-self: end;
}
.user-header {
display: flex;
& > :nth-child(4) {
-ms-grid-row: 2;
-ms-grid-column: 1;
-ms-grid-column-span: 3;
}
}
&__current-class {
margin-right: $large-spacing;
}
.user-header {
display: flex;
&__sidebar-link {
cursor: pointer;
display: none;
&__current-class {
margin-right: $large-spacing;
}
@include desktop {
display: flex;
}
&__sidebar-link {
cursor: pointer;
display: none;
@include desktop {
display: flex;
}
}
}
</style>

View File

@ -10,67 +10,66 @@
</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 {
props: ['text'],
export default {
props: ['text'],
components: {
InfoIcon
}
};
components: {
InfoIcon,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.helpful-tooltip {
position: relative;
.helpful-tooltip {
position: relative;
&__icon {
width: 20px;
height: 20px;
fill: $color-silver-dark;
}
&__icon {
width: 20px;
height: 20px;
fill: $color-silver-dark;
}
&__tooltip {
visibility: hidden;
&__tooltip {
visibility: hidden;
position: absolute;
left: 30px;
top: 0px;
width: 400px;
}
&__text {
display: inline-table;
width: auto;
background-color: $color-white;
border: 1px solid $color-silver-dark;
border-radius: 5px;
padding: $small-spacing;
@include small-text;
&::before {
content: '';
position: absolute;
left: 30px;
top: 0px;
width: 400px;
}
&__text {
display: inline-table;
width: auto;
left: 0;
top: 18px;
margin-left: -1px;
border-left: 1px solid $color-silver-dark;
border-top: 1px solid $color-silver-dark;
background-color: $color-white;
border: 1px solid $color-silver-dark;
border-radius: 5px;
padding: $small-spacing;
@include small-text;
&::before {
content: '';
position: absolute;
left: 0;
top: 18px;
margin-left: -1px;
border-left: 1px solid $color-silver-dark;
border-top: 1px solid $color-silver-dark;
background-color: $color-white;
width: 10px;
height: 10px;
transform: rotate(-45deg) translateY(-50%);
}
}
&:hover &__tooltip {
visibility: visible;
width: 10px;
height: 10px;
transform: rotate(-45deg) translateY(-50%);
}
}
&:hover &__tooltip {
visibility: visible;
}
}
</style>

View File

@ -1,58 +1,54 @@
<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 {
props: {
loading: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
}
export default {
props: {
loading: {
type: Boolean,
default: false,
},
components: {
LoadingIcon
}
};
disabled: {
type: Boolean,
default: false,
},
label: {
type: String,
default: '',
},
},
components: {
LoadingIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.loading-button {
height: 52px;
min-width: 100px;
display: inline-flex;
justify-content: center;
.loading-button {
height: 52px;
min-width: 100px;
display: inline-flex;
justify-content: center;
&__icon {
width: 14px;
height: 14px;
margin: 0 auto;
@include spin;
fill: $color-brand;
}
&__icon {
width: 14px;
height: 14px;
margin: 0 auto;
@include spin;
fill: $color-brand;
}
}
</style>

View File

@ -4,66 +4,60 @@
<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 {
mixins: [me, openSidebar],
export default {
mixins: [me, openSidebar],
components: {
Logo,
Hamburger,
UserWidget,
components: {
Logo,
Hamburger,
UserWidget,
},
methods: {
showMobileNavigation() {
this.$store.dispatch('showMobileNavigation', true);
},
methods: {
showMobileNavigation() {
this.$store.dispatch('showMobileNavigation', true);
},
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.mobile-header {
justify-content: space-between;
align-items: center;
.mobile-header {
justify-content: space-between;
align-items: center;
display: flex;
display: flex;
@include desktop {
display: none;
}
padding: 0 $medium-spacing;
&__hamburger {
width: 30px;
height: 30px;
fill: $color-silver-dark;
}
@include desktop {
display: none;
}
padding: 0 $medium-spacing;
&__hamburger {
width: 30px;
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,160 +28,159 @@
</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 {
props: {
hideHeader: {
type: Boolean,
default: false
},
fullscreen: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
}
export default {
props: {
hideHeader: {
type: Boolean,
default: false,
},
components: {
Cross
fullscreen: {
type: Boolean,
default: false,
},
small: {
type: Boolean,
default: false,
},
},
methods: {
hideModal() {
this.$store.dispatch('hideModal');
}
}
};
components: {
Cross,
},
methods: {
hideModal() {
this.$store.dispatch('hideModal');
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.modal {
align-self: center;
justify-self: center;
width: 700px;
height: 80vh;
background-color: $color-white;
border-radius: 12px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
border: 1px solid $color-silver-light;
display: -ms-grid;
.modal {
align-self: center;
justify-self: center;
width: 700px;
height: 80vh;
background-color: $color-white;
border-radius: 12px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
border: 1px solid $color-silver-light;
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
grid-template-rows: auto 1fr 65px;
grid-template-areas: 'header' 'body' 'footer';
-ms-grid-rows: auto 1fr 65px;
position: relative;
&__backdrop {
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid;
}
grid-template-rows: auto 1fr 65px;
grid-template-areas: "header" "body" "footer";
-ms-grid-rows: auto 1fr 65px;
position: relative;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba($color-white, 0.8);
z-index: 90;
}
&__backdrop {
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid;
}
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba($color-white, 0.8);
z-index: 90;
}
&__header {
grid-area: header;
-ms-grid-row: 1;
padding: 10px $modal-lateral-padding;
border-bottom: 1px solid $color-silver-light;
}
&__header {
grid-area: header;
-ms-grid-row: 1;
padding: 10px $modal-lateral-padding;
border-bottom: 1px solid $color-silver-light;
}
&__body {
grid-area: body;
-ms-grid-row: 2;
padding: 10px $modal-lateral-padding;
overflow: auto;
box-sizing: border-box;
min-height: 30vh;
}
&__body {
grid-area: body;
-ms-grid-row: 2;
padding: 10px $modal-lateral-padding;
overflow: auto;
box-sizing: border-box;
min-height: 30vh;
}
&__close-button {
display: none;
cursor: pointer;
position: absolute;
right: 15px;
top: 15px;
background: rgba($color-white, 0.5);
border-radius: 40px;
padding: 10px;
align-content: center;
}
&__close-button {
&__footer {
grid-area: footer;
-ms-grid-row: 3;
border-top: 1px solid $color-silver-light;
padding: 16px $modal-lateral-padding;
}
$parent: &;
&--hide-header {
grid-template-rows: 1fr 65px;
grid-template-areas: 'body' 'footer';
#{$parent}__header {
display: none;
cursor: pointer;
position: absolute;
right: 15px;
top: 15px;
background: rgba($color-white, 0.5);
border-radius: 40px;
padding: 10px;
align-content: center;
}
&__footer {
grid-area: footer;
-ms-grid-row: 3;
border-top: 1px solid $color-silver-light;
padding: 16px $modal-lateral-padding;
}
$parent: &;
&--hide-header {
grid-template-rows: 1fr 65px;
grid-template-areas: "body" "footer";
#{$parent}__header {
display: none;
}
#{$parent}__body {
padding: $default-padding;
}
}
&--fullscreen {
width: 95vw;
height: auto;
grid-template-rows: 1fr;
-ms-grid-rows: 1fr;
grid-template-areas: "body";
overflow: hidden;
#{$parent}__footer {
display: none;
}
#{$parent}__body {
padding: 0;
scrollbar-width: none;
margin-right: -5px;
height: auto;
max-height: 95vh;
&::-webkit-scrollbar {
display: none;
}
}
#{$parent}__close-button {
display: flex;
}
}
&--small {
height: auto;
#{$parent}__body {
min-height: 0;
}
#{$parent}__body {
padding: $default-padding;
}
}
&--fullscreen {
width: 95vw;
height: auto;
grid-template-rows: 1fr;
-ms-grid-rows: 1fr;
grid-template-areas: 'body';
overflow: hidden;
#{$parent}__footer {
display: none;
}
#{$parent}__body {
padding: 0;
scrollbar-width: none;
margin-right: -5px;
height: auto;
max-height: 95vh;
&::-webkit-scrollbar {
display: none;
}
}
#{$parent}__close-button {
display: flex;
}
}
&--small {
height: auto;
#{$parent}__body {
min-height: 0;
}
}
}
</style>

View File

@ -1,68 +1,60 @@
<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 {
components: {
WidgetPopover,
Ellipses
},
export default {
components: {
WidgetPopover,
Ellipses,
},
data() {
return {
showMenu: false
};
}
};
data() {
return {
showMenu: false,
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.more-options {
display: flex;
justify-content: flex-end;
.more-options {
display: flex;
justify-content: flex-end;
&__ellipses {
width: 30px;
height: 30px;
fill: $color-charcoal-dark;
margin-top: -7px;
}
&__more-link {
background-color: rgba($color-white, 0.9);
width: 35px;
height: 15px;
border-radius: 15px;
display: flex;
justify-content: center;
}
&__popover {
min-width: 200px;
@include popover-defaults();
}
&__ellipses {
width: 30px;
height: 30px;
fill: $color-charcoal-dark;
margin-top: -7px;
}
&__more-link {
background-color: rgba($color-white, 0.9);
width: 35px;
height: 15px;
border-radius: 15px;
display: flex;
justify-content: center;
}
&__popover {
min-width: 200px;
@include popover-defaults();
}
}
</style>

View File

@ -1,79 +1,74 @@
<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 {
components: {
ArrowUp
},
export default {
components: {
ArrowUp,
},
data() {
return {
scroll: 0
};
},
data() {
return {
scroll: 0,
};
},
mounted() {
let html = document.scrollingElement;
document.body.onscroll = () => {
this.scroll = html.scrollTop;
};
},
mounted() {
let html = document.scrollingElement;
document.body.onscroll = () => {
this.scroll = html.scrollTop;
};
},
unmounted() {
document.body.onscroll = null;
},
unmounted() {
document.body.onscroll = null;
},
methods: {
scrollTop() {
document.scrollingElement.scrollTop = 0;
}
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 {
position: fixed;
right: $large-spacing;
bottom: $large-spacing;
padding: $medium-spacing;
border-radius: 100px;
@include default-box-shadow;
cursor: pointer;
background-color: $color-white;
border: 1px solid $color-silver;
z-index: 2;
&__icon {
width: 50px;
height: 50px;
fill: $color-brand;
}
.scroll-up {
position: fixed;
right: $large-spacing;
bottom: $large-spacing;
padding: $medium-spacing;
border-radius: 100px;
@include default-box-shadow;
cursor: pointer;
background-color: $color-white;
border: 1px solid $color-silver;
z-index: 2;
&__icon {
width: 50px;
height: 50px;
fill: $color-brand;
}
}
.fade-enter-active, .fade-leave-active {
transition: opacity .3s;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to /* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
.fade-enter-from, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View File

@ -1,44 +1,43 @@
<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 {
name: 'StudentSubmissionDocument',
props: ['document'],
components: { DocumentIcon },
export default {
name: 'StudentSubmissionDocument',
props: ['document'],
components: { DocumentIcon },
computed: {
filename() {
return filenameFromUrl(this.document);
}
computed: {
filename() {
return filenameFromUrl(this.document);
},
};
},
};
</script>
<style scoped lang="scss">
.content {
display: flex;
.content {
display: flex;
&__icon {
width: 25px;
align-self: center;
}
&__text {
align-self: center;
padding-left: 5px;
}
&__icon {
width: 25px;
align-self: center;
}
&__text {
align-self: center;
padding-left: 5px;
}
}
</style>

View File

@ -1,82 +1,72 @@
<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 {
props: {
avatarUrl: {
type: String
}
export default {
props: {
avatarUrl: {
type: String,
},
},
emits: ['click'],
emits: ['click'],
components: {
Avatar
emits: ['click'],components: {
Avatar,
},
computed: {
isProfile() {
return this.$route.meta.isProfile;
},
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;
align-items: center;
position: relative;
// todo: do we need the margin right always? just do it where needed --> content block actions and objecives override this
margin-right: $medium-spacing;
&__popover {
top: 40px;
white-space: nowrap;
}
&__name {
padding: 0px $small-spacing;
color: $color-silver-dark;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
// todo: do we need the margin right always? just do it where needed --> content block actions and objecives override this
margin-right: $medium-spacing;
font-family: $sans-serif-font-family;
}
&__popover {
top: 40px;
white-space: nowrap;
}
&__date {
font-family: $sans-serif-font-family;
}
&__name {
padding: 0px $small-spacing;
color: $color-silver-dark;
font-family: $sans-serif-font-family;
}
&__avatar {
width: 30px;
height: 30px;
fill: $color-silver-dark;
cursor: pointer;
}
&__date {
font-family: $sans-serif-font-family;
}
&__avatar {
width: 30px;
height: 30px;
fill: $color-silver-dark;
cursor: pointer;
}
&--is-profile {
& > span {
color: $color-brand;
}
&--is-profile {
& > span {
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,16 +67,13 @@
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"
class="content-navigation__link content-navigation__link--secondary"
@click="close"
>Support
>Support
</a>
</div>
</div>
@ -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,134 +89,135 @@
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
export default {
props: {
isSidebar: {
default: false
}
export default {
props: {
isSidebar: {
default: false,
},
},
mixins: [sidebarMixin, meMixin],
mixins: [sidebarMixin, meMixin],
components: {
TopicNavigation,
Logo
components: {
TopicNavigation,
Logo,
},
computed: {
showPortfolio() {
return this.$flavor.showPortfolio;
},
},
computed: {
showPortfolio() {
return this.$flavor.showPortfolio;
}
methods: {
isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1;
},
methods: {
isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1;
},
isRoomUrl() {
return this.$route.path.indexOf('room') > -1;
},
close() {
this.closeSidebar('navigation');
}
}
};
isRoomUrl() {
return this.$route.path.indexOf('room') > -1;
},
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 {
display: flex;
align-items: center;
.content-navigation {
display: flex;
align-items: center;
&__link {
padding: 0 24px;
@include navigation-link;
}
&__link {
padding: 0 24px;
@include navigation-link;
}
&__primary, &__secondary {
display: none;
flex-direction: row;
&__primary,
&__secondary {
display: none;
flex-direction: row;
@include desktop {
display: flex;
}
}
&__logo {
color: #17A887;
font-size: 36px;
font-weight: 800;
font-family: $sans-serif-font-family;
@include desktop {
display: flex;
justify-self: center;
/*
* For IE10+
*/
-ms-grid-column: 2;
-ms-grid-row-align: center;
-ms-grid-column-align: center;
}
&__logo-icon {
width: auto;
height: 31px;
}
&__link {
&--secondary {
@include regular-text;
}
&--active {
color: $color-brand;
}
}
$parent: &;
&--sidebar {
flex-direction: column;
#{$parent}__primary, #{$parent}__secondary {
display: flex;
flex-direction: column;
width: 100%;
}
#{$parent}__link {
@include heading-4;
line-height: 2.5em;
padding: 0;
display: block;
margin-bottom: 0.5*$small-spacing;
&:only-child {
margin-bottom: 0;
}
}
#{$parent}__item {
width: 100%;
//border-bottom: 1px solid $color-white;
/*&:nth-child(1) {*/
/* order: 3;*/
/* border-bottom: 0;*/
/*}*/
/*&:nth-child(2) {*/
/* order: 1;*/
/*}*/
/*&:nth-child(3) {*/
/* order: 2;*/
/*}*/
}
}
}
&__logo {
color: #17a887;
font-size: 36px;
font-weight: 800;
font-family: $sans-serif-font-family;
display: flex;
justify-self: center;
/*
* For IE10+
*/
-ms-grid-column: 2;
-ms-grid-row-align: center;
-ms-grid-column-align: center;
}
&__logo-icon {
width: auto;
height: 31px;
}
&__link {
&--secondary {
@include regular-text;
}
&--active {
color: $color-brand;
}
}
$parent: &;
&--sidebar {
flex-direction: column;
#{$parent}__primary,
#{$parent}__secondary {
display: flex;
flex-direction: column;
width: 100%;
}
#{$parent}__link {
@include heading-4;
line-height: 2.5em;
padding: 0;
display: block;
margin-bottom: 0.5 * $small-spacing;
&:only-child {
margin-bottom: 0;
}
}
#{$parent}__item {
width: 100%;
//border-bottom: 1px solid $color-white;
/*&:nth-child(1) {*/
/* order: 3;*/
/* border-bottom: 0;*/
/*}*/
/*&:nth-child(2) {*/
/* order: 1;*/
/*}*/
/*&:nth-child(3) {*/
/* order: 2;*/
/*}*/
}
}
}
</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,94 +10,95 @@
</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 {
mixins: [sidebarMixin],
export default {
mixins: [sidebarMixin],
components: {
ContentNavigation,
Cross
components: {
ContentNavigation,
Cross,
},
methods: {
close() {
this.closeSidebar('navigation');
},
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 {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: white;
z-index: 20;
.navigation-sidebar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: white;
z-index: 20;
@include desktop {
box-shadow: 0px 2px 9px rgba(0, 0, 0, 0.12);
}
display: grid;
grid-template-columns: 1fr 50px;
grid-template-rows: 50px max-content auto 100px;
grid-template-areas: "m m" "m m" "s s" "s s";
&--with-subnavigation {
grid-template-areas: "m m" "m m" "sub sub" "s s";
}
height: 100vh;
overflow-y: auto;
@include desktop {
width: $desktop-width;
}
&__main {
padding: $medium-spacing;
grid-area: m;
}
&__main-link {
}
&__close-button {
grid-row: 1;
grid-column: 2;
align-self: center;
justify-self: center;
cursor: pointer;
}
@include desktop {
box-shadow: 0px 2px 9px rgba(0, 0, 0, 0.12);
}
.slide {
&-enter-active, &-leave-active {
transition: left 0.2s;
}
display: grid;
&-enter-from, &-leave-to {
left: -100vw;
@include desktop {
left: -$desktop-width;
}
grid-template-columns: 1fr 50px;
grid-template-rows: 50px max-content auto 100px;
grid-template-areas: 'm m' 'm m' 's s' 's s';
&--with-subnavigation {
grid-template-areas: 'm m' 'm m' 'sub sub' 's s';
}
height: 100vh;
overflow-y: auto;
@include desktop {
width: $desktop-width;
}
&__main {
padding: $medium-spacing;
grid-area: m;
}
&__main-link {
}
&__close-button {
grid-row: 1;
grid-column: 2;
align-self: center;
justify-self: center;
cursor: pointer;
}
}
.slide {
&-enter-active,
&-leave-active {
transition: left 0.2s;
}
&-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 {
props: ['title'],
export default {
props: ['title'],
components: {
ChevronDown,
ChevronUp
components: {
ChevronDown,
ChevronUp,
},
data() {
return {
show: false,
};
},
watch: {
$route() {
this.show = false;
},
},
data() {
return {
show: false
};
methods: {
close() {
this.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,40 +26,42 @@
default: false,
},
},
},
mixins: [sidebarMixin],
mixins: [sidebarMixin],
data() {
return {
topics: [],
};
},
data() {
return {
topics: [],
};
},
methods: {
topicId(id) {
return atob(id);
},
},
},
apollo: {
topics: {
query: ALL_TOPICS_QUERY,
manual: true,
result({data, loading}) {
if (!loading) {
this.topics = this.$getRidOfEdges(data).topics;
}
},
apollo: {
topics: {
query: ALL_TOPICS_QUERY,
manual: true,
result({ data, loading }) {
if (!loading) {
this.topics = this.$getRidOfEdges(data).topics;
}
},
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
.topic-navigation {
&__topic {
}
.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,289 +63,292 @@
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 {
props: {
element: {
type: Object,
default: null,
},
// is this element at the top level, or is it nested? we assume top level
topLevel: {
type: Boolean,
default: true,
},
firstElement: {
type: Boolean,
required: true,
},
lastElement: {
type: Boolean,
required: true,
},
export default {
props: {
element: {
type: Object,
default: null,
},
components: {
ContentElementActions,
ContentFormSection,
TrashIcon,
ContentBlockElementChooserWidget,
LinkForm,
VideoForm,
ImageForm,
DocumentForm,
AssignmentForm,
TextForm,
SubtitleForm,
SurveyBlock,
Solution,
ImageBlock,
Instruction,
ModuleRoomSlug,
CmsDocumentBlock,
InfogramBlock,
ThinglinkBlock,
Assignment
// is this element at the top level, or is it nested? we assume top level
topLevel: {
type: Boolean,
default: true,
},
computed: {
actions() {
return {
up: !this.firstElement,
down: !this.lastElement,
extended: this.topLevel,
};
},
isChooser() {
return this.component === CHOOSER;
},
type() {
return this.getType(this.element);
},
component() {
return this.type.component;
},
title() {
return this.type.title;
},
icon() {
return this.type.icon;
},
firstElement: {
type: Boolean,
required: true,
},
lastElement: {
type: Boolean,
required: true,
},
},
methods: {
getType(element) {
switch (element.type) {
case 'subtitle':
return {
component: 'subtitle-form',
title: 'Untertitel',
icon: 'title-icon',
};
case 'link_block':
return {
component: 'link-form',
title: 'Link',
icon: 'link-icon',
};
case 'video_block':
return {
component: 'video-form',
title: 'Video',
icon: 'video-icon',
};
case 'image_url_block':
return {
component: 'image-form',
title: 'Bild',
icon: 'image-icon',
};
case 'text_block':
return {
component: 'text-form',
title: 'Text',
icon: 'text-icon',
};
case 'assignment':
return {
component: element.id ? 'assignment' : 'assignment-form', // prevent editing of existing assignments
title: 'Aufgabe & Ergebnis',
icon: 'speech-bubble-icon',
};
case 'document_block':
return {
component: 'document-form',
title: 'Dokument',
icon: 'document-icon',
};
case 'survey':
return {
component: 'survey-block',
title: 'Übung',
};
case 'solution':
return {
component: 'solution',
title: 'Lösung',
};
case 'image_block':
return {
component: 'image-block',
title: 'Bild',
};
case 'instruction':
return {
component: 'instruction',
title: 'Instruktion',
};
case 'module_room_slug':
return {
component: 'module-room-slug',
title: 'Raum',
};
case 'cms_document_block':
return {
component: 'cms-document-block',
title: 'Dokument',
};
case 'thinglink_block':
return {
component: 'thinglink-block',
title: 'Interaktive Grafik'
};
case 'infogram_block':
return {
component: 'infogram-block',
title: 'Interaktive Grafik'
};
}
return {
component: CHOOSER,
title: '',
icon: '',
};
},
_updateProperty(value, key) {
// const content = this.localContentBlock.contents[index];
const content = this.element;
this.update({
...content,
value: {
...content.value,
[key]: value,
},
});
},
changeUrl(value) {
this._updateProperty(value, 'url');
},
changeText(value) {
this._updateProperty(value, 'text');
},
changeAssignmentTitle(value) {
this._updateProperty(value, 'title');
},
changeAssignmentAssignment(value) {
this._updateProperty(value, 'assignment');
},
changeType({type, convertToList}, value) {
let el = {
type: type,
value: Object.assign({}, value),
};
switch (type) {
case 'subtitle':
el = {
...el,
value: {
text: '',
},
};
break;
case 'text_block':
el = {
...el,
value: {
text: '',
},
};
break;
case 'link_block':
el = {
...el,
value: {
text: '',
url: '',
},
};
break;
case 'video_block':
el = {
...el,
value: {
url: '',
},
};
break;
case 'document_block':
el = {
...el,
value: Object.assign({
url: '',
}, value),
};
break;
case 'image_url_block':
el = {
...el,
value: {
url: '',
},
};
break;
}
components: {
ContentElementActions,
ContentFormSection,
TrashIcon,
ContentBlockElementChooserWidget,
LinkForm,
VideoForm,
ImageForm,
DocumentForm,
AssignmentForm,
TextForm,
SubtitleForm,
SurveyBlock,
Solution,
ImageBlock,
Instruction,
ModuleRoomSlug,
CmsDocumentBlock,
InfogramBlock,
ThinglinkBlock,
Assignment,
},
if (convertToList) {
el = {
type: 'content_list_item',
contents: [el],
computed: {
actions() {
return {
up: !this.firstElement,
down: !this.lastElement,
extended: this.topLevel,
};
},
isChooser() {
return this.component === CHOOSER;
},
type() {
return this.getType(this.element);
},
component() {
return this.type.component;
},
title() {
return this.type.title;
},
icon() {
return this.type.icon;
},
},
methods: {
getType(element) {
switch (element.type) {
case 'subtitle':
return {
component: 'subtitle-form',
title: 'Untertitel',
icon: 'title-icon',
};
}
this.update(el);
},
update(element) {
this.$emit('update', element);
},
switchToDocument(value) {
this.changeType('document_block', value);
},
case 'link_block':
return {
component: 'link-form',
title: 'Link',
icon: 'link-icon',
};
case 'video_block':
return {
component: 'video-form',
title: 'Video',
icon: 'video-icon',
};
case 'image_url_block':
return {
component: 'image-form',
title: 'Bild',
icon: 'image-icon',
};
case 'text_block':
return {
component: 'text-form',
title: 'Text',
icon: 'text-icon',
};
case 'assignment':
return {
component: element.id ? 'assignment' : 'assignment-form', // prevent editing of existing assignments
title: 'Aufgabe & Ergebnis',
icon: 'speech-bubble-icon',
};
case 'document_block':
return {
component: 'document-form',
title: 'Dokument',
icon: 'document-icon',
};
case 'survey':
return {
component: 'survey-block',
title: 'Übung',
};
case 'solution':
return {
component: 'solution',
title: 'Lösung',
};
case 'image_block':
return {
component: 'image-block',
title: 'Bild',
};
case 'instruction':
return {
component: 'instruction',
title: 'Instruktion',
};
case 'module_room_slug':
return {
component: 'module-room-slug',
title: 'Raum',
};
case 'cms_document_block':
return {
component: 'cms-document-block',
title: 'Dokument',
};
case 'thinglink_block':
return {
component: 'thinglink-block',
title: 'Interaktive Grafik',
};
case 'infogram_block':
return {
component: 'infogram-block',
title: 'Interaktive Grafik',
};
}
return {
component: CHOOSER,
title: '',
icon: '',
};
},
};
_updateProperty(value, key) {
// const content = this.localContentBlock.contents[index];
const content = this.element;
this.update({
...content,
value: {
...content.value,
[key]: value,
},
});
},
changeUrl(value) {
this._updateProperty(value, 'url');
},
changeText(value) {
this._updateProperty(value, 'text');
},
changeAssignmentTitle(value) {
this._updateProperty(value, 'title');
},
changeAssignmentAssignment(value) {
this._updateProperty(value, 'assignment');
},
changeType({ type, convertToList }, value) {
let el = {
type: type,
value: Object.assign({}, value),
};
switch (type) {
case 'subtitle':
el = {
...el,
value: {
text: '',
},
};
break;
case 'text_block':
el = {
...el,
value: {
text: '',
},
};
break;
case 'link_block':
el = {
...el,
value: {
text: '',
url: '',
},
};
break;
case 'video_block':
el = {
...el,
value: {
url: '',
},
};
break;
case 'document_block':
el = {
...el,
value: Object.assign(
{
url: '',
},
value
),
};
break;
case 'image_url_block':
el = {
...el,
value: {
url: '',
},
};
break;
}
if (convertToList) {
el = {
type: 'content_list_item',
contents: [el],
};
}
this.update(el);
},
update(element) {
this.$emit('update', element);
},
switchToDocument(value) {
this.changeType('document_block', value);
},
},
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.content-element {
display: flex;
flex-direction: column;
.content-element {
display: flex;
flex-direction: column;
&__actions {
display: inline-flex;
justify-self: flex-end;
align-self: flex-end;
}
&__section {
display: grid;
//grid-template-columns: 1fr 50px;
grid-auto-rows: auto;
/*width: 95%; // reserve space for scrollbar*/
}
&__chooser {
grid-column: 1 / span 2;
}
&__actions {
display: inline-flex;
justify-self: flex-end;
align-self: flex-end;
}
&__section {
display: grid;
//grid-template-columns: 1fr 50px;
grid-auto-rows: auto;
/*width: 95%; // reserve space for scrollbar*/
}
&__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,46 +50,46 @@
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.content-form-section {
@include default-box-shadow;
border-radius: $default-border-radius;
padding: $small-spacing $medium-spacing;
margin-bottom: $medium-spacing;
.content-form-section {
@include default-box-shadow;
border-radius: $default-border-radius;
padding: $small-spacing $medium-spacing;
margin-bottom: $medium-spacing;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
grid-template-areas: 'h a' 'c c';
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
grid-template-areas: 'h a' 'c c';
align-items: center;
grid-row-gap: $medium-spacing;
&__heading {
display: flex;
align-items: center;
grid-row-gap: $medium-spacing;
&__heading {
display: flex;
align-items: center;
grid-area: h;
margin-bottom: 0;
}
&__actions {
grid-area: a;
justify-self: end;
}
&__title {
@include heading-3;
margin-bottom: 0;
}
&__icon {
width: 28px;
height: 28px;
margin-right: $small-spacing;
}
&__content {
grid-area: c;
}
grid-area: h;
margin-bottom: 0;
}
&__actions {
grid-area: a;
justify-self: end;
}
&__title {
@include heading-3;
margin-bottom: 0;
}
&__icon {
width: 28px;
height: 28px;
margin-right: $small-spacing;
}
&__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,116 +51,117 @@
const Modal = defineAsyncComponent(() => import('@/components/Modal'));
const Checkbox = defineAsyncComponent(() => import('@/components/ui/Checkbox'));
export default {
props: {
contentBlock: Object,
blockType: {
type: String,
default: 'ContentBlock',
},
showTaskSelection: {
type: Boolean,
default: false,
},
disableSave: {
type: Boolean,
default: false,
},
export default {
props: {
contentBlock: Object,
blockType: {
type: String,
default: 'ContentBlock',
},
components: {
ContentElement,
Modal,
ModalInput,
AddContentElement,
Checkbox,
showTaskSelection: {
type: Boolean,
default: false,
},
disableSave: {
type: Boolean,
default: false,
},
},
data() {
return {
error: false,
localContentBlock: Object.assign({}, {
components: {
ContentElement,
Modal,
ModalInput,
AddContentElement,
Checkbox,
},
data() {
return {
error: false,
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: {},
};
},
apollo: {
me: meQuery,
},
computed: {
titlePlaceholder() {
return this.blockType === 'RoomEntry' ? 'Titel für Raumeintrag erfassen' : 'Titel für Inhaltsblock erfassen';
},
taskSelection() {
return this.showTaskSelection && this.me.permissions.includes('users.can_manage_school_class_content');
},
},
methods: {
setContentBlockType(checked) {
this.localContentBlock.isAssignment = checked;
},
update(index, element) {
this.localContentBlock.contents.splice(index, 1, element);
},
save() {
if (!this.disableSave) {
if (!this.localContentBlock.title) {
this.error = true;
return false;
}
this.$emit('save', this.localContentBlock);
}
},
updateTitle(title) {
this.localContentBlock.title = title;
this.error = false;
},
addElement(index) {
this.localContentBlock.contents.splice(index + 1, 0, {
hideAssignment: this.blockType !== 'ContentBlock',
});
},
remove(index) {
this.localContentBlock.contents.splice(index, 1);
},
),
me: {},
};
},
apollo: {
me: meQuery,
},
computed: {
titlePlaceholder() {
return this.blockType === 'RoomEntry' ? 'Titel für Raumeintrag erfassen' : 'Titel für Inhaltsblock erfassen';
},
};
taskSelection() {
return this.showTaskSelection && this.me.permissions.includes('users.can_manage_school_class_content');
},
},
methods: {
setContentBlockType(checked) {
this.localContentBlock.isAssignment = checked;
},
update(index, element) {
this.localContentBlock.contents.splice(index, 1, element);
},
save() {
if (!this.disableSave) {
if (!this.localContentBlock.title) {
this.error = true;
return false;
}
this.$emit('save', this.localContentBlock);
}
},
updateTitle(title) {
this.localContentBlock.title = title;
this.error = false;
},
addElement(index) {
this.localContentBlock.contents.splice(index + 1, 0, {
hideAssignment: this.blockType !== 'ContentBlock',
});
},
remove(index) {
this.localContentBlock.contents.splice(index, 1);
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.contents-form {
/* top level does not exist, because of the modal */
.contents-form {
/* top level does not exist, because of the modal */
&__element {
}
&__element-component {
margin-bottom: 25px;
}
&__remove {
}
&__trash-icon {
}
&__add {
grid-column: 1 / span 2;
}
&__task {
margin: 15px 0 10px;
}
&__element {
}
&__element-component {
margin-bottom: 25px;
}
&__remove {
}
&__trash-icon {
}
&__add {
grid-column: 1 / span 2;
}
&__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';
@ -28,17 +23,18 @@
};
},
created() {
// debugger;
},
created() {
// debugger;
},
methods: {
hideModal() {
this.$store.dispatch('resetCurrentNoteBlock');
this.$store.dispatch('hideModal');
},
saveContentBlock(contentBlock) {
this.$apollo.mutate({
methods: {
hideModal() {
this.$store.dispatch('resetCurrentNoteBlock');
this.$store.dispatch('hideModal');
},
saveContentBlock(contentBlock) {
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,37 +127,36 @@ 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 {
position: relative;
.content-component {
position: relative;
&--bookmarked {
}
&--subtitle {
margin-top: $section-spacing;
margin-bottom: $large-spacing;
}
&--section_title {
margin-top: $section-spacing;
margin-bottom: $large-spacing;
}
&--text_block {
margin-bottom: $large-spacing;
}
&--document_block {
margin-bottom: $large-spacing;
}
&--bookmarked {
}
&--subtitle {
margin-top: $section-spacing;
margin-bottom: $large-spacing;
}
&--section_title {
margin-top: $section-spacing;
margin-bottom: $large-spacing;
}
&--text_block {
margin-bottom: $large-spacing;
}
&--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,60 +13,60 @@
const DocumentIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon'));
const TrashIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/TrashIcon'));
export default {
props: {
value: Object,
showTrashIcon: Boolean,
},
export default {
props: {
value: Object,
showTrashIcon: Boolean,
},
components: {
DocumentIcon,
TrashIcon,
},
components: {
DocumentIcon,
TrashIcon,
},
computed: {
urlName: function() {
if (this.value && this.value.url) {
const parts = this.value.url.split('/');
return parts[parts.length - 1];
}
return null;
computed: {
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 {
display: grid;
grid-template-columns: 50px 1fr 50px;
align-items: center;
.document-block {
display: grid;
grid-template-columns: 50px 1fr 50px;
align-items: center;
&__icon {
width: 30px;
height: 30px;
}
&__link {
text-decoration: underline;
}
&__remove {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
}
&__trash-icon {
width: 25px;
height: 25px;
fill: $color-silver-dark;
cursor: pointer;
justify-self: center;
}
&__icon {
width: 30px;
height: 30px;
}
&__link {
text-decoration: underline;
}
&__remove {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
}
&__trash-icon {
width: 25px;
height: 25px;
fill: $color-silver-dark;
cursor: pointer;
justify-self: center;
}
}
</style>

View File

@ -1,57 +1,51 @@
<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 {
props: ['value'],
export default {
props: ['value'],
mixins: [me],
mixins: [me],
components: {
BulbIcon
components: {
BulbIcon,
},
computed: {
text() {
return this.value.text ? this.value.text : 'Anweisungen';
},
computed: {
text() {
return this.value.text ? this.value.text : 'Anweisungen';
},
url() {
return this.value.document ? this.value.document.url : this.value.url;
}
}
};
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 {
margin-bottom: 1rem;
display: flex;
align-items: center;
.instruction {
margin-bottom: 1rem;
display: flex;
align-items: center;
&__icon {
width: 40px;
height: 40px;
margin-right: $small-spacing;
}
&__link {
@include heading-3;
}
&__icon {
width: 40px;
height: 40px;
margin-right: $small-spacing;
}
&__link {
@include heading-3;
}
}
</style>

View File

@ -1,60 +1,53 @@
<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 {
props: {
value: Object,
noMargin: {
default: false
}
export default {
props: {
value: Object,
noMargin: {
default: false,
},
},
components: {
LinkIcon
components: {
LinkIcon,
},
computed: {
href() {
const url = this.value.url;
return url.startsWith('http') ? this.value.url : `http://${this.value.url}`;
},
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 {
margin-bottom: 30px;
display: grid;
grid-template-columns: 50px 1fr;
align-items: center;
.link-block {
margin-bottom: 30px;
display: grid;
grid-template-columns: 50px 1fr;
align-items: center;
&--no-margin {
margin-bottom: 0;
}
&__icon {
width: 30px;
height: 30px;
}
&__link {
text-decoration: underline;
}
&--no-margin {
margin-bottom: 0;
}
&__icon {
width: 30px;
height: 30px;
}
&__link {
text-decoration: underline;
}
}
</style>

View File

@ -1,109 +1,101 @@
<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 {
props: {
userInput: {
type: Object,
default: () => ({})
},
showReopen: {
type: Boolean,
default: true
},
sharedMsg: {
type: String,
default: ''
}
export default {
props: {
userInput: {
type: Object,
default: () => ({}),
},
components: {
InfoIcon,
DocumentBlock,
showReopen: {
type: Boolean,
default: true,
},
sharedMsg: {
type: String,
default: '',
},
},
computed: {
text() {
return newLineToParagraph(this.userInput.text);
}
}
};
components: {
InfoIcon,
DocumentBlock,
},
computed: {
text() {
return newLineToParagraph(this.userInput.text);
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.final-submission {
&__text {
background-color: $color-white;
@include input-box-shadow;
border-radius: $input-border-radius;
padding: 15px;
font-size: toRem(17px);
font-family: $sans-serif-font-family;
margin-bottom: 20px;
font-weight: $font-weight-regular;
.final-submission {
&__text {
background-color: $color-white;
@include input-box-shadow;
border-radius: $input-border-radius;
padding: 15px;
font-size: toRem(17px);
font-family: $sans-serif-font-family;
margin-bottom: 20px;
font-weight: $font-weight-regular;
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
word-break: break-word;
}
&__document {
margin-bottom: $small-spacing;
}
&__explanation {
display: flex;
align-items: center;
}
&__explanation-icon {
width: 40px;
height: 40px;
fill: $color-brand;
margin-right: 8px;
}
&__explanation-text {
color: $color-brand;
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
margin-right: $medium-spacing;
}
&__reopen {
@include small-text;
cursor: pointer;
color: $color-charcoal-light;
}
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
word-break: break-word;
}
&__document {
margin-bottom: $small-spacing;
}
&__explanation {
display: flex;
align-items: center;
}
&__explanation-icon {
width: 40px;
height: 40px;
fill: $color-brand;
margin-right: 8px;
}
&__explanation-text {
color: $color-brand;
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
margin-right: $medium-spacing;
}
&__reopen {
@include small-text;
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,116 +41,113 @@
</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 {
props: {
userInput: Object,
saved: Boolean,
placeholder: String,
action: String,
reopen: Function,
document: String,
readOnly: {
type: Boolean,
default: false,
},
spellcheck: {
type: Boolean,
default: false,
},
spellcheckLoading: {
type: Boolean,
default: false,
},
sharedMsg: String,
export default {
props: {
userInput: Object,
saved: Boolean,
placeholder: String,
action: String,
reopen: Function,
document: String,
readOnly: {
type: Boolean,
default: false,
},
components: {
FileUpload,
SubmissionInput,
FinalSubmission,
spellcheck: {
type: Boolean,
default: false,
},
computed: {
final() {
return !!this.userInput && this.userInput.final;
},
isFinalOrReadOnly() {
return this.final || this.readOnly;
},
allowsDocuments() {
return 'document' in this.userInput;
},
showSpellcheckButton() {
return this.spellcheck && process.env.VUE_APP_ENABLE_SPELLCHECK;
},
spellcheckText() {
if (!this.spellcheckLoading) {
return 'Rechtschreibung prüfen';
} else {
return 'Wird geprüft...';
}
},
spellcheckLoading: {
type: Boolean,
default: false,
},
sharedMsg: String,
},
methods: {
reopenSubmission() {
this.$emit('reopen');
},
saveInput(input) {
this.$emit('saveInput', input);
},
changeDocumentUrl(documentUrl) {
this.$emit('changeDocumentUrl', documentUrl);
},
components: {
FileUpload,
SubmissionInput,
FinalSubmission,
},
computed: {
final() {
return !!this.userInput && this.userInput.final;
},
isFinalOrReadOnly() {
return this.final || this.readOnly;
},
allowsDocuments() {
return 'document' in this.userInput;
},
showSpellcheckButton() {
return this.spellcheck && process.env.VUE_APP_ENABLE_SPELLCHECK;
},
spellcheckText() {
if (!this.spellcheckLoading) {
return 'Rechtschreibung prüfen';
} else {
return 'Wird geprüft...';
}
},
},
};
methods: {
reopenSubmission() {
this.$emit('reopen');
},
saveInput(input) {
this.$emit('saveInput', input);
},
changeDocumentUrl(documentUrl) {
this.$emit('changeDocumentUrl', documentUrl);
},
},
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.submission-form-container {
@include form-with-border;
.submission-form-container {
@include form-with-border;
margin-bottom: $medium-spacing;
margin-bottom: $medium-spacing;
display: none;
@include desktop {
display: block;
}
display: none;
@include desktop {
display: block;
}
&__inputs {
margin-bottom: 12px;
}
&__inputs {
margin-bottom: 12px;
}
&__submit {
margin-right: $medium-spacing;
}
&__submit {
margin-right: $medium-spacing;
}
&__actions {
display: flex;
align-items: center;
}
&__actions {
display: flex;
align-items: center;
}
&__document {
&:hover {
cursor: pointer;
}
}
&__spellcheck {
/* so the button does not change size when changing the text */
width: 235px;
text-align: center;
display: inline-block;
&__document {
&:hover {
cursor: pointer;
}
}
&__spellcheck {
/* so the button does not change size when changing the text */
width: 235px;
text-align: center;
display: inline-block;
}
}
</style>

View File

@ -4,75 +4,73 @@
: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 {
props: {
inputText: String,
saved: Boolean,
readonly: Boolean,
placeholder: {
type: String,
default: 'Ergebnis erfassen'
}
export default {
props: {
inputText: String,
saved: Boolean,
readonly: Boolean,
placeholder: {
type: String,
default: 'Ergebnis erfassen',
},
components: {
TickCircleIcon,
LoadingIcon
}
};
},
components: {
TickCircleIcon,
LoadingIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.submission-form {
display: flex;
flex-direction: row;
justify-content: space-between;
.submission-form {
display: flex;
flex-direction: row;
justify-content: space-between;
&__textarea {
@include borderless-textarea;
}
&__save-status {
position: relative;
align-items: center;
}
&__save-status-icon {
width: 22px;
height: 22px;
fill: $color-silver-dark;
}
&__saving-icon {
@include spin;
}
&__textarea {
@include borderless-textarea;
}
&__save-status {
position: relative;
align-items: center;
}
&__save-status-icon {
width: 22px;
height: 22px;
fill: $color-silver-dark;
}
&__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,41 +23,41 @@
import {defineAsyncComponent} from 'vue';
const InfoIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/InfoIcon'));
export default {
props: ['value', 'index'],
export default {
props: ['value', 'index'],
components: {
InfoIcon
}
};
components: {
InfoIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.assignment-form {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 13px;
grid-template-columns: 40px 1fr;
grid-column-gap: 16px;
align-items: center;
.assignment-form {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 13px;
grid-template-columns: 40px 1fr;
grid-column-gap: 16px;
align-items: center;
&__title {
width: $modal-input-width;
grid-column: span 2;
}
&__exercise-text {
width: $modal-input-width;
grid-column: span 2;
}
&__help-icon {
width: 40px;
height: 40px;
}
&__help-description {
}
&__title {
width: $modal-input-width;
grid-column: span 2;
}
&__exercise-text {
width: $modal-input-width;
grid-column: span 2;
}
&__help-icon {
width: 40px;
height: 40px;
}
&__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,100 +18,103 @@
const DocumentIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DocumentIcon'));
export default {
props: ['value', 'index'],
export default {
props: ['value', 'index'],
components: {
LoadingIcon,
DocumentIcon,
components: {
LoadingIcon,
DocumentIcon,
},
data() {
return {
loading: false,
};
},
computed: {
previewUrl() {
if (this.value && this.value.url) {
return this.value.url;
}
return null;
},
data() {
return {
loading: false,
};
previewLink() {
if (this.value && this.value.url) {
const parts = this.value.url.split('/');
return parts[parts.length - 1];
}
return '';
},
},
computed: {
previewUrl() {
if (this.value && this.value.url) {
return this.value.url;
}
return null;
},
previewLink() {
if (this.value && this.value.url) {
const parts = this.value.url.split('/');
return parts[parts.length - 1];
}
return '';
},
},
mounted() {
uploadcare(this, url => {
mounted() {
uploadcare(
this,
(url) => {
this.$emit('change-url', url, this.index);
this.loading = false;
}, () => {
},
() => {
this.loading = true;
});
},
};
}
);
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.document-form {
&__uploaded {
display: flex;
align-items: center;
}
.document-form {
&__uploaded {
display: flex;
align-items: center;
}
&__link {
text-decoration: underline;
}
&__link {
text-decoration: underline;
}
&__spinner {
width: 100%;
&__spinner {
width: 100%;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
}
&__loading-icon {
@include spin;
fill: $color-silver-dark;
}
&__icon {
width: 30px;
height: 30px;
margin-right: $small-spacing;
}
&__file-input {
width: 0.1px;
height: 0.1px;
overflow: hidden;
opacity: 0;
position: absolute;
z-index: -1;
& + label {
cursor: pointer;
background-color: $color-silver-light;
height: 150px;
display: flex;
align-items: center;
width: 100%;
justify-content: center;
}
&__loading-icon {
@include spin;
fill: $color-silver-dark;
}
&__icon {
width: 30px;
height: 30px;
margin-right: $small-spacing;
}
&__file-input {
width: 0.1px;
height: 0.1px;
overflow: hidden;
opacity: 0;
position: absolute;
z-index: -1;
& + label {
cursor: pointer;
background-color: $color-silver-light;
height: 150px;
display: flex;
width: 100%;
justify-content: center;
align-items: center;
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
text-decoration: underline;
}
align-items: center;
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
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,67 +30,65 @@
</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 {
props: ['value', 'index'],
export default {
props: ['value', 'index'],
components: {
InfoIcon,
YoutubeEmbed,
VimeoEmbed,
SrfEmbed
components: {
InfoIcon,
YoutubeEmbed,
VimeoEmbed,
SrfEmbed,
},
computed: {
isYoutube() {
return isYoutubeUrl(this.value.url);
},
computed: {
isYoutube() {
return isYoutubeUrl(this.value.url);
},
isVimeo() {
return isVimeoUrl(this.value.url);
},
isSrf() {
return isSrfUrl(this.value.url);
}
}
};
isVimeo() {
return isVimeoUrl(this.value.url);
},
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 {
display: grid;
grid-auto-rows: auto;
grid-template-columns: 40px 1fr;
grid-column-gap: 16px;
grid-row-gap: 20px;
align-items: center;
.video-form {
display: grid;
grid-auto-rows: auto;
grid-template-columns: 40px 1fr;
grid-column-gap: 16px;
grid-row-gap: 20px;
align-items: center;
&__help-icon {
}
&__help-description {
}
&__platform-link {
font-family: $sans-serif-font-family;
text-decoration: underline;
font-weight: $font-weight-regular;
font-size: toRem(17px);
}
&__video-link {
grid-column: 1 / span 2;
width: $modal-input-width
}
&__help-icon {
}
&__help-description {
}
&__platform-link {
font-family: $sans-serif-font-family;
text-decoration: underline;
font-weight: $font-weight-regular;
font-size: toRem(17px);
}
&__video-link {
grid-column: 1 / span 2;
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,179 +1,169 @@
<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 {
props: {
text: {
type: String,
required: true,
},
id: {
type: String,
default: '',
},
isCategory: {
type: Boolean,
default: false,
},
category: {
type: Object,
default: () => ({}),
},
export default {
props: {
text: {
type: String,
required: true,
},
id: {
type: String,
default: '',
},
isCategory: {
type: Boolean,
default: false,
},
category: {
type: Object,
default: () => ({}),
},
},
components: {
ChevronRight,
},
components: {
ChevronRight,
},
emits: ['filter'],
apollo: {
instrumentFilter: {
query: INSTRUMENT_FILTER_QUERY,
},
apollo: {
instrumentFilter: {
query: INSTRUMENT_FILTER_QUERY,
},
},
data() {
data() {
return {
instrumentFilter: {
currentFilter: '',
},
};
},
computed: {
isActive() {
if (!this.instrumentFilter.currentFilter) {
return this.id === '';
}
// eslint-disable-next-line
const [_, identifier] = this.instrumentFilter.currentFilter.split(':');
return this.id === identifier;
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
activeStyle() {
if (this.isActive) {
return {
backgroundColor: this.category.background,
};
}
return {};
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
categoryStyle() {
if (this.isCategory) {
return {
color: this.category.foreground,
};
}
return {};
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
typeClass() {
return {
instrumentFilter: {
currentFilter: '',
},
'filter-entry--active': this.isActive,
'filter-entry--category': this.isCategory,
};
},
computed: {
isActive() {
if (!this.instrumentFilter.currentFilter) {
return this.id === '';
}
// eslint-disable-next-line
const [_, identifier] = this.instrumentFilter.currentFilter.split(':');
return this.id === identifier;
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
activeStyle() {
if (this.isActive) {
return {
backgroundColor: this.category.background,
};
}
return {};
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
categoryStyle() {
if (this.isCategory) {
return {
color: this.category.foreground,
};
}
return {};
},
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
typeClass() {
return {
'filter-entry--active': this.isActive,
'filter-entry--category': this.isCategory,
};
},
},
};
},
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.filter-entry {
.filter-entry {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
&__text {
@include sub-heading;
line-height: 1.5;
color: inherit;
}
&__icon-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
justify-content: center;
height: 20px;
width: 20px;
border-radius: 10px;
}
&__text {
@include sub-heading;
line-height: 1.5;
color: inherit;
&__icon {
width: 10px;
height: 10px;
}
$root: &;
@mixin filter-block($color) {
&#{$root}--category {
color: $color;
}
&__icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 20px;
width: 20px;
border-radius: 10px;
}
&__icon {
width: 10px;
height: 10px;
}
$root: &;
@mixin filter-block($color) {
&#{$root}--category {
color: $color;
}
&#{$root}--active {
#{$root}__icon-wrapper {
background-color: $color;
}
}
#{$root}__icon {
fill: $color;
}
}
&--language-communication {
@include filter-block($color-accent-1-dark);
}
&--society {
@include filter-block($color-accent-2-dark);
}
&--interdisciplinary {
@include filter-block($color-accent-4-dark);
}
&--active {
#{$root}__text {
font-weight: 600;
}
&#{$root}--active {
#{$root}__icon-wrapper {
background-color: black;
background-color: $color;
}
}
#{$root}__icon {
fill: white;
}
#{$root}__icon {
fill: $color;
}
}
&--language-communication {
@include filter-block($color-accent-1-dark);
}
&--society {
@include filter-block($color-accent-2-dark);
}
&--interdisciplinary {
@include filter-block($color-accent-4-dark);
}
&--active {
#{$root}__text {
font-weight: 600;
}
#{$root}__icon-wrapper {
background-color: black;
}
#{$root}__icon {
fill: white;
}
}
}
</style>

View File

@ -24,77 +24,76 @@
</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({
props: {
title: {
type: String,
default: '',
},
types: {
type: Array,
default: () => [],
},
category: {
type: Object,
default: () => ({}),
},
export default defineComponent({
props: {
title: {
type: String,
default: '',
},
components: {
FilterEntry,
types: {
type: Array,
default: () => [],
},
apollo: {
instrumentFilter: {
query: INSTRUMENT_FILTER_QUERY
}
category: {
type: Object,
default: () => ({}),
},
data() {
return {
instrumentFilter: {
currentFilter: ''
}
};
},
inheritAttrs: false,
components: {
FilterEntry,
},
methods: {
setCategoryFilter(category) {
if (category) {
this.setFilter(`category:${category}`);
} else {
this.setFilter(``);
}
apollo: {
instrumentFilter: {
query: INSTRUMENT_FILTER_QUERY,
},
},
data() {
return {
instrumentFilter: {
currentFilter: '',
},
setFilter(filter) {
this.$apollo.mutate({
mutation: SET_FILTER_MUTATION,
variables: {
filter
}
});
};
},
inheritAttrs: false,
methods: {
setCategoryFilter(category) {
if (category) {
this.setFilter(`category:${category}`);
} else {
this.setFilter(``);
}
},
setFilter(filter) {
this.$apollo.mutate({
mutation: SET_FILTER_MUTATION,
variables: {
filter,
},
});
},
});
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.filter-group {
border-bottom: 1px solid $color-silver;
padding: $medium-spacing 0;
display: flex;
flex-direction: column;
.filter-group {
border-bottom: 1px solid $color-silver;
padding: $medium-spacing 0;
display: flex;
flex-direction: column;
&__children {
padding-left: $medium-spacing;
}
&__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,8 +16,8 @@
</template>
<script>
export default {
props: ['metaTitle', 'title', 'teaser', 'id', 'slug', 'heroImage'],
export default {
props: ['metaTitle', 'title', 'teaser', 'id', 'slug', 'heroImage'],
computed: {
moduleLink() {
@ -38,55 +32,56 @@
return {};
}
}
}
};
},
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
.module-teaser {
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
border: 1px solid #E2E2E2;
height: 330px;
max-width: 380px;
width: 100%;
border-radius: 12px;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
.module-teaser {
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
border: 1px solid #e2e2e2;
height: 330px;
max-width: 380px;
width: 100%;
border-radius: 12px;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
&--small {
height: 300px;
}
&__image {
width: 100%;
max-height: 150px;
height: 150px;
background-position: center;
background-size: 100% auto;
background-repeat: no-repeat;
}
&__body {
padding: 20px;
}
&__meta-title {
color: $color-silver-dark;
margin-bottom: $large-spacing;
@include regular-text;
}
&__title {
@include heading-3;
margin-bottom: 5px;
}
&__description {
line-height: $default-line-height;
font-size: 1.2rem;
}
&--small {
height: 300px;
}
&__image {
width: 100%;
max-height: 150px;
height: 150px;
background-position: center;
background-size: 100% auto;
background-repeat: no-repeat;
}
&__body {
padding: 20px;
}
&__meta-title {
color: $color-silver-dark;
margin-bottom: $large-spacing;
@include regular-text;
}
&__title {
@include heading-3;
margin-bottom: 5px;
}
&__description {
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,69 +34,69 @@
const AddNoteIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddNoteIcon'));
const NoteIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/NoteIcon'));
export default {
props: {
bookmarked: {
type: Boolean,
default: false
},
note: {
type: [Object, Boolean],
default: false
},
editMode: {
type: Boolean,
default: false
}
export default {
props: {
bookmarked: {
type: Boolean,
default: false,
},
components: {
BookmarkIcon,
AddNoteIcon,
NoteIcon
note: {
type: [Object, Boolean],
default: false,
},
};
editMode: {
type: Boolean,
default: false,
},
},
components: {
BookmarkIcon,
AddNoteIcon,
NoteIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.bookmark-actions {
height: 100%;
min-height: 60px;
.bookmark-actions {
height: 100%;
min-height: 60px;
padding: 0 2*$large-spacing;
position: absolute;
right: -5*$large-spacing;
padding: 0 2 * $large-spacing;
position: absolute;
right: -5 * $large-spacing;
display: none;
display: none;
@include desktop {
display: flex;
}
@include desktop {
display: flex;
}
flex-direction: column;
align-content: center;
flex-direction: column;
align-content: center;
&__action {
opacity: 0;
transition: opacity 0.3s;
cursor: pointer;
width: 26px;
display: flex;
justify-content: center;
&__action {
opacity: 0;
transition: opacity 0.3s;
cursor: pointer;
width: 26px;
display: flex;
justify-content: center;
&--bookmarked, &--noted {
opacity: 1;
}
}
$parent: &;
&:hover {
#{$parent}__action {
opacity: 1;
}
&--bookmarked,
&--noted {
opacity: 1;
}
}
$parent: &;
&:hover {
#{$parent}__action {
opacity: 1;
}
}
}
</style>

View File

@ -6,40 +6,37 @@
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 {
props: ['objective'],
export default {
props: ['objective'],
components: {
ModalInput,
TrashIcon
}
};
components: {
ModalInput,
TrashIcon,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import '@/styles/_variables.scss';
.objective-form {
display: grid;
grid-template-columns: 1fr 50px;
margin-bottom: 10px;
.objective-form {
display: grid;
grid-template-columns: 1fr 50px;
margin-bottom: 10px;
&__input {
width: $modal-input-width;
}
&__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>
@ -15,35 +12,35 @@
props: ['project'],
components: {PlusIcon},
methods: {
addProjectEntry() {
this.$store.dispatch('addProjectEntry', this.project);
}
}
};
methods: {
addProjectEntry() {
this.$store.dispatch('addProjectEntry', this.project);
},
},
};
</script>
<style lang="scss" scoped>
@import "~styles/helpers";
@import '~styles/helpers';
.add-project-entry {
display: flex;
align-items: center;
justify-content: center;
border: 2px solid $color-brand;
border-radius: $default-border-radius;
cursor: pointer;
.add-project-entry {
display: flex;
align-items: center;
justify-content: center;
border: 2px solid $color-brand;
border-radius: $default-border-radius;
cursor: pointer;
&__icon {
width: 20px;
height: 20px;
fill: $color-brand;
margin-right: $small-spacing;
}
&__text {
@include navigation-link;
color: $color-brand;
}
&__icon {
width: 20px;
height: 20px;
fill: $color-brand;
margin-right: $small-spacing;
}
&__text {
@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,25 +21,25 @@
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.portfolio-onboarding {
@include onboarding-page;
.portfolio-onboarding {
@include onboarding-page;
&__heading {
@include heading-1;
}
&__subheading {
@include heading-2;
}
&__illustration {
@include onboarding-illustration;
}
&__text {
@include onboarding-text;
}
&__heading {
@include heading-1;
}
&__subheading {
@include heading-2;
}
&__illustration {
@include onboarding-illustration;
}
&__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,42 +65,50 @@ 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({
mutation: DELETE_PROJECT_MUTATION,
variables: {
input: {
slug,
this.$apollo
.mutate({
mutation: DELETE_PROJECT_MUTATION,
variables: {
input: {
slug,
},
},
},
update(store, {data: {deleteProject: {success}}}) {
if (success) {
const {projects: prevProjects} = store.readQuery({query: PROJECTS_QUERY});
if (prevProjects) {
let index = prevProjects.findIndex(project => project.slug === slug);
const projects = removeAtIndex(prevProjects, index);
const data = {
projects
};
store.writeQuery({query: PROJECTS_QUERY, data});
update(
store,
{
data: {
deleteProject: { success },
},
}
}
},
}).then(() => {
this.$router.push('/portfolio');
});
) {
if (success) {
const { projects: prevProjects } = store.readQuery({ query: PROJECTS_QUERY });
if (prevProjects) {
let index = prevProjects.findIndex((project) => project.slug === slug);
const projects = removeAtIndex(prevProjects, index);
const data = {
projects,
};
store.writeQuery({ query: PROJECTS_QUERY, data });
}
}
},
})
.then(() => {
this.$router.push('/portfolio');
});
},
},
};
</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,100 +35,106 @@
const DocumentBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock'));
export default {
props: ['description', 'documentUrl', 'created', 'id', 'readOnly'],
components: {
DocumentBlock,
MoreOptionsWidget,
},
export default {
props: ['description', 'documentUrl', 'created', 'id', 'readOnly'],
components: {
DocumentBlock,
MoreOptionsWidget,
},
computed: {
createdDate() {
return dateFilter(this.created);
},
createdDateTime() {
return dateTimeFilter(this.created);
},
computed: {
createdDate() {
return dateFilter(this.created);
},
createdDateTime() {
return dateTimeFilter(this.created);
},
},
methods: {
editProjectEntry() {
this.$store.dispatch('editProjectEntry', this.id);
},
deleteProjectEntry() {
const projectEntry = this; // otherwise we run into scope errors
this.$apollo.mutate({
mutation: DELETE_PROJECT_ENTRY_MUTATION,
variables: {
input: {
id: this.id,
methods: {
editProjectEntry() {
this.$store.dispatch('editProjectEntry', this.id);
},
deleteProjectEntry() {
const projectEntry = this; // otherwise we run into scope errors
this.$apollo.mutate({
mutation: DELETE_PROJECT_ENTRY_MUTATION,
variables: {
input: {
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,
}
) {
if (success) {
const query = PROJECT_QUERY;
const variables = {
slug: projectEntry.$route.params.slug,
};
const { project } = store.readQuery({ query, variables });
if (project) {
const index = project.entries.findIndex((entry) => entry.id === projectEntry.id);
const entries = removeAtIndex(project.entries, index);
const data = {
project: {
...project,
entries,
},
};
const {project} = store.readQuery({query, variables});
if (project) {
const index = project.entries.findIndex(entry => entry.id === projectEntry.id);
const entries = removeAtIndex(project.entries, index);
const data = {
project: {
...project,
entries,
},
};
store.writeQuery({query, variables, data});
}
store.writeQuery({ query, variables, data });
}
},
});
},
}
},
});
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.project-entry {
background-color: $color-white;
border-radius: $default-border-radius;
padding: 30px 20px;
position: relative;
&__heading {
font-size: toRem(22px);
margin-bottom: 6px;
}
&__paragraph {
margin-bottom: 30px;
white-space: pre-line;
}
&__date {
font-family: $sans-serif-font-family;
color: $color-silver-dark;
font-size: toRem(17px);
}
&__link {
cursor: pointer;
@include heading-4;
}
&__more {
position: absolute;
top: 10px;
right: 10px;
display: none;
@include desktop {
display: block;
}
}
.project-entry {
background-color: $color-white;
border-radius: $default-border-radius;
padding: 30px 20px;
position: relative;
&__heading {
font-size: toRem(22px);
margin-bottom: 6px;
}
&__paragraph {
margin-bottom: 30px;
white-space: pre-line;
}
&__date {
font-family: $sans-serif-font-family;
color: $color-silver-dark;
font-size: toRem(17px);
}
&__link {
cursor: pointer;
@include heading-4;
}
&__more {
position: absolute;
top: 10px;
right: 10px;
display: none;
@include desktop {
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,106 +31,102 @@
</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 {
props: {
projectEntry: {
type: Object,
default: null,
},
export default {
props: {
projectEntry: {
type: Object,
default: null,
},
},
components: {
FileUpload,
ButtonWithIconAndText,
Modal,
},
components: {
FileUpload,
ButtonWithIconAndText,
Modal,
},
data() {
return {
localProjectEntry: Object.assign({}, {
data() {
return {
localProjectEntry: Object.assign(
{},
{
...this.projectEntry,
}),
};
},
}
),
};
},
methods: {
setDocumentUrl(url) {
this.localProjectEntry.documentUrl = url;
},
useTemplate() {
this.localProjectEntry.description = `${this.localProjectEntry.description}${PROJECT_ENTRY_TEMPLATE}`;
},
methods: {
setDocumentUrl(url) {
this.localProjectEntry.documentUrl = url;
},
};
useTemplate() {
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;
&__form-field {
@include inputstyle;
padding: 0;
display: flex;
flex-direction: column;
&__form-field {
@include inputstyle;
padding: 0;
display: flex;
flex-direction: column;
grid-template-rows: auto 1rem;
grid-template-columns: 1fr 1fr;
width: 100%;
}
&__textarea {
@include auto-grow;
border: 0;
min-height: 400px;
padding: $medium-spacing;
}
&__buttons {
display: grid;
grid-template-columns: 1fr 1fr;
padding: $medium-spacing;
}
&__button {
@include regular-text;
&--template {
}
&--document {
}
}
&__heading {
@include heading-3;
margin-bottom: 0;
}
grid-template-rows: auto 1rem;
grid-template-columns: 1fr 1fr;
width: 100%;
}
&__textarea {
@include auto-grow;
border: 0;
min-height: 400px;
padding: $medium-spacing;
}
&__buttons {
display: grid;
grid-template-columns: 1fr 1fr;
padding: $medium-spacing;
}
&__button {
@include regular-text;
&--template {
}
&--document {
}
}
&__heading {
@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"
@ -68,22 +68,20 @@ export default {
align-items: flex-start;
flex-direction: column;
@include desktop {
display: flex;
flex: 80%;
flex-direction: row;
align-items: center;
}
}
display: flex;
flex: 80%;
flex-direction: row;align-items: center;
}}
&__title {
flex: 50%;
@include heading-4;
grid-column: 1 / span 2;
grid-column: 1 / span 2;
}
&__owner {
flex: 40%;
grid-column: 1 / span 1;
grid-column: 1 / span 1;
}
&__actions {
@ -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,37 +10,37 @@
</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 {
props: {
final: {
type: Boolean,
default: false,
},
export default {
props: {
final: {
type: Boolean,
default: false,
},
emits: ['share'],
components: {ShareIcon},
};
},
emits: ['share'],
components: { ShareIcon },
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.share-icon {
display: flex;
align-items: center;
cursor: pointer;
.share-icon {
display: flex;
align-items: center;
cursor: pointer;
&__icon {
width: 20px;
height: 20px;
margin-right: $small-spacing;
}
&__text {
@include large-link;
}
&__icon {
width: 20px;
height: 20px;
margin-right: $small-spacing;
}
&__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,104 +32,104 @@
const DefaultAvatar = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/DefaultAvatar'));
const PenIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/PenIcon'));
export default {
props: {
avatarUrl: {
type: String
},
iconHighlighted: {},
editable: {
default: false
}
export default {
props: {
avatarUrl: {
type: String,
},
components: {
DefaultAvatar,
PenIcon
iconHighlighted: {},
editable: {
default: false,
},
data() {
return {
isAvatarLoaded: false
};
},
mounted() {
if (this.avatarUrl !== '') {
this.$refs.fakeImage.addEventListener('load', () => {
if (this.$refs.fakeImage) {
this.$refs.fakeImage.remove();
this.isAvatarLoaded = true;
}
});
}
},
methods: {
closeSidebar() {
this.$apollo.mutate({
mutation: TOGGLE_SIDEBAR,
variables: {
sidebar: {
profile: false
}
}
});
}
},
components: {
DefaultAvatar,
PenIcon,
},
data() {
return {
isAvatarLoaded: false,
};
},
mounted() {
if (this.avatarUrl !== '') {
this.$refs.fakeImage.addEventListener('load', () => {
if (this.$refs.fakeImage) {
this.$refs.fakeImage.remove();
this.isAvatarLoaded = true;
}
});
}
};
},
methods: {
closeSidebar() {
this.$apollo.mutate({
mutation: TOGGLE_SIDEBAR,
variables: {
sidebar: {
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;
text-align: center;
&__placeholder {
height: $max-width;
width: $max-width;
overflow: hidden;
text-align: center;
fill: $color-silver-dark;
border-radius: 50%;
&__placeholder {
&--highlighted {
fill: $color-brand;
}
}
&__image {
background-size: cover;
background-position: center center;
height: 100%;
width: 100%;
border: 0;
border-radius: 50%;
&--landscape {
width: auto;
height: $max-width;
fill: $color-silver-dark;
border-radius: 50%;
&--highlighted {
fill: $color-brand;
}
}
}
&__image {
background-size: cover;
background-position: center center;
height: 100%;
width: 100%;
border: 0;
border-radius: 50%;
&__fake-image {
width: 0;
height: 0;
}
&--landscape {
width: auto;
height: $max-width;
}
}
&__fake-image {
width: 0;
height: 0;
}
&__edit {
position: absolute;
box-sizing: border-box;
width: 34px;
height: 34px;
display: block;
left: 50%;
bottom: -3px;
transform: translateX(80%);
background-color: $color-white;
border-radius: 50%;
padding: 6px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}
&__edit {
position: absolute;
box-sizing: border-box;
width: 34px;
height: 34px;
display: block;
left: 50%;
bottom: -3px;
transform: translateX(80%);
background-color: $color-white;
border-radius: 50%;
padding: 6px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}
.fade-leave-active, .show-enter-active {
transition: opacity .5s;
@ -146,8 +138,8 @@
opacity: 0;
}
.show-enter-to {
opacity: 1;
}
.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,32 +15,32 @@
import {defineAsyncComponent} from 'vue';
const LinkBlock = defineAsyncComponent(() => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/LinkBlock'));
export default {
props: ['bookmark'],
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);
},
text() {
return this.content.value.text ? this.content.value.text : 'TO BE DEFINED';
},
type() {
switch (this.content.type) {
case 'assignment':
return 'Aufgabe & Ergebnis';
case 'link_block':
return this.content;
case 'survey':
return 'Übung';
case 'image_url_block':
return 'Bild';
default:
return this.content.type;
}
export default {
props: ['bookmark'],
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);
},
text() {
return this.content.value.text ? this.content.value.text : 'TO BE DEFINED';
},
type() {
switch (this.content.type) {
case 'assignment':
return 'Aufgabe & Ergebnis';
case 'link_block':
return this.content;
case 'survey':
return 'Übung';
case 'image_url_block':
return 'Bild';
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 {
components: {
PenIcon
}
};
export default {
components: {
PenIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/_variables.scss";
@import '~styles/_variables.scss';
.edit-group-name {
&__icon {
width: 20px;
height: 20px;
fill: $color-brand;
}
.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;
text-align: right;
}
}
@include desktop {flex: 0 1 110px;
text-align: right;
}}
&__action {
@include desktop {
flex: 0 1 110px;
padding-left: $large-spacing;
}
@include desktop {flex: 0 1 110px;
padding-left: $large-spacing;
}
}
}}
</style>

View File

@ -1,105 +1,102 @@
<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 {
components: {
AvatarUploadForm,
Avatar,
TrashIcon
},
export default {
components: {
AvatarUploadForm,
Avatar,
TrashIcon,
},
data() {
return {
me: {
avatarUrl: ''
}
};
},
apollo: {
data() {
return {
me: {
query: ME_QUERY,
avatarUrl: '',
},
};
},
apollo: {
me: {
query: ME_QUERY,
},
methods: {
deleteAvatar () {
this.updateAvatar('');
},
updateAvatar (url) {
this.$apollo.mutate({
},
methods: {
deleteAvatar() {
this.updateAvatar('');
},
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 {
display: flex;
flex-direction: row;
.profile-avatar {
display: flex;
flex-direction: row;
&__image {
height: 230px;
width: 230px;
}
}
.profile-avatar {
margin-bottom: $large-spacing;
&__image {
height: 230px;
width: 230px;
}
}
.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,112 +38,113 @@
</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'));
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 {
export default {
mixins: [sidebar, me],
mixins: [sidebar, me],
components: {
LogoutWidget,
ClassSelectionWidget,
ProfileWidget,
Cross,
},
components: {
LogoutWidget,
ClassSelectionWidget,
ProfileWidget,
Cross,
computed: {
myTeamPage() {
return {
name: MY_TEAM,
};
},
},
computed: {
myTeamPage() {
return {
name: MY_TEAM,
};
},
methods: {
close() {
this.closeSidebar('profile');
},
methods: {
close() {
this.closeSidebar('profile');
},
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
$desktop-width: 333px;
$desktop-width: 333px;
.profile-sidebar {
padding: $large-spacing 0;
box-sizing: border-box;
position: fixed;
right: 0;
top: 0;
bottom: 0;
height: 100vh;
background-color: $color-white;
z-index: 15;
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
overflow-y: scroll;
.profile-sidebar {
padding: $large-spacing 0;
box-sizing: border-box;
position: fixed;
right: 0;
top: 0;
bottom: 0;
height: 100vh;
background-color: $color-white;
z-index: 15;
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
overflow-y: scroll;
width: 100%;
width: 100%;
@include desktop {
width: $desktop-width;
}
display: flex;
flex-direction: column;
justify-content: flex-start;
&__section {
margin-bottom: $large-spacing;
&:last-of-type {
margin-top: auto;
}
}
&__item {
padding: $small-spacing $medium-spacing;
}
&__subtitle {
@include small-text;
margin: 0;
margin-bottom: $small-spacing;
}
&__link {
@include default-link;
display: block;
}
&__close-link {
position: absolute;
right: $small-spacing;
top: $small-spacing;
cursor: pointer;
}
}
.slide {
&-enter-active,
&-leave-active {
transition: right 0.2s;
}
&-enter-from,
&-leave-to {
right: -100vw;
@include desktop {
width: $desktop-width;
}
display: flex;
flex-direction: column;
justify-content: flex-start;
&__section {
margin-bottom: $large-spacing;
&:last-of-type {
margin-top: auto;
}
}
&__item {
padding: $small-spacing $medium-spacing;
}
&__subtitle {
@include small-text;
margin: 0;
margin-bottom: $small-spacing;
}
&__link {
@include default-link;
display: block;
}
&__close-link {
position: absolute;
right: $small-spacing;
top: $small-spacing;
cursor: pointer;
}
}
.slide {
&-enter-active, &-leave-active {
transition: right 0.2s;
}
&-enter-from, &-leave-to {
right: -100vw;
@include desktop {
right: -$desktop-width;
}
right: -$desktop-width;
}
}
}
</style>

View File

@ -32,19 +32,16 @@ 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);
letter-spacing: toRem(10px);
@include desktop {
font-size: toRem(120px);
letter-spacing: toRem(20px);
letter-spacing: toRem(20px);
}
font-weight: 600;
}

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,52 +10,52 @@
const PlusIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/PlusIcon'));
import { ADD_ROOM_ENTRY_PAGE } from '@/router/room.names';
export default {
props: ['parent'],
export default {
props: ['parent'],
components: {
PlusIcon,
},
components: {
PlusIcon,
},
data() {
return {
addRoomEntryRoute: {
name: ADD_ROOM_ENTRY_PAGE,
},
};
},
};
data() {
return {
addRoomEntryRoute: {
name: ADD_ROOM_ENTRY_PAGE,
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.add-room-entry-button {
border: 2px solid $color-white;
border-radius: 12px;
height: 150px;
box-sizing: border-box;
margin-bottom: 25px;
justify-content: center;
align-items: center;
break-inside: avoid-column;
overflow: hidden;
cursor: pointer;
.add-room-entry-button {
border: 2px solid $color-white;
border-radius: 12px;
height: 150px;
box-sizing: border-box;
margin-bottom: 25px;
justify-content: center;
align-items: center;
break-inside: avoid-column;
overflow: hidden;
cursor: pointer;
display: none;
@include desktop {
display: flex;
}
&__icon {
width: 20px;
fill: $color-white;
margin-right: $small-spacing;
}
&__text {
@include regular-text;
color: $color-white;
}
display: none;
@include desktop {
display: flex;
}
&__icon {
width: 20px;
fill: $color-white;
margin-right: $small-spacing;
}
&__text {
@include regular-text;
color: $color-white;
}
}
</style>

View File

@ -1,56 +1,57 @@
<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 {
props: {
entryCount: {
type: Number,
},
verbose: {
type: Boolean,
default: true,
},
icon: {
type: String,
default: 'cards'
}
export default {
props: {
entryCount: {
type: Number,
},
verbose: {
type: Boolean,
default: true,
},
icon: {
type: String,
default: 'cards',
},
},
components: {
'speech-bubble': SpeechBubbleIcon,
SpeechBubbleIcon,
Cards,
},
};
components: {
'speech-bubble': SpeechBubbleIcon,
SpeechBubbleIcon,
Cards,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.entry-count-widget {
display: flex;
align-items: center;
opacity: 0.6;
margin-right: $medium-spacing;
.entry-count-widget {
display: flex;
align-items: center;
opacity: 0.6;
margin-right: $medium-spacing;
svg {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
}
& > span {
@include room-widget-text-style;
}
svg {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
}
& > span {
@include room-widget-text-style;
}
}
</style>

View File

@ -1,71 +1,67 @@
<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 {
props: {
background: {
type: Boolean,
default: false
}
export default {
props: {
background: {
type: Boolean,
default: false,
},
},
components: {
Ellipses,
WidgetPopover,
},
data() {
return {
showMenu: false,
};
},
components: {
Ellipses,
WidgetPopover,
},
data() {
return {
showMenu: false,
};
},
methods: {
toggleMenu: function () {
this.showMenu = !this.showMenu;
},
methods: {
toggleMenu: function () {
this.showMenu = !this.showMenu;
},
};
},
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.more-actions {
svg {
width: 30px;
fill: $color-charcoal-dark;
//margin-right: 15px;
}
.more-actions {
svg {
width: 30px;
fill: $color-charcoal-dark;
//margin-right: 15px;
}
&__toggle {
display: flex;
border-radius: 5px;
&__toggle {
display: flex;
border-radius: 5px;
&--background {
background: white;
}
&--background {
background: white;
}
}
}
</style>

View File

@ -1,107 +1,82 @@
<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 {
props: ['room'],
export default {
props: ['room'],
components: {
ColorChooser,
PageForm,
PageFormInput
components: {
ColorChooser,
PageForm,
PageFormInput,
},
data() {
return {
localRoom: Object.assign({}, this.room),
me: {},
};
},
created() {
this.$store.dispatch('setSpecialContainerClass', this.localRoom.appearance);
},
beforeUnmount() {
this.$store.dispatch('setSpecialContainerClass', '');
},
methods: {
updateColor(newColor) {
this.localRoom.appearance = newColor;
this.$store.dispatch('setSpecialContainerClass', newColor);
},
},
data() {
return {
localRoom: Object.assign({}, this.room),
me: {}
};
apollo: {
me: {
query: ME_QUERY,
},
created() {
this.$store.dispatch('setSpecialContainerClass', this.localRoom.appearance);
},
beforeUnmount() {
this.$store.dispatch('setSpecialContainerClass', '');
},
methods: {
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 {
&__property-heading {
@include page-form-input-heading;
}
&__input,
&__textarea {
width: 100%;
margin-bottom: 35px;
}
&__save-button {
margin-right: 15px;
}
.room-form {
&__property-heading {
@include page-form-input-heading;
}
&__input,
&__textarea {
width: 100%;
margin-bottom: 35px;
}
&__save-button {
margin-right: 15px;
}
}
</style>

View File

@ -8,34 +8,34 @@
</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 {
props: ['name'],
export default {
props: ['name'],
components: {
Group
}
};
components: {
Group,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.room-group-widget {
display: flex;
align-items: center;
opacity: 0.6;
.room-group-widget {
display: flex;
align-items: center;
opacity: 0.6;
svg {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
}
& > span {
@include room-widget-text-style;;
}
svg {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
}
& > span {
@include room-widget-text-style;
}
}
</style>

View File

@ -13,42 +13,43 @@
<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 {
props: {
restricted: {
type: Boolean,
default: false
}
export default {
props: {
restricted: {
type: Boolean,
default: false,
},
},
components: {
ClosedEyeIcon,
EyeIcon
}
};
components: {
ClosedEyeIcon,
EyeIcon,
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.room-visibility-widget {
display: flex;
align-items: center;
opacity: 0.6;
.room-visibility-widget {
display: flex;
align-items: center;
opacity: 0.6;
&__icon {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
}
& > span {
@include room-widget-text-style;
}
&__icon {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
}
& > 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,43 +14,45 @@
</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 {
props: {
isTeacher: {
type: Boolean,
default: false,
},
export default {
props: {
isTeacher: {
type: Boolean,
default: false,
},
components: {RoomsIllustration},
},
components: { RoomsIllustration },
data() {
return {
newRoomRoute: NEW_ROOM_PAGE,
};
},
};
data() {
return {
newRoomRoute: NEW_ROOM_PAGE,
};
},
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.rooms-onboarding {
@include onboarding-page;
.rooms-onboarding {
@include onboarding-page;
&__heading {
margin-bottom: $large-spacing;
}
&__illustration {
@include onboarding-illustration;
}
&__text {
@include onboarding-text;
}
&__heading {
margin-bottom: $large-spacing;
}
&__illustration {
@include onboarding-illustration;
}
&__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,37 +64,36 @@
const ChevronDown = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/ChevronDown'));
const AddIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/AddIcon'));
export default {
props: {
mobile: {
type: Boolean,
default: false
}
export default {
props: {
mobile: {
type: Boolean,
default: false,
},
},
mixins: [updateSelectedClassMixin, sidebarMixin, meMixin],
components: {
WidgetPopover,
ChevronDown,
CurrentClass,
AddIcon
},
mixins: [updateSelectedClassMixin, sidebarMixin, meMixin],
components: {
WidgetPopover,
ChevronDown,
CurrentClass,
AddIcon,
},
data() {
return {
showPopover: false
};
},
data() {
return {
showPopover: false,
};
},
computed: {
currentClassSelection() {
let currentClass = this.me.schoolClasses.find(schoolClass => {
return schoolClass.id === this.me.selectedClass.id;
});
return currentClass || this.me.schoolClasses[0];
}
computed: {
currentClassSelection() {
let currentClass = this.me.schoolClasses.find((schoolClass) => {
return schoolClass.id === this.me.selectedClass.id;
});
return currentClass || this.me.schoolClasses[0];
},
},
methods: {
toggle() {
@ -111,47 +105,46 @@
this.closeSidebar('profile');
}
},
};
},
};
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.class-selection {
position: relative;
cursor: pointer;
margin-bottom: $medium-spacing;
border-radius: 4px;
.class-selection {
position: relative;
cursor: pointer;
margin-bottom: $medium-spacing;
border-radius: 4px;
&__popover {
white-space: nowrap;
top: 100%;
left: 0;
transform: translateY($small-spacing);
}
&__popover {
white-space: nowrap;
top: 100%;
left: 0;
transform: translateY($small-spacing);
}
}
.selected-class {
width: 100%;
box-sizing: border-box;
padding: $small-spacing 0;
display: flex;
align-items: center;
justify-content: flex-start;
&__text {
line-height: $large-spacing;
@include heading-4;
margin-right: $small-spacing;
}
.selected-class {
width: 100%;
box-sizing: border-box;
padding: $small-spacing 0;
display: flex;
align-items: center;
justify-content: flex-start;
&__text {
line-height: $large-spacing;
@include heading-4;
margin-right: $small-spacing;
}
&__dropdown-icon {
width: 20px;
height: 20px;
fill: $color-charcoal-dark;
}
&__dropdown-icon {
width: 20px;
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 {
mixins: [me],
};
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 {
display: flex;
flex-direction: column;
align-self: center;
line-height: 1;
@include regular-text;
}
.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 {
props: {
label: String,
checked: {
type: Boolean
},
item: Object,
type: String
export default {
props: {
label: String,
checked: {
type: Boolean,
},
item: Object,
type: String,
},
components: {
Tick,
CircleIcon
}
};
components: {
Tick,
CircleIcon,
},
};
</script>

View File

@ -1,65 +1,57 @@
<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 {
props: {
icon: {
type: String,
default: '',
},
text: {
type: String,
default: '',
},
export default {
props: {
icon: {
type: String,
default: '',
},
components: {
EyeIcon,
TrashIcon,
PenIcon,
text: {
type: String,
default: '',
},
};
},
components: {
EyeIcon,
TrashIcon,
PenIcon,
},
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
@import '~styles/helpers';
.popover-link {
@include popover-link;
.popover-link {
@include popover-link;
&__icon {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
display: flex;
flex-basis: auto;
flex-shrink: 0;
}
&__text {
width: auto;
display: flex;
flex-basis: auto;
flex-shrink: 0;
}
&__icon {
width: 30px;
fill: $color-charcoal-dark;
margin-right: 15px;
display: flex;
flex-basis: auto;
flex-shrink: 0;
}
&__text {
width: auto;
display: flex;
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,26 +14,27 @@
</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 {
props: {
document: {
type: String,
default: '',
},
withText: {
type: Boolean,
default: false
}
export default {
props: {
document: {
type: String,
default: '',
},
components: {SimpleFileUpload, DocumentBlock},
};
withText: {
type: Boolean,
default: false,
},
},
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 {
&__icon {
width: 25px;
fill: $color-silver-dark;
}
.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({
id: String,
label: String
}) ;
defineProps({
id: String,
label: String,
});
</script>

View File

@ -1,93 +1,87 @@
<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 {
props: {
block: {
type: Object,
default: () => ({})
export default {
props: {
block: {
type: Object,
default: () => ({}),
},
type: {
type: String,
default: CONTENT_TYPE,
validator: (value) => {
// value must be one of TYPES
return TYPES.indexOf(value) !== -1;
},
type: {
type: String,
default: CONTENT_TYPE,
validator: value => {
// value must be one of TYPES
return TYPES.indexOf(value) !== -1;
}
}
},
},
mixins: [me],
mixins: [me],
components: {
EyeIcon,
ClosedEyeIcon
components: {
EyeIcon,
ClosedEyeIcon,
},
computed: {
hidden() {
return hidden({ type: this.type, block: this.block, schoolClass: this.schoolClass });
},
},
computed: {
hidden() {
return hidden({type: this.type, block: this.block, schoolClass: this.schoolClass});
}
},
methods: {
toggleVisibility() {
const hidden = !this.hidden;
const schoolClassId = this.schoolClass.id;
methods: {
toggleVisibility() {
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
});
},
this.$apollo.mutate({
mutation,
variables,
});
},
};
},
};
</script>
<style scoped lang="scss">
.visibility-action {
margin-top: 9px;
.visibility-action {
margin-top: 9px;
position: absolute;
left: -70px;
top: 0px;
display: grid;
position: absolute;
left: -70px;
top: 0px;
display: grid;
&__visibility-menu {
top: 40px;
}
&__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,99 +35,99 @@
const HepLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/HepLogo'));
const EhbLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EhbLogo'));
export default {
components: {
HepLogo,
EhbLogo
}
};
export default {
components: {
HepLogo,
EhbLogo,
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
.default-footer {
background-color: $color-silver-light;
max-width: 100vw;
overflow: hidden;
.default-footer {
background-color: $color-silver-light;
max-width: 100vw;
overflow: hidden;
&__section {
width: 100%;
border-bottom: $color-silver 1px solid;
display: flex;
justify-content: center;
}
&__section {
width: 100%;
border-bottom: $color-silver 1px solid;
display: flex;
justify-content: center;
}
&__info {
width: 100%;
max-width: $footer-width;
padding: 2*$large-spacing 0;
display: flex;
flex-direction: column;
&__info {
width: 100%;
max-width: $footer-width;
padding: 2 * $large-spacing 0;
display: flex;
flex-direction: column;
@include desktop {
flex-direction: row;
justify-content: space-between;
}
}
&__who-are-we {
width: 100%;
margin-bottom: $large-spacing;
@include desktop {
width: 330px;
margin-bottom: 0;
}
}
&__logo-hep {
width: auto;
height: 35px;
margin-bottom: $large-spacing;
@include desktop {
width: 147px;
margin-bottom: 0;
}
}
&__logo-ehb {
width: 100px;
height: 32px;
}
&__links {
width: 100%;
max-width: $footer-width;
padding: $large-spacing 0;
display: flex;
flex-direction: column;
@include desktop {
flex-direction: row;
}
}
&__link {
@include aside-with-cheese;
margin-right: $large-spacing;
margin-bottom: $small-spacing;
@include desktop {
margin-bottom: 0;
}
@include desktop {
flex-direction: row;
justify-content: space-between;
}
}
.who-are-we {
&__title {
@include heading-4;
}
&__who-are-we {
width: 100%;
margin-bottom: $large-spacing;
&__text {
@include aside-text;
@include desktop {
width: 330px;
margin-bottom: 0;
}
}
&__logo-hep {
width: auto;
height: 35px;
margin-bottom: $large-spacing;
@include desktop {
width: 147px;
margin-bottom: 0;
}
}
&__logo-ehb {
width: 100px;
height: 32px;
}
&__links {
width: 100%;
max-width: $footer-width;
padding: $large-spacing 0;
display: flex;
flex-direction: column;
@include desktop {
flex-direction: row;
}
}
&__link {
@include aside-with-cheese;
margin-right: $large-spacing;
margin-bottom: $small-spacing;
@include desktop {
margin-bottom: 0;
}
}
}
.who-are-we {
&__title {
@include heading-4;
}
&__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 {
components: {
Cross
},
export default {
components: {
Cross,
},
computed: {
specialContainerClass() {
let cls = this.$store.state.specialContainerClass;
return [cls ? `skillbox--${cls}` : ''];
}
computed: {
specialContainerClass() {
let cls = this.$store.state.specialContainerClass;
return [cls ? `skillbox--${cls}` : ''];
},
methods: {
back() {
this.$router.go(-1);
}
}
};
},
methods: {
back() {
this.$router.go(-1);
},
},
};
</script>
<style lang="scss" scoped>
@import "@/styles/_default-layout.scss";
@import '@/styles/_default-layout.scss';
.close-button {
margin-top: $medium-spacing;
margin-right: $medium-spacing;
justify-self: end;
cursor: pointer;
display:flex;
justify-content:flex-end;
}
.close-button {
margin-top: $medium-spacing;
margin-right: $medium-spacing;
justify-self: end;
cursor: pointer;
display: flex;
justify-content: flex-end;
}
</style>

View File

@ -1,80 +1,74 @@
<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 {
components: {
Logo,
DefaultFooter
},
};
export default {
components: {
Logo,
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 {
padding-right: $medium-spacing;
padding-left: $medium-spacing;
max-width: 800px;
min-width: 320px;
width: 100%;
margin: 0 auto;
}
@mixin content-block {
padding-right: $medium-spacing;
padding-left: $medium-spacing;
max-width: 800px;
min-width: 320px;
width: 100%;
margin: 0 auto;
}
.logo {
position: relative;
.logo {
position: relative;
width: auto;
height: 43px;
}
.public {
grid-template-areas: 'h' 'c' 'f';
&__content {
@include content-block();
margin-bottom: $large-spacing;
width: auto;
height: 43px;
}
.public {
grid-template-areas: "h" "c" "f";
&__content {
@include content-block();
margin-bottom: $large-spacing;
width: auto;
}
&__logo {
@include content-block();
margin-top: $medium-spacing
}
&__footer {
background-color: $color-silver-light;
display: block;
}
&__logo {
@include content-block();
margin-top: $medium-spacing;
}
.footer {
padding: $large-spacing $medium-spacing 0;
&__content {
@include content-block();
}
&__footer {
background-color: $color-silver-light;
display: block;
}
}
.footer {
padding: $large-spacing $medium-spacing 0;
&__content {
@include content-block();
}
}
</style>

View File

@ -1,107 +1,98 @@
<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 {
components: {
Cross,
SimpleFooter
},
export default {
components: {
Cross,
SimpleFooter,
},
computed: {
enableFooter() {
if (this.$route.meta.hideFooter) {
return false;
}
return this.$flavor.showFooter;
computed: {
enableFooter() {
if (this.$route.meta.hideFooter) {
return false;
}
return this.$flavor.showFooter;
},
},
methods: {
back() {
this.$router.go(-1);
}
}
};
methods: {
back() {
this.$router.go(-1);
},
},
};
</script>
<style lang="scss" scoped>
@import "~styles/helpers";
@import '~styles/helpers';
.layout {
&--simple {
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
width: 100%;
@include desktop {
grid-template-columns: 1fr 640px 1fr;
grid-template-rows: 60px auto-fill 105px;
-ms-grid-columns: 1fr 640px 1fr;
& > :nth-child(2) {
grid-column: 2;
-ms-grid-column: 2;
}
}
.layout {
&--simple {
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
$parent: &;
&--full-width {
#{$parent}__content {
grid-column: 1 / span 3;
grid-row: 1 / span 2;
}
}
&__footer {
@include desktop {
grid-column: 1 / span 3;
}
}
}
.close-button {
justify-self: end;
cursor: pointer;
display:flex;
justify-content:flex-end;
margin-right: $small-spacing;
margin-top: $small-spacing;
width: 100%;
@include desktop {
grid-column: 3;
grid-row: 1;
-ms-grid-column: 3;
-ms-grid-row: 1;
margin-right: $medium-spacing;
margin-top: $medium-spacing;
grid-template-columns: 1fr 640px 1fr;
grid-template-rows: 60px auto-fill 105px;
-ms-grid-columns: 1fr 640px 1fr;
& > :nth-child(2) {
grid-column: 2;
-ms-grid-column: 2;
}
}
}
$parent: &;
&--full-width {
#{$parent}__content {
grid-column: 1 / span 3;
grid-row: 1 / span 2;
}
}
&__footer {
@include desktop {
grid-column: 1 / span 3;
}
}
}
.close-button {
justify-self: end;
cursor: pointer;
display: flex;
justify-content: flex-end;
margin-right: $small-spacing;
margin-top: $small-spacing;
@include desktop {
grid-column: 3;
grid-row: 1;
-ms-grid-column: 3;
-ms-grid-row: 1;
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,145 +19,147 @@
const HelloMyKVIllustration = defineAsyncComponent(() => import(/* webpackChunkName: "illustrations" */'@/components/illustrations/HelloMyKVIllustration'));
const Hello = flavorValues.appFlavor === 'my-kv' ? HelloMyKVIllustration : HelloIllustration;
export default {
components: {
contents: ContentsIllustration,
portfolio: PortfolioIllustration,
rooms: RoomsIllustration,
hello: Hello
},
export default {
components: {
contents: ContentsIllustration,
portfolio: PortfolioIllustration,
rooms: RoomsIllustration,
hello: Hello,
},
computed: {
illustration() {
return this.$route.meta.illustration;
},
illustrationAlignment() {
return this.$route.meta.illustrationAlign ? `split-view__illustration--${this.$route.meta.illustrationAlign}` : '';
}
computed: {
illustration() {
return this.$route.meta.illustration;
},
};
illustrationAlignment() {
return this.$route.meta.illustrationAlign
? `split-view__illustration--${this.$route.meta.illustrationAlign}`
: '';
},
},
};
</script>
<style lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.split-view {
background-color: $color-brand;
.split-view {
background-color: $color-brand;
display: flex;
position: relative;
width: 100%;
min-height: 100vh;
display: flex;
position: relative;
width: 100%;
min-height: 100vh;
&--illustration {
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 50%;
background: $color-brand-dark;
}
}
&__illustration {
width: 30vw;
min-width: 400px;
align-self: center;
background-color: $color-brand;
z-index: 1;
display: none;
&--top {
align-self: center;
margin-top: -400px;
}
& > svg {
max-width: 300px;
max-height: 400px;
}
@include desktop {
display: flex;
justify-content: center;
}
}
&__content {
background-color: $color-white;
padding: $medium-spacing;
display: flex;
flex-direction: column;
z-index: 1;
width: 100%;
@include desktop {
padding: 2*$large-spacing;
}
}
&__logo {
width: 300px;
height: 50px;
margin-bottom: $large-spacing;
@include desktop {
margin-bottom: 70px;
}
}
&__page-subheading {
@include regular-text;
color: $color-brand;
margin-bottom: $small-spacing;
}
&__page-heading {
@include heading-2;
color: $color-brand;
margin-bottom: 2*$large-spacing;
}
&__heading {
@include heading-2;
margin-bottom: $small-spacing;
}
&__claim {
@include heading-2;
margin-bottom: 70px;
}
&__paragraph {
@include regular-text;
margin-bottom: $medium-spacing;
&:last-of-type {
margin-bottom: 2*$large-spacing;
}
}
&__button {
@include regular-text;
flex-grow: 0;
align-self: flex-start;
min-width: 150px;
display: inline-flex;
box-sizing: border-box;
justify-content: center;
margin-bottom: $large-spacing;
cursor: pointer;
}
&__secondary-link {
@include inline-title;
cursor: pointer;
@include desktop {
margin-top: auto;
}
&--illustration {
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 50%;
background: $color-brand-dark;
}
}
&__illustration {
width: 30vw;
min-width: 400px;
align-self: center;
background-color: $color-brand;
z-index: 1;
display: none;
&--top {
align-self: center;
margin-top: -400px;
}
& > svg {
max-width: 300px;
max-height: 400px;
}
@include desktop {
display: flex;
justify-content: center;
}
}
&__content {
background-color: $color-white;
padding: $medium-spacing;
display: flex;
flex-direction: column;
z-index: 1;
width: 100%;
@include desktop {
padding: 2 * $large-spacing;
}
}
&__logo {
width: 300px;
height: 50px;
margin-bottom: $large-spacing;
@include desktop {
margin-bottom: 70px;
}
}
&__page-subheading {
@include regular-text;
color: $color-brand;
margin-bottom: $small-spacing;
}
&__page-heading {
@include heading-2;
color: $color-brand;
margin-bottom: 2 * $large-spacing;
}
&__heading {
@include heading-2;
margin-bottom: $small-spacing;
}
&__claim {
@include heading-2;
margin-bottom: 70px;
}
&__paragraph {
@include regular-text;
margin-bottom: $medium-spacing;
&:last-of-type {
margin-bottom: 2 * $large-spacing;
}
}
&__button {
@include regular-text;
flex-grow: 0;
align-self: flex-start;
min-width: 150px;
display: inline-flex;
box-sizing: border-box;
justify-content: center;
margin-bottom: $large-spacing;
cursor: pointer;
}
&__secondary-link {
@include inline-title;
cursor: pointer;
@include desktop {
margin-top: auto;
}
}
}
</style>

View File

@ -96,16 +96,16 @@
ErrorMessage,
},
data() {
return {
email: '',
password: '',
emailErrors: [],
passwordErrors: [],
loginError: '',
submitted: false,
};
},
data() {
return {
email: '',
password: '',
emailErrors: [],
passwordErrors: [],
loginError: '',
submitted: false,
};
},
methods: {
required(value, {field}) {
@ -124,13 +124,14 @@
},
};
const redirectUrl = this.$route.query.redirect ? this.$route.query.redirect : '/';
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,24 +160,22 @@
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
.actions {
display: flex;
justify-content: space-between;
&__reset {
display: inline-block;
margin-left: $large-spacing;
padding: 15px;
line-height: 19px;
}
.actions {
display: flex;
justify-content: space-between;
&__reset {
display: inline-block;
margin-left: $large-spacing;
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,59 +22,62 @@
default: ''
}
},
},
components: {
ContentBlockForm,
components: {
ContentBlockForm,
},
data: () => ({
contentBlock: {
title: '',
isAssignment: false,
contents: [],
},
}),
data: () => ({
contentBlock: {
title: '',
isAssignment: false,
contents: [
]},
}),
methods: {
save({title, contents, isAssignment}) {
let cleanedContents = cleanUpContents(contents);
const contentBlock = {
title: title,
contents: cleanedContents,
type: setUserBlockType(isAssignment),
methods: {
save({ title, contents, isAssignment }) {
let cleanedContents = cleanUpContents(contents);
const contentBlock = {
title: title,
contents: cleanedContents,
type: setUserBlockType(isAssignment),
};
let input;
const { parent, after, slug } = this.$route.params;
if (after) {
input = {
contentBlock,
after,
};
let input;
const { parent, after, slug } = this.$route.params;
if(after) {
input = {
contentBlock,
after
};
} else {
input = {
contentBlock,
parent
};
}
this.$apollo.mutate({
} else {
input = {
contentBlock,
parent,
};
}
this.$apollo
.mutate({
mutation: NEW_CONTENT_BLOCK_MUTATION,
variables: {
input
input,
},
refetchQueries: [{
query: MODULE_DETAILS_QUERY,
variables: {
slug
}
}]
}).then(this.goToModule);
},
goToModule() {
// use the history, so the scroll position is preserved
this.$router.go(-1);
}
}
});
refetchQueries: [
{
query: MODULE_DETAILS_QUERY,
variables: {
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,128 +57,127 @@
const EhbLogo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/EhbLogo'));
const Logo = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */'@/components/icons/Logo'));
export default {
components: {
HepLogoNoClaim,
EhbLogo,
Logo
},
export default {
components: {
HepLogoNoClaim,
EhbLogo,
Logo,
},
data() {
return {
email: '',
submitted: false,
loading: false
};
},
};
data() {
return {
email: '',
submitted: 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;
@include desktop {
max-width: 600px;
margin: 0 auto;
}
}
}
.logo {
display: block;
width: 300px;
margin: $small-spacing auto $hello-block-margin;
.logo {
display: block;
width: 300px;
margin: $small-spacing auto $hello-block-margin;
@include desktop {
display: none;
}
}
.about {
@include desktop {
display: none;
margin-bottom: $hello-block-margin;
}
}
.about {
display: none;
margin-bottom: $hello-block-margin;
@include desktop {
display: block;
}
&__text {
margin-top: $medium-spacing;
@include regular-text;
}
&__logos {
& a:first-child {
margin-right: $large-spacing;
}
}
}
.logos {
&__logo {
height: 30px;
}
}
.login-actions {
@include widget-shadow;
padding: $medium-spacing;
margin-bottom: $hello-block-margin;
&__title {
font-size: 2.125rem; // 34px
font-weight: 700;
}
&__register {
margin-top: $large-spacing;
> p,
a {
@include regular-text;
}
}
}
.information {
margin-top: $hello-block-margin;
> p,
a {
@include regular-text;
}
}
.links {
margin-top: $hello-block-margin;
display: flex;
&__list-item {
color: $color-silver-dark;
> a {
@include regular-text;
}
flex-direction: column;
margin-top: $medium-spacing;
&:first-child {
margin-top: 0;
}
@include desktop {
display: block;
}
display: inline-block;
flex-direction: row;
&__text {
margin-top: $medium-spacing;
@include regular-text;
}
&__logos {
& a:first-child {
margin-right: $large-spacing;
&:not(:last-child) {
margin-right: 1rem;
}
}
}
.logos {
&__logo {
height: 30px;
}
}
.login-actions {
@include widget-shadow;
padding: $medium-spacing;
margin-bottom: $hello-block-margin;
&__title {
font-size: 2.125rem; // 34px
font-weight: 700;
}
&__register {
margin-top: $large-spacing;
> p, a {
@include regular-text;
}
}
}
.information {
margin-top: $hello-block-margin;
> p, a {
@include regular-text;
}
}
.links {
margin-top: $hello-block-margin;
display: flex;
&__list-item {
color: $color-silver-dark;
> a {
@include regular-text;
}
flex-direction: column;
margin-top: $medium-spacing;
&:first-child {
margin-top: 0;
}
@include desktop {
display: inline-block;
flex-direction: row;
&:not(:last-child) {
margin-right: 1rem;
}
}
}
}
}
</style>

View File

@ -87,10 +87,11 @@ export default {
.instrument-overview {
display: grid;
@include desktop {
grid-template-columns: 300px auto;
}
grid-column-gap: $small-spacing;
grid-template-columns: 300px auto;
}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],
@ -105,90 +98,90 @@
Field
},
data() {
return {
coupon: '',
couponErrors: [],
loginError: '',
submitted: false,
me: {
email: '',
},
teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`,
studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`,
loading: false,
};
},
methods: {
required(value, {field}) {
if (value && value.trim()) {
return true;
}
return `${field} ist ein Pflichtfeld`;
data() {
return {
coupon: '',
couponErrors: [],
loginError: '',
submitted: false,
me: {
email: '',
},
validateBeforeSubmit({coupon: couponCode}) {
console.log('coupon', couponCode);
this.submitted = true;
this.loading = true;
this.$apollo.mutate({
teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`,
studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`,
loading: false,
};
},
methods: {
required(value, { field }) {
if (value && value.trim()) {
return true;
}
return `${field} ist ein Pflichtfeld`;
},
validateBeforeSubmit({ coupon: couponCode }) {
console.log('coupon', couponCode);
this.submitted = true;
this.loading = true;
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({
query: ME_QUERY,
fetchPolicy: 'network-only',
}).then(() => this.$router.push('/'));
this.$apollo
.query({
query: ME_QUERY,
fetchPolicy: 'network-only',
})
.then(() => this.$router.push('/'));
}
},
}).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.'];
}
})
.finally(() => {
this.loading = false;
});
},
})
.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.',
];
}
})
.finally(() => {
this.loading = false;
});
},
});
},
});
</script>
<style scoped lang="scss">
@import "~styles/helpers";
@import '~styles/helpers';
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
.actions {
&__reset {
display: inline-block;
margin-left: $large-spacing;
}
}
.actions {
&__reset {
display: inline-block;
margin-left: $large-spacing;
}
.get-license {
margin-top: $large-spacing;
}
.license-links {
&__item {
margin-bottom: $medium-spacing;
}
.get-license {
margin-top: $large-spacing
}
.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,127 +16,114 @@
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'));
import { defineAsyncComponent } from 'vue';
const EyeIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/EyeIcon'));
export default {
export default {
mixins: [me],
components: {
EyeIcon,
},
mixins: [me],
components: {
EyeIcon,
data() {
return {
selectedClassId: '',
};
},
computed: {
schoolClasses() {
return this.me.schoolClasses.filter((schoolClass) => schoolClass.id !== this.me.selectedClass.id);
},
data() {
return {
selectedClassId: '',
};
slug() {
return this.$route.params.slug;
},
},
computed: {
schoolClasses() {
return this.me.schoolClasses.filter(schoolClass => schoolClass.id !== this.me.selectedClass.id);
},
slug() {
return this.$route.params.slug;
}
methods: {
select(selectedClassId) {
this.selectedClassId = selectedClassId;
},
methods: {
select(selectedClassId) {
this.selectedClassId = selectedClassId;
},
sync() {
if (this.selectedClassId) {
const slug = this.slug;
this.$apollo.mutate({
mutation: SYNC_VISIBILITY_MUTATION,
variables: {
input: {
module: slug,
templateSchoolClass: this.selectedClassId,
schoolClass: this.me.selectedClass.id,
sync() {
if (this.selectedClassId) {
const slug = this.slug;
this.$apollo
.mutate({
mutation: SYNC_VISIBILITY_MUTATION,
variables: {
input: {
module: slug,
templateSchoolClass: this.selectedClassId,
schoolClass: this.me.selectedClass.id,
},
},
refetchQueries: [
{
query: MODULE_DETAILS_QUERY,
variables: {
slug,
},
},
refetchQueries: [
{
query: MODULE_DETAILS_QUERY,
variables: {
slug,
},
},
],
},
).then(() => {
],
})
.then(() => {
this.$router.push({
name: MODULE_PAGE,
params: {
slug
}
slug,
},
});
});
}
},
}
},
};
},
};
</script>
<style scoped lang="scss">
@import '~styles/_helpers';
@import '~styles/_helpers';
.module-visibility {
@include settings-page;
.module-visibility {
@include settings-page;
margin: 0 auto;
margin: 0 auto;
&__inline-icon {
width: 25px;
height: 25px;
vertical-align: middle;
}
&__dropdown {
width: 200px;
margin: 0 $medium-spacing;
}
&__form {
display: flex;
align-items: center;
@include regular-text;
font-weight: 600;
}
&__inline-icon {
width: 25px;
height: 25px;
vertical-align: middle;
}
&__dropdown {
width: 200px;
margin: 0 $medium-spacing;
}
&__form {
display: flex;
align-items: center;
@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 {
components: {
Logo
},
};
export default {
components: {
Logo,
},
};
</script>

View File

@ -137,15 +137,14 @@ export default {
@include desktop {
grid-template-areas:
'b a'
't t'
'd d'
'm m';
}
}
't t'
'd d'
'm m';
}}
&__back {
grid-area: b;
margin-bottom: $medium-spacing;
margin-bottom: $medium-spacing;
@include desktop {
margin-bottom: 0;
}
@ -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,29 +195,25 @@ 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;
margin-left: $large-spacing;
margin-bottom: 0;
}
}
& > :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,81 +24,87 @@
required: true
}
},
},
components: {
ContentBlockForm,
},
components: {
ContentBlockForm,
},
data() {
return {
features: ROOMS_FEATURE_SET,
roomEntry: {
title: '',
contents: [],
},
};
},
apollo: {
data() {
return {
features: ROOMS_FEATURE_SET,
roomEntry: {
query: ROOM_ENTRY_QUERY,
variables() {
return {
slug: this.entrySlug
};
}
}
},
methods: {
goBack() {
this.$router.push({
name: ROOM_PAGE,
params: {
slug: this.slug
}
});
title: '',
contents: [],
},
save({title, contents}) {
const entry = {
slug: this.roomEntry.slug,
title,
contents,
};
},
apollo: {
roomEntry: {
query: ROOM_ENTRY_QUERY,
variables() {
return {
slug: this.entrySlug,
};
this.$apollo.mutate({
},
},
},
methods: {
goBack() {
this.$router.push({
name: ROOM_PAGE,
params: {
slug: this.slug,
},
});
},
save({ title, contents }) {
const entry = {
slug: this.roomEntry.slug,
title,
contents,
};
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,107 +1,104 @@
<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( {
props: {
slug: {
type: String,
required: true
}
export default defineComponent({
props: {
slug: {
type: String,
required: true,
},
},
components: {
ContentBlockForm,
},
components: {
ContentBlockForm,
},
data() {
return {
features: ROOMS_FEATURE_SET,
roomEntry: {
title: '',
contents: [],
},
};
},
methods: {
goBack() {
this.$router.push({
name: ROOM_PAGE,
params: {
slug: this.slug
}
});
data() {
return {
features: ROOMS_FEATURE_SET,
roomEntry: {
title: '',
contents: [],
},
save({title, contents}) {
const entry = {
title,
contents,
roomSlug: this.slug
};
this.$apollo.mutate({
};
},
methods: {
goBack() {
this.$router.push({
name: ROOM_PAGE,
params: {
slug: this.slug,
},
});
},
save({ title, contents }) {
const entry = {
title,
contents,
roomSlug: this.slug,
};
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;
},
@ -226,7 +225,7 @@ export default {
this.survey.data = data; // reapply it
this.saveDisabled = false;
},
loadSurveyFromServer(survey) {
loadSurveyFromServer(survey) {
let json = JSON.parse(survey.data);
json.showTitle = false;
json.showProgressBar = 'bottom';
@ -254,7 +253,7 @@ export default {
result({ data, loading }) {
if (!loading) {
this.surveyData = data.survey;
this.loadSurveyFromServer(data.survey);
this.loadSurveyFromServer(data.survey);
const module = data.survey.module;
this.$apollo.addSmartQuery('module', {

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,173 +35,180 @@
</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'));
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 {
export default {
mixins: [me],
components: {
TopicNavigation,
ModuleTeaser,
PlayIcon,
BulbIcon,
},
mixins: [me],
components: {
TopicNavigation,
ModuleTeaser,
PlayIcon,
BulbIcon,
},
apollo: {
topic() {
return {
query: TOPIC_QUERY,
variables: {
slug: this.$route.params.topicSlug,
},
update(data) {
return this.$getRidOfEdges(data).topic || {};
},
result() {
if (this.saveMe) {
this.saveMe = false;
this.updateLastVisitedTopic(this.topic.id);
}
},
};
},
},
data() {
apollo: {
topic() {
return {
topic: {
modules: {
edges: [],
},
query: TOPIC_QUERY,
variables: {
slug: this.$route.params.topicSlug,
},
update(data) {
return this.$getRidOfEdges(data).topic || {};
},
result() {
if (this.saveMe) {
this.saveMe = false;
this.updateLastVisitedTopic(this.topic.id);
}
},
saveMe: false,
};
},
},
computed: {
modules() {
return this.topic.modules;
data() {
return {
topic: {
modules: {
edges: [],
},
},
},
saveMe: false,
};
},
mounted() {
if (!this.topic.id) { // component was loaded before topic, apollo not ready yet
this.saveMe = true; // needs saving, apollo will do this
} else {
this.updateLastVisitedTopic(this.topic.id);
computed: {
modules() {
return this.topic.modules;
},
},
mounted() {
if (!this.topic.id) {
// component was loaded before topic, apollo not ready yet
this.saveMe = true; // needs saving, apollo will do this
} else {
this.updateLastVisitedTopic(this.topic.id);
}
},
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
},
updateLastVisitedTopic(topicId) {
if (!topicId) {
return;
}
},
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
},
updateLastVisitedTopic(topicId) {
if (!topicId) {
return;
}
this.$apollo.mutate({
mutation: UPDATE_LAST_TOPIC_MUTATION,
variables: {
input: {
id: topicId,
this.$apollo.mutate({
mutation: UPDATE_LAST_TOPIC_MUTATION,
variables: {
input: {
id: topicId,
},
},
update(
store,
{
data: {
updateLastTopic: { topic },
},
},
update(store, {data: {updateLastTopic: {topic}}}) {
if (topic) {
const query = ME_QUERY;
const {me} = store.readQuery({query});
if (me) {
const data = {
me: {
...me,
lastTopic: topic,
},
};
store.writeQuery({query, data});
}
}
) {
if (topic) {
const query = ME_QUERY;
const { me } = store.readQuery({ query });
if (me) {
const data = {
me: {
...me,
lastTopic: topic,
},
};
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 {
display: grid;
padding: $large-spacing 0;
grid-template-columns: 1fr;
.topic {
display: grid;
padding: $large-spacing 0;
grid-template-columns: 1fr;
@include desktop {
grid-template-columns: 300px 1fr;
}
&__navigation {
padding: 0 $medium-spacing;
display: none;
@include desktop {
grid-template-columns: 300px 1fr;
}
&__navigation {
padding: 0 $medium-spacing;
display: none;
@include desktop {
display: block;
}
}
&__teaser {
color: $color-charcoal-dark;
width: 90%;
@include lead-paragraph;
margin-bottom: $large-spacing;
}
&__links {
margin-bottom: $large-spacing;
display: flex;
}
&__link {
cursor: pointer;
display: flex;
align-items: center;
}
&__video-link {
margin-right: $large-spacing;
}
&__link-icon {
width: 40px;
height: 40px;
margin-right: $medium-spacing;
}
&__link-description {
@include heading-3;
}
&__modules {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
@supports (display: grid) {
display: grid;
}
grid-column-gap: $large-spacing;
grid-row-gap: $large-spacing;
@include desktop {
grid-template-columns: repeat(3, minmax(auto, 380px));
}
display: block;
}
}
&__teaser {
color: $color-charcoal-dark;
width: 90%;
@include lead-paragraph;
margin-bottom: $large-spacing;
}
&__links {
margin-bottom: $large-spacing;
display: flex;
}
&__link {
cursor: pointer;
display: flex;
align-items: center;
}
&__video-link {
margin-right: $large-spacing;
}
&__link-icon {
width: 40px;
height: 40px;
margin-right: $medium-spacing;
}
&__link-description {
@include heading-3;
}
&__modules {
margin-top: 40px;
display: flex;
flex-wrap: wrap;
@supports (display: grid) {
display: grid;
}
grid-column-gap: $large-spacing;
grid-row-gap: $large-spacing;
@include desktop {
grid-template-columns: repeat(3, minmax(auto, 380px));
}
}
}
</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