Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • alexander.vorwerk/studip
  • hochschule-wismar/stud-ip
  • tleilax/studip
  • marcus/studip
  • manschwa/studip
  • eberhardt/studip
  • uol/studip
  • pluta/studip
  • thienel/extern-uni-b
  • studip/studip
  • strohm/studip
  • uni-osnabrueck/studip
  • FloB/studip
  • universit-t-rostock/studip
  • Robinyyy/studip
  • jakob.diel/studip
  • HyperSpeeed/studip
  • ann/studip
  • nod3zer0/stud-ip-siple-saml-php-plugin
19 results
Show changes
Showing
with 654 additions and 513 deletions
STUDIP.domReady(() => {
const node = document.querySelector('.admin-courses-vue-app');
if (!node) {
return;
}
Promise.all([
STUDIP.Vue.load(),
import('../../../vue/store/AdminCoursesStore.js').then((config) => config.default),
import('../../../vue/components/AdminCourses.vue').then((component) => component.default),
]).then(([{ createApp, store }, storeConfig, AdminCourses]) => {
store.registerModule('admincourses', storeConfig);
Object.entries(window.AdminCoursesStoreData ?? {}).forEach(([key, value]) => {
store.commit(`admincourses/${key}`, value);
})
const vm = createApp({
components: { AdminCourses },
});
vm.$mount(node);
STUDIP.AdminCourses.App = vm.$refs.app;
});
$('.admin-courses-options').find('.options-radio, .options-checkbox').on('click', function () {
$(this).toggleClass(['options-checked', 'options-unchecked']);
$(this).attr('aria-checked', $(this).is('.options-checked') ? 'true' : 'false');
......
/**
* Stud.IP: Administration of available cache types, like database, Memcached, Redis etc.
*
* @author Thomas Hackl <studip@thomas-hackl.name>
* @license GPL2 or any later version
* @copyright Stud.IP core group
* @since Stud.IP 5.0
*/
import CacheAdministration from '../../../vue/components/CacheAdministration.vue'
STUDIP.domReady(() => {
if (document.getElementById('cache-admin-container')) {
STUDIP.Vue.load().then(({ createApp }) => {
createApp({
el: '#cache-admin-container',
components: { CacheAdministration }
})
})
}
});
STUDIP.domReady(() => {
const node = document.querySelector('.content-modules-vue-app');
if (!node) {
return;
}
Promise.all([
STUDIP.Vue.load(),
import('../../../vue/store/ContentModulesStore.js').then((config) => config.default),
import('../../../vue/components/ContentModules.vue').then((component) => component.default),
]).then(([{ createApp, store }, storeConfig, ContentModules]) => {
store.registerModule('contentmodules', storeConfig);
Object.entries(window.ContentModulesStoreData ?? {}).forEach(([key, value]) => {
store.commit(`contentmodules/${key}`, value);
});
const vm = createApp({
components: { ContentModules }
});
vm.$mount(node);
});
});
STUDIP.dialogReady((event) => {
let target = event.target ?? document;
if (target instanceof jQuery) {
target = target.get(0);
}
const node = target.querySelector('.content-modules-controls-vue-app');
if (!node) {
return;
}
Promise.all([
STUDIP.Vue.load(),
import('../../../vue/components/ContentModulesControl.vue').then((component) => component.default),
]).then(([{ createApp }, ContentModulesControl]) => {
const vm = createApp({
components: { ContentModulesControl }
});
vm.$mount(node);
});
});
import MyCourses from '../../../vue/components/MyCourses.vue';
import storeConfig from '../../../vue/store/MyCoursesStore.js';
STUDIP.domReady(async () => {
if ($('.my-courses-vue-app').length === 0) {
return;
}
const { createApp, store } = await STUDIP.Vue.load();
store.registerModule('mycourses', storeConfig);
store.commit('mycourses/setCourses', window.STUDIP.MyCoursesData['courses']);
store.commit('mycourses/setGroups', window.STUDIP.MyCoursesData['groups']);
store.commit('mycourses/setUserId', window.STUDIP.MyCoursesData['user_id']);
store.commit('mycourses/setConfig', window.STUDIP.MyCoursesData['config']);
const vm = createApp({
components: { MyCourses }
});
vm.$mount('.my-courses-vue-app');
});
import Quicksearch from '../../../vue/components/Quicksearch.vue';
STUDIP.domReady(() => {
if (jQuery(".oer_search").length) {
STUDIP.OER.initSearch();
}
jQuery(".serversettings .index_server a").on("click", function () {
var host_id = jQuery(this).closest("tr").data("host_id");
var active = jQuery(this).is(".checked") ? 0 : 1;
......
import ResponsiveNavigation from '../../../vue/components/responsive/ResponsiveNavigation.vue';
STUDIP.domReady(() => {
STUDIP.Vue.load().then(({ createApp }) => {
createApp({
el: '#responsive-menu',
components: { ResponsiveNavigation }
});
});
});
import StudipTree from '../../../vue/components/tree/StudipTree.vue'
STUDIP.ready(() => {
document.querySelectorAll('[data-studip-tree]:not(.vueified)').forEach(element => {
element.classList.add('vueified');
STUDIP.Vue.load().then(({ createApp }) => {
createApp({
el: element,
components: { StudipTree }
})
})
});
});
......@@ -6,51 +6,68 @@
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
STUDIP.ready(() => {
$('[data-vue-app]').each(function () {
if ($(this).is('[data-vue-app-created]')) {
return;
}
const config = Object.assign({}, {
id: false,
components: [],
store: false
}, $(this).data().vueApp);
let data = {};
if (config.id && window.STUDIP.AppData && window.STUDIP.AppData[config.id] !== undefined) {
data = window.STUDIP.AppData[config.id];
}
document.querySelectorAll('[data-vue-app]:not([data-vue-app-created])').forEach((node) => {
const config = Object.assign(
{
components: [],
stores: {}
},
JSON.parse(node.dataset.vueApp)
);
let components = {};
config.components.forEach(component => {
components[component] = () => import(`../../../vue/components/${component}.vue`);
const name = component.split('/').reverse()[0];
components[name] = () => import(`../../../vue/components/${component}.vue`);
});
STUDIP.Vue.load().then(async ({createApp, store}) => {
let vm;
if (config.store) {
const storeConfig = await import(`../../../vue/store/${config.store}.js`);
console.log('store', storeConfig.default);
for (const [index, name] of Object.entries(config.stores)) {
import(`../../../vue/store/${name}.js`).then(storeConfig => {
store.registerModule(index, storeConfig.default);
store.registerModule(config.id, storeConfig.default, {root: true});
const dataElement = document.getElementById(`vue-store-data-${index}`);
if (dataElement) {
const data = JSON.parse(dataElement.innerText);
Object.keys(data).forEach(command => {
store.commit(`${index}/${command}`, data[command]);
});
Object.keys(data).forEach(command => {
store.commit(`${config.id}/${command}`, data[command]);
dataElement.remove();
}
});
vm = createApp({components});
} else {
vm = createApp({data, components});
}
// import myCoursesStore from '../stores/MyCoursesStore.js';
//
// myCoursesStore.namespaced = true;
//
// store.registerModule('my-courses', myCoursesStore);
createApp({
components,
store,
vm.$mount(this);
beforeCreate() {
STUDIP.Vue.emit('VueAppWillCreate', this);
},
created() {
STUDIP.Vue.emit('VueAppDidCreate', this);
},
beforeMount() {
STUDIP.Vue.emit('VueAppWillMount', this);
},
mounted() {
STUDIP.Vue.emit('VueAppDidMount', this);
},
beforeUpdate() {
STUDIP.Vue.emit('VueAppWillUpdate', this);
},
updated() {
STUDIP.Vue.emit('VueAppDidUpdate', this);
},
beforeDestroy() {
STUDIP.Vue.emit('VueAppWillDestroy', this);
},
destroyed() {
STUDIP.Vue.emit('VueAppDidDestroy', this);
},
}).$mount(node);
});
$(this).attr('data-vue-app-created', '');
node.dataset.vueAppCreated = 'true';
});
});
......@@ -39,6 +39,9 @@ Vue.mixin({
globalOn(...args) {
eventBus.on(...args);
},
globalOff(...args) {
eventBus.off(...args);
},
getStudipConfig: store.getters['studip/getConfig']
},
});
......
......@@ -16,8 +16,6 @@ import "./init.js"
import "./bootstrap/responsive.js"
import "./bootstrap/vue.js"
import "./bootstrap/my-courses.js";
import "./studip-ui.js"
import "./bootstrap/fullscreen.js"
import "./bootstrap/tfa.js"
......@@ -76,12 +74,8 @@ import "./bootstrap/blubber.js"
import "./bootstrap/consultations.js"
import "./bootstrap/scroll_to_top.js"
import "./bootstrap/admin-courses.js"
import "./bootstrap/cache-admin.js"
import "./bootstrap/oer.js"
import "./bootstrap/courseware.js"
import "./bootstrap/contentmodules.js"
import "./bootstrap/responsive-navigation.js"
import "./bootstrap/treeview.js"
import "./bootstrap/stock-images.js"
import "./bootstrap/external_pages.js"
......
const AdminCourses = {
App: null,
App: {
loadCourse(courseId) {
STUDIP.Vue.emit('AdminCourses/loadCourse', courseId);
},
changeFilter(filters) {
STUDIP.Vue.emit('AdminCourses/changeFilter', filters);
},
changeActionArea(area) {
STUDIP.Vue.emit('AdminCourses/changeActionArea', area);
}
},
changeFiltersDependendOnInstitute(institut_id) {
STUDIP.AdminCourses.App.changeFilter({ institut_id });
AdminCourses.App.changeFilter({ institut_id });
//change Studiengangteil filter
$.get(
STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_stdgangteil_selector/' + institut_id)
......
......@@ -32,212 +32,7 @@ const OER = {
} else if (player.webkitRequestFullscreen) {
player.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
},
initSearch: function () {
STUDIP.Vue.load().then(({createApp}) => {
STUDIP.OER.Search = createApp({
el: ".oer_search",
data() {
return {
browseMode: false,
tags: $(".oer_search").data("tags"),
tagHistory: [],
searchtext: "",
activeFilterPanel: false,
difficulty: [1, 12],
category: null,
results: false,
material_select_url_template: $(".oer_search").data("material_select_url_template")
};
},
methods: {
sync_search_text: function () {
this.searchtext = $(".oer_search input[name=search]").val();
},
triggerFilterPanel: function () {
this.activeFilterPanel = !this.activeFilterPanel;
},
showFilterPanel: function () {
this.activeFilterPanel = true;
},
hideFilterPanel: function () {
this.activeFilterPanel = false;
},
clearAllFilters: function (keep_results) {
this.clearCategory();
this.clearDifficulty();
if (this.searchtext != '') {
this.searchtext = '';
}
$(".oer_search input[name=search]").val('');
if (keep_results !== true) {
this.results = false;
}
},
clearDifficulty: function () {
if ((this.difficulty[0] != 1) && (this.difficulty[1] != 12)) {
this.difficulty = [1, 12];
}
jQuery("#difficulty_slider").slider("values", this.difficulty);
},
clearCategory: function () {
if (this.category != null) {
this.category = null;
}
},
getIconShape: function (result) {
if (result.category === "video") {
return "video";
}
if (result.category === "audio") {
return "file-audio";
}
if (result.category === "presentation") {
return "file-pdf";
}
if (result.category === "elearning") {
return "learnmodule";
}
if (result.content_type === "application/zip") {
return "archive3";
}
return "file";
},
search: function () {
let v = this;
this.browseMode = false;
$.ajax({
url: STUDIP.URLHelper.getURL("dispatch.php/oer/market/search"),
data: {
type: this.category,
difficulty: this.difficulty.join(","),
search: this.searchtext
},
dataType: "json",
success: function (output) {
$("#new_ones").hide();
v.results = output.materials;
v.activeFilterPanel = false;
$(".material_navigation").toggle(v.results.length == 0);
$(".mainlist").toggle(v.results.length == 0);
$(".new_ones").toggle(v.results.length == 0);
}
});
return false;
},
browseTag: function (tag_hash, name) {
let v = this;
this.clearAllFilters(true);
let tags = [];
for (let i in this.tagHistory) {
tags.push(this.tagHistory[i].tag_hash);
}
if (tag_hash && (tags.indexOf(tag_hash) === -1)) {
tags.push(tag_hash);
}
let p = new Promise(function (resolve, reject) {
$.ajax({
url: STUDIP.URLHelper.getURL("dispatch.php/oer/market/get_tags"),
data: {
tags: tags
},
dataType: "json",
success: function (output) {
v.results = output.results.materials;
v.tags = output.tags;
if (tag_hash) {
v.tagHistory.push({
tag_hash: tag_hash,
name: name
});
}
if (v.tagHistory.length > 0) {
$("#new_ones").hide();
}
resolve();
},
error: function (jqXHR, textStatus, errorThrown) {
reject(new Error(errorThrown));
}
});
});
return p;
},
backInCloud: function () {
if (this.tagHistory.length === 0) {
this.browseMode = false;
return;
}
this.tagHistory.pop();
let tag_hash = null;
let tag_name = null;
if (this.tagHistory.length > 0) {
tag_hash = this.tagHistory[this.tagHistory.length - 1].tag_hash;
tag_name = this.tagHistory[this.tagHistory.length - 1].name;
}
let v = this;
this.tagHistory.pop();
this.browseTag(tag_hash, tag_name).then(function () {
if (v.tagHistory.length === 0) {
$("#new_ones").show();
}
});
},
getTagStyle: function (tag_hash) {
return "position: relative; top: " + Math.floor(Math.random() * 15 - 15) + "px";
},
capitalizeFirstLetter: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
getMaterialURL: function (material_id) {
return this.material_select_url_template.replace("__material_id__", material_id);
},
shortenName: function (name) {
if (name.length > 55) {
return name.substring(0, 50) + ' ...';
} else {
return name;
}
}
},
mounted: function () {
this.results = $(this.$el).data('searchresults');
if (this.results !== false) {
$("#new_ones").hide();
}
if ($(this.$el).data('filteredcategory')) {
this.category = $(this.$el).data('filteredcategory');
}
},
updated: function () {
this.$nextTick(function () {
if (!jQuery("#difficulty_slider.ui-slider").length) { //to prevent an endless loop
let v = this;
jQuery("#difficulty_slider").slider({
range: true,
min: 1,
max: 12,
values: [v.difficulty[0], v.difficulty[1]],
change: function (event, ui) {
v.difficulty = ui.values;
}
});
}
});
}
});
});
jQuery(document).on("click", function (evnt) {
if (!jQuery(evnt.target).is(".searchform *") && STUDIP.OER.Search) {
STUDIP.OER.Search.hideFilterPanel();
}
});
}
};
export default OER;
const load = async function () {
return STUDIP.loadChunk('vue');
};
class Vue
{
static async load()
{
return STUDIP.loadChunk('vue');
}
const on = async function (...args) {
const { eventBus } = await load();
eventBus.on(...args);
};
static async on(...args)
{
const { eventBus } = await this.load();
eventBus.on(...args);
}
const emit = async function (...args) {
const { eventBus } = await load();
eventBus.emit(...args);
};
static async off(...args) {
const { eventBus } = await this.load();
eventBus.off(...args);
}
export default { load, on, emit };
static async emit(...args)
{
const { eventBus } = await this.load();
eventBus.emit(...args);
}
}
export default Vue;
......@@ -275,7 +275,7 @@ ul.reviews, ol.reviews {
margin-left: 5px;
}
button {
button[type="button"] {
border-right: none;
border-bottom: none;
border-top: none;
......@@ -296,7 +296,7 @@ ul.reviews, ol.reviews {
}
}
button {
button[type="button"] {
border: thin solid var(--content-color-40);
background-color: var(--content-color-20);
display: flex;
......@@ -304,10 +304,6 @@ ul.reviews, ol.reviews {
justify-content: center;
width: 35px;
}
> button {
margin-left: 10px;
}
}
.filterpanel {
......
<template>
<table class="default">
<caption>
{{ $gettext('Veranstaltungen') }}
<span class="actions" v-if="isLoading">
<img :src="loadingIndicator" width="20" height="20" :title="$gettext('Daten werden geladen')">
</span>
<span class="actions" v-else-if="coursesCount > 0">
{{ coursesCount + ' ' + $gettext('Veranstaltungen') }}
</span>
</caption>
<thead>
<tr class="sortable">
<th v-if="showComplete" :class="sort.by === 'completion' ? 'sort' + sort.direction.toLowerCase() : ''">
<a
@click.prevent="changeSort('completion')"
class="course-completion"
:title="$gettext('Bearbeitungsstatus')"
>
{{ $gettext('Bearbeitungsstatus') }}
</a>
</th>
<th v-for="activeField in sortedActivatedFields" :key="`field-${activeField}`" :class="sort.by === activeField ? 'sort' + sort.direction.toLowerCase() : ''">
<a href="#"
@click.prevent="changeSort(activeField)"
:title="sort.by === activeField && sort.direction === 'ASC' ? $gettextInterpolate('Sortiert aufsteigend nach %{field}', {field: fields[activeField]}) : (sort.by === activeField && sort.direction === 'DESC' ? $gettextInterpolate('Sortiert absteigend nach %{ field } ', { field: fields[activeField]}) : $gettextInterpolate('Sortieren nach %{ field }', { field: fields[activeField]}))"
v-if="!unsortableFields.includes(activeField)"
>
{{ fields[activeField] }}
</a>
<template v-else>
{{ fields[activeField] }}
</template>
</th>
<th class="actions">
{{ $gettext('Aktion') }}
<studip-action-menu class="filter" :title="$gettext('Darstellungsfilter')" :items="availableFields" @toggleActiveField="toggleActiveField"></studip-action-menu>
</th>
</tr>
<tr v-if="buttons.top">
<th v-html="buttons.top" style="text-align: right" :colspan="colspan"></th>
</tr>
</thead>
<tbody :class="{ loading: isLoading }">
<tr v-for="course in sortedCourses"
:key="course.id"
:class="course.id === currentLine ? 'selected' : ''"
@click="currentLine = course.id">
<td v-if="showComplete">
<button :href="getURL('dispatch.php/admin/courses/toggle_complete/' + course.id)"
class="course-completion undecorated"
:data-course-completion="course.completion"
:title="(course.completion > 0 ? (course.completion == 1 ? $gettext('Veranstaltung in Bearbeitung.') : $gettext('Veranstaltung komplett.')) : $gettext('Veranstaltung neu.')) + ' ' + $gettext('Klicken zum Ändern des Status.')"
@click.prevent="toggleCompletionState(course.id)">
{{ $gettext('Bearbeitungsstatus ändern') }}
</button>
</td>
<td v-for="active_field in sortedActivatedFields" :key="active_field">
<div v-html="course[active_field]"></div>
<a v-if="active_field === 'name' && getChildren(course).length > 0"
@click.prevent="toggleOpenChildren(course.id)"
href="">
<studip-icon :shape="open_children.indexOf(course.id) === -1 ? 'add' : 'remove'" class="text-bottom"></studip-icon>
{{ $gettextInterpolate(
$gettext('%{ n } Unterveranstaltungen'),
{ n: getChildren(course).length }
) }}
</a>
</td>
<td class="actions" v-html="course.action">
</td>
</tr>
<tr v-if="coursesCount === 0 && coursesLoaded">
<td :colspan="colspan">
{{ $gettext('Keine Ergebnisse') }}
</td>
</tr>
<tr v-if="coursesCount > 0 && sortedCourses.length === 0">
<td :colspan="colspan">
{{
$gettextInterpolate(
$gettext(`%{ n } Veranstaltungen entsprechen Ihrem Filter. Schränken Sie nach Möglichkeit die Filter weiter ein.`),
{ n: coursesCount }
)
}}
<a href="" @click.prevent="loadCourses({withoutLimit: true});">
{{ $gettext('Alle anzeigen') }}
</a>
</td>
</tr>
<tr v-if="!coursesLoaded">
<td :colspan="colspan">
{{ $gettext('Daten werden geladen ...') }}
</td>
</tr>
</tbody>
<tfoot v-if="buttons.bottom">
<tr>
<td v-html="buttons.bottom" style="text-align: right" :colspan="colspan"></td>
</tr>
</tfoot>
</table>
<form method="post">
<input type="hidden" :name="csrf.name" :value="csrf.value">
<table class="default course-admin">
<caption>
{{ $gettext('Veranstaltungen') }}
<span class="actions" v-if="isLoading">
<img :src="loadingIndicator" width="20" height="20" :title="$gettext('Daten werden geladen')">
</span>
<span class="actions" v-else-if="coursesCount > 0">
{{ coursesCount + ' ' + $gettext('Veranstaltungen') }}
</span>
</caption>
<thead>
<tr class="sortable">
<th v-if="showComplete" :class="sort.by === 'completion' ? 'sort' + sort.direction.toLowerCase() : ''">
<a
@click.prevent="changeSort('completion')"
class="course-completion"
:title="$gettext('Bearbeitungsstatus')"
>
{{ $gettext('Bearbeitungsstatus') }}
</a>
</th>
<th v-for="activeField in sortedActivatedFields" :key="`field-${activeField}`" :class="sort.by === activeField ? 'sort' + sort.direction.toLowerCase() : ''">
<a href="#"
@click.prevent="changeSort(activeField)"
:title="sort.by === activeField && sort.direction === 'ASC' ? $gettextInterpolate('Sortiert aufsteigend nach %{field}', {field: fields[activeField]}) : (sort.by === activeField && sort.direction === 'DESC' ? $gettextInterpolate('Sortiert absteigend nach %{ field } ', { field: fields[activeField]}) : $gettextInterpolate('Sortieren nach %{ field }', { field: fields[activeField]}))"
v-if="!unsortableFields.includes(activeField)"
>
{{ fields[activeField] }}
</a>
<template v-else>
{{ fields[activeField] }}
</template>
</th>
<th class="actions">
{{ $gettext('Aktion') }}
<studip-action-menu class="filter" :title="$gettext('Darstellungsfilter')" :items="availableFields" @toggleActiveField="toggleActiveField"></studip-action-menu>
</th>
</tr>
<tr v-if="buttons.top">
<th v-html="buttons.top" style="text-align: right" :colspan="colspan"></th>
</tr>
</thead>
<tbody :class="{ loading: isLoading }">
<tr v-for="course in sortedCourses"
:key="course.id"
:class="course.id === currentLine ? 'selected' : ''"
@click="currentLine = course.id">
<td v-if="showComplete">
<button :href="getURL('dispatch.php/admin/courses/toggle_complete/' + course.id)"
class="course-completion undecorated"
:data-course-completion="course.completion"
:title="(course.completion > 0 ? (course.completion == 1 ? $gettext('Veranstaltung in Bearbeitung.') : $gettext('Veranstaltung komplett.')) : $gettext('Veranstaltung neu.')) + ' ' + $gettext('Klicken zum Ändern des Status.')"
@click.prevent="toggleCompletionState(course.id)">
{{ $gettext('Bearbeitungsstatus ändern') }}
</button>
</td>
<td v-for="active_field in sortedActivatedFields" :key="active_field">
<div v-html="course[active_field]"></div>
<a v-if="active_field === 'name' && getChildren(course).length > 0"
@click.prevent="toggleOpenChildren(course.id)"
href="">
<studip-icon :shape="open_children.indexOf(course.id) === -1 ? 'add' : 'remove'" class="text-bottom"></studip-icon>
{{ $gettextInterpolate(
$gettext('%{ n } Unterveranstaltungen'),
{ n: getChildren(course).length }
) }}
</a>
</td>
<td class="actions" v-html="course.action">
</td>
</tr>
<tr v-if="coursesCount === 0 && coursesLoaded">
<td :colspan="colspan">
{{ $gettext('Keine Ergebnisse') }}
</td>
</tr>
<tr v-if="coursesCount > 0 && sortedCourses.length === 0">
<td :colspan="colspan">
{{
$gettextInterpolate(
$gettext(`%{ n } Veranstaltungen entsprechen Ihrem Filter. Schränken Sie nach Möglichkeit die Filter weiter ein.`),
{ n: coursesCount }
)
}}
<a href="" @click.prevent="loadCourses({withoutLimit: true});">
{{ $gettext('Alle anzeigen') }}
</a>
</td>
</tr>
<tr v-if="!coursesLoaded">
<td :colspan="colspan">
{{ $gettext('Daten werden geladen ...') }}
</td>
</tr>
</tbody>
<tfoot v-if="buttons.bottom">
<tr>
<td v-html="buttons.bottom" style="text-align: right" :colspan="colspan"></td>
</tr>
</tfoot>
</table>
</form>
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
......@@ -130,6 +133,15 @@ export default {
},
created() {
this.loadCourses();
this.globalOn('AdminCourses/changeActionArea', this.changeActionArea.bind(this));
this.globalOn('AdminCourses/changeFilter', this.changeFilter.bind(this));
this.globalOn('AdminCourses/loadCourse', this.loadCourse.bind(this));
},
destroyed() {
this.globalOff('AdminCourses/changeActionArea', this.changeActionArea.bind(this));
this.globalOff('AdminCourses/changeFilter', this.changeFilter.bind(this));
this.globalOff('AdminCourses/loadCourse', this.loadCourse.bind(this));
},
computed: {
...mapState('admincourses', [
......@@ -143,6 +155,9 @@ export default {
...mapGetters('admincourses', [
'isLoading',
]),
csrf() {
return STUDIP.CSRF_TOKEN;
},
colspan () {
let colspan = this.activatedFields.length + 1;
if (this.showComplete) {
......@@ -284,6 +299,41 @@ export default {
getURL(url, params = {}) {
return STUDIP.URLHelper.getURL(url, params);
},
}
},
};
</script>
<style lang="scss">
@import '../../assets/stylesheets/mixins.scss';
.course-admin {
.course-completion {
@include hide-text();
@include square(16px);
background-repeat: no-repeat;
display: block;
}
th .course-completion {
@include background-icon(radiobutton-checked, clickable);
}
td .course-completion {
@include background-icon(span-empty, status-red);
&[data-course-completion="1"] {
@include background-icon(span-2quarter, status-yellow);
}
&[data-course-completion="2"] {
@include background-icon(span-full, status-green);
}
&.ajaxing {
background-image: url("#{$image-path}/loading-indicator.svg");
}
}
> tbody.loading > tr > td {
opacity: 0.5;
}
}
</style>
<template>
<form class="default" :action="actionUrl" method="post" ref="configForm">
<StudipMessageBox v-if="!enabled" type="warning" :hide-close="true">
{{ $gettext('Caching ist systemweit ausgeschaltet, daher kann hier nichts konfiguriert werden.') }}
</StudipMessageBox>
<form v-else class="default" :action="actionUrl" method="post" ref="configForm">
<fieldset>
<legend>
<translate>Cachetyp</translate>
......@@ -36,10 +39,12 @@
import FileCacheConfig from './FileCacheConfig.vue'
import MemcachedCacheConfig from './MemcachedCacheConfig.vue'
import RedisCacheConfig from './RedisCacheConfig.vue'
import StudipMessageBox from './StudipMessageBox.vue';
export default {
name: 'CacheAdministration',
components: {
StudipMessageBox,
FileCacheConfig,
MemcachedCacheConfig,
RedisCacheConfig
......@@ -61,6 +66,10 @@ export default {
props: []
};
}
},
enabled: {
type: Boolean,
required: true,
}
},
data () {
......
<template>
<form class="oer_search" :action="url">
<div class="searchform">
<div class="oneliner">
<div class="frame">
<span v-if="category != null"
class="category activefilter" :title="$gettext('Aktiver Filter der Kategorie')">
<span>{{ category }}</span>
<a href="#"
@click.prevent="clearCategory()"
class="erasefilter"
:title="$gettext('Filter der Kategorie entfernen')">
<studip-icon shape="decline" class="text-bottom"></studip-icon>
</a>
</span>
<span v-if="difficulty[0] != 1 || difficulty[1] != 12"
class="niveau activefilter"
title="$gettext('Aktiver Filter für das Niveau')"
>
{{ $gettext('Niveau') }}:
<span>{{ difficulty[0] }}</span>
-
<span>{{ difficulty[1] }}</span>
<a href="#"
@click.prevent="clearDifficulty()"
class="erasefilter"
:title="$gettext('Filter des Niveaus entfernen')">
<studip-icon shape="decline" class="text-bottom"></studip-icon>
</a>
</span>
<input type="text"
name="search"
v-model.trim="searchtext"
@focus="toggleFilterPanel(true)"
@keydown.enter.prevent="search()">
<button v-if="difficulty[0] != 1 || difficulty[1] != 12 || (category != null) || (searchtext.length > 0)"
class="erase"
type="button"
:title="$gettext('Suchformular zurücksetzen')"
@click="clearAllFilters">
<studip-icon shape="decline"></studip-icon>
</button>
<button @click="toggleFilterPanel()"
type="button"
:title="$gettext('Suchfilter einstellen')"
:class="activeFilterPanel ? 'active' : ''">
<studip-icon shape="filter" :role="activeFilterPanel ? 'info_alt' : 'clickable'"></studip-icon>
</button>
<div v-if="activeFilterPanel" class="filterpanel_shadow"></div>
<div v-if="activeFilterPanel" class="filterpanel">
<div>
<h3>{{ $gettext('Kategorien') }}</h3>
<ul class="clean">
<li>
<button class="as-link" @click.prevent="category = 'audio'">
<studip-icon :shape="category === 'audio' ? 'radiobutton-checked' : 'radiobutton-unchecked'" class="text-bottom"></studip-icon>
{{ $gettext('Audio') }}
</button>
</li>
<li>
<button class="as-link" @click.prevent="category = 'video'">
<studip-icon :shape="category === 'video' ? 'radiobutton-checked' : 'radiobutton-unchecked'" class="text-bottom"></studip-icon>
{{ $gettext('Video') }}
</button>
</li>
<li>
<button class="as-link" @click.prevent="category = 'presentation'">
<studip-icon :shape="category === 'presentation' ? 'radiobutton-checked' : 'radiobutton-unchecked'" class="text-bottom"></studip-icon>
{{ $gettext('Folien') }}
</button>
</li>
<li>
<button class="as-link" @click.prevent="category = 'elearning'">
<studip-icon :shape="category === 'elearning' ? 'radiobutton-checked' : 'radiobutton-unchecked'" class="text-bottom"></studip-icon>
{{ $gettext('Lernmodule') }}
</button>
</li>
<li>
<button class="as-link" @click.prevent="category = null">
<studip-icon shape="link-intern" class="text-bottom"></studip-icon>
{{ $gettext('Alles') }}
</button>
</li>
</ul>
</div>
<div class="level_filter">
<h3>{{ $gettext('Niveau') }}</h3>
<div class="level_labels">
<div>{{ $gettext('Leicht') }}</div>
<div>{{ $gettext('Schwer') }}</div>
</div>
<div class="level_numbers">
<div v-for="i in 12" :key="i" style="text-align: right">
{{ i }}
</div>
</div>
<div id="difficulty_slider"></div>
<input type="hidden" id="difficulty" name="difficulty" value="">
</div>
</div>
<button :title="$gettext('Suche starten')"
@click.prevent="search()"
@focus="toggleFilterPanel(false)"
type="button"
>
<studip-icon shape="search"></studip-icon>
</button>
</div>
</div>
</div>
<div class="browser">
<div v-if="browseMode === false" class="intro">
<StudipAssetImg file="oer-keyvisual-negative.svg" class="illustration responsive-hidden"></StudipAssetImg>
<div>
<h3>{{ $gettext('Wertvolle Lernmaterialien entdecken!') }}</h3>
<div class="responsive-hidden">
{{ $gettext('Neue und spannende Lernmaterialien zu finden, ist ganz einfach. Mit dem Entdeckermodus können Sie nach Schlagwörtern stöbern und durch Themengebiete surfen.') }}
</div>
<div>
<button class="button" @click.prevent="browseMode = true">
{{ $gettext('Zum Entdeckermodus') }}
</button>
</div>
</div>
</div>
<div v-if="browseMode === true" class="tagcloud">
<div>
<h3>{{ $gettext('Wertvolle Materialien entdecken!') }}</h3>
{{ $gettext('Klicken Sie auf die Schlagwörter und entdecken Sie Lernmaterialien zum Thema.') }}
</div>
<a href="" @click.prevent="backInCloud" class="back-button">
<studip-icon shape="arr_1left" :size="50"></studip-icon>
</a>
<ul class="tags clean">
<li v-for="tag in tagCloud">
<a href="#"
class="button"
:style="getTagStyle(tag.tag_hash)"
:title="tag.name"
@click.prevent="browseTag(tag.tag_hash, tag.name)"
>
#{{ tag.name }}
</a>
</li>
</ul>
</div>
</div>
<div v-if="browseMode && results && results.length === 0" class="oer_no_results">
<StudipMessageBox type="info" :hide-close="true">
{{ $gettext('Keine Ergebnisse gefunden.') }}
</StudipMessageBox>
</div>
<ul class="results oer_material_overview" v-if="results && results.length > 0">
<li v-for="result in results" :key="result.material_id">
<article class="contentbox" :title="result.name">
<a :href="getMaterialURL(result.material_id)" target="_blank">
<header>
<h1>
<studip-icon :shape="getIconShape(result)"
class="text-bottom"></studip-icon>
{{ shortenName(result.name) }}
</h1>
</header>
<div class="image" :style="`background-image: url(${result.logo_url});${!result.front_image_content_type ? ' background-size: 60% auto;' : ''}`"></div>
</a>
</article>
</li>
</ul>
</form>
</template>
<script>
import StudipMessageBox from './StudipMessageBox.vue';
import StudipAssetImg from './StudipAssetImg.vue';
export default {
name: 'OERSearch',
components: {StudipAssetImg, StudipMessageBox},
props: {
searchResults: [Array, Boolean],
filteredTag: String,
filteredCategory: String,
tags: Array,
materialSelectUrlTemplate: String,
toPlugin: String,
toFolderId: String,
url: {
type: String,
required: true,
}
},
data() {
return {
browseMode: false,
tagHistory: [],
searchtext: '',
activeFilterPanel: false,
difficulty: [1, 12],
category: null,
results: this.searchResults || false,
};
},
computed: {
tagCloud() {
const history = this.tagHistory.map(tag => tag.tag_hash);
return this.tags.filter(tag => !history.includes(tag.tag_hash));
}
},
methods: {
toggleFilterPanel(state = null) {
this.activeFilterPanel = state ?? !this.activeFilterPanel;
},
clearAllFilters(keep_results) {
this.clearCategory();
this.clearDifficulty();
this.searchtext = '';
if (!keep_results) {
this.results = false;
}
},
clearDifficulty() {
if (this.difficulty[0] != 1 && this.difficulty[1] != 12) {
this.difficulty = [1, 12];
}
jQuery("#difficulty_slider").slider("values", this.difficulty);
},
clearCategory() {
if (this.category != null) {
this.category = null;
}
},
getIconShape(result) {
if (result.category === 'video') {
return 'video';
}
if (result.category === 'audio') {
return 'file-audio';
}
if (result.category === 'presentation') {
return 'file-pdf';
}
if (result.category === 'elearning') {
return 'learnmodule';
}
if (result.content_type === 'application/zip') {
return 'archive3';
}
return 'file';
},
search() {
this.browseMode = false;
$.getJSON(STUDIP.URLHelper.getURL('dispatch.php/oer/market/search', {
type: this.category,
difficulty: this.difficulty.join(','),
search: this.searchtext
})).done(output => {
this.results = output.materials.length;
this.activeFilterPanel = false;
this.toggleElements(
this.results.length === 0,
'.material_navigation',
'.mainlist',
'#new_ones'
);
});
},
browseTag(tag_hash, name) {
this.clearAllFilters(true);
let tags = this.tagHistory.map(i => i.tag_hash);
if (tag_hash && !tags.includes(tag_hash)) {
tags.push(tag_hash);
}
return $.getJSON(STUDIP.URLHelper.getURL('dispatch.php/oer/market/get_tags', { tags })).done(output => {
this.results = output.results.materials;
this.$emit('update:tags', output.tags);
if (tag_hash) {
this.tagHistory.push({ tag_hash, name });
}
if (this.tagHistory.length > 0) {
this.toggleElements(false, '#new_ones');
}
});
},
backInCloud() {
if (this.tagHistory.length === 0) {
this.browseMode = false;
return;
}
this.tagHistory.pop();
let tag_hash = null;
let tag_name = null;
if (this.tagHistory.length > 0) {
tag_hash = this.tagHistory.at(-1).tag_hash;
tag_name = this.tagHistory.at(-1).name;
}
this.tagHistory.pop();
this.browseTag(tag_hash, tag_name).done(() => {
if (this.tagHistory.length === 0) {
this.toggleElements(true, '#new_ones');
}
});
},
getTagStyle(tag_hash) {
return {
position: 'relative',
top: Math.floor(Math.random() * 15 - 15) + 'px'
};
},
capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
getMaterialURL(material_id) {
return STUDIP.URLHelper.getURL(
this.materialSelectUrlTemplate.replace('__material_id__', material_id),
{
to_plugin: this.toPlugin,
to_folder_id: this.toFolderId
}
);
},
shortenName(name, maxLength = 55) {
if (name.length > maxLength) {
return name.substring(0, maxLength - 3) + ' ...';
}
return name;
},
hideFilterPanelListener(event) {
if (!event.target.closest('.oer_search .searchform')) {
this.toggleFilterPanel(false);
}
},
toggleElements(state, ...selectors) {
document.querySelectorAll(selectors.join(',')).forEach(node => {
node.style.display = state ? '' : 'none';
});
}
},
mounted() {
this.toggleElements(this.results === false, '#new_ones');
document.body.addEventListener('click', this.hideFilterPanelListener);
},
beforeDestroy() {
document.body.removeEventListener('click', this.hideFilterPanelListener)
},
updated() {
this.$nextTick(() => {
jQuery("#difficulty_slider:not(.ui-slider)").slider({
range: true,
min: 1,
max: 12,
values: [this.difficulty[0], this.difficulty[1]],
change: (event, ui) => {
this.difficulty = ui.values;
}
});
});
}
};
</script>
......@@ -66,14 +66,14 @@ if ($navigation) {
'username' => $user->username,
'perm' => $GLOBALS['perm']->get_perm()
];
?>
<? } else {
} else {
$me = ['username' => 'nobody'];
} ?>
<responsive-navigation :me="<?= htmlReady(json_encode($me)) ?>"
context="<?= htmlReady(Context::get() ? Context::get()->getFullName() : '') ?>"
:navigation="<?= htmlReady(json_encode(ResponsiveHelper::getNavigationObject($_COOKIE['responsive-navigation-hash'] ?? null))) ?>"
></responsive-navigation>
<?= Studip\VueApp::create('responsive/ResponsiveNavigation')->withProps([
'context' => Context::get()?->getFullName() ?? '',
'me' => $me,
'navigation' => ResponsiveHelper::getNavigationObject($_COOKIE['responsive-navigation-hash'] ?? null),
]) ?>
</div>
<div id="site-title">
<?= htmlReady(Config::get()->UNI_NAME_CLEAN) ?>
......
<?php
/**
* @var array $attributes
* @var array $storeData
*/
?>
<? foreach ($storeData as $store => $data): ?>
<script type="application/json" id="vue-store-data-<?= htmlReady($store) ?>"><?= json_encode($data) ?></script>
<? endforeach; ?>
<div <?= arrayToHtmlAttributes($attributes) ?>></div>