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

Überarbeitung der Verwaltungsseite: Werkzeuge

Closes #3089

Merge request !2070
parent 4381d299
No related branches found
No related tags found
No related merge requests found
<template> <template>
<draggable v-model="sortedModules" handle=".dragarea"> <div class="content-modules-wrapper">
<transition-group name="admin_contentmodules" <draggable v-model="sortedModules" handle=".dragarea">
class="admin_contentmodules studip-grid" <transition-group
tag="div" name="admin_contentmodules"
role="listbox" class="admin_contentmodules studip-grid"
tag="div"
role="listbox"
>
<div
v-for="module in activeModules"
:key="module.id"
role="option"
class="studip-grid-element"
:class="getModuleCSSClasses(module, activated[module.id])"
v-cloak
>
<div>
<a class="upper_part dragarea" :href="getDescriptionURL(module)" data-dialog>
<div>
<img :src="module.icon" width="40" height="40" v-if="module.icon" />
</div>
<div>
<h3>{{ module.displayname }}</h3>
{{ module.summary }}
</div>
</a>
<div class="down_part">
<div>
<a
class="dragarea"
tabindex="0"
:aria-label="
$gettextInterpolate(
$gettext(
'Sortierelement für Werkzeug %{module}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'
),
{ module: module.displayname }
)
"
@keydown="keyboardHandler($event, module)"
v-if="filterCategory === null"
:ref="`draghandle-${module.id}`"
>
<span class="drag-handle"></span>
</a>
<label v-if="!module.mandatory">
<input
type="checkbox"
:checked="activated[module.id]"
@click="toggleModule(module)"
:ref="'checkbox_' + module.id"
/>
{{ $gettext('Werkzeug ist aktiv') }}
</label>
</div>
<div class="icons_right">
<a
href="#"
class="toggle_visibility"
role="checkbox"
v-if="!module.mandatory"
:aria-checked="module.visibility !== 'tutor' ? 'true' : 'false'"
@click.prevent="toggleModuleVisibility(module)"
>
<studip-icon
:shape="
module.visibility !== 'tutor'
? 'visibility-visible'
: 'visibility-invisible'
"
class="text-bottom"
:title="
$gettextInterpolate(
$gettext(
'Inhaltsmoduls %{ name } für Teilnehmende unsichtbar bzw. sichtbar schalten'
),
{ name: module.displayname }
)
"
></studip-icon>
</a>
<a :href="getRenameURL(module)" data-dialog="size=medium">
<studip-icon
shape="edit"
class="text-bottom"
:title="
$gettextInterpolate(
$gettext(
'Umbenennen des Inhaltsmoduls %{ name }'
),
{ name: module.displayname }
)
"
></studip-icon>
</a>
</div>
</div>
</div>
</div>
</transition-group>
</draggable>
<transition-group
name="admin_contentmodules"
class="admin_contentmodules studip-grid inactive-modules"
tag="div"
role="listbox"
> >
<div v-for="module in sortedModules" <div
:key="module.id" v-for="module in inactiveModules"
role="option" :key="module.id"
class="studip-grid-element" role="option"
:class="getModuleCSSClasses(module, activated[module.id])" class="studip-grid-element"
v-cloak :class="getModuleCSSClasses(module, activated[module.id])"
v-cloak
> >
<div> <div>
<a :class="'upper_part' + (module.active && filterCategory === null ? ' dragarea' : '')" :href="getDescriptionURL(module)" data-dialog> <a class="upper_part" :href="getDescriptionURL(module)" data-dialog>
<div> <div>
<img :src="module.icon" width="40" height="40" v-if="module.icon"> <img :src="module.icon" width="40" height="40" v-if="module.icon" />
</div> </div>
<div> <div>
<h3>{{module.displayname}}</h3> <h3>{{ module.displayname }}</h3>
{{module.summary}} {{ module.summary }}
</div> </div>
</a> </a>
<div class="down_part"> <div class="down_part">
<div> <div>
<a class="dragarea"
tabindex="0"
:title="$gettextInterpolate('Sortierelement für Module %{module}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {module: module.displayname})"
@keydown="keyboardHandler($event, module)"
v-if="module.active && filterCategory === null"
:ref="`draghandle-${module.id}`">
<span class="drag-handle"></span>
</a>
<label v-if="!module.mandatory"> <label v-if="!module.mandatory">
<input type="checkbox" :checked="activated[module.id]" @click="toggleModule(module)" :ref="'checkbox_' + module.id"> <input
{{ module.active ? $gettext('Werkzeug ist aktiv') : $gettext('Werkzeug ist inaktiv') }} type="checkbox"
:checked="activated[module.id]"
@click="toggleModule(module)"
:ref="'checkbox_' + module.id"
/>
{{ $gettext('Werkzeug ist inaktiv') }}
</label> </label>
</div> </div>
<div class="icons_right">
<a href="#"
class="toggle_visibility"
role="checkbox"
v-if="module.active && !module.mandatory"
:aria-checked="module.visibility !== 'tutor' ? 'true' : 'false'"
@click.prevent="toggleModuleVisibility(module)">
<studip-icon :shape="module.visibility !== 'tutor' ? 'visibility-visible' : 'visibility-invisible'"
class="text-bottom"
:title="$gettextInterpolate($gettext('Inhaltsmoduls %{ name } für Teilnehmende unsichtbar bzw. sichtbar schalten'), { name: module.displayname})"></studip-icon>
</a>
<a :href="getRenameURL(module)" data-dialog="size=medium" v-if="module.active">
<studip-icon shape="edit"
class="text-bottom"
:title="$gettextInterpolate($gettext('Umbenennen des Inhaltsmoduls %{ name }'), { name: module.displayname })"></studip-icon>
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</transition-group> </transition-group>
</draggable> </div>
</template> </template>
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
...@@ -74,42 +157,41 @@ export default { ...@@ -74,42 +157,41 @@ export default {
timeouts: {}, timeouts: {},
}), }),
computed: { computed: {
...mapState('contentmodules', [ ...mapState('contentmodules', ['modules']),
'modules'
]),
}, },
methods: { methods: {
toggleModule(module) { toggleModule(module) {
Vue.set(this.activated, module.id, !this.activated[module.id]); Vue.set(this.activated, module.id, !this.activated[module.id]);
this.toggleModuleActivation(module);
if (this.timeouts[module.id] ?? null) {
clearTimeout(this.timeouts[module.id] ?? null);
this.timeouts[module.id] = null;
} else {
this.timeouts[module.id] = setTimeout(() => {
this.toggleModuleActivation(module);
this.timeouts[module.id] = null;
}, 700);
}
}, },
}, },
watch: { watch: {
modules: { modules: {
immediate: true, immediate: true,
handler(current) { handler(current) {
current.forEach(module => Vue.set(this.activated, module.id, module.active)); current.forEach((module) => Vue.set(this.activated, module.id, module.active));
} },
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.content-modules-wrapper {
max-width: 1410px;
}
.inactive-modules {
margin-top: 1em;
border-top: solid thin var(--content-color-40);
padding-top: 1em;
}
.studip-grid-element { .studip-grid-element {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
background-color: var(--white); background-color: var(--white);
border-left: 1px solid var(--dark-gray-color-60); border-left: 1px solid var(--dark-gray-color-60);
transition: all var(--transition-duration) ease; margin: 2px 0;
&.visibility-visible { &.visibility-visible {
border-left-color: var(--green); border-left-color: var(--green);
> div { > div {
...@@ -122,6 +204,38 @@ export default { ...@@ -122,6 +204,38 @@ export default {
border-left-color: var(--yellow); border-left-color: var(--yellow);
} }
} }
&.sortable-ghost {
border: dashed 2px var(--content-color-40);
margin: 0;
* {
opacity: 0;
}
}
&.pulse:not(.sortable-ghost) {
box-shadow: 0 0 0 0 rgb(255, 189, 51, 1);
animation: pulse 2s;
animation-iteration-count: 1;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(255, 189, 51, 1);
}
25% {
box-shadow: 0 0 0 5px rgba(255, 189, 51, 0.8);
}
50% {
box-shadow: 0 0 0 5px rgba(255, 189, 51, 0.6);
}
75% {
box-shadow: 0 0 0 5px rgba(255, 189, 51, 0.4);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 189, 51, 0);
}
}
> div { > div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
......
<template> <template>
<table class="admin_contentmodules table default"> <table class="admin_contentmodules table default">
<colgroup> <colgroup>
<col style="width: 20px" v-if="filterCategory === null"> <col style="width: 20px" v-if="filterCategory === null" />
<col style="width: 20px"> <col style="width: 20px" />
<col> <col />
<col style="width: 24px"> <col style="width: 24px" />
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
<th v-if="filterCategory === null"></th> <th v-if="filterCategory === null"></th>
<th></th> <th></th>
<th>{{ $gettext('Name') }}</th> <th>{{ $gettext('Name') }}</th>
<th class="actions">{{ $gettext('Aktionen') }}</th> <th class="actions">{{ $gettext('Aktionen') }}</th>
</tr> </tr>
</thead> </thead>
<draggable v-model="sortedModules" handle=".dragarea" tag="tbody"> <draggable v-model="sortedModules" handle=".dragarea" tag="tbody">
<tr v-for="module in sortedModules" <tr v-for="module in activeModules" :key="module.id" :class="getModuleCSSClasses(module)" v-cloak>
:key="module.id"
:class="getModuleCSSClasses(module)"
v-cloak>
<td v-if="filterCategory === null"> <td v-if="filterCategory === null">
<a class="dragarea" <a
tabindex="0" class="dragarea"
:title="$gettextInterpolate('Sortierelement für Module %{module}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {module: module.displayname})" tabindex="0"
@keydown="keyboardHandler($event, module)" :aria-label="
v-if="module.active" $gettextInterpolate(
:ref="`draghandle-${module.id}`" $gettext(
'Sortierelement für Module %{module}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'
),
{ module: module.displayname }
)
"
@keydown="keyboardHandler($event, module)"
v-if="module.active"
:ref="`draghandle-${module.id}`"
> >
<span class="drag-handle"></span> <span class="drag-handle"></span>
</a> </a>
</td> </td>
<td> <td>
<input type="checkbox" <input
v-model="module.active" type="checkbox"
@click="toggleModuleActivation(module)" v-model="module.active"
v-if="!module.mandatory" @click="toggleModuleActivation(module)"
:ref="'checkbox_' + module.id"> v-if="!module.mandatory"
:ref="'checkbox_' + module.id"
/>
</td> </td>
<td> <td>
<a class="upper_part" <a
:class="{ dragrea: module.active }" class="upper_part"
:href="getDescriptionURL(module)" :class="{ dragrea: module.active }"
data-dialog :href="getDescriptionURL(module)"
data-dialog
> >
<img :src="module.icon" width="20" height="20" v-if="module.icon" class="text-bottom"> <img :src="module.icon" width="20" height="20" v-if="module.icon" class="text-bottom" />
{{ module.displayname }} {{ module.displayname }}
</a> </a>
</td> </td>
<td class="actions"> <td class="actions">
<a href="#" <a
v-if="module.active && !module.mandatory" href="#"
role="checkbox" v-if="module.active && !module.mandatory"
:aria-checked="module.visibility !== 'tutor' ? 'true' : 'false'" role="checkbox"
@click.prevent="toggleModuleVisibility(module)"> :aria-checked="module.visibility !== 'tutor' ? 'true' : 'false'"
<studip-icon :shape="module.visibility !== 'tutor' ? 'visibility-visible' : 'visibility-invisible'" @click.prevent="toggleModuleVisibility(module)"
class="text-bottom" >
:title="$gettextInterpolate($gettext('Inhaltsmoduls %{ name } für Teilnehmende unsichtbar bzw. sichtbar schalten'), { name: module.displayname })"></studip-icon> <studip-icon
:shape="module.visibility !== 'tutor' ? 'visibility-visible' : 'visibility-invisible'"
class="text-bottom"
:title="
$gettextInterpolate(
$gettext(
'Inhaltsmoduls %{ name } für Teilnehmende unsichtbar bzw. sichtbar schalten'
),
{ name: module.displayname }
)
"
></studip-icon>
</a> </a>
<a :href="getRenameURL(module)" data-dialog="size=auto" v-if="module.active"> <a :href="getRenameURL(module)" data-dialog="size=auto" v-if="module.active">
<studip-icon shape="edit" class="text-bottom" :title="$gettextInterpolate($gettext('Umbenennen des Inhaltsmoduls %{ name }'), { name: module.displayname })"></studip-icon> <studip-icon
shape="edit"
class="text-bottom"
:title="
$gettextInterpolate($gettext('Umbenennen des Inhaltsmoduls %{ name }'), {
name: module.displayname,
})
"
></studip-icon>
</a> </a>
</td> </td>
</tr> </tr>
</draggable> </draggable>
<tbody>
<tr v-for="module in inactiveModules" :key="module.id" :class="getModuleCSSClasses(module)" v-cloak>
<td></td>
<td>
<input
type="checkbox"
v-model="module.active"
@click="toggleModuleActivation(module)"
v-if="!module.mandatory"
:ref="'checkbox_' + module.id"
/>
</td>
<td>
<a class="upper_part" :href="getDescriptionURL(module)" data-dialog>
<img :src="module.icon" width="20" height="20" v-if="module.icon" class="text-bottom" />
{{ module.displayname }}
</a>
</td>
<td></td>
</tr>
</tbody>
</table> </table>
</template> </template>
...@@ -74,10 +121,17 @@ export default { ...@@ -74,10 +121,17 @@ export default {
name: 'contentmodules-edit-table', name: 'contentmodules-edit-table',
mixins: [ContentModulesMixin], mixins: [ContentModulesMixin],
} };
</script> </script>
<style lang="scss"> <style lang="scss">
table.admin_contentmodules > tbody > tr { @use '../../assets/stylesheets/mixins/colors.scss';
table.admin_contentmodules > tbody > tr {
&.sortable-ghost {
* {
opacity: 0;
}
}
> td:first-child { > td:first-child {
background-image: linear-gradient(var(--dark-gray-color-60), var(--dark-gray-color-60)); background-image: linear-gradient(var(--dark-gray-color-60), var(--dark-gray-color-60));
background-repeat: no-repeat; background-repeat: no-repeat;
......
...@@ -19,6 +19,9 @@ export default { ...@@ -19,6 +19,9 @@ export default {
activeModules() { activeModules() {
return this.sortedModules.filter(module => module.active); return this.sortedModules.filter(module => module.active);
}, },
inactiveModules() {
return this.sortedModules.filter(module => !module.active);
},
sortedModules: { sortedModules: {
get() { get() {
return Object.values(this.modules) return Object.values(this.modules)
...@@ -89,6 +92,7 @@ export default { ...@@ -89,6 +92,7 @@ export default {
}); });
}, },
toggleModuleActivation(module) { toggleModuleActivation(module) {
module.pulse = true;
this.setModuleActive({ this.setModuleActive({
moduleId: module.id, moduleId: module.id,
active: !module.active, active: !module.active,
...@@ -96,6 +100,7 @@ export default { ...@@ -96,6 +100,7 @@ export default {
if (output.tabs) { if (output.tabs) {
$('.tabs_wrapper').replaceWith(output.tabs); $('.tabs_wrapper').replaceWith(output.tabs);
} }
module.pulse = false;
}); });
}, },
toggleModuleVisibility(module) { toggleModuleVisibility(module) {
...@@ -115,11 +120,17 @@ export default { ...@@ -115,11 +120,17 @@ export default {
return STUDIP.URLHelper.getURL(`dispatch.php/course/contentmodules/info/${module.id}`); return STUDIP.URLHelper.getURL(`dispatch.php/course/contentmodules/info/${module.id}`);
}, },
getModuleCSSClasses(module, active= null) { getModuleCSSClasses(module, active= null) {
let classes = [];
classes.push(module.pulse ? 'pulse' : '');
if (!(active ?? module.active)) { if (!(active ?? module.active)) {
return 'inactive'; classes.push('inactive');
} else {
classes.push(module.visibility === 'tutor' ? 'visibility-invisible' : 'visibility-visible');
} }
return module.visibility === 'tutor' ? 'visibility-invisible' : 'visibility-visible';
return classes.join(' ');
}, },
}, },
}; };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment