From d2c7c69e75f4ba549413584d9c803b90073d328f Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Willms <tleilax+github@gmail.com> Date: Fri, 31 May 2024 09:18:26 +0200 Subject: [PATCH] integration complete --- app/controllers/my_courses.php | 26 ++---- app/views/my_courses/index.php | 11 ++- lib/classes/StudipController.php | 36 +------- lib/classes/VueApp.php | 92 +++++++++++++++---- .../javascripts/bootstrap/my-courses.js | 22 ----- resources/assets/javascripts/bootstrap/vue.js | 81 +++++++++------- resources/assets/javascripts/entry-base.js | 2 - .../assets/javascripts/lib/studip-vue.js | 12 --- templates/vue-app.php | 10 ++ 9 files changed, 149 insertions(+), 143 deletions(-) delete mode 100644 resources/assets/javascripts/bootstrap/my-courses.js create mode 100644 templates/vue-app.php diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php index c5c10dd27d1..e17fc91b38b 100644 --- a/app/controllers/my_courses.php +++ b/app/controllers/my_courses.php @@ -113,19 +113,13 @@ class MyCoursesController extends AuthenticatedController } $this->setupSidebar($sem_key, $group_field, $this->check_for_new($sem_courses, $group_field)); - $data = $this->getMyCoursesData($sem_courses, $group_field); - PageLayout::addHeadElement( - 'script', - ['type' => 'text/javascript'], - 'window.STUDIP.MyCoursesData = ' . json_encode($data) . ';' - ); - - $this->render_vue_app( - Studip\VueApp::create('MyCourses') - ->withStore('MyCoursesStore') - ->withStoreData('mycourses', $data) - ); + $this->vueApp = Studip\VueApp::create('MyCourses') + ->withStore( + 'MyCoursesStore', + 'mycourses', + $this->getMyCoursesData($sem_courses, $group_field) + ); } /** @@ -803,10 +797,10 @@ class MyCoursesController extends AuthenticatedController } return [ - 'courses' => $this->sanitizeNavigations(array_map([$this, 'convertCourse'], $temp_courses)), - 'groups' => $groups, - 'user_id' => $GLOBALS['user']->id, - 'config' => [ + 'setCourses' => $this->sanitizeNavigations(array_map([$this, 'convertCourse'], $temp_courses)), + 'setGroups' => $groups, + 'setUserId' => $GLOBALS['user']->id, + 'setConfig' => [ 'allow_dozent_visibility' => Config::get()->ALLOW_DOZENT_VISIBILITY, 'open_groups' => array_values($GLOBALS['user']->cfg->MY_COURSES_OPEN_GROUPS), 'sem_number' => Config::get()->IMPORTANT_SEMNUMBER, diff --git a/app/views/my_courses/index.php b/app/views/my_courses/index.php index 1bbfd7388e8..8ec7703a80b 100644 --- a/app/views/my_courses/index.php +++ b/app/views/my_courses/index.php @@ -1,10 +1,15 @@ +<?php +/** + * @var Studip\VueApp $vueApp + * @var array $my_bosses + * @var array $waiting_list + */ +?> <? if ($waiting_list) : ?> <?= $this->render_partial('my_courses/waiting_list.php', compact('waiting_list')) ?> <? endif ?> -<div class="my-courses-vue-app"> - <my-courses /> -</div> +<?= $vueApp->render() ?> <? if (count($my_bosses) > 0) : ?> <?= $this->render_partial('my_courses/_deputy_bosses'); ?> diff --git a/lib/classes/StudipController.php b/lib/classes/StudipController.php index ad6a4811291..d19da920a3b 100644 --- a/lib/classes/StudipController.php +++ b/lib/classes/StudipController.php @@ -590,41 +590,7 @@ abstract class StudipController extends Trails\Controller public function render_vue_app(\Studip\VueApp $app): void { - $attributes = [ - 'data-vue-app' => json_encode([ - 'components' => [$app->getBaseComponent()], - 'store' => $app->getStore(), - ]), - 'is' => $app->getBaseComponent(), - - ...$app->getProps(), - ]; - - $content = ''; - - if ($app->getStoreData()) { - $content .= '<script>'; - foreach ($app->getStoreData() as $key => $data) { - $content .= sprintf( - "window.STUDIP.Vue.setStoreData('%s', %s);", - $key, - json_encode($data), - ); - } - $content .= '</script>'; - } - - $content .= '<div ' . arrayToHtmlAttributes($attributes) . '></div>'; - - - if ($this->layout) { - $content = $this->get_template_factory()->render( - $this->layout, - ['content_for_layout' => $content] - ); - } - - $this->render_text($content); + $this->render_template($app->getTemplate(), $this->layout); } /** diff --git a/lib/classes/VueApp.php b/lib/classes/VueApp.php index 6c40c9e9763..16ebf545ac5 100644 --- a/lib/classes/VueApp.php +++ b/lib/classes/VueApp.php @@ -1,26 +1,50 @@ <?php namespace Studip; -class VueApp +use Flexi\Template; +use Stringable; + +final class VueApp implements Stringable { public static function create(string $base_component, array $props = []): VueApp { return new self($base_component, $props); } - protected ?string $store = null; - protected array $storeData = []; + private array $components = []; + private array $stores = []; + private array $storeData = []; - public function __construct( - protected string $base_component, - protected array $props = [] + private function __construct( + private string $base_component, + private array $props = [] ) { } public function withBaseComponent(string $base_component): VueApp { - $this->base_component = $base_component; - return $this; + $clone = clone $this; + $clone->base_component = $base_component; + + return $clone; + } + + public function withComponents(string ...$components): VueApp + { + $clone = clone $this; + foreach ($components as $component) { + $clone = $clone->withAddedComponent($component); + } + return $clone; + } + + public function withAddedComponent(string $component): VueApp + { + $clone = clone $this; + if (!in_array($component, $clone->components)) { + $clone->components[] = $component; + } + return $clone; } public function getBaseComponent(): string @@ -30,8 +54,9 @@ class VueApp public function withProps(array $props): VueApp { - $this->props = $props; - return $this; + $clone = clone $this; + $clone->props = $props; + return $clone; } public function getProps(): array @@ -39,25 +64,52 @@ class VueApp return $this->props; } - public function withStore(?string $store): VueApp + public function withStore(?string $store, ?string $index = null, ?array $data = null): VueApp { - $this->store = $store; - return $this; - } + $clone = clone $this; - public function getStore(): ?string - { - return $this->store; + $clone->stores[$index ?? $store] = $store; + + if ($data !== null) { + $clone->storeData[$index ?? $store] = $data; + } + + return $clone; } - public function withStoreData(string $key, array $data): VueApp + public function getStores(): array { - $this->storeData[$key] = $data; - return $this; + return $this->stores; } public function getStoreData(): array { return $this->storeData; } + + public function getTemplate(): Template + { + $template = $GLOBALS['template_factory']->open('vue-app.php'); + $template->attributes = [ + 'data-vue-app' => json_encode([ + 'components' => [$this->base_component, ...$this->components], + 'stores' => $this->stores, + ]), + 'is' => $this->base_component, + + ...$this->props, + ]; + $template->storeData = $this->storeData; + return $template; + } + + public function render(): string + { + return $this->getTemplate()->render(); + } + + public function __toString(): string + { + return $this->render(); + } } diff --git a/resources/assets/javascripts/bootstrap/my-courses.js b/resources/assets/javascripts/bootstrap/my-courses.js deleted file mode 100644 index 40e0c24dc8d..00000000000 --- a/resources/assets/javascripts/bootstrap/my-courses.js +++ /dev/null @@ -1,22 +0,0 @@ -import MyCourses from '../../../vue/components/MyCourses.vue'; -import storeConfig from '../../../vue/store/MyCoursesStore.js'; - -STUDIP.domReady(async () => { - if ($('.my-courses-vue-app').length === 0) { - return; - } - - const { createApp, store } = await STUDIP.Vue.load(); - - store.registerModule('mycourses', storeConfig); - - store.commit('mycourses/setCourses', window.STUDIP.MyCoursesData['courses']); - store.commit('mycourses/setGroups', window.STUDIP.MyCoursesData['groups']); - store.commit('mycourses/setUserId', window.STUDIP.MyCoursesData['user_id']); - store.commit('mycourses/setConfig', window.STUDIP.MyCoursesData['config']); - - const vm = createApp({ - components: { MyCourses } - }); - vm.$mount('.my-courses-vue-app'); -}); diff --git a/resources/assets/javascripts/bootstrap/vue.js b/resources/assets/javascripts/bootstrap/vue.js index b3341d06da0..2c64b1095f6 100644 --- a/resources/assets/javascripts/bootstrap/vue.js +++ b/resources/assets/javascripts/bootstrap/vue.js @@ -6,21 +6,14 @@ * Eg. ./components/ExampleComponent.vue -> <example-component></example-component> */ STUDIP.ready(() => { - $('[data-vue-app]').each(function () { - if ($(this).is('[data-vue-app-created]')) { - return; - } - - const config = Object.assign({}, { - id: false, - components: [], - store: false - }, $(this).data().vueApp); - - let data = {}; - if (config.id && window.STUDIP.AppData && window.STUDIP.AppData[config.id] !== undefined) { - data = window.STUDIP.AppData[config.id]; - } + document.querySelectorAll('[data-vue-app]:not([data-vue-app-created])').forEach((node) => { + const config = Object.assign( + { + components: [], + stores: {} + }, + JSON.parse(node.dataset.vueApp) + ); let components = {}; config.components.forEach(component => { @@ -28,29 +21,51 @@ STUDIP.ready(() => { }); STUDIP.Vue.load().then(async ({createApp, store}) => { - let vm; - if (config.store) { - const storeConfig = await import(`../../../vue/store/${config.store}.js`); - console.log('store', storeConfig.default); + for (const [index, name] of Object.entries(config.stores)) { + import(`../../../vue/store/${name}.js`).then(storeConfig => { + store.registerModule(index, storeConfig.default); - store.registerModule(config.id, storeConfig.default); - - Object.keys(data).forEach(command => { - store.commit(`${config.id}/${command}`, data[command]); + const dataElement = document.getElementById(`vue-store-data-${index}`); + if (dataElement) { + const data = JSON.parse(dataElement.innerText); + Object.keys(data).forEach(command => { + store.commit(`${index}/${command}`, data[command]); + }); + } }); - vm = createApp({components}); - } else { - vm = createApp({data, components}); + } - // import myCoursesStore from '../stores/MyCoursesStore.js'; - // - // myCoursesStore.namespaced = true; - // - // store.registerModule('my-courses', myCoursesStore); + createApp({ + components, + store, - vm.$mount(this); + beforeCreate() { + STUDIP.Vue.emit('VueAppWillCreate', this); + }, + created() { + STUDIP.Vue.emit('VueAppDidCreate', this); + }, + beforeMount() { + STUDIP.Vue.emit('VueAppWillMount', this); + }, + mounted() { + STUDIP.Vue.emit('VueAppDidMount', this); + }, + beforeUpdate() { + STUDIP.Vue.emit('VueAppWillUpdate', this); + }, + updated() { + STUDIP.Vue.emit('VueAppDidUpdate', this); + }, + beforeDestroy() { + STUDIP.Vue.emit('VueAppWillDestroy', this); + }, + destroyed() { + STUDIP.Vue.emit('VueAppDidDestroy', this); + }, + }).$mount(node); }); - $(this).attr('data-vue-app-created', ''); + node.dataset.vueAppCreated = 'true'; }); }); diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js index 26834395e6d..0b10750cd13 100644 --- a/resources/assets/javascripts/entry-base.js +++ b/resources/assets/javascripts/entry-base.js @@ -16,8 +16,6 @@ import "./init.js" import "./bootstrap/responsive.js" import "./bootstrap/vue.js" -import "./bootstrap/my-courses.js"; - import "./studip-ui.js" import "./bootstrap/fullscreen.js" import "./bootstrap/tfa.js" diff --git a/resources/assets/javascripts/lib/studip-vue.js b/resources/assets/javascripts/lib/studip-vue.js index 395add9d141..e70b1c040c7 100644 --- a/resources/assets/javascripts/lib/studip-vue.js +++ b/resources/assets/javascripts/lib/studip-vue.js @@ -1,7 +1,5 @@ class Vue { - static #storeData = {}; - static async load() { return STUDIP.loadChunk('vue'); @@ -18,16 +16,6 @@ class Vue const { eventBus } = await this.load(); eventBus.emit(...args); } - - static setStoreData(key, data) - { - this.#storeData[key] = data; - } - - static getStoreData(key) - { - return this.#storeData[key] ?? null; - } } export default Vue; diff --git a/templates/vue-app.php b/templates/vue-app.php new file mode 100644 index 00000000000..ba2bbdccc3e --- /dev/null +++ b/templates/vue-app.php @@ -0,0 +1,10 @@ +<?php +/** + * @var array $attributes + * @var array $storeData + */ +?> +<? foreach ($storeData as $store => $data): ?> +<script type="application/json" id="vue-store-data-<?= htmlReady($store) ?>"><?= json_encode($data) ?></script> +<? endforeach; ?> +<div <?= arrayToHtmlAttributes($attributes) ?>></div> -- GitLab