aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de>2025-05-13 08:19:25 +0200
committerMarcus Eibrink-Lunzenauer <marcus@elan-ev.de>2026-01-07 14:14:20 +0100
commit0a20239f787a10dc102a4827d8fffc00f25899e4 (patch)
treec242d527dcbe5b637d66880f83d5d0b177fd7844
parent4f00fb8d9fea5ceead39a8145ac10d859ebdd287 (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
-rw-r--r--app/views/admin/courseware/index.php3
-rw-r--r--app/views/contents/courseware/bookmarks.php10
-rw-r--r--app/views/contents/courseware/courseware.php20
-rw-r--r--app/views/contents/courseware/index.php14
-rwxr-xr-xapp/views/contents/courseware/shared_content_courseware.php21
-rw-r--r--app/views/course/courseware/activities.php13
-rw-r--r--app/views/course/courseware/courseware.php22
-rw-r--r--app/views/course/courseware/index.php17
-rw-r--r--app/views/course/courseware/tasks.php15
-rw-r--r--resources/assets/javascripts/bootstrap/courseware.js72
-rw-r--r--resources/assets/javascripts/bootstrap/vue.js11
-rw-r--r--resources/assets/javascripts/chunks/vue.js12
-rw-r--r--resources/vue/apps/courseware/ActivitiesApp.vue137
-rw-r--r--resources/vue/apps/courseware/AdminApp.vue38
-rw-r--r--resources/vue/apps/courseware/BookmarksApp.vue28
-rw-r--r--resources/vue/apps/courseware/IndexApp.vue178
-rw-r--r--resources/vue/apps/courseware/ShelfApp.vue85
-rw-r--r--resources/vue/apps/courseware/TasksApp.vue20
-rw-r--r--resources/vue/components/courseware/ContentBookmarkApp.vue21
-rw-r--r--resources/vue/components/courseware/ShelfApp.vue68
-rw-r--r--resources/vue/components/courseware/plugin-manager.js6
-rw-r--r--resources/vue/courseware-activities-app.js56
-rw-r--r--resources/vue/courseware-admin-app.js18
-rw-r--r--resources/vue/courseware-content-bookmark-app.js52
-rw-r--r--resources/vue/courseware-index-app.js137
-rw-r--r--resources/vue/courseware-shelf-app.js85
-rw-r--r--resources/vue/courseware-tasks-app.js91
-rw-r--r--resources/vue/plugins/courseware-activities.js13
-rw-r--r--resources/vue/plugins/courseware-index.js59
-rw-r--r--resources/vue/plugins/courseware-shelf.js19
-rw-r--r--resources/vue/plugins/courseware-tasks.js47
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;
+ },
+};