diff --git a/assets/backoffice.js b/assets/backoffice.js index 5af73cd..6057deb 100644 --- a/assets/backoffice.js +++ b/assets/backoffice.js @@ -1,6 +1,8 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap-icons/font/bootstrap-icons.min.css'; import './styles/backoffice.scss'; +import {Turbo} from '@hotwired/turbo'; +Turbo.session.drive = false; import './stimulus.js'; import './bootstrap.js'; import * as Sentry from '@sentry/browser'; diff --git a/assets/controllers/bo/modal_controller.js b/assets/controllers/bo/modal_controller.js new file mode 100644 index 0000000..75a10d8 --- /dev/null +++ b/assets/controllers/bo/modal_controller.js @@ -0,0 +1,48 @@ +import {Controller} from '@hotwired/stimulus'; +import {Modal} from 'bootstrap'; + +export default class extends Controller { + static targets = ['modal', 'frame']; + + open(event) { + event.preventDefault(); + const {src, modalTitle} = event.currentTarget.dataset; + if (modalTitle) { + const titleEl = this.modalTarget.querySelector('.modal-title'); + if (titleEl) titleEl.textContent = modalTitle; + } + this._resetDirty(); + this.frameTarget.innerHTML = ''; + this.frameTarget.removeAttribute('src'); + this.frameTarget.setAttribute('src', src); + Modal.getOrCreateInstance(this.modalTarget).show(); + } + + frameLoad() { + this._bindDirty(); + } + + frameSubmitEnd(event) { + if (event.detail.success) { + Modal.getOrCreateInstance(this.modalTarget).hide(); + window.location.reload(); + } + } + + _bindDirty() { + const modal = Modal.getOrCreateInstance(this.modalTarget); + const markDirty = () => { + modal._config.backdrop = 'static'; + modal._config.keyboard = false; + }; + this.frameTarget.addEventListener('input', markDirty, {once: true}); + this.frameTarget.addEventListener('change', markDirty, {once: true}); + this.modalTarget.addEventListener('hidden.bs.modal', () => this._resetDirty(), {once: true}); + } + + _resetDirty() { + const modal = Modal.getOrCreateInstance(this.modalTarget); + modal._config.backdrop = true; + modal._config.keyboard = true; + } +} diff --git a/assets/controllers/bo/modal_form_controller.js b/assets/controllers/bo/modal_form_controller.js deleted file mode 100644 index 20d70e7..0000000 --- a/assets/controllers/bo/modal_form_controller.js +++ /dev/null @@ -1,83 +0,0 @@ -import {Controller} from '@hotwired/stimulus'; -import {Modal} from 'bootstrap'; - -export default class extends Controller { - static targets = ['modal', 'modalBody', 'modalFooter']; - - async open(event) { - event.preventDefault(); - const url = event.currentTarget.dataset.url; - const title = event.currentTarget.dataset.modalTitle; - if (title) { - const titleEl = this.modalTarget.querySelector('.modal-title'); - if (titleEl) titleEl.textContent = title; - } - const modal = Modal.getOrCreateInstance(this.modalTarget); - this.modalBodyTarget.innerHTML = '
'; - if (this.hasModalFooterTarget) this.modalFooterTarget.innerHTML = ''; - modal.show(); - await this._loadForm(url); - } - - async _loadForm(url) { - const res = await fetch(url, {headers: {'X-Modal-Request': '1'}}); - this.modalBodyTarget.innerHTML = await res.text(); - this._moveFooter(); - this._bindDirty(); - this._bindForm(url); - } - - _bindDirty() { - const modal = Modal.getOrCreateInstance(this.modalTarget); - modal._config.backdrop = true; - modal._config.keyboard = true; - - const markDirty = () => { - modal._config.backdrop = 'static'; - modal._config.keyboard = false; - }; - - this.modalBodyTarget.addEventListener('input', markDirty, {once: true}); - this.modalBodyTarget.addEventListener('change', markDirty, {once: true}); - - this.modalTarget.addEventListener('hidden.bs.modal', () => { - modal._config.backdrop = true; - modal._config.keyboard = true; - }, {once: true}); - } - - _moveFooter() { - if (!this.hasModalFooterTarget) return; - const template = this.modalBodyTarget.querySelector('template[data-modal-footer]'); - this.modalFooterTarget.innerHTML = template ? template.innerHTML : ''; - if (template) template.remove(); - } - - _bindForm(url) { - const form = this.modalBodyTarget.querySelector('form'); - if (!form) return; - - if (this.hasModalFooterTarget) { - const saveBtn = this.modalFooterTarget.querySelector('[type="submit"]'); - saveBtn?.addEventListener('click', () => form.requestSubmit(), {once: true}); - } - - form.addEventListener('submit', async (e) => { - e.preventDefault(); - const res = await fetch(url, { - method: 'POST', - headers: {'X-Modal-Request': '1'}, - body: new FormData(form), - }); - if (res.status === 204) { - Modal.getOrCreateInstance(this.modalTarget).hide(); - window.location.reload(); - } else { - this.modalBodyTarget.innerHTML = await res.text(); - this._moveFooter(); - this._bindDirty(); - this._bindForm(url); - } - }, {once: true}); - } -} diff --git a/assets/styles/backoffice.scss b/assets/styles/backoffice.scss index 4ab8c9f..cb69351 100644 --- a/assets/styles/backoffice.scss +++ b/assets/styles/backoffice.scss @@ -1,3 +1,5 @@ .col-result-xs { width: 10%; } .col-result-sm { width: 15%; } .col-result-md { width: 20%; } + +.modal-content > turbo-frame { display: contents; } diff --git a/src/Controller/Backoffice/QuestionBankController.php b/src/Controller/Backoffice/QuestionBankController.php index 1aedc6d..56afa5a 100644 --- a/src/Controller/Backoffice/QuestionBankController.php +++ b/src/Controller/Backoffice/QuestionBankController.php @@ -88,7 +88,7 @@ class QuestionBankController extends AbstractController { $bankQuestion = new BankQuestion(); - $isModalRequest = $request->headers->has('X-Modal-Request'); + $isTurboFrame = $request->headers->has('Turbo-Frame'); $form = $this->createForm(BankQuestionFormType::class, $bankQuestion, ['season' => $season]); $form->handleRequest($request); @@ -101,22 +101,21 @@ class QuestionBankController extends AbstractController $this->addFlash(FlashType::Success, $this->translator->trans('Question added to the question bank')); - if ($isModalRequest) { - return new Response('', Response::HTTP_NO_CONTENT); + if ($isTurboFrame) { + return new Response(''); } return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]); } - $template = $isModalRequest - ? 'backoffice/question_bank/_form_body.html.twig' + $template = $isTurboFrame + ? 'backoffice/question_bank/_frame.html.twig' : 'backoffice/question_bank/form.html.twig'; $response = $this->render($template, [ 'season' => $season, 'form' => $form, 'bankQuestion' => null, - 'isModal' => $isModalRequest, ]); if ($form->isSubmitted()) { @@ -137,7 +136,7 @@ class QuestionBankController extends AbstractController { $this->assertSameSeason($season, $bankQuestion->season); - $isModalRequest = $request->headers->has('X-Modal-Request'); + $isTurboFrame = $request->headers->has('Turbo-Frame'); $form = $this->createForm(BankQuestionFormType::class, $bankQuestion, ['season' => $season]); $form->handleRequest($request); @@ -149,22 +148,21 @@ class QuestionBankController extends AbstractController $this->addFlash(FlashType::Success, $this->translator->trans('Question updated')); - if ($isModalRequest) { - return new Response('', Response::HTTP_NO_CONTENT); + if ($isTurboFrame) { + return new Response(''); } return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]); } - $template = $isModalRequest - ? 'backoffice/question_bank/_form_body.html.twig' + $template = $isTurboFrame + ? 'backoffice/question_bank/_frame.html.twig' : 'backoffice/question_bank/form.html.twig'; $response = $this->render($template, [ 'season' => $season, 'form' => $form, 'bankQuestion' => $bankQuestion, - 'isModal' => $isModalRequest, ]); if ($form->isSubmitted()) { diff --git a/src/Controller/Backoffice/QuizQuestionController.php b/src/Controller/Backoffice/QuizQuestionController.php index e55a37e..27d3669 100644 --- a/src/Controller/Backoffice/QuizQuestionController.php +++ b/src/Controller/Backoffice/QuizQuestionController.php @@ -44,7 +44,7 @@ class QuizQuestionController extends AbstractController throw new NotFoundHttpException(); } - $isModalRequest = $request->headers->has('X-Modal-Request'); + $isTurboFrame = $request->headers->has('Turbo-Frame'); $form = $this->createForm(QuestionFormType::class, $question); $form->handleRequest($request); @@ -55,8 +55,8 @@ class QuizQuestionController extends AbstractController $this->addFlash(FlashType::Success, $this->translator->trans('Question updated')); - if ($isModalRequest) { - return new Response('', Response::HTTP_NO_CONTENT); + if ($isTurboFrame) { + return new Response(''); } return $this->redirectToRoute('tvdt_backoffice_quiz_overview', [ @@ -65,8 +65,8 @@ class QuizQuestionController extends AbstractController ]); } - $template = $isModalRequest - ? 'backoffice/quiz/_question_form_body.html.twig' + $template = $isTurboFrame + ? 'backoffice/quiz/_question_frame.html.twig' : 'backoffice/quiz/question_form.html.twig'; $response = $this->render($template, [ @@ -74,7 +74,6 @@ class QuizQuestionController extends AbstractController 'quiz' => $quiz, 'question' => $question, 'form' => $form, - 'isModal' => $isModalRequest, ]); if ($form->isSubmitted()) { @@ -95,7 +94,7 @@ class QuizQuestionController extends AbstractController throw new NotFoundHttpException(); } - return $this->render('backoffice/quiz/_question_detail_body.html.twig', [ + return $this->render('backoffice/quiz/_question_detail_frame.html.twig', [ 'question' => $question, ]); } diff --git a/templates/backoffice/question_bank/_frame.html.twig b/templates/backoffice/question_bank/_frame.html.twig new file mode 100644 index 0000000..6ec44df --- /dev/null +++ b/templates/backoffice/question_bank/_frame.html.twig @@ -0,0 +1,34 @@ +{% import 'backoffice/partials/answer_row.html.twig' as macros %} + + + {{ form_start(form, {attr: {novalidate: 'novalidate'}}) }} + + + {{ form_end(form) }} + diff --git a/templates/backoffice/quiz/_question_detail_frame.html.twig b/templates/backoffice/quiz/_question_detail_frame.html.twig new file mode 100644 index 0000000..d941e10 --- /dev/null +++ b/templates/backoffice/quiz/_question_detail_frame.html.twig @@ -0,0 +1,23 @@ + + + + diff --git a/templates/backoffice/quiz/_question_frame.html.twig b/templates/backoffice/quiz/_question_frame.html.twig new file mode 100644 index 0000000..e0e0002 --- /dev/null +++ b/templates/backoffice/quiz/_question_frame.html.twig @@ -0,0 +1,32 @@ +{% import 'backoffice/partials/answer_row.html.twig' as macros %} + + + {{ form_start(form, {attr: {novalidate: 'novalidate'}}) }} + + + {{ form_end(form) }} + diff --git a/templates/backoffice/quiz/tab_overview.html.twig b/templates/backoffice/quiz/tab_overview.html.twig index b247281..885809f 100644 --- a/templates/backoffice/quiz/tab_overview.html.twig +++ b/templates/backoffice/quiz/tab_overview.html.twig @@ -85,7 +85,8 @@ -
{% if is_granted('QUIZ_MODIFY_CONTENT', question) %} {% elseif quiz.isLocked %} @@ -139,7 +140,7 @@
diff --git a/templates/backoffice/season/tab_question_bank.html.twig b/templates/backoffice/season/tab_question_bank.html.twig index c278c97..a715bc6 100644 --- a/templates/backoffice/season/tab_question_bank.html.twig +++ b/templates/backoffice/season/tab_question_bank.html.twig @@ -1,9 +1,10 @@
-
+
@@ -126,8 +127,8 @@ {% endif %}
- - +
diff --git a/templates/backoffice/season/tab_tests.html.twig b/templates/backoffice/season/tab_tests.html.twig index 49d650b..5f070d4 100644 --- a/templates/backoffice/season/tab_tests.html.twig +++ b/templates/backoffice/season/tab_tests.html.twig @@ -2,9 +2,9 @@
{% for quiz in season.quizzes %}