diff --git a/package-lock.json b/package-lock.json index 708c6eac1f05341e6399a3a1061695343d62fd70..cbbcf3b56d317f0217a343238dcbf4e96a6b4028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@fullcalendar/resource-timegrid": "^4.3.0", "@fullcalendar/resource-timeline": "^4.3.0", "@fullcalendar/timegrid": "^4.3.0", + "@popperjs/core": "^2.11.2", "autoprefixer": "^10.2.5", "axios": "^0.21.0", "babel-loader": "^8.2.1", @@ -1307,6 +1308,16 @@ "node": ">=10" } }, + "node_modules/@popperjs/core": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", + "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@trysound/sax": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.1.1.tgz", @@ -15361,6 +15372,12 @@ } } }, + "@popperjs/core": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", + "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==", + "dev": true + }, "@trysound/sax": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.1.1.tgz", diff --git a/package.json b/package.json index 683e2c1039f1be3611d64da19c5c5ad6e0fd418a..e6720293dea3bb757b6a243c7cecc6fd482e8091 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@fullcalendar/resource-timegrid": "^4.3.0", "@fullcalendar/resource-timeline": "^4.3.0", "@fullcalendar/timegrid": "^4.3.0", + "@popperjs/core": "^2.11.2", "autoprefixer": "^10.2.5", "axios": "^0.21.0", "babel-loader": "^8.2.1", diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index cd4c5c83bf373dc17a21c15783aec07428ce25b2..91351b653f49805522ec79fec6eb846e52e9a56f 100755 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -122,6 +122,56 @@ c o n t e n t s .cw-loading-indicator-content { margin-top: 76px; } +.cw-content-loading { + /* Loading animation from activity feed */ + .loading-indicator { + text-align: center; + padding: 1em 0; + } + + .loading-indicator span { + background-color: #CCCCDD; + border-radius: 50%; + height: 10px; + position: relative; + width: 10px; + display: inline-block; + } + + .loading-indicator span.load-1 { + animation: loading-animation-1 1s linear 20; + } + + .loading-indicator span.load-2 { + animation: loading-animation-2 1s linear 20; + } + + .loading-indicator span.load-3 { + animation: loading-animation-3 1s linear 20; + } + + @keyframes loading-animation-1 { + 0% { transform: scale(1); } + 16% { transform: scale(1.3); } + 33% { transform: scale(1); } + 100% { transform: scale(1); } + } + + @keyframes loading-animation-2 { + 0% { transform: scale(1); } + 33% { transform: scale(1); } + 49% { transform: scale(1.3); } + 65% { transform: scale(1); } + 100% { transform: scale(1); } + } + + @keyframes loading-animation-3 { + 0% { transform: scale(1); } + 66% { transform: scale(1); } + 81% { transform: scale(1.3); } + 100% { transform: scale(1); } + } +} /* * * * * * * * * * * c o n t e n t s e n d @@ -1959,6 +2009,41 @@ d a s h b o a r d padding: 1em; scrollbar-width: thin; scrollbar-color: $base-color $dark-gray-color-5; + + .cw-dashboard-empty-info { + margin-top: 10px; + } + } + } + &.cw-dashboard-task-view { + display: unset; + max-width: unset; + flex-wrap: unset; + } + &.cw-dashboard-activity-view { + .cw-dashboard-activities { + max-height: 760px; + } + + } +} + +#course-courseware-dashboard { + .action-menu-item a { + cursor: pointer; + } +} + +.responsive-display { + .cw-dashboard { + .cw-dashboard-box { + + &.cw-dashboard-box-full { + width: 100% + } + &.cw-dashboard-box-half { + width: 100% + } } } } @@ -4183,31 +4268,6 @@ cw tiles cw tiles end */ -/* -vSelect -*/ -.cw-vs-select { - max-width: 48em; - - .vs__dropdown-toggle { - border: solid thin $content-color-40; - border-radius: 0; - } - .vs__option-with-icon{ - padding-left: 8px; - } - .vs__option-color { - border: solid thin $content-color-40; - padding-left: 20px; - height: 20px; - margin-right: 4px; - } -} - -/* -vSelect end -*/ - /* cw manager copy */ .cw-manager-copy-selector { diff --git a/resources/assets/stylesheets/scss/select.scss b/resources/assets/stylesheets/scss/select.scss new file mode 100755 index 0000000000000000000000000000000000000000..f7a44410fa1151e5a1fd26a1f1bfded48f8bc377 --- /dev/null +++ b/resources/assets/stylesheets/scss/select.scss @@ -0,0 +1,38 @@ +.studip-v-select, .studip-v-select-detachted-ul { + max-width: 48em; + + .vs__option-with-icon{ + padding-left: 8px; + } + + .vs__option-color { + border: solid thin $content-color-40; + padding-left: 20px; + height: 20px; + margin-right: 4px; + } + + .vs__dropdown-toggle { + border: solid thin $content-color-40; + border-radius: 0; + } + + .vs__dropdown-menu, &.vs__dropdown-menu { + border-radius: 0; + } + + &.studip-v-select-drop-up { + border-bottom: solid thin $content-color-40; + border-top: none; + } + + &.studip-v-select-ul-drop-up { + border-bottom: none; + border-top: solid thin $content-color-40; + box-shadow: 0px -3px 6px 0 rgba(0, 0, 0, 0.15); + } + + &.studip-v-select-ul-dialog { + z-index: 3002; + } +} diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss index 2f46b79b9a1732088f055db08ed3ba234689463d..1c92ea605a748f76fde83e566064f2ee1e7a0935 100644 --- a/resources/assets/stylesheets/studip.scss +++ b/resources/assets/stylesheets/studip.scss @@ -28,6 +28,7 @@ @import "scss/tooltip"; @import "scss/table_of_contents"; @import "scss/wiki"; +@import "scss/select"; @import "scss/grid"; diff --git a/resources/vue/base-components.js b/resources/vue/base-components.js index 54d52fe01835a8c0dabd6b828f96b2c11971493c..b57094e559b94bb2ee1532aa700e1205d7f88f97 100644 --- a/resources/vue/base-components.js +++ b/resources/vue/base-components.js @@ -10,6 +10,7 @@ import StudipMessageBox from './components/StudipMessageBox.vue'; import StudipProxyCheckbox from './components/StudipProxyCheckbox.vue'; import StudipProxiedCheckbox from './components/StudipProxiedCheckbox.vue'; import StudipTooltipIcon from './components/StudipTooltipIcon.vue'; +import StudipSelect from './components/StudipSelect.vue'; const BaseComponents = { Quicksearch, @@ -24,6 +25,7 @@ const BaseComponents = { StudipProxyCheckbox, StudipProxiedCheckbox, StudipTooltipIcon, + StudipSelect, }; export default BaseComponents; diff --git a/resources/vue/components/StudipSelect.vue b/resources/vue/components/StudipSelect.vue new file mode 100755 index 0000000000000000000000000000000000000000..6347b2964ffd161b1fb5e248456b2b60e852d96e --- /dev/null +++ b/resources/vue/components/StudipSelect.vue @@ -0,0 +1,102 @@ +<template> + <v-select ref="select" + @change="updateValue" + v-bind="{...$props, ...$attrs}" + v-on="$listeners" + :calculate-position="withPopper" + class="studip-v-select" + append-to-body + > + <template v-for="(index, name) in $scopedSlots" v-slot:[name]="data"> + <slot :name="name" v-bind="data"></slot> + </template> + </v-select> +</template> + +<script> +import vSelect from 'vue-select'; +import { createPopper } from '@popperjs/core' +import 'vue-select/dist/vue-select.css' +export default { + name: 'studip-select', + inheritAttrs: false, + components: { + vSelect, + }, + props: { + maxHeight: { + type: String, + default: '12em' + }, + }, + methods: { + updateValue(val) { + this.$emit('input', val) + }, + withPopper(dropdownList, component, { width }) { + if (component.$el?.offsetParent.classList.contains('studip-dialog-content')) { + dropdownList.classList.add('studip-v-select-ul-dialog'); + } + dropdownList.style.width = width + dropdownList.style.maxHeight = this.maxHeight; + dropdownList.classList.add('studip-v-select-detachted-ul'); + let dropdownListHeight = parseFloat(this.getStyleValue(dropdownList, 'height')) + + parseFloat(this.getStyleValue(dropdownList, 'paddingTop')) + + parseFloat(this.getStyleValue(dropdownList, 'paddingBottom')); + const popper = createPopper(component.$refs.toggle, dropdownList, { + placement: this.calculatePlacement(dropdownListHeight), + modifiers: [ + { + name: 'offset', + options: { + offset: [0, -1], + }, + }, + { + name: 'toggleClass', + enabled: true, + phase: 'write', + fn({ state }) { + component.$refs.dropdownMenu.classList.toggle( + 'studip-v-select-ul-drop-up', + state.placement === 'top' + ) + component.$el.classList.toggle( + 'studip-v-select-drop-up', + state.placement === 'top' + ) + }, + }, + ], + }) + return () => popper.destroy() + }, + calculatePlacement(dropdownListHeight) { + let scrollTop = window.pageYOffset || document.documentElement.scrollTop; + let selectBottom = Math.ceil( + this.$refs.select.$el.getBoundingClientRect().bottom + scrollTop + ); + let totalExpandedList = selectBottom + dropdownListHeight; + let totalDocHeight = Math.max( + document.body.scrollHeight, + document.body.offsetHeight, + document.documentElement.clientHeight, + document.documentElement.scrollHeight, + document.documentElement.offsetHeight + ); + let footerHeight = document.getElementById('layout_footer').offsetHeight; + let functionalAreaHeight = totalDocHeight - footerHeight; + return totalExpandedList >= functionalAreaHeight ? 'top' : 'bottom'; + }, + getStyleValue(element, styleProp) { + let result = ''; + if (window.getComputedStyle) { + result = getComputedStyle(element)[styleProp]; + } else if (element.currentStyle) { + result = element.currentStyle[styleProp]; + } + return result; + } + } +}; +</script> diff --git a/resources/vue/components/courseware/CoursewareAccordionContainer.vue b/resources/vue/components/courseware/CoursewareAccordionContainer.vue index 4a318c30f29f0799c1532250a7f04b87b4f32843..5c586481f6170ff8742a148787f1d91384931563 100755 --- a/resources/vue/components/courseware/CoursewareAccordionContainer.vue +++ b/resources/vue/components/courseware/CoursewareAccordionContainer.vue @@ -28,6 +28,24 @@ <courseware-block-adder-area :container="container" :section="index" @updateContainerContent="updateContent"/> </li> </ul> + <draggable + v-if="sortMode && canEdit" + class="cw-container-list-block-list cw-container-list-sort-mode" + :class="[section.blocks.length === 0 ? 'cw-container-list-sort-mode-empty' : '']" + tag="ul" + v-model="section.blocks" + v-bind="dragOptions" + handle=".cw-sortable-handle" + @start="isDragging = true" + @end="isDragging = false" + > + <transition-group type="transition" name="flip-blocks" tag="div"> + <li v-for="block in section.blocks" :key="block.id" class="cw-block-item cw-block-item-sortable"> + <component :is="component(block)" :block="block" :canEdit="canEdit" :isTeacher="isTeacher" /> + </li> + </transition-group> + + </draggable> </courseware-collapsible-box> </template> <template v-slot:containerEditDialog> @@ -39,7 +57,7 @@ </label> <label> <translate>Icon</translate> - <v-select :options="icons" v-model="section.icon" class="cw-vs-select"> + <studip-select :options="icons" v-model="section.icon"> <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> </template> @@ -52,7 +70,7 @@ <template #option="option"> <studip-icon :shape="option.label"/> <span class="vs__option-with-icon">{{option.label}}</span> </template> - </v-select> + </studip-select> </label> <label class="cw-container-section-delete" diff --git a/resources/vue/components/courseware/CoursewareChartBlock.vue b/resources/vue/components/courseware/CoursewareChartBlock.vue index 4bce8b46c7f031896db6378a4ac24e50ae3d400d..6a05413048e92e332e3ba7f3d04adb9a3e2da117 100755 --- a/resources/vue/components/courseware/CoursewareChartBlock.vue +++ b/resources/vue/components/courseware/CoursewareChartBlock.vue @@ -49,13 +49,12 @@ </label> <label> <translate>Farbe</translate> - <v-select + <studip-select :options="colors" :reduce="colors => colors.value" label="rgb" :clearable="false" v-model="item.color" - class="cw-vs-select" @option:selected="buildChart" > <template #open-indicator="selectAttributes"> @@ -70,7 +69,7 @@ <template #option="{name, rgb}"> <span class="vs__option-color" :style="{'background-color': 'rgb(' + rgb + ')'}"></span><span>{{name}}</span> </template> - </v-select> + </studip-select> </label> </fieldset> </form> diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue index 349ac9545c262ff6147f5ba5e9dd906d9ed1431e..12aaa8b456ef99761f01b2df8ffe92449413f9ba 100755 --- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue +++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue @@ -57,13 +57,12 @@ </label> <label> <translate>Textfarbe</translate> - <v-select + <studip-select :options="colors" label="hex" :reduce="color => color.hex" :clearable="false" v-model="currentTextColor" - class="cw-vs-select" > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> @@ -77,11 +76,11 @@ <template #option="{name, hex}"> <span class="vs__option-color" :style="{'background-color': hex}"></span><span>{{name}}</span> </template> - </v-select> + </studip-select> </label> <label> <translate>Icon</translate> - <v-select :clearable="false" :options="icons" v-model="currentIcon" class="cw-vs-select"> + <studip-select :clearable="false" :options="icons" v-model="currentIcon"> <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> </template> @@ -94,17 +93,16 @@ <template #option="option"> <studip-icon :shape="option.label"/> <span class="vs__option-with-icon">{{option.label}}</span> </template> - </v-select> + </studip-select> </label> <label> <translate>Icon-Farbe</translate> - <v-select + <studip-select :options="iconColors" label="name" :reduce="iconColor => iconColor.class" :clearable="false" v-model="currentIconColor" - class="cw-vs-select" > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> @@ -118,7 +116,7 @@ <template #option="{name, hex}"> <span class="vs__option-color" :style="{'background-color': hex}"></span><span>{{name}}</span> </template> - </v-select> + </studip-select> </label> <label> <translate>Hintergrundtyp</translate> @@ -129,13 +127,12 @@ </label> <label v-if="currentBackgroundType === 'color'"> <translate>Hintergrundfarbe</translate> - <v-select + <studip-select :options="colors" label="hex" :reduce="color => color.hex" v-model="currentBackgroundColor" :clearable="false" - class="cw-vs-select" > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> @@ -149,7 +146,7 @@ <template #option="{name, hex}"> <span class="vs__option-color" :style="{'background-color': hex}"></span><span>{{name}}</span> </template> - </v-select> + </studip-select> </label> <label v-if="currentBackgroundType === 'image'"> <translate>Hintergrundbild</translate> diff --git a/resources/vue/components/courseware/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/CoursewareImageMapBlock.vue index 082e2ac9ef33d68ef61bb848c6f6bb76f3d5636e..190925d42902561519eef09ed1bd5e779941ed19 100755 --- a/resources/vue/components/courseware/CoursewareImageMapBlock.vue +++ b/resources/vue/components/courseware/CoursewareImageMapBlock.vue @@ -60,13 +60,12 @@ > <label> <translate>Farbe</translate> - <v-select + <studip-select :options="colors" label="name" :reduce="color => color.class" :clearable="false" v-model="shape.data.color" - class="cw-vs-select" > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> @@ -80,7 +79,7 @@ <template #option="{name, rgba}"> <span class="vs__option-color" :style="{'background-color': rgba}"></span><span>{{name}}</span> </template> - </v-select> + </studip-select> </label> <label v-if="shape.type === 'arc'" class="cw-block-image-map-dimensions"> X: <input type="number" v-model="shape.data.centerX" @change="drawScreen" /> Y: @@ -376,7 +375,7 @@ export default { shapeWidth = shapeWidth || 0; let newText = []; - + if (shapeWidth <= 0) { return [text]; } diff --git a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue index 7c57f2a6e5c166b584d33855dcf787106803a485..d2a00f490b69ba67da2f3074d2d95c5ed7983293 100755 --- a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue +++ b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue @@ -29,13 +29,12 @@ <label for="cw-keypoint-color"> <translate>Farbe</translate> - <v-select + <studip-select :options="colors" label="icon" :clearable="false" :reduce="option => option.icon" v-model="currentColor" - class="cw-vs-select" > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> @@ -49,12 +48,12 @@ <template #option="{name, hex}"> <span class="vs__option-color" :style="{'background-color': hex}"></span><span>{{name}}</span> </template> - </v-select> + </studip-select> </label> <label for="cw-keypoint-icons"> <translate>Icon</translate> - <v-select :options="icons" :clearable="false" v-model="currentIcon" class="cw-vs-select"> + <studip-select :options="icons" :clearable="false" v-model="currentIcon"> <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> </template> @@ -67,7 +66,7 @@ <template #option="option"> <studip-icon :shape="option.label"/> <span class="vs__option-with-icon">{{option.label}}</span> </template> - </v-select> + </studip-select> </label> </form> </template> diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue index 605de1f668a8f42eab35783f3b5836703202d55d..1c65588affe6ecea610f894bffa5e69c0c3b3dd2 100755 --- a/resources/vue/components/courseware/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue @@ -134,12 +134,11 @@ <form class="default" @submit.prevent=""> <label> <translate>Farbe</translate> - <v-select + <studip-select v-model="currentElement.attributes.payload.color" :options="colors" :reduce="(color) => color.class" label="class" - class="cw-vs-select" > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes" @@ -157,7 +156,7 @@ <span class="vs__option-color" :style="{ 'background-color': hex }"></span ><span>{{ name }}</span> </template> - </v-select> + </studip-select> </label> <label> <translate>Zweck</translate> diff --git a/resources/vue/components/courseware/CoursewareTabsContainer.vue b/resources/vue/components/courseware/CoursewareTabsContainer.vue index f8502bd85425705a5b841ac33600b84b26d13551..e599aecf9232e4af82a00ff28afbda67f855efe0 100755 --- a/resources/vue/components/courseware/CoursewareTabsContainer.vue +++ b/resources/vue/components/courseware/CoursewareTabsContainer.vue @@ -42,7 +42,7 @@ </label> <label> <translate>Icon</translate> - <v-select :options="icons" v-model="section.icon" class="cw-vs-select"> + <studip-select :options="icons" v-model="section.icon"> <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span> </template> @@ -55,7 +55,7 @@ <template #option="option"> <studip-icon :shape="option.label"/> <span class="vs__option-with-icon">{{option.label}}</span> </template> - </v-select> + </studip-select> </label> <label class="cw-container-section-delete" diff --git a/resources/vue/courseware-index-app.js b/resources/vue/courseware-index-app.js index 2ecb252c6d2337e50c4915a5c94f690688b515bf..1b87c8b31c5d33f7a1967f35ef3feb9041db1737 100755 --- a/resources/vue/courseware-index-app.js +++ b/resources/vue/courseware-index-app.js @@ -8,10 +8,6 @@ import VueRouter from 'vue-router'; import Vuex from 'vuex'; import axios from 'axios'; import { mapResourceModules } from '@elan-ev/reststate-vuex'; -import vSelect from 'vue-select'; -import 'vue-select/dist/vue-select.css' - -Vue.component('v-select', vSelect); const mountApp = (STUDIP, createApp, element) => { const getHttpClient = () =>