Skip to content
Snippets Groups Projects
Commit 9f4aa63a authored by Ron Lucke's avatar Ron Lucke
Browse files

Courseware - Änderung des Abschnittstyps ermöglichen

Closes #2019

Merge request studip/studip!1466
parent 898ff763
No related branches found
No related tags found
No related merge requests found
Showing
with 294 additions and 25 deletions
......@@ -73,6 +73,12 @@ class ContainersUpdate extends JsonApiController
'data.relationships.structural-element.data.id'
);
}
if (self::arrayHas($json, 'data.attributes.container-type')) {
$resource->container_type = self::arrayGet(
$json,
'data.attributes.container-type'
);
}
$resource->position = $json['data']['attributes']['position'];
......
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m56,20v24H8v-24h48m4-4H4v32h56V16h0Z"/></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m4,16v32h56V16H4Zm39,4v24h-22v-24h22Zm-35,0h9v24H8v-24Zm48,24h-9v-24h9v24Z"/></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m4,16v32h56V16H4Zm4,4h22v24H8v-24Zm48,24h-22v-24h22v24Z"/></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#fff;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m56,20v24H8v-24h48m4-4H4v32h56V16h0Z"/></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#fff;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m4,16v32h56V16H4Zm39,4v24h-22v-24h22Zm-35,0h9v24H8v-24Zm48,24h-9v-24h9v24Z"/></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#fff;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m4,16v32h56V16H4Zm4,4h22v24H8v-24Zm48,24h-22v-24h22v24Z"/></g></g></svg>
\ No newline at end of file
......@@ -2006,7 +2006,54 @@ b l o c k a d d e r
}
}
.cw-container-style-selector {
display: flex;
margin-bottom: 8px;
label {
border: solid thin var(--content-color-40);
padding: calc(0.5em + 32px) 1em 0.5em 1em;
color: var(--base-color);
text-align: center;
width: 33%;
background-position: center 0.5em;
background-repeat: no-repeat;
cursor: pointer;
&.full {
@include background-icon(column-full, clickable, 32);
}
&.half {
@include background-icon(column-half, clickable, 32);
}
&.half-center {
@include background-icon(column-half-centered, clickable, 32);
}
&:hover {
color: var(--active-color);
}
&:not(:first-child) {
border-left: solid thin transparent;
}
&.cw-container-style-selector-active {
background-color: var(--content-color-20);
border: solid thin var(--base-color);
}
}
input[type=radio] {
position: absolute;
opacity: 0;
width: 0;
&:focus + label {
outline-color: Highlight;
outline-color: -webkit-focus-ring-color;
outline-style: auto;
outline-width: 1px;
}
}
}
/* * * * * * * * * * * * *
b l o c k a d d e r e n d
......@@ -5577,3 +5624,75 @@ s h e l f i t e m s
/* * * * * * * * * * * * * * * * * *
s h e l f i t e m s e n d
* * * * * * * * * * * * * * * * * */
/* * * * * * * * *
r a d i o s e t
* * * * * * * * */
.cw-radioset {
display: flex;
flex-direction: row;
justify-content: center;
margin-bottom: 1em;
.cw-radioset-box {
width: 128px;
height: 128px;
text-align: center;
margin-right: 16px;
border: solid thin var(--content-color-40);
&.selected {
border-color: var(--base-color);
background-color: var(--content-color-20);
}
&:last-child {
margin-right: 0;
}
label {
height: 100%;
width: 100%;
margin: 0;
cursor: pointer;
.label-icon {
background-position: center 8px;
background-repeat: no-repeat;
height: 64px;
padding: 8px;
&.accordion {
@include background-icon(block-accordion, clickable, 64);
}
&.list {
@include background-icon(view-list, clickable, 64);
}
&.tabs {
@include background-icon(block-tabs, clickable, 64);
}
&.full {
@include background-icon(column-full, clickable, 64);
}
&.half {
@include background-icon(column-half, clickable, 64);
}
&.half-center {
@include background-icon(column-half-centered, clickable, 64);
}
}
}
input[type=radio] {
position: absolute;
opacity: 0;
width: 0;
&:focus + label {
outline-color: Highlight;
outline-color: -webkit-focus-ring-color;
outline-style: auto;
outline-width: 1px;
}
}
}
}
/* * * * * * * * * * * *
r a d i o s e t e n d
* * * * * * * * * * * */
......@@ -4,6 +4,7 @@
:items="menuItems"
:context="container.attributes.title"
@editContainer="editContainer"
@changeContainer="changeContainer"
@deleteContainer="deleteContainer"
@removeLock="removeLock"
/>
......@@ -41,7 +42,8 @@ export default {
if (this.container.attributes["container-type"] !== 'list') {
menuItems.push({ id: 1, label: this.$gettext('Abschnitt bearbeiten'), icon: 'edit', emit: 'editContainer' });
}
menuItems.push({ id: 2, label: this.$gettext('Abschnitt löschen'), icon: 'trash', emit: 'deleteContainer' });
menuItems.push({ id: 2, label: this.$gettext('Abschnitt verändern'), icon: 'settings', emit: 'changeContainer' });
menuItems.push({ id: 3, label: this.$gettext('Abschnitt löschen'), icon: 'trash', emit: 'deleteContainer' });
}
if (this.blocked && this.blockedByAnotherUser && this.userIsTeacher) {
......@@ -67,6 +69,9 @@ export default {
editContainer() {
this.$emit('editContainer');
},
changeContainer() {
this.$emit('changeContainer');
},
deleteContainer() {
this.$emit('deleteContainer');
},
......
......@@ -17,6 +17,7 @@
:canEdit="canEdit"
:container="container"
@editContainer="displayEditDialog"
@changeContainer="displayChangeDialog"
@deleteContainer="displayDeleteDialog"
@removeLock="displayRemoveLockDialog"
/>
......@@ -47,6 +48,60 @@
</template>
</studip-dialog>
<studip-dialog
v-if="showChangeDialog"
:title="$gettext('Abschnitt verändern')"
:confirmText="$gettext('Speichern')"
confirmClass="accept"
:closeText="$gettext('Abbrechen')"
closeClass="cancel"
@close="closeChange"
@confirm="storeChange"
height="520"
width="480"
>
<template v-slot:dialogContent>
<form class="default" @submit.prevent="">
<div class="cw-radioset-wrapper" role="group" aria-labelledby="container-type">
<p id="container-type">{{ $gettext('Typ') }}</p>
<div class="cw-radioset">
<div
v-for="(container, index) in containerTypes"
:key="index"
class="cw-radioset-box"
:class="[container.type === changeType ? 'selected' : '']"
>
<input type="radio" :id="'type-' + container.type" :value="container.type" v-model="changeType" name="container-type"/>
<label :for="'type-' + container.type" >
<div class="label-icon" :class="[container.type, container.type === changeType ? 'selected' : '']"></div>
<p>{{ container.title }}</p>
</label>
</div>
</div>
</div>
<div class="cw-radioset-wrapper" role="group" aria-labelledby="container-style">
<p id="container-style">{{ $gettext('Stil') }}</p>
<div class="cw-radioset">
<div
v-for="(style, index) in containerStyles"
:key="index"
class="cw-radioset-box"
:class="[style.colspan === changeStyle ? 'selected' : '']"
>
<input type="radio" :id="'style-' + style.colspan" :value="style.colspan" v-model="changeStyle" name="container-style"/>
<label :for="'style-' + style.colspan">
<div class="label-icon" :class="[style.colspan, style.colspan === changeStyle ? 'selected' : '']"></div>
<p>{{ style.title }}</p>
</label>
</div>
</div>
</div>
</form>
</template>
</studip-dialog>
<studip-dialog
v-if="showDeleteDialog"
:title="textDeleteTitle"
......@@ -92,6 +147,7 @@ export default {
return {
showDeleteDialog: false,
showEditDialog: false,
showChangeDialog: false,
showRemoveLockDialog: false,
textEditConfirm: this.$gettext('Speichern'),
textEditClose: this.$gettext('Schließen'),
......@@ -101,6 +157,9 @@ export default {
textRemoveLockTitle: this.$gettext('Sperre aufheben'),
textRemoveLockAlert: this.$gettext('Möchten Sie die Sperre dieses Abschnitts wirklich aufheben?'),
isOpen: true,
changeType: '',
changeStyle: '',
};
},
computed: {
......@@ -109,7 +168,8 @@ export default {
userId: 'userId',
userById: 'users/byId',
viewMode: 'viewMode',
currentElementisLink: 'currentElementisLink'
currentElementisLink: 'currentElementisLink',
containerTypes: 'containerTypes',
}),
showEditMode() {
return this.viewMode === 'edit' && !this.currentElementisLink;
......@@ -139,11 +199,22 @@ export default {
blockingUserName() {
return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : '';
},
containerStyles() {
return [
{ title: this.$gettext('Volle Breite'), colspan: 'full'},
{ title: this.$gettext('Halbe Breite'), colspan: 'half' },
{ title: this.$gettext('Halbe Breite (zentriert)'), colspan: 'half-center' },
];
},
type() {
return this.container.attributes['container-type'];
}
},
methods: {
...mapActions({
companionInfo: 'companionInfo',
companionWarning: 'companionWarning',
updateContainer: 'updateContainer',
loadContainer: 'courseware-containers/loadById',
deleteContainer: 'deleteContainer',
lockObject: 'lockObject',
......@@ -161,6 +232,53 @@ export default {
await this.lockObject({ id: this.container.id, type: 'courseware-containers' });
this.showEditDialog = true;
},
async displayChangeDialog() {
await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } });
if (this.blockedByAnotherUser) {
this.companionInfo({ info: this.$gettext('Dieser Abschnitt wird bereits bearbeitet.') });
return false;
}
await this.lockObject({ id: this.container.id, type: 'courseware-containers' });
this.changeType = this.type;
this.changeStyle = this.colSpan;
this.showChangeDialog = true;
},
async storeChange() {
await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } });
this.closeChange();
if (this.blockedByAnotherUser) {
this.companionWarning({
info: this.$gettextInterpolate(
this.$gettext('Ihre Änderungen konnten nicht gespeichert werden, da %{blockingUserName} die Bearbeitung übernommen hat.'),
{blockingUserName: this.blockingUserName}
)
});
return;
}
if (this.blockerId === null) {
await this.lockObject({ id: this.container.id, type: 'courseware-containers' });
}
let container = this.container;
container.attributes['container-type'] = this.changeType;
container.attributes.payload.colspan = this.changeStyle;
await this.updateContainer({
container: container,
structuralElementId: this.container.relationships['structural-element'].data.id,
});
await this.unlockObject({ id: this.container.id, type: 'courseware-containers' });
await this.loadContainer({id : this.container.id });
},
async closeChange() {
await this.loadContainer({ id: this.container.id });
this.showChangeDialog = false;
if (this.blockedByThisUser) {
await this.unlockObject({ id: this.container.id, type: 'courseware-containers' });
}
await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } });
},
async closeEdit() {
await this.loadContainer({ id: this.container.id });
this.$emit('closeEdit');
......
......@@ -65,23 +65,39 @@
/>
</courseware-tab>
<courseware-tab :name="$gettext('Abschnitte')" :selected="showContaineradder" :index="1" :style="{ maxHeight: maxHeight + 'px' }">
<courseware-collapsible-box
v-for="(style, index) in containerStyles"
:key="index"
:title="style.title"
:open="index === 0"
<div class="cw-container-style-selector" role="group" aria-labelledby="cw-containeradder-style">
<p class="sr-only" id="cw-containeradder-style">{{ $gettext('Abschnitt-Stil') }}</p>
<template
v-for="style in containerStyles"
>
<input
:key="style.key + '-input'"
type="radio"
name="container-style"
:id="'style-' + style.colspan"
v-model="selectedContainerStyle"
:value="style.colspan"
/>
<label
:key="style.key + '-label'"
:for="'style-' + style.colspan"
:class="[selectedContainerStyle === style.colspan ? 'cw-container-style-selector-active' : '', style.colspan]"
>
{{ style.title }}
</label>
</template>
</div>
<courseware-container-adder-item
v-for="(container, index) in containerTypes"
:key="index"
v-for="container in containerTypes"
:key="container.type"
:title="container.title"
:type="container.type"
:colspan="style.colspan"
:colspan="selectedContainerStyle"
:description="container.description"
:firstSection="$gettext('erstes Element')"
:secondSection="$gettext('zweites Element')"
></courseware-container-adder-item>
</courseware-collapsible-box>
</courseware-tab>
</courseware-tabs>
</div>
......@@ -90,7 +106,6 @@
<script>
import CoursewareTabs from './CoursewareTabs.vue';
import CoursewareTab from './CoursewareTab.vue';
import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue';
import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue';
import CoursewareContainerAdderItem from './CoursewareContainerAdderItem.vue';
import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
......@@ -101,7 +116,6 @@ export default {
components: {
CoursewareTabs,
CoursewareTab,
CoursewareCollapsibleBox,
CoursewareBlockadderItem,
CoursewareContainerAdderItem,
CoursewareCompanionBox,
......@@ -119,7 +133,8 @@ export default {
searchInput: '',
currentFilterCategory: '',
filteredBlockTypes: [],
categorizedBlocks: []
categorizedBlocks: [],
selectedContainerStyle: 'full'
};
},
computed: {
......@@ -140,9 +155,9 @@ export default {
},
containerStyles() {
return [
{ title: this.$gettext('Standard'), colspan: 'full'},
{ title: this.$gettext('Halbe Breite'), colspan: 'half' },
{ title: this.$gettext('Halbe Breite (zentriert)'), colspan: 'half-center' },
{ key: 0, title: this.$gettext('Volle Breite'), colspan: 'full'},
{ key: 1, title: this.$gettext('Halbe Breite'), colspan: 'half' },
{ key: 2, title: this.$gettext('Halbe Breite (zentriert)'), colspan: 'half-center' },
];
},
blockCategories() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment