diff options
| author | Marcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de> | 2025-05-13 08:19:25 +0200 |
|---|---|---|
| committer | Marcus Eibrink-Lunzenauer <marcus@elan-ev.de> | 2026-01-07 14:14:20 +0100 |
| commit | 0a20239f787a10dc102a4827d8fffc00f25899e4 (patch) | |
| tree | c242d527dcbe5b637d66880f83d5d0b177fd7844 | |
| parent | 4f00fb8d9fea5ceead39a8145ac10d859ebdd287 (diff) | |
feat(cw): migrate courseware to VueApp::createfeature/vue-app-create-for-cw
These files are migrated:
- courseware-activities-app.js
- courseware-admin-app.js
- courseware-content-bookmark-app.js
- courseware-index-app.js
- courseware-shelf-app.js
- courseware-tasks-app.js
31 files changed, 715 insertions, 673 deletions
diff --git a/app/views/admin/courseware/index.php b/app/views/admin/courseware/index.php index b40dc54..dbd9089 100644 --- a/app/views/admin/courseware/index.php +++ b/app/views/admin/courseware/index.php @@ -1 +1,2 @@ -<div id="courseware-admin-app"></div>
\ No newline at end of file +<?= Studip\VueApp::create('courseware/AdminApp') + ->withVuexStore('courseware/courseware-admin.module', 'courseware') ?> diff --git a/app/views/contents/courseware/bookmarks.php b/app/views/contents/courseware/bookmarks.php index 988c692..8deb9c7 100644 --- a/app/views/contents/courseware/bookmarks.php +++ b/app/views/contents/courseware/bookmarks.php @@ -1,6 +1,4 @@ -<div - id="courseware-content-bookmark-app" - entry-type="users" - entry-id="<?= htmlReady($user_id) ?>" -> -</div> +<?= Studip\VueApp::create('courseware/BookmarksApp') + ->withVuexStore('courseware/courseware.module', 'courseware', [ + 'coursewareContextSet' => ['id' => (string) $user_id, 'type' => 'users'], + ]) ?> diff --git a/app/views/contents/courseware/courseware.php b/app/views/contents/courseware/courseware.php index 338c6b2..38bb537 100644 --- a/app/views/contents/courseware/courseware.php +++ b/app/views/contents/courseware/courseware.php @@ -1,11 +1,13 @@ <? if (!$unitsNotFound): ?> - <div - id="courseware-index-app" - entry-element-id="<?= htmlReady($entry_element_id) ?>" - entry-type="users" - entry-id="<?= htmlReady($user_id) ?>" - unit-id="<?= htmlReady($unit_id) ?>" - licenses='<?= htmlReady($licenses) ?>' - > - </div> + <?= Studip\VueApp::create('courseware/IndexApp') + ->withPlugin('CoursewareIndexApp', 'courseware-index') + ->withPlugin('StockImagesPlugin', 'stock-images') + ->withVuexStore('courseware/courseware.module', 'courseware', [ + 'coursewareContextSet' => ['id' => "$user_id", 'type' => 'users', 'unit' => "$unit_id"], + 'coursewareCurrentElementSet' => "$entry_element_id", + 'licensesSet' => json_decode($licenses), + ]) + ->withVuexStore('courseware/structure.module', 'courseware-structure') + ->withVuexStore('file-chooser', 'file-chooser') + ->withVuexStore('courseware/courseware-tasks.module', 'tasks') ?> <? endif; ?> diff --git a/app/views/contents/courseware/index.php b/app/views/contents/courseware/index.php index b05d731..e97f885 100644 --- a/app/views/contents/courseware/index.php +++ b/app/views/contents/courseware/index.php @@ -1,6 +1,8 @@ -<div - id="courseware-shelf-app" - entry-type="users" - entry-id="<?= $user_id ?>" - licenses='<?= $licenses ?>' -></div>
\ No newline at end of file +<?= Studip\VueApp::create('courseware/ShelfApp') + ->withPlugin('CoursewareShelfApp', 'courseware-shelf') + ->withPlugin('StockImagesPlugin', 'stock-images') + ->withVuexStore('courseware/courseware-shelf.module', 'courseware-shelf', [ + 'setContext' => ['id' => "$user_id", 'type' => 'users'], + 'setLicenses' => json_decode($licenses), + ]) +?> diff --git a/app/views/contents/courseware/shared_content_courseware.php b/app/views/contents/courseware/shared_content_courseware.php index 05ed3cc..b6c0631 100755 --- a/app/views/contents/courseware/shared_content_courseware.php +++ b/app/views/contents/courseware/shared_content_courseware.php @@ -1,9 +1,12 @@ -<div - id="courseware-index-app" - entry-element-id="<?= $entry_element_id ?>" - entry-type="sharedusers" - entry-id="<?= $entry_element_id ?>" - oer-title="<?= Config::get()->OER_TITLE ?>" - licenses='<?= $licenses ?>' - > -</div> +<?= Studip\VueApp::create('courseware/IndexApp') + ->withProps(['oer-title' => Config::get()->OER_TITLE]) + ->withPlugin('CoursewareIndexApp', 'courseware-index') + ->withPlugin('StockImagesPlugin', 'stock-images') + ->withVuexStore('courseware/courseware.module', 'courseware', [ + 'coursewareContextSet' => ['id' => "$entry_element_id", 'type' => 'sharedusers'], + 'coursewareCurrentElementSet' => "$entry_element_id", + 'licensesSet' => json_decode($licenses), + ]) + ->withVuexStore('courseware/structure.module', 'courseware-structure') + ->withVuexStore('file-chooser', 'file-chooser') + ->withVuexStore('courseware/courseware-tasks.module', 'tasks') ?> diff --git a/app/views/course/courseware/activities.php b/app/views/course/courseware/activities.php index 67726e0..20d8a31 100644 --- a/app/views/course/courseware/activities.php +++ b/app/views/course/courseware/activities.php @@ -1,6 +1,7 @@ -<div - id="courseware-activities-app" - entry-type="courses" - entry-id="<?= htmlReady(Context::getId()) ?>" -> -</div> +<?= Studip\VueApp::create('courseware/ActivitiesApp') + ->withPlugin('CoursewareActivitiesApp', 'courseware-activities') + ->withVuexStore('courseware/courseware.module', 'courseware', [ + 'coursewareContext' => ['id' => (string) Context::getId(), 'type' => 'courses'], + ]) + ->withVuexStore('courseware/structure.module', 'courseware-structure') + ->withVuexStore('courseware/courseware-activities.module', 'courseware-activities') ?> diff --git a/app/views/course/courseware/courseware.php b/app/views/course/courseware/courseware.php index 9de924d..a6ad20f 100644 --- a/app/views/course/courseware/courseware.php +++ b/app/views/course/courseware/courseware.php @@ -1,12 +1,14 @@ <? if (!$unitsNotFound): ?> - <div - id="courseware-index-app" - entry-element-id="<?= htmlReady($entry_element_id) ?>" - entry-type="courses" - entry-id="<?= htmlReady(Context::getId()) ?>" - unit-id="<?= htmlReady($unit_id) ?>" - licenses='<?= htmlReady($licenses) ?>' - feedback-settings='<?= htmlReady($feedback_settings) ?>' - > - </div> + <?= Studip\VueApp::create('courseware/IndexApp') + ->withPlugin('CoursewareIndexApp', 'courseware-index') + ->withPlugin('StockImagesPlugin', 'stock-images') + ->withVuexStore('courseware/courseware.module', 'courseware', [ + 'coursewareContextSet' => ['id' => (string) Context::getId(), 'type' => 'courses', 'unit' => "$unit_id"], + 'coursewareCurrentElementSet' => "$entry_element_id", + 'licensesSet' => json_decode($licenses), + 'setFeedbackSettings' => json_decode($feedback_settings), + ]) + ->withVuexStore('courseware/structure.module', 'courseware-structure') + ->withVuexStore('file-chooser', 'file-chooser') + ->withVuexStore('courseware/courseware-tasks.module', 'tasks') ?> <? endif; ?> diff --git a/app/views/course/courseware/index.php b/app/views/course/courseware/index.php index 0152c37..8494e65 100644 --- a/app/views/course/courseware/index.php +++ b/app/views/course/courseware/index.php @@ -1,8 +1,9 @@ -<div - id="courseware-shelf-app" - entry-type="courses" - entry-id="<?= Context::getId() ?>" - licenses='<?= $licenses ?>' - feedback-settings='<?= htmlReady($feedback_settings) ?>' - is-teacher='<?= var_export($isTeacher) ?>' -></div> +<?= Studip\VueApp::create('courseware/ShelfApp') + ->withPlugin('CoursewareShelfApp', 'courseware-shelf') + ->withPlugin('StockImagesPlugin', 'stock-images') + ->withVuexStore('courseware/courseware-shelf.module', 'courseware-shelf', [ + 'setContext' => ['id' => (string) Context::getId(), 'type' => 'courses'], + 'setLicenses' => json_decode($licenses), + 'setFeedbackSettings' => json_decode($feedback_settings), + 'setUserIsTeacher' => (boolean) $isTeacher, + ]) ?> diff --git a/app/views/course/courseware/tasks.php b/app/views/course/courseware/tasks.php index 9f0e854..cef4f71 100644 --- a/app/views/course/courseware/tasks.php +++ b/app/views/course/courseware/tasks.php @@ -1,7 +1,8 @@ -<div - id="courseware-tasks-app" - entry-type="courses" - entry-id="<?= htmlReady(Context::getId()) ?>" - is-teacher='<?= var_export($isTeacher) ?>' -> -</div> +<?= Studip\VueApp::create('courseware/TasksApp') + ->withPlugin('CoursewareTasksApp', 'courseware-tasks') + ->withVuexStore('courseware/courseware.module', 'courseware', [ + 'coursewareContextSet' => ['id' => (string) Context::getId(), 'type' => 'courses'], + 'setUserIsTeacherInCourse' => (boolean) $isTeacher, + ]) + ->withVuexStore('courseware/courseware-tasks.module', 'tasks') + ->withVuexStore('courseware/structure.module', 'courseware-structure') ?> diff --git a/resources/assets/javascripts/bootstrap/courseware.js b/resources/assets/javascripts/bootstrap/courseware.js index 72503e0..a0d76c9 100644 --- a/resources/assets/javascripts/bootstrap/courseware.js +++ b/resources/assets/javascripts/bootstrap/courseware.js @@ -1,76 +1,4 @@ STUDIP.domReady(() => { - if (document.getElementById('courseware-shelf-app')) { - Promise.all([ - STUDIP.loadChunk('courseware'), - import( - /* webpackChunkName: "courseware-shelf-app" */ - '@/vue/courseware-shelf-app.js' - ), - ]).then(([{ createApp, store }, { default: mountApp }]) => { - return mountApp(STUDIP, createApp, store, '#courseware-shelf-app'); - }); - } - - if (document.getElementById('courseware-index-app')) { - Promise.all([ - STUDIP.loadChunk('courseware'), - import( - /* webpackChunkName: "courseware-index-app" */ - '@/vue/courseware-index-app.js' - ), - ]).then(([{ createApp, store }, { default: mountApp }]) => { - return mountApp(STUDIP, createApp, store, '#courseware-index-app'); - }); - } - - if (document.getElementById('courseware-activities-app')) { - Promise.all([ - STUDIP.loadChunk('courseware'), - import( - /* webpackChunkName: "courseware-activities-app" */ - '@/vue/courseware-activities-app.js' - ), - ]).then(([{ createApp, store }, { default: mountApp }]) => { - return mountApp(STUDIP, createApp, store, '#courseware-activities-app'); - }); - } - - if (document.getElementById('courseware-tasks-app')) { - Promise.all([ - STUDIP.loadChunk('courseware'), - import( - /* webpackChunkName: "courseware-tasks-app" */ - '@/vue/courseware-tasks-app.js' - ), - ]).then(([{ createApp, store }, { default: mountApp }]) => { - return mountApp(STUDIP, createApp, store, '#courseware-tasks-app'); - }); - } - - if (document.getElementById('courseware-content-bookmark-app')) { - Promise.all([ - STUDIP.loadChunk('courseware'), - import( - /* webpackChunkName: "courseware-content-bookmark-app" */ - '@/vue/courseware-content-bookmark-app.js' - ), - ]).then(([{ createApp, store }, { default: mountApp }]) => { - return mountApp(STUDIP, createApp, store, '#courseware-content-bookmark-app'); - }); - } - - if (document.getElementById('courseware-admin-app')) { - Promise.all([ - STUDIP.loadChunk('courseware'), - import( - /* webpackChunkName: "courseware-content-bookmark-app" */ - '@/vue/courseware-admin-app.js' - ), - ]).then(([{ createApp, store }, { default: mountApp }]) => { - return mountApp(STUDIP, createApp, store, '#courseware-admin-app'); - }); - } - if (document.getElementById('courseware-public-app')) { Promise.all([ STUDIP.loadChunk('courseware'), diff --git a/resources/assets/javascripts/bootstrap/vue.js b/resources/assets/javascripts/bootstrap/vue.js index 19270ff..cf8878d 100644 --- a/resources/assets/javascripts/bootstrap/vue.js +++ b/resources/assets/javascripts/bootstrap/vue.js @@ -13,7 +13,7 @@ async function mountVueApp(node) { return; } - const { createApp, h, store } = await STUDIP.Vue.load(); + const { createApp, h, httpClient, store } = await STUDIP.Vue.load(); const [appComponent, plugins] = await loadAppDependencies(config, store); const app = createApp({ @@ -30,7 +30,7 @@ async function mountVueApp(node) { ...createLifecycleHooks(), }); - plugins.forEach((plugin) => app.use(plugin, { store })); + plugins.forEach((plugin) => app.use(plugin, { httpClient, store })); app.mount(node); @@ -62,7 +62,7 @@ function parseVueAppConfig(node) { async function loadAppDependencies(config, store) { const promises = [ import(`@/vue/apps/${config.appPath}.vue`), - initializePlugins(config), + Promise.all(initializePlugins(config)), ...initializeVuexStores(config, store), ...initializePiniaStores(config), ]; @@ -113,8 +113,9 @@ function initializeVuexStores(config, store) { if (!store.hasModule(index)) { store.registerModule(index, storeConfig); } - Object.entries(config.vuexStoreData[index]).forEach(([type, payload]) => - store.commit(`${index}/${type}`, payload), + + Object.entries(config.vuexStoreData[index] ?? []).forEach(([type, payload]) => + store.commit(storeConfig.namespaced ? `${index}/${type}` : type, payload), ); }), ); diff --git a/resources/assets/javascripts/chunks/vue.js b/resources/assets/javascripts/chunks/vue.js index 2896166..ea9f07c 100644 --- a/resources/assets/javascripts/chunks/vue.js +++ b/resources/assets/javascripts/chunks/vue.js @@ -4,10 +4,10 @@ import { createPinia } from 'pinia'; import eventBus from '../lib/event-bus'; import gettext from '../lib/gettext'; import PortalVue from 'portal-vue'; -import BaseComponents from '../../../vue/base-components.js'; -import BaseDirectives from "../../../vue/base-directives.js"; -import StudipStore from "../../../vue/store/StudipStore.js"; -import { resourceModule } from '@/assets/javascripts/lib/reststate-vuex.js'; +import BaseComponents from '@/vue/base-components.js'; +import BaseDirectives from '@/vue/base-directives.js'; +import StudipStore from '@/vue/store/StudipStore.js'; +import { resourceModule } from '../lib/reststate-vuex.js'; import axios from 'axios'; import CKEditor from '@ckeditor/ckeditor5-vue'; @@ -37,7 +37,7 @@ const createVuexStore = () => { }); return store; -} +}; // Setup stores const store = createVuexStore(); @@ -61,7 +61,7 @@ function createApp(options = {}, ...args) { globalOff(...args) { eventBus.off(...args); }, - getStudipConfig: store.getters['studip/getConfig'] + getStudipConfig: store.getters['studip/getConfig'], }, }); diff --git a/resources/vue/apps/courseware/ActivitiesApp.vue b/resources/vue/apps/courseware/ActivitiesApp.vue new file mode 100644 index 0000000..8807154 --- /dev/null +++ b/resources/vue/apps/courseware/ActivitiesApp.vue @@ -0,0 +1,137 @@ +<template> + <section class="contentbox"> + <header> + <h1>{{ $gettext('Aktivitäten') }}</h1> + </header> + <section> + <studip-progress-indicator v-show="loading" :description="$gettext('Lade Aktivitäten…')" /> + <courseware-companion-box + v-if="filteredActivitiesList.length === 0 && !loading" + mood="sad" + :msgCompanion="$gettext('Es wurden keine Aktivitäten gefunden.')" + /> + <ul class="cw-activities"> + <courseware-activity-item v-for="(item, index) in filteredActivitiesList" :key="index" :item="item" /> + </ul> + </section> + </section> +</template> + +<script> +import CoursewareActivityItem from '@/vue/components/courseware/CoursewareActivityItem.vue'; +import CoursewareCompanionBox from '@/vue/components/courseware/layouts/CoursewareCompanionBox.vue'; +import StudipProgressIndicator from '@/vue/components/StudipProgressIndicator.vue'; + +import { mapActions, mapGetters } from 'vuex'; + +export default { + name: 'courseware-activities', + components: { + CoursewareActivityItem, + CoursewareCompanionBox, + StudipProgressIndicator, + }, + data() { + return { + activitiesList: [], + loading: false, + }; + }, + computed: { + ...mapGetters({ + userId: 'userId', + getUserById: 'users/byId', + context: 'context', + getStructuralElementById: 'courseware-structural-elements/byId', + getCoursewareUnitById: 'courseware-units/byId', + coursewareUnits: 'courseware-units/all', + + typeFilter: 'typeFilter', + unitFilter: 'unitFilter', + }), + filteredActivitiesList() { + let list = this.activitiesList.slice().sort((a, b) => b.timestamp - a.timestamp); + if (['edited', 'created', 'answered', 'interacted', 'voided'].includes(this.typeFilter)) { + list = list.filter((activity) => activity.type === this.typeFilter); + } + if (this.unitFilter !== 'all') { + list = list.filter((activity) => activity.unitId === this.unitFilter); + } + + return list; + }, + }, + mounted() { + this.getActivities(); + }, + methods: { + ...mapActions({ + loadCoursewareActivities: 'loadCoursewareActivities', + loadStructuralElementById: 'courseware-structural-elements/loadById', + }), + + async loadActivitiesElements(activities) { + const results = []; + for (const activity of activities) { + const structuralElementId = activity.relationships.object.meta['object-id']; + results.push( + this.loadStructuralElementById({ id: structuralElementId, options: { include: 'ancestors' } }), + ); + } + // activity might contain structural element hidden for current user + return Promise.all(results).catch((e) => { + if (e.status !== 403) { + console.error(e); + } + }); + }, + + async getActivities() { + this.loading = true; + let activities = await this.loadCoursewareActivities({ userId: this.userId, courseId: this.context.id }); + this.activitiesList = []; + + await this.loadActivitiesElements(activities); + + for (const activity of activities) { + let error = false; + let username = this.getUserById({ id: activity.relationships.actor.data.id }).attributes[ + 'formatted-name' + ]; + const date = new Date(activity.attributes.mkdate); + const structuralElementId = activity.relationships.object.meta['object-id']; + + const activityStructuralElement = this.getStructuralElementById({ id: structuralElementId }); + if (activityStructuralElement === undefined || !activityStructuralElement.attributes['can-visit']) { + error = true; + } + if (!error) { + const unitId = activityStructuralElement.relationships?.unit?.data?.id ?? null; + const unit = this.getCoursewareUnitById({ id: unitId }); + let options = { year: 'numeric', month: '2-digit', day: '2-digit' }; + let data = { + username: username, + timestamp: date.getTime(), + readableDate: date.toLocaleString('de-DE', options), + type: activity.attributes.verb, + title: activity.attributes.title, + elementId: structuralElementId, + unitId: unitId, + unit: unit, + contextId: activity.relationships.context.data.id, + content: activity.attributes.content, + }; + this.activitiesList.push(data); + } + } + this.loading = false; + }, + }, + + async beforeCreate() { + this.$store.dispatch('setUserId', STUDIP.USER_ID); + await this.$store.dispatch('users/loadById', { id: STUDIP.USER_ID }); + await this.$store.dispatch('loadCourseUnits', this.context.id); + }, +}; +</script> diff --git a/resources/vue/apps/courseware/AdminApp.vue b/resources/vue/apps/courseware/AdminApp.vue new file mode 100644 index 0000000..4efaaf5 --- /dev/null +++ b/resources/vue/apps/courseware/AdminApp.vue @@ -0,0 +1,38 @@ +<template> + <div class="cw-admin"> + <courseware-admin-templates v-if="templatesView" /> + <Teleport to="#courseware-admin-view-widget" name="sidebar-views"> + <courseware-admin-view-widget /> + </Teleport> + <Teleport to="#courseware-admin-action-widget" name="sidebar-views"> + <courseware-admin-action-widget /> + </Teleport> + </div> +</template> + +<script> +import { mapGetters } from 'vuex'; +import CoursewareAdminActionWidget from '@/vue/components/courseware/widgets/CoursewareAdminActionWidget.vue'; +import CoursewareAdminTemplates from '@/vue/components/courseware/CoursewareAdminTemplates.vue'; +import CoursewareAdminViewWidget from '@/vue/components/courseware/widgets/CoursewareAdminViewWidget.vue'; + +export default { + components: { + CoursewareAdminActionWidget, + CoursewareAdminTemplates, + CoursewareAdminViewWidget, + }, + computed: { + ...mapGetters({ + adminViewMode: 'adminViewMode', + }), + templatesView() { + return this.adminViewMode === 'templates'; + }, + }, + beforeCreate() { + STUDIP.loadChunk('courseware'); + this.$store.dispatch('courseware-templates/loadAll'); + }, +}; +</script> diff --git a/resources/vue/apps/courseware/BookmarksApp.vue b/resources/vue/apps/courseware/BookmarksApp.vue new file mode 100644 index 0000000..3749d54 --- /dev/null +++ b/resources/vue/apps/courseware/BookmarksApp.vue @@ -0,0 +1,28 @@ +<template> + <div class="cw-content-bookmark"> + <CoursewareContentBookmarks /> + <Teleport to="#courseware-content-bookmark-filter-widget" name="sidebar-views"> + <CoursewareContentBookmarkFilterWidget /> + </Teleport> + </div> +</template> + +<script> +import CoursewareContentBookmarks from '@/vue/components/courseware/CoursewareContentBookmarks.vue'; +import CoursewareContentBookmarkFilterWidget from '@/vue/components/courseware/CoursewareContentBookmarkFilterWidget.vue'; + +export default { + components: { + CoursewareContentBookmarks, + CoursewareContentBookmarkFilterWidget, + }, + + async beforeCreate() { + STUDIP.loadChunk('courseware'); + + this.$store.dispatch('setUserId', STUDIP.USER_ID); + this.$store.dispatch('users/loadById', { id: STUDIP.USER_ID }); + this.$store.dispatch('loadUsersBookmarks', STUDIP.USER_ID); + }, +}; +</script> diff --git a/resources/vue/apps/courseware/IndexApp.vue b/resources/vue/apps/courseware/IndexApp.vue new file mode 100644 index 0000000..b741610 --- /dev/null +++ b/resources/vue/apps/courseware/IndexApp.vue @@ -0,0 +1,178 @@ +<template> + <div> + <div v-if="structureLoadingState === 'done'"> + <courseware-search-results v-show="showSearchResults" /> + <courseware-structural-element + v-show="!showSearchResults" + :canVisit="canVisit" + :structural-element="selected" + :ordered-structural-elements="orderedStructuralElements" + @select-element="selectStructuralElement" + ></courseware-structural-element> + <Teleport to="#courseware-search-widget" name="sidebar-search"> + <courseware-search-widget v-if="selected !== null"></courseware-search-widget> + </Teleport> + </div> + <studip-progress-indicator + v-if="structureLoadingState === 'loading'" + class="loading-indicator-content" + :description="$gettext('Lade Lernmaterial...')" + /> + <courseware-companion-box + v-if="structureLoadingState === 'error'" + mood="sad" + :msgCompanion="loadingErrorMessage" + /> + <courseware-companion-overlay /> + </div> +</template> + +<script> +import CoursewareStructuralElement from '@/vue/components/courseware/structural-element/CoursewareStructuralElement.vue'; +import CoursewareSearchResults from '@/vue/components/courseware/structural-element/CoursewareSearchResults.vue'; +import CoursewareCompanionBox from '@/vue/components/courseware/layouts/CoursewareCompanionBox.vue'; +import CoursewareCompanionOverlay from '@/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue'; +import CoursewareSearchWidget from '@/vue/components/courseware/widgets/CoursewareSearchWidget.vue'; + +import StudipProgressIndicator from '@/vue/components/StudipProgressIndicator.vue'; + +import { mapActions, mapGetters } from 'vuex'; + +export default { + components: { + CoursewareStructuralElement, + CoursewareSearchResults, + CoursewareCompanionBox, + StudipProgressIndicator, + CoursewareSearchWidget, + CoursewareCompanionOverlay, + }, + data: () => ({ + canVisit: null, + selected: null, + structureLoadingState: 'idle', + loadingErrorStatus: null, + }), + computed: { + ...mapGetters({ + context: 'context', + courseware: 'courseware', + orderedStructuralElements: 'courseware-structure/ordered', + relatedStructuralElement: 'courseware-structural-elements/related', + showSearchResults: 'showSearchResults', + structuralElementLastMeta: 'courseware-structural-elements/lastMeta', + structuralElements: 'courseware-structural-elements/all', + structuralElementById: 'courseware-structural-elements/byId', + userId: 'userId', + userIsTeacher: 'userIsTeacher', + }), + loadingErrorMessage() { + switch (this.loadingErrorStatus) { + case 404: + return this.$gettext('Die Seite konnte nicht gefunden werden.'); + case 403: + return this.$gettext('Diese Seite steht Ihnen leider nicht zur Verfügung.'); + default: + return this.$gettext('Beim Laden der Seite ist ein Fehler aufgetreten.'); + } + }, + }, + methods: { + ...mapActions({ + buildStructure: 'courseware-structure/build', + coursewareBlockAdder: 'coursewareBlockAdder', + invalidateStructureCache: 'courseware-structure/invalidateCache', + loadCoursewareStructure: 'courseware-structure/load', + loadStructuralElement: 'loadStructuralElement', + }), + async selectStructuralElement(id) { + if (!id) { + return; + } + + try { + await this.loadStructuralElement(id); + } catch (error) { + this.loadingErrorStatus = error.status; + this.structureLoadingState = 'error'; + return; + } + + this.$nextTick(() => { + this.canVisit = this.structuralElementLastMeta['can-visit']; + this.selected = this.structuralElementById({ id }); + }); + }, + }, + async mounted() { + this.structureLoadingState = 'loading'; + try { + await this.loadCoursewareStructure(); + } catch (error) { + this.loadingErrorStatus = error.status; + this.structureLoadingState = 'error'; + return; + } + + this.structureLoadingState = 'done'; + console.debug("mounted", this.$route.params); + const selectedId = this.$route.params?.id; + await this.selectStructuralElement(selectedId); + }, + watch: { + $route(to) { + // reset block adder on navigate + this.coursewareBlockAdder({}); + + const selectedId = to.params?.id; + this.selectStructuralElement(selectedId); + window.scrollTo({ top: 0 }); + }, + structuralElements: { + async handler() { + // compute order of structural elements once more + await this.buildStructure(); + + // throw away stale cache + this.invalidateStructureCache(); + }, + deep: true, + }, + }, + + async beforeCreate() { + STUDIP.loadChunk('courseware'); + + const httpClient = this.$store.getters.httpClient; + httpClient.get('studip/properties').then((response) => { + response.data.data.forEach((prop) => { + this.$store.dispatch('studip-properties/storeRecord', prop); + }); + }); + + this.$store.dispatch('setUrlHelper', STUDIP.URLHelper); + this.$store.dispatch('setUserId', STUDIP.USER_ID); + this.$store.dispatch('users/loadById', { id: STUDIP.USER_ID }); + + const { type, unit } = this.$store.getters["context"]; + this.$store.dispatch('courseware-units/loadById', { id: unit }); + if (type === 'courses') { + this.$store.dispatch('loadProgresses'); + await this.$store.dispatch('courseware-units/loadById', { + id: unit, + options: { include: 'structural-element' }, + }); + } + + this.$store.dispatch('courseware-templates/loadAll'); + this.$store.dispatch('loadUserClipboards', STUDIP.USER_ID); + + STUDIP.JSUpdater.register( + 'coursewareclipboard', + () => { this.$store.dispatch('loadUserClipboards', STUDIP.USER_ID)}, + () => { return { 'counter' : this.$store.getters['courseware-clipboards/all'].length };}, + 5000 + ); + }, +}; +</script> diff --git a/resources/vue/apps/courseware/ShelfApp.vue b/resources/vue/apps/courseware/ShelfApp.vue new file mode 100644 index 0000000..9c3533b --- /dev/null +++ b/resources/vue/apps/courseware/ShelfApp.vue @@ -0,0 +1,85 @@ +<template> + <div v-if="context"> + <div class="cw-shelf"> + <CoursewareUnitItems /> + <CoursewareSharedItems v-if="!inCourseContext" /> + </div> + <courseware-shelf-dialog-add-chooser v-if="showUnitAddDialog" /> + <courseware-shelf-dialog-add v-if="showUnitNewDialog" /> + <courseware-shelf-dialog-copy v-if="showUnitCopyDialog" /> + <courseware-shelf-dialog-import v-if="showUnitImportDialog" /> + <courseware-shelf-dialog-topics v-if="showUnitTopicsDialog" /> + <Teleport v-if="userIsTeacher || !inCourseContext" to="#courseware-action-widget" name="sidebar-actions"> + <courseware-shelf-action-widget></courseware-shelf-action-widget> + </Teleport> + <courseware-companion-overlay /> + </div> +</template> + +<script> +import CoursewareShelfActionWidget from '@/vue/components/courseware/widgets/CoursewareShelfActionWidget.vue'; +import CoursewareShelfDialogAdd from '@/vue/components/courseware/unit/CoursewareShelfDialogAdd.vue'; +import CoursewareShelfDialogAddChooser from '@/vue/components/courseware/unit/CoursewareShelfDialogAddChooser.vue'; +import CoursewareShelfDialogCopy from '@/vue/components/courseware/unit/CoursewareShelfDialogCopy.vue'; +import CoursewareShelfDialogImport from '@/vue/components/courseware/unit/CoursewareShelfDialogImport.vue'; +import CoursewareShelfDialogTopics from '@/vue/components/courseware/unit/CoursewareShelfDialogTopics.vue'; +import CoursewareUnitItems from '@/vue/components/courseware/unit/CoursewareUnitItems.vue'; +import CoursewareSharedItems from '@/vue/components/courseware/unit/CoursewareSharedItems.vue'; +import CoursewareCompanionOverlay from '@/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue'; + +import { mapGetters } from 'vuex'; + +export default { + components: { + CoursewareShelfActionWidget, + CoursewareShelfDialogAdd, + CoursewareShelfDialogAddChooser, + CoursewareShelfDialogCopy, + CoursewareShelfDialogImport, + CoursewareShelfDialogTopics, + CoursewareUnitItems, + CoursewareSharedItems, + CoursewareCompanionOverlay, + }, + data() { + return { + rate: 0, + }; + }, + computed: { + ...mapGetters({ + showUnitAddDialog: 'showUnitAddDialog', + showUnitCopyDialog: 'showUnitCopyDialog', + showUnitImportDialog: 'showUnitImportDialog', + showUnitLinkDialog: 'showUnitLinkDialog', + showUnitNewDialog: 'showUnitNewDialog', + showUnitTopicsDialog: 'showUnitTopicsDialog', + licenses: 'licenses', + context: 'context', + userIsTeacher: 'userIsTeacher', + userId: 'userId', + }), + inCourseContext() { + return this.context?.type === 'courses'; + }, + }, + async beforeCreate() { + STUDIP.loadChunk('courseware'); + this.$store.dispatch('setUrlHelper', STUDIP.URLHelper); + this.$store.dispatch('setUserId', STUDIP.USER_ID); + this.$store.dispatch('users/loadById', { id: STUDIP.USER_ID }); + + const { id, type } = this.$store.getters.context; + const feedbackSettings = this.$store.getters.feedbackSettings; + if (type === 'courses') { + const isTeacher = this.$store.getters.userIsTeacher + this.$store.dispatch('setUserIsTeacher', isTeacher); + await this.$store.dispatch('loadCourseUnits', id); + await this.$store.dispatch('setFeedbackSettings', feedbackSettings); + } else { + await this.$store.dispatch('loadUserUnits', id); + await this.$store.dispatch('courseware-structural-elements-shared/loadAll', { options: { include: 'owner' } }); + } + }, +}; +</script> diff --git a/resources/vue/apps/courseware/TasksApp.vue b/resources/vue/apps/courseware/TasksApp.vue new file mode 100644 index 0000000..60677b8 --- /dev/null +++ b/resources/vue/apps/courseware/TasksApp.vue @@ -0,0 +1,20 @@ +<template> + <RouterView /> +</template> + +<script> +import { RouterView } from 'vue-router'; + +export default { + components: { RouterView }, + async beforeCreate() { + STUDIP.loadChunk('courseware'); + + const { id } = this.$store.getters['context']; + + this.$store.dispatch('setUserId', STUDIP.USER_ID); + await this.$store.dispatch('users/loadById', { id: STUDIP.USER_ID }); + await this.$store.dispatch('tasks/loadTasksOfCourse', { cid: id }); + }, +}; +</script> diff --git a/resources/vue/components/courseware/ContentBookmarkApp.vue b/resources/vue/components/courseware/ContentBookmarkApp.vue deleted file mode 100644 index faf4db6..0000000 --- a/resources/vue/components/courseware/ContentBookmarkApp.vue +++ /dev/null @@ -1,21 +0,0 @@ -<template> - <div class="cw-content-bookmark"> - <courseware-content-bookmarks /> - <Teleport to="#courseware-content-bookmark-filter-widget" name="sidebar-views"> - <courseware-content-bookmark-filter-widget /> - </Teleport> - </div> -</template> - -<script> -import CoursewareContentBookmarks from './CoursewareContentBookmarks.vue'; -import CoursewareContentBookmarkFilterWidget from './CoursewareContentBookmarkFilterWidget.vue'; - -export default { - components: { - CoursewareContentBookmarks, - CoursewareContentBookmarkFilterWidget - }, - -} -</script> diff --git a/resources/vue/components/courseware/ShelfApp.vue b/resources/vue/components/courseware/ShelfApp.vue deleted file mode 100644 index 51cf5f7..0000000 --- a/resources/vue/components/courseware/ShelfApp.vue +++ /dev/null @@ -1,68 +0,0 @@ -<template> - <div> - <div class="cw-shelf"> - <courseware-unit-items /> - <courseware-shared-items v-if="!inCourseContext" /> - </div> - <courseware-shelf-dialog-add-chooser v-if="showUnitAddDialog"/> - <courseware-shelf-dialog-add v-if="showUnitNewDialog" /> - <courseware-shelf-dialog-copy v-if="showUnitCopyDialog" /> - <courseware-shelf-dialog-import v-if="showUnitImportDialog" /> - <courseware-shelf-dialog-topics v-if="showUnitTopicsDialog" /> - <Teleport v-if="userIsTeacher || !inCourseContext" to="#courseware-action-widget" name="sidebar-actions"> - <courseware-shelf-action-widget></courseware-shelf-action-widget> - </Teleport> - <courseware-companion-overlay /> - </div> -</template> - -<script> -import CoursewareShelfActionWidget from './widgets/CoursewareShelfActionWidget.vue'; -import CoursewareShelfDialogAdd from './unit/CoursewareShelfDialogAdd.vue'; -import CoursewareShelfDialogAddChooser from './unit/CoursewareShelfDialogAddChooser.vue'; -import CoursewareShelfDialogCopy from './unit/CoursewareShelfDialogCopy.vue'; -import CoursewareShelfDialogImport from './unit/CoursewareShelfDialogImport.vue'; -import CoursewareShelfDialogTopics from './unit/CoursewareShelfDialogTopics.vue'; -import CoursewareUnitItems from './unit/CoursewareUnitItems.vue'; -import CoursewareSharedItems from './unit/CoursewareSharedItems.vue'; -import CoursewareCompanionOverlay from './layouts/CoursewareCompanionOverlay.vue'; - -import { mapGetters } from 'vuex'; - -export default { - components: { - CoursewareShelfActionWidget, - CoursewareShelfDialogAdd, - CoursewareShelfDialogAddChooser, - CoursewareShelfDialogCopy, - CoursewareShelfDialogImport, - CoursewareShelfDialogTopics, - CoursewareUnitItems, - CoursewareSharedItems, - CoursewareCompanionOverlay, - }, - data() { - return { - rate: 0 - } - }, - computed: { - ...mapGetters({ - showUnitAddDialog: 'showUnitAddDialog', - showUnitCopyDialog: 'showUnitCopyDialog', - showUnitImportDialog: 'showUnitImportDialog', - showUnitLinkDialog: 'showUnitLinkDialog', - showUnitNewDialog: 'showUnitNewDialog', - showUnitTopicsDialog: 'showUnitTopicsDialog', - licenses: 'licenses', - context:'context', - userIsTeacher: 'userIsTeacher', - userId: 'userId' - }), - inCourseContext() { - return this.context.type === 'courses'; - } - - }, -} -</script> diff --git a/resources/vue/components/courseware/plugin-manager.js b/resources/vue/components/courseware/plugin-manager.js index 5326fd9..7e5ee74 100644 --- a/resources/vue/components/courseware/plugin-manager.js +++ b/resources/vue/components/courseware/plugin-manager.js @@ -22,9 +22,15 @@ class PluginManager { registerComponentsLocally(component) { for (const [name, block] of Object.entries(this.blocks)) { + if (!component.$options.components) { + component.$options.components = {}; + } component.$options.components[name] = block; } for (const [name, container] of Object.entries(this.containers)) { + if (!component.$options.components) { + component.$options.components = {}; + } component.$options.components[name] = container; } } diff --git a/resources/vue/courseware-activities-app.js b/resources/vue/courseware-activities-app.js deleted file mode 100644 index 858082f..0000000 --- a/resources/vue/courseware-activities-app.js +++ /dev/null @@ -1,56 +0,0 @@ -import ActivitiesApp from './components/courseware/ActivitiesApp.vue'; -import CoursewareModule from './store/courseware/courseware.module'; -import CoursewareActivitiesModule from './store/courseware/courseware-activities.module'; -import CoursewareStructureModule from './store/courseware/structure.module'; -import axios from 'axios'; -import { h } from "vue"; - -const mountApp = async (STUDIP, createApp, store, element) => { - const getHttpClient = () => - axios.create({ - baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true), - headers: { - 'Content-Type': 'application/vnd.api+json', - }, - }); - - const httpClient = getHttpClient(); - - store.registerModule('courseware', CoursewareModule); - store.registerModule('courseware-structure', CoursewareStructureModule); - store.registerModule('courseware-activities', CoursewareActivitiesModule); - - let entry_id = null; - let entry_type = null; - let elem; - - if ((elem = document.getElementById(element.substring(1))) !== undefined) { - if (elem.attributes !== undefined) { - if (elem.attributes['entry-type'] !== undefined) { - entry_type = elem.attributes['entry-type'].value; - } - - if (elem.attributes['entry-id'] !== undefined) { - entry_id = elem.attributes['entry-id'].value; - } - } - } - - store.dispatch('setUserId', STUDIP.USER_ID); - await store.dispatch('users/loadById', {id: STUDIP.USER_ID}); - store.dispatch('setHttpClient', httpClient); - store.dispatch('coursewareContext', { - id: entry_id, - type: entry_type, - }); - await store.dispatch('loadCourseUnits', entry_id); - - const app = createApp({ - render: () => h(ActivitiesApp), - }); - app.mount(element); - - return app; -}; - -export default mountApp; diff --git a/resources/vue/courseware-admin-app.js b/resources/vue/courseware-admin-app.js deleted file mode 100644 index 57164a3..0000000 --- a/resources/vue/courseware-admin-app.js +++ /dev/null @@ -1,18 +0,0 @@ -import AdminApp from './components/courseware/AdminApp.vue'; -import CoursewareAdminModule from './store/courseware/courseware-admin.module'; -import { h } from "vue"; - -const mountApp = (STUDIP, createApp, store, element) => { - store.registerModule('courseware', CoursewareAdminModule); - - store.dispatch('courseware-templates/loadAll'); - - const app = createApp({ - render: () => h(AdminApp), - }); - app.mount(element); - - return app; -} - -export default mountApp; diff --git a/resources/vue/courseware-content-bookmark-app.js b/resources/vue/courseware-content-bookmark-app.js deleted file mode 100644 index 7a05033..0000000 --- a/resources/vue/courseware-content-bookmark-app.js +++ /dev/null @@ -1,52 +0,0 @@ -import ContentBookmarkApp from './components/courseware/ContentBookmarkApp.vue'; -import CoursewareModule from './store/courseware/courseware.module'; -import axios from 'axios'; -import { h } from "vue"; - -const mountApp = (STUDIP, createApp, store, element) => { - const getHttpClient = () => - axios.create({ - baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true), - headers: { - 'Content-Type': 'application/vnd.api+json', - }, - }); - - const httpClient = getHttpClient(); - - store.registerModule('courseware', CoursewareModule); - - let entry_id = null; - let entry_type = null; - let elem; - - if ((elem = document.getElementById(element.substring(1))) !== undefined) { - if (elem.attributes !== undefined) { - if (elem.attributes['entry-type'] !== undefined) { - entry_type = elem.attributes['entry-type'].value; - } - - if (elem.attributes['entry-id'] !== undefined) { - entry_id = elem.attributes['entry-id'].value; - } - } - } - - store.dispatch('setUserId', STUDIP.USER_ID); - store.dispatch('users/loadById', {id: STUDIP.USER_ID}); - store.dispatch('loadUsersBookmarks', STUDIP.USER_ID); - store.dispatch('setHttpClient', httpClient); - store.dispatch('coursewareContext', { - id: entry_id, - type: entry_type, - }); - - const app = createApp({ - render: () => h(ContentBookmarkApp), - }); - app.mount(element); - - return app; -} - -export default mountApp; diff --git a/resources/vue/courseware-index-app.js b/resources/vue/courseware-index-app.js deleted file mode 100644 index 79679fb..0000000 --- a/resources/vue/courseware-index-app.js +++ /dev/null @@ -1,137 +0,0 @@ -import CoursewareModule from './store/courseware/courseware.module'; -import CoursewareStructureModule from './store/courseware/structure.module'; -import FileChooserStore from './store/file-chooser.js'; -import CoursewareStructuralElement from './components/courseware/structural-element/CoursewareStructuralElement.vue'; -import CoursewareTasksModule from './store/courseware/courseware-tasks.module'; -import IndexApp from './components/courseware/IndexApp.vue'; -import PluginManager from './components/courseware/plugin-manager.js'; -import { StockImagesPlugin } from './plugins/stock-images.js'; -import { createRouter, createWebHashHistory } from 'vue-router'; -import { h } from "vue"; - -const mountApp = async (STUDIP, c, store, element) => { - // get id of parent structural element - let elem_id = null; - let entry_id = null; - let entry_type = null; - let unit_id = null; - let licenses = null; - let elem; - let feedbackSettings = null; - - if ((elem = document.getElementById(element.substring(1))) !== undefined) { - if (elem.attributes !== undefined) { - if (elem.attributes['entry-element-id'] !== undefined) { - elem_id = elem.attributes['entry-element-id'].value; - } - - if (elem.attributes['entry-type'] !== undefined) { - entry_type = elem.attributes['entry-type'].value; - } - - if (elem.attributes['entry-id'] !== undefined) { - entry_id = elem.attributes['entry-id'].value; - } - - if (elem.attributes['unit-id'] !== undefined) { - unit_id = elem.attributes['unit-id'].value; - } - - // we need a route for License SORM - if (elem.attributes['licenses'] !== undefined) { - licenses = JSON.parse(elem.attributes['licenses'].value); - } - - if (elem.attributes['feedback-settings'] !== undefined) { - feedbackSettings = JSON.parse(elem.attributes['feedback-settings'].value); - } - } - } - - const { createApp, httpClient } = await STUDIP.Vue.load(); - - let base = new URL( - STUDIP.URLHelper.parameters.cid - ? STUDIP.URLHelper.getURL('dispatch.php/course/courseware/courseware/' + unit_id, { cid: STUDIP.URLHelper.parameters.cid }, true) - : STUDIP.URLHelper.getURL('dispatch.php/contents/courseware/courseware/' + unit_id) - ); - if (entry_type === 'courses') { - base.search += '&'; - } - - const routes = [ - { - path: '/', - redirect: '/structural_element/' + elem_id, - }, - { - path: '/structural_element/:id', - name: 'CoursewareStructuralElement', - component: CoursewareStructuralElement, - }, - ]; - - - const router = createRouter({ - history: createWebHashHistory(base.toString()), - routes - }); - - store.registerModule('courseware', CoursewareModule); - store.registerModule('courseware-structure', CoursewareStructureModule); - store.registerModule('file-chooser', FileChooserStore); - store.registerModule('tasks', CoursewareTasksModule); - - httpClient.get('studip/properties') - .then(response => { - response.data.data.forEach(prop => { - store.dispatch('studip-properties/storeRecord', prop); - }); - }); - - store.dispatch('setUrlHelper', STUDIP.URLHelper); - store.dispatch('setUserId', STUDIP.USER_ID); - await store.dispatch('users/loadById', {id: STUDIP.USER_ID}); - store.dispatch('setHttpClient', httpClient); - - store.dispatch('coursewareContext', { - id: entry_id, - type: entry_type, - unit: unit_id - }); - store.dispatch('courseware-units/loadById', { id: unit_id }); - if (entry_type === 'courses') { - store.dispatch('loadProgresses'); - await store.dispatch('setFeedbackSettings', feedbackSettings); - await store.dispatch('courseware-units/loadById', { id: unit_id, options: {include: 'structural-element'} }); - } - - store.dispatch('coursewareCurrentElement', elem_id); - - store.dispatch('licenses', licenses); - store.dispatch('courseware-templates/loadAll'); - store.dispatch('loadUserClipboards', STUDIP.USER_ID); - - const pluginManager = new PluginManager(); - store.dispatch('setPluginManager', pluginManager); - STUDIP.eventBus.emit('courseware:init-plugin-manager', pluginManager); - - STUDIP.JSUpdater.register( - 'coursewareclipboard', - () => { store.dispatch('loadUserClipboards', STUDIP.USER_ID)}, - () => { return { 'counter' : store.getters['courseware-clipboards/all'].length };}, - 5000 - ); - - const app = createApp({ - render: () => h(IndexApp), - }); - - app.use(router); - app.use(StockImagesPlugin, { store }); - app.mount(element); - - return app; -}; - -export default mountApp; diff --git a/resources/vue/courseware-shelf-app.js b/resources/vue/courseware-shelf-app.js deleted file mode 100644 index efbf0ca..0000000 --- a/resources/vue/courseware-shelf-app.js +++ /dev/null @@ -1,85 +0,0 @@ -import CoursewareShelfModule from './store/courseware/courseware-shelf.module'; -import ShelfApp from './components/courseware/ShelfApp.vue'; -import { resourceModule } from '@/assets/javascripts/lib/reststate-vuex.js'; -import { StockImagesPlugin } from './plugins/stock-images.js'; -import { h } from 'vue'; - -const mountApp = async (STUDIP, c, store, element) => { - // handle studip 5.0 to 5.2 urls - const elemId = window.location.hash.match(/structural_element\/(\d+)/); - - if (elemId) { - let url = new URL(window.location.href); - url.searchParams.set('element_id', elemId[1]); - window.location.href = url; - - return false; - } - - let elem; - let entry_id = null; - let entry_type = null; - let licenses = null; - let feedbackSettings = null; - let isTeacher = false; - - if ((elem = document.getElementById(element.substring(1))) !== undefined) { - if (elem.attributes !== undefined) { - if (elem.attributes['entry-type'] !== undefined) { - entry_type = elem.attributes['entry-type'].value; - } - - if (elem.attributes['entry-id'] !== undefined) { - entry_id = elem.attributes['entry-id'].value; - } - - if (elem.attributes['licenses'] !== undefined) { - licenses = JSON.parse(elem.attributes['licenses'].value); - } - if (elem.attributes['feedback-settings'] !== undefined) { - feedbackSettings = JSON.parse(elem.attributes['feedback-settings'].value); - } - if (elem.attributes['is-teacher'] !== undefined) { - isTeacher = JSON.parse(elem.attributes['is-teacher'].value); - } - } - } - - const { createApp, httpClient } = await STUDIP.Vue.load(); - store.registerModule('courseware-shelf', CoursewareShelfModule); - - store.registerModule( - 'courseware-structural-elements-shared', - resourceModule({ - name: 'courseware-structural-elements-shared', - httpClient - }) - ); - - store.dispatch('setUrlHelper', STUDIP.URLHelper); - store.dispatch('setHttpClient', httpClient); - store.dispatch('setLicenses', licenses); - store.dispatch('setUserId', STUDIP.USER_ID); - await store.dispatch('users/loadById', {id: STUDIP.USER_ID}); - store.dispatch('setContext', { - id: entry_id, - type: entry_type, - }); - if (entry_type === 'courses') { - store.dispatch('setUserIsTeacher', isTeacher); - await store.dispatch('loadCourseUnits', entry_id); - await store.dispatch('setFeedbackSettings', feedbackSettings); - } else { - await store.dispatch('loadUserUnits', entry_id); - await store.dispatch('courseware-structural-elements-shared/loadAll', { options: { include: 'owner' } }); - } - - const app = createApp({ - render: () => h(ShelfApp), - }); - app.use(StockImagesPlugin, { store }); - app.mount(element); - -}; - -export default mountApp; diff --git a/resources/vue/courseware-tasks-app.js b/resources/vue/courseware-tasks-app.js deleted file mode 100644 index af72430..0000000 --- a/resources/vue/courseware-tasks-app.js +++ /dev/null @@ -1,91 +0,0 @@ -import TaskGroupsIndex from './components/courseware/tasks/PagesTaskGroupsIndex.vue'; -import TaskGroupsShow from './components/courseware/tasks/PagesTaskGroupsShow.vue'; -import { createRouter, RouterView, createWebHashHistory } from 'vue-router'; -import CoursewareModule from './store/courseware/courseware.module'; -import CoursewareTasksModule from './store/courseware/courseware-tasks.module'; -import CoursewareStructureModule from './store/courseware/structure.module'; -import axios from 'axios'; -import {h} from "vue"; - -const mountApp = async (STUDIP, createApp, store, element) => { - const getHttpClient = () => - axios.create({ - baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true), - headers: { - 'Content-Type': 'application/vnd.api+json', - }, - }); - - const httpClient = getHttpClient(); - - const routes = [ - { - path: '/', - name: 'task-groups-index', - component: TaskGroupsIndex, - }, - { - path: '/task-groups/:id', - name: 'task-groups-show', - component: TaskGroupsShow, - props: true, - }, - ]; - - const router = createRouter({ - history: createWebHashHistory(), - routes, - }); - router.beforeEach((to, from, next) => { - if (to?.query?.cid !== undefined) { - next(); - } else { - next({ ...to, query: { ...to.query, cid: window.STUDIP.URLHelper.parameters.cid } }); - } - }); - - store.registerModule('courseware', CoursewareModule); - store.registerModule('tasks', CoursewareTasksModule); - store.registerModule('courseware-structure', CoursewareStructureModule); - - let entry_id = null; - let entry_type = null; - let isTeacher = false; - let elem; - - if ((elem = document.getElementById(element.substring(1))) !== undefined) { - if (elem.attributes !== undefined) { - if (elem.attributes['entry-type'] !== undefined) { - entry_type = elem.attributes['entry-type'].value; - } - - if (elem.attributes['entry-id'] !== undefined) { - entry_id = elem.attributes['entry-id'].value; - } - - if (elem.attributes['is-teacher'] !== undefined) { - isTeacher = JSON.parse(elem.attributes['is-teacher'].value); - } - } - } - - store.dispatch('setUserId', STUDIP.USER_ID); - await store.dispatch('users/loadById', { id: STUDIP.USER_ID }); - store.dispatch('setUserIsTeacherInCourse', isTeacher); - store.dispatch('setHttpClient', httpClient); - store.dispatch('coursewareContext', { - id: entry_id, - type: entry_type, - }); - await store.dispatch('tasks/loadTasksOfCourse', { cid: entry_id }); - - const app = createApp({ - render: () => h(RouterView), - }); - app.use(router); - app.mount(element); - - return app; -}; - -export default mountApp; diff --git a/resources/vue/plugins/courseware-activities.js b/resources/vue/plugins/courseware-activities.js new file mode 100644 index 0000000..b5a82ee --- /dev/null +++ b/resources/vue/plugins/courseware-activities.js @@ -0,0 +1,13 @@ +export const CoursewareActivitiesApp = { + install(app, options = {}) { + if (!('store' in options)) { + throw new Error('You must provide the vuex store via the options argument'); + } + if (!('httpClient' in options)) { + throw new Error('You must provide the httpClient via the options argument'); + } + const { httpClient, store } = options; + + store.dispatch('setHttpClient', httpClient); + }, +}; diff --git a/resources/vue/plugins/courseware-index.js b/resources/vue/plugins/courseware-index.js new file mode 100644 index 0000000..7747664 --- /dev/null +++ b/resources/vue/plugins/courseware-index.js @@ -0,0 +1,59 @@ +import { createRouter, createWebHashHistory } from 'vue-router'; +import PluginManager from '@/vue/components/courseware/plugin-manager.js'; +import CoursewareStructuralElement from '@/vue/components/courseware/structural-element/CoursewareStructuralElement.vue'; + +export const CoursewareIndexApp = { + install(app, options = {}) { + if (!('store' in options)) { + throw new Error('You must provide the vuex store via the options argument'); + } + if (!('httpClient' in options)) { + throw new Error('You must provide the httpClient via the options argument'); + } + + const { httpClient, store } = options; + store.dispatch('setHttpClient', httpClient); + + const router = this.initializeRouter(store); + app.use(router); + + const pluginManager = new PluginManager(); + store.dispatch('setPluginManager', pluginManager); + STUDIP.eventBus.emit('courseware:init-plugin-manager', pluginManager); + }, + + initializeRouter(store) { + const elem_id = store.getters.currentElement; + const context = store.getters.context; + + const base = new URL( + STUDIP.URLHelper.parameters.cid + ? STUDIP.URLHelper.getURL( + 'dispatch.php/course/courseware/courseware/' + context.unit, + { cid: STUDIP.URLHelper.parameters.cid }, + true, + ) + : STUDIP.URLHelper.getURL('dispatch.php/contents/courseware/courseware/' + context.unit), + ); + if (context.type === 'courses') { + base.search += '&'; + } + + const routes = [ + { + path: '/', + redirect: '/structural_element/' + elem_id, + }, + { + path: '/structural_element/:id', + name: 'CoursewareStructuralElement', + component: CoursewareStructuralElement, + }, + ]; + + return createRouter({ + history: createWebHashHistory(base.toString()), + routes, + }); + }, +}; diff --git a/resources/vue/plugins/courseware-shelf.js b/resources/vue/plugins/courseware-shelf.js new file mode 100644 index 0000000..be066c2 --- /dev/null +++ b/resources/vue/plugins/courseware-shelf.js @@ -0,0 +1,19 @@ +import { resourceModule } from '@/assets/javascripts/lib/reststate-vuex.js'; + +export const CoursewareShelfApp = { + install(app, options = {}) { + if (!('store' in options)) { + throw new Error('You must provide the vuex store via the options argument'); + } + if (!('httpClient' in options)) { + throw new Error('You must provide the httpClient via the options argument'); + } + const { httpClient, store } = options; + + store.dispatch('setHttpClient', httpClient); + store.registerModule( + 'courseware-structural-elements-shared', + resourceModule({ name: 'courseware-structural-elements-shared', httpClient }), + ); + }, +}; diff --git a/resources/vue/plugins/courseware-tasks.js b/resources/vue/plugins/courseware-tasks.js new file mode 100644 index 0000000..0d8bc53 --- /dev/null +++ b/resources/vue/plugins/courseware-tasks.js @@ -0,0 +1,47 @@ +import { createRouter, createWebHashHistory } from 'vue-router'; +import TaskGroupsIndex from '@/vue/components/courseware/tasks/PagesTaskGroupsIndex.vue'; +import TaskGroupsShow from '@/vue/components/courseware/tasks/PagesTaskGroupsShow.vue'; + +export const CoursewareTasksApp = { + install(app, options = {}) { + if (!('store' in options)) { + throw new Error('You must provide the vuex store via the options argument'); + } + if (!('httpClient' in options)) { + throw new Error('You must provide the httpClient via the options argument'); + } + const { httpClient, store } = options; + + store.dispatch('setHttpClient', httpClient); + + const router = this.initializeRouter(); + app.use(router); + }, + + initializeRouter() { + const routes = [ + { + path: '/', + name: 'task-groups-index', + component: TaskGroupsIndex, + }, + { + path: '/task-groups/:id', + name: 'task-groups-show', + component: TaskGroupsShow, + props: true, + }, + ]; + + const router = createRouter({ history: createWebHashHistory(), routes }); + router.beforeEach((to, from, next) => { + if (to?.query?.cid !== undefined) { + next(); + } else { + next({ ...to, query: { ...to.query, cid: window.STUDIP.URLHelper.parameters.cid } }); + } + }); + + return router; + }, +}; |
