From e3fc466217f43e3ceb0c18fa926334cf985f76de Mon Sep 17 00:00:00 2001 From: Laurent Muller Date: Fri, 1 Nov 2024 19:11:12 +0100 Subject: [PATCH] Reworked diagrams. --- composer.lock | 31 +-- public/js/compiled/diagram.js | 2 +- public/js/test/diagram.js | 260 +++++++++--------- public/vendor.json | 8 +- resources/diagrams/product.mmd | 1 + src/Controller/AbstractController.php | 8 +- src/Controller/CalculationController.php | 4 +- src/Controller/CalculationStateController.php | 4 +- src/Controller/CalendarController.php | 4 +- src/Controller/CategoryController.php | 4 +- src/Controller/CommandController.php | 2 +- src/Controller/CustomerController.php | 4 +- src/Controller/DiagramController.php | 50 +++- src/Controller/GlobalMarginController.php | 4 +- src/Controller/GroupController.php | 4 +- src/Controller/HelpController.php | 8 +- src/Controller/ProductController.php | 4 +- src/Controller/ResetPasswordController.php | 2 +- src/Controller/SchemaController.php | 2 +- src/Controller/TaskController.php | 4 +- src/Controller/UserController.php | 8 +- .../vertical/navigation_test.html.twig | 2 +- templates/test/diagram.html.twig | 8 +- .../CalculationBelowControllerTest.php | 11 + .../CalculationEmptyControllerTest.php | 16 +- tests/Controller/CalendarControllerTest.php | 27 ++ translations/messages.fr_CH.yaml | 2 +- 27 files changed, 274 insertions(+), 210 deletions(-) diff --git a/composer.lock b/composer.lock index 61aa42328..7b601da62 100644 --- a/composer.lock +++ b/composer.lock @@ -1150,16 +1150,16 @@ }, { "name": "doctrine/persistence", - "version": "3.3.3", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "b337726451f5d530df338fc7f68dee8781b49779" + "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/b337726451f5d530df338fc7f68dee8781b49779", - "reference": "b337726451f5d530df338fc7f68dee8781b49779", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/0ea965320cec355dba75031c1b23d4c78362e3ff", + "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff", "shasum": "" }, "require": { @@ -1173,12 +1173,11 @@ "require-dev": { "doctrine/coding-standard": "^12", "doctrine/common": "^3.0", - "phpstan/phpstan": "1.11.1", + "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.24.0" + "phpunit/phpunit": "^8.5.38 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -1227,7 +1226,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.3.3" + "source": "https://github.com/doctrine/persistence/tree/3.4.0" }, "funding": [ { @@ -1243,7 +1242,7 @@ "type": "tidelift" } ], - "time": "2024-06-20T10:14:30+00:00" + "time": "2024-10-30T19:48:12+00:00" }, { "name": "doctrine/sql-formatter", @@ -10180,16 +10179,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.10", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1" + "reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f7d5782044bedf93aeb3f38e09c91148ee90e5a1", - "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/270c2ee1478d1f8dc5121f539e890017bd64b04c", + "reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c", "shasum": "" }, "require": { @@ -10246,9 +10245,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.10" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.11" }, - "time": "2024-09-26T18:14:50+00:00" + "time": "2024-10-30T12:07:21+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/public/js/compiled/diagram.js b/public/js/compiled/diagram.js index e6950bbdd..4c139b293 100644 --- a/public/js/compiled/diagram.js +++ b/public/js/compiled/diagram.js @@ -9,7 +9,7 @@ * Vendor Files */ - + /** * Specific Files diff --git a/public/js/test/diagram.js b/public/js/test/diagram.js index bdecdfe5e..e18a40dcd 100644 --- a/public/js/test/diagram.js +++ b/public/js/test/diagram.js @@ -1,49 +1,29 @@ /**! compression tag for ftp-deployment */ -/* globals Toaster, mermaid, svgPanZoom */ - -/** - * @typedef {Object} ShadowViewport - * @property {function} destroy - */ - -/** - * @typedef {Object} SvgPoint - * @property {number} x - * @property {number} y - */ - -/** - * @typedef {Object} SvgViewBox - * @property {number} x - * @property {number} y - * @property {number} width - * @property {number} height - */ - -/** - * @typedef {Object} SvgSizes - * @property {number} width - * @property {number} height - * @property {number} realZoom - * @property {SvgViewBox} viewBox - */ +/* globals Toaster, mermaid, Panzoom */ (() => { 'use strict'; const THEME_DARK = 'dark'; const THEME_ATTRIBUTE = 'data-bs-theme'; + const DATA_CODE = 'data-code'; const DATA_PROCESSED = 'data-processed'; + const DIAGRAM_SELECTOR = '#diagram'; + const SVG_SELECTOR = '#diagram svg'; + const CLASS_REGEX = /classId-(.*)-\d+/; const REPLACE_REGEX = /([a-z])([A-Z])/g; const REPLACE_TARGET = '$1_$2'; + const MIN_SCALE = 0.5; + const MAX_SCALE = 3.0; + /** * The diagram renderer. - * @var {jQuery} + * @var {jQuery} */ const $diagram = $(DIAGRAM_SELECTOR); @@ -56,16 +36,51 @@ // the HTML document element const targetNode = document.documentElement; - // returns if the dark theme is selected - const isDarkTheme = () => targetNode.getAttribute(THEME_ATTRIBUTE) === THEME_DARK; - - // buttons + /** + * The zoom-in button + */ const $zoomIn = $('.btn-zoom-in'); + + /** + * The zoom-out button. + */ const $zoomOut = $('.btn-zoom-out'); - const $center = $('.btn-center'); + + /** + * The reset button. + */ + const $reset = $('.btn-reset'); + + /** + * The zoom label. + */ const $zoom = $('#zoom'); - /** @type {ShadowViewport|null} */ - let panZoom = null; + + /** + * The SVG pan zoom. + */ + let panzoom = null; + + /** + * Gets SVG element. + * @return {SVGSVGElement} + */ + const getSvgDiagram = () => document.querySelector(SVG_SELECTOR); + + /** + * Returns if the dark theme is selected. + * @return {boolean} + */ + const isDarkTheme = () => targetNode.getAttribute(THEME_ATTRIBUTE) === THEME_DARK; + + /** + * The number to format zoom. + */ + const zoomFormatter = new Intl.NumberFormat('default', { + style: 'percent', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }); /** * Show an error message. @@ -94,11 +109,15 @@ }); } if (!found) { - showError(`Unable to find a corresponding diagram for the node "${nodeId}".`); + const message = $diagram.data('error').replace('%name%', nodeId); + showError(message); } }; - // Gets the color variables, depending on the selected theme. + /** + * Gets the color variables, depending on the selected theme. + * @return {object} + */ const getThemeVariables = () => { if (isDarkTheme()) { return { @@ -121,119 +140,83 @@ }; /** - * @param {number} value + * Handle the pan zoom change event. */ - const formatZoom = (value) => { - const text = new Intl.NumberFormat('default', { - style: 'percent', - minimumFractionDigits: 0, - maximumFractionDigits: 0 - }).format(value); - $zoom.text(text); + const changeHandler = function (event) { + const scale = event.detail.scale; + const initial = scale === 1 && event.detail.x === 0 && event.detail.y === 0; + $zoomOut.attr('disabled', scale <= MIN_SCALE ? 'disabled' : null); + $zoomIn.attr('disabled', scale >= MAX_SCALE ? 'disabled' : null); + $reset.attr('disabled', initial ? 'disabled' : null); + $zoom.text(zoomFormatter.format(scale)); }; - /** @return {SVGSVGElement} */ - const getDiagramSVG = () => $diagram.find('svg:first')[0]; /** - * @param {ShadowViewport} [panZoom] - * @return {null} + * Destroy the SVG pan zoom. */ - const destroySvgPanZoom = (panZoom) => { - if (panZoom) { - $center.off('click'); - $zoomIn.off('click'); + const destroyPanzoom = (panzoom) => { + if (panzoom) { + const svgDiagram = getSvgDiagram(); + svgDiagram.parentElement.removeEventListener('wheel', panzoom.zoomWithWheel); + svgDiagram.removeEventListener('panzoomchange', changeHandler) $zoomOut.off('click'); - panZoom.destroy(); + $zoomIn.off('click'); + $reset.off('click'); + panzoom.destroy(); } return null; - }; - - - /** @param {number} zoom */ - const zoomHandler = function (zoom) { - // const svg = getDiagramSVG(); - // const sizes = this.getSizes(); - // if (zoom > 1.0) { - // svg.style.height = String(sizes.height * zoom); - // } else { - // svg.style.height = String(sizes.height); - // } - formatZoom(zoom); - }; + } /** - * @param {SvgPoint} oldPane - * @param {SvgPoint} newPan - * @return {SvgPoint|null} + * Create the SVG pan zoom. */ - const beforePanHandler = function (oldPane, newPan) { - const margin = 100; - const svg = getDiagramSVG(); - /** @type {SvgSizes} */ - const sizes = this.getSizes(); - const width = Math.max(sizes.width, svg.clientWidth); - const height = Math.max(sizes.height, svg.clientHeight); - const left = -((sizes.viewBox.x + sizes.viewBox.width) * sizes.realZoom) + margin; - const right = width - margin - (sizes.viewBox.x * sizes.realZoom); - const top = -((sizes.viewBox.y + height) * sizes.realZoom) + margin; - const bottom = height - margin - (sizes.viewBox.y * sizes.realZoom); + const createPanZoom = () => { + // initialize + const svgDiagram = getSvgDiagram(); + const panzoom = Panzoom(svgDiagram, { + minScale: MIN_SCALE, maxScale: MAX_SCALE, + }); - return { - x: Math.max(left, Math.min(right, newPan.x)), - y: Math.max(top, Math.min(bottom, newPan.y)) - }; + // set handlers + $reset.on('click', () => panzoom.reset()); + $zoomIn.on('click', () => panzoom.zoomIn()); + $zoomOut.on('click', () => panzoom.zoomOut()); + svgDiagram.addEventListener('panzoomchange', changeHandler) + svgDiagram.parentElement.addEventListener('wheel', panzoom.zoomWithWheel); + + return panzoom; }; /** - * @return {ShadowViewport} + * load the diagram. */ - const createSvgPanZoom = () => { - const svg = getDiagramSVG(); - const panZoom = svgPanZoom(svg, { - onZoom: zoomHandler, beforePan: beforePanHandler, - }); - /** @type {SvgSizes} */ - const sizes = panZoom.getSizes(); - formatZoom(sizes.realZoom); - //svg.style.height = String(sizes.height); - svg.style.height = '100%'; - svg.style.maxWidth = '100%'; - svg.style.width = '100%'; - - $zoomOut.on('click', () => panZoom.zoomOut()); - $zoomIn.on('click', () => panZoom.zoomIn()); - $center.on('click', () => { - panZoom.reset(); - panZoom.center(); - }); - - return panZoom; - }; - - // load the diagram. const loadDiagram = () => { mermaid.initialize({ theme: 'base', - useMaxWidth: false, startOnLoad: false, + useMaxWidth: false, securityLevel: 'loose', themeVariables: getThemeVariables() }); mermaid.run({ - nodes: [$diagram[0]], + querySelector: DIAGRAM_SELECTOR, }).then(() => { - panZoom = destroySvgPanZoom(panZoom); - panZoom = createSvgPanZoom(); + panzoom = destroyPanzoom(panzoom); + panzoom = createPanZoom(); }); }; - // Save the HTML content of the diagram. + /** + * Save the HTML content of the diagram. + */ const saveDiagram = () => { $diagram.attr(DATA_CODE, $diagram.html()); }; - // Reset processed diagram. + /** + * Reset processed diagram. + */ const resetDiagram = () => { const code = $diagram.attr(DATA_CODE); if (code) { @@ -242,7 +225,26 @@ } }; - // Handle diagrams change selection. + /** + * Add a listener for the theme attribute. + */ + const addThemeObserver = () => { + const mutationCallback = (mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.attributeName === THEME_ATTRIBUTE) { + resetDiagram(); + loadDiagram(); + break; + } + } + }; + const observer = new MutationObserver(mutationCallback); + observer.observe(targetNode, {attributes: true}); + }; + + /** + * Handle diagrams change selection. + */ $diagrams.on('change', function () { const url = $('#diagram').data('url'); if (!url) { @@ -250,7 +252,7 @@ return; } const data = { - 'name': $(this).val() + 'name': $diagrams.val() }; $.getJSON(url, data, function (response) { // error? @@ -272,7 +274,9 @@ }); }).trigger('focus'); - // Handle history pop state + /** + * Handle history pop state. + */ window.addEventListener('popstate', (e) => { if (e.state && e.state.name) { const name = e.state.name; @@ -284,16 +288,6 @@ saveDiagram(); loadDiagram(); - // add a listener to the theme attribute - const mutationCallback = (mutationsList) => { - for (const mutation of mutationsList) { - if (mutation.attributeName !== THEME_ATTRIBUTE) { - return; - } - resetDiagram(); - loadDiagram(); - } - }; - const observer = new MutationObserver(mutationCallback); - observer.observe(targetNode, {attributes: true}); + // add a listener for the theme attribute + addThemeObserver(); })(); diff --git a/public/vendor.json b/public/vendor.json index 6bb221367..f3e283d85 100644 --- a/public/vendor.json +++ b/public/vendor.json @@ -218,20 +218,20 @@ }, { "name": "mermaid", - "version": "11.3.0", + "version": "11.4.0", "source": "cdnjs", "files": [ "mermaid.js" ] }, { - "name": "svg-pan-zoom", - "version": "3.6.2", + "name": "@panzoom/panzoom", + "version": "4.5.1", "source": "jsdelivr", "prefix": "dist/", "target": "mermaid", "files": [ - "dist/svg-pan-zoom.js" + "dist/panzoom.js" ] } ] diff --git a/resources/diagrams/product.mmd b/resources/diagrams/product.mmd index 7584053b3..e468552d8 100644 --- a/resources/diagrams/product.mmd +++ b/resources/diagrams/product.mmd @@ -20,6 +20,7 @@ classDiagram class AbstractEntity { <> } + class EntityInterface { <> } diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index a2c1c2d35..de3183202 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -258,7 +258,7 @@ protected function createFormHelper(?string $labelPrefix = null, mixed $data = n * @param ?\Throwable $previous the previous throwable used for * the exception chaining */ - protected function createTranslateNotFoundException( + protected function createTranslatedNotFoundException( string|\Stringable|TranslatableInterface $id, array $parameters = [], ?\Throwable $previous = null, @@ -384,7 +384,7 @@ protected function renderFormException(string $id, \Throwable $e, ?LoggerInterfa protected function renderPdfDocument(PdfDocument $doc, bool $inline = true, string $name = ''): PdfResponse { if ($doc instanceof AbstractReport && !$doc->render()) { - throw $this->createTranslateNotFoundException('errors.render_document'); + throw $this->createTranslatedNotFoundException('errors.render_document'); } if (!StringUtils::isString($name) && StringUtils::isString($doc->getTitle())) { $name = $doc->getTitle(); @@ -411,7 +411,7 @@ protected function renderSpreadsheetDocument( string $name = '' ): SpreadsheetResponse { if ($doc instanceof AbstractDocument && !$doc->render()) { - throw $this->createTranslateNotFoundException('errors.render_document'); + throw $this->createTranslatedNotFoundException('errors.render_document'); } if (!StringUtils::isString($name) && StringUtils::isString($doc->getTitle())) { /** @psalm-var string $name */ @@ -436,7 +436,7 @@ protected function renderSpreadsheetDocument( protected function renderWordDocument(WordDocument $doc, bool $inline = true, string $name = ''): WordResponse { if ($doc instanceof AbstractWordDocument && !$doc->render()) { - throw $this->createTranslateNotFoundException('errors.render_document'); + throw $this->createTranslatedNotFoundException('errors.render_document'); } if (!StringUtils::isString($name) && StringUtils::isString($doc->getTitle())) { /** @psalm-var string $name */ diff --git a/src/Controller/CalculationController.php b/src/Controller/CalculationController.php index bf4894f3b..fbcec8df1 100644 --- a/src/Controller/CalculationController.php +++ b/src/Controller/CalculationController.php @@ -132,7 +132,7 @@ public function excel(): SpreadsheetResponse { $entities = $this->getEntities(['id' => SortModeInterface::SORT_DESC]); if ([] === $entities) { - throw $this->createTranslateNotFoundException('calculation.list.empty'); + throw $this->createTranslatedNotFoundException('calculation.list.empty'); } $doc = new CalculationsDocument($this, $entities); @@ -180,7 +180,7 @@ public function pdf(): PdfResponse 'id' => SortModeInterface::SORT_DESC, ]); if ([] === $entities) { - throw $this->createTranslateNotFoundException('calculation.list.empty'); + throw $this->createTranslatedNotFoundException('calculation.list.empty'); } $doc = new CalculationsReport($this, $entities); diff --git a/src/Controller/CalculationStateController.php b/src/Controller/CalculationStateController.php index ec5f244cd..fc2cc7488 100644 --- a/src/Controller/CalculationStateController.php +++ b/src/Controller/CalculationStateController.php @@ -129,7 +129,7 @@ public function excel(): SpreadsheetResponse { $entities = $this->getEntities('code'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('calculationstate.list.empty'); + throw $this->createTranslatedNotFoundException('calculationstate.list.empty'); } $doc = new CalculationStatesDocument($this, $entities); @@ -160,7 +160,7 @@ public function pdf(): PdfResponse { $entities = $this->getEntities('code'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('calculationstate.list.empty'); + throw $this->createTranslatedNotFoundException('calculationstate.list.empty'); } $doc = new CalculationStatesReport($this, $entities); diff --git a/src/Controller/CalendarController.php b/src/Controller/CalendarController.php index d61b28cfb..3bec52590 100644 --- a/src/Controller/CalendarController.php +++ b/src/Controller/CalendarController.php @@ -380,7 +380,7 @@ private function validateMonth(?int $month = null): int { $month = (int) ($month ?? \date('n')); if ($month < 1 || $month > 12) { - throw $this->createTranslateNotFoundException('calendar.invalid_month'); + throw $this->createTranslatedNotFoundException('calendar.invalid_month'); } return $month; @@ -399,7 +399,7 @@ private function validateWeek(?int $week = null): int { $week = (int) ($week ?? \date('W')); if ($week < 1 || $week > 53) { - throw $this->createTranslateNotFoundException('calendar.invalid_week'); + throw $this->createTranslatedNotFoundException('calendar.invalid_week'); } return $week; diff --git a/src/Controller/CategoryController.php b/src/Controller/CategoryController.php index db5205549..25d296a78 100644 --- a/src/Controller/CategoryController.php +++ b/src/Controller/CategoryController.php @@ -144,7 +144,7 @@ public function excel(): SpreadsheetResponse { $entities = $this->getEntities('code'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('category.list.empty'); + throw $this->createTranslatedNotFoundException('category.list.empty'); } $doc = new CategoriesDocument($this, $entities); @@ -175,7 +175,7 @@ public function pdf(): PdfResponse { $entities = $this->getEntities('code'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('category.list.empty'); + throw $this->createTranslatedNotFoundException('category.list.empty'); } $doc = new CategoriesReport($this, $entities); diff --git a/src/Controller/CommandController.php b/src/Controller/CommandController.php index 8ab4ba887..03dfe642f 100644 --- a/src/Controller/CommandController.php +++ b/src/Controller/CommandController.php @@ -111,7 +111,7 @@ public function execute( CommandDataService $dataService, ): Response { if (!$service->hasCommand($name)) { - throw $this->createTranslateNotFoundException('command.list.error', ['%name%' => $name]); + throw $this->createTranslatedNotFoundException('command.list.error', ['%name%' => $name]); } /** @psalm-var CommandType $command */ diff --git a/src/Controller/CustomerController.php b/src/Controller/CustomerController.php index 0621dce7f..b2216bf57 100644 --- a/src/Controller/CustomerController.php +++ b/src/Controller/CustomerController.php @@ -88,7 +88,7 @@ public function excel(CustomerRepository $repository): SpreadsheetResponse { $entities = $repository->findByNameAndCompany(); if ([] === $entities) { - throw $this->createTranslateNotFoundException('customer.list.empty'); + throw $this->createTranslatedNotFoundException('customer.list.empty'); } $doc = new CustomersDocument($this, $entities); @@ -118,7 +118,7 @@ public function pdf(Request $request, CustomerRepository $repository): PdfRespon { $entities = $repository->findByNameAndCompany(); if ([] === $entities) { - throw $this->createTranslateNotFoundException('customer.list.empty'); + throw $this->createTranslatedNotFoundException('customer.list.empty'); } $grouped = $this->getRequestBoolean($request, 'grouped', true); $report = new CustomersReport($this, $entities, $grouped); diff --git a/src/Controller/DiagramController.php b/src/Controller/DiagramController.php index 33d12b9de..a25d009f6 100644 --- a/src/Controller/DiagramController.php +++ b/src/Controller/DiagramController.php @@ -16,9 +16,9 @@ use App\Interfaces\RoleInterface; use App\Service\DiagramService; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; -use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -33,20 +33,21 @@ class DiagramController extends AbstractController { private const DEFAULT_DIAGRAM = 'entity_interface'; + private const KEY_DIAGRAM = 'last_diagram'; public function __construct(private readonly DiagramService $service) { } /** - * Display a diagram. + * Index of the diagram view. */ #[Get(path: '', name: 'index')] - public function index(#[MapQueryParameter] string $name = self::DEFAULT_DIAGRAM): Response + public function index(Request $request): Response { - $file = $this->getFile($name); - if (null === $file) { - throw $this->createTranslateNotFoundException('diagram.error_not_found', ['%name%' => $name]); + $file = $this->findFile($request); + if (\is_string($file)) { + throw $this->createNotFoundException($file); } return $this->render('test/diagram.html.twig', [ @@ -59,26 +60,45 @@ public function index(#[MapQueryParameter] string $name = self::DEFAULT_DIAGRAM) * Load a diagram. */ #[Get(path: '/load', name: 'load')] - public function load(#[MapQueryParameter] string $name = self::DEFAULT_DIAGRAM): JsonResponse + public function load(Request $request): JsonResponse { - $file = $this->getFile($name); - if (null === $file) { - return $this->jsonFalse(['message' => $this->trans('diagram.error_not_found', ['%name%' => $name])]); + $file = $this->findFile($request); + if (\is_string($file)) { + return $this->jsonFalse(['message' => $file]); } return $this->jsonTrue(['file' => $file]); } - private function getFile(string $name): ?array + private function findFile(Request $request): array|string { - return $this->service->getFile($name); + $name = $this->getQueryName($request); + $file = $this->service->getFile($name); + if (null === $file) { + return $this->trans('diagram.error_not_found', ['%name%' => $name]); + } + $this->setDiagramSession($request, $name); + + return $file; + } + + private function getDiagramSession(Request $request): string + { + return (string) $request->getSession()->get(self::KEY_DIAGRAM, self::DEFAULT_DIAGRAM); } - /** - * @return array - */ private function getFiles(): array { return \array_column($this->service->getFiles(), 'title', 'name'); } + + private function getQueryName(Request $request): string + { + return $request->query->getString('name', $this->getDiagramSession($request)); + } + + private function setDiagramSession(Request $request, string $name): void + { + $request->getSession()->set(self::KEY_DIAGRAM, $name); + } } diff --git a/src/Controller/GlobalMarginController.php b/src/Controller/GlobalMarginController.php index ff77f9be4..7154fbd96 100644 --- a/src/Controller/GlobalMarginController.php +++ b/src/Controller/GlobalMarginController.php @@ -90,7 +90,7 @@ public function excel(): SpreadsheetResponse { $entities = $this->getEntities('minimum'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('globalmargin.list.empty'); + throw $this->createTranslatedNotFoundException('globalmargin.list.empty'); } $doc = new GlobalMarginsDocument($this, $entities); @@ -121,7 +121,7 @@ public function pdf(): PdfResponse { $entities = $this->getEntities('minimum'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('globalmargin.list.empty'); + throw $this->createTranslatedNotFoundException('globalmargin.list.empty'); } $report = new GlobalMarginsReport($this, $entities); diff --git a/src/Controller/GroupController.php b/src/Controller/GroupController.php index 7f60ba7b1..8cf61be45 100644 --- a/src/Controller/GroupController.php +++ b/src/Controller/GroupController.php @@ -137,7 +137,7 @@ public function excel(): SpreadsheetResponse { $entities = $this->getEntities('code'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('group.list.empty'); + throw $this->createTranslatedNotFoundException('group.list.empty'); } $doc = new GroupsDocument($this, $entities); @@ -168,7 +168,7 @@ public function pdf(): PdfResponse { $entities = $this->getEntities('code'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('group.list.empty'); + throw $this->createTranslatedNotFoundException('group.list.empty'); } $doc = new GroupsReport($this, $entities); diff --git a/src/Controller/HelpController.php b/src/Controller/HelpController.php index 4ffa8b381..08e29955c 100644 --- a/src/Controller/HelpController.php +++ b/src/Controller/HelpController.php @@ -52,7 +52,7 @@ public function dialog(string $id): Response { $dialog = $this->service->findDialog($id); if (null === $dialog) { - throw $this->createTranslateNotFoundException('help.errors.page_not_found', ['%id%' => $id]); + throw $this->createTranslatedNotFoundException('help.errors.page_not_found', ['%id%' => $id]); } $entity = $this->service->findEntity($dialog); @@ -71,7 +71,7 @@ public function dialogs(): Response { $dialogs = $this->service->getDialogs(); if ([] === $dialogs) { - throw $this->createTranslateNotFoundException('help.errors.dialogs_not_found'); + throw $this->createTranslatedNotFoundException('help.errors.dialogs_not_found'); } $this->service->sortByName($dialogs); @@ -120,7 +120,7 @@ public function entities(): Response { $entities = $this->service->getEntities(); if ([] === $entities) { - throw $this->createTranslateNotFoundException('help.errors.entities_not_found'); + throw $this->createTranslatedNotFoundException('help.errors.entities_not_found'); } $this->service->sortByName($entities); @@ -138,7 +138,7 @@ public function entity(string $id): Response { $entity = $this->service->findEntity($id); if (null === $entity) { - throw $this->createTranslateNotFoundException('help.errors.page_not_found', ['%id%' => $id]); + throw $this->createTranslatedNotFoundException('help.errors.page_not_found', ['%id%' => $id]); } return $this->render('help/help_entity.html.twig', [ diff --git a/src/Controller/ProductController.php b/src/Controller/ProductController.php index e70cccfef..807a03090 100644 --- a/src/Controller/ProductController.php +++ b/src/Controller/ProductController.php @@ -110,7 +110,7 @@ public function excel(ProductRepository $repository): SpreadsheetResponse { $entities = $repository->findByDescription(); if ([] === $entities) { - throw $this->createTranslateNotFoundException('product.list.empty'); + throw $this->createTranslatedNotFoundException('product.list.empty'); } $doc = new ProductsDocument($this, $entities); @@ -140,7 +140,7 @@ public function pdf(ProductRepository $repository): PdfResponse { $entities = $repository->findByGroup(); if ([] === $entities) { - throw $this->createTranslateNotFoundException('product.list.empty'); + throw $this->createTranslatedNotFoundException('product.list.empty'); } $doc = new ProductsReport($this, $entities); diff --git a/src/Controller/ResetPasswordController.php b/src/Controller/ResetPasswordController.php index 2fdbb5424..e9ffc133d 100644 --- a/src/Controller/ResetPasswordController.php +++ b/src/Controller/ResetPasswordController.php @@ -104,7 +104,7 @@ public function reset(Request $request, Security $security, ?string $token = nul } $token = $this->getTokenFromSession(); if (null === $token) { - throw $this->createTranslateNotFoundException('reset_not_found_password_token', domain: 'security'); + throw $this->createTranslatedNotFoundException('reset_not_found_password_token', domain: 'security'); } try { diff --git a/src/Controller/SchemaController.php b/src/Controller/SchemaController.php index 04beb7b8b..fd1937358 100644 --- a/src/Controller/SchemaController.php +++ b/src/Controller/SchemaController.php @@ -69,7 +69,7 @@ public function table(string $name, SchemaService $service): Response return $this->redirectToRoute('schema_index'); } catch (\Doctrine\DBAL\Exception $e) { - throw $this->createTranslateNotFoundException('schema.table.error', ['%name%' => $name], $e); + throw $this->createTranslatedNotFoundException('schema.table.error', ['%name%' => $name], $e); } } } diff --git a/src/Controller/TaskController.php b/src/Controller/TaskController.php index caacb4bef..7b1f0375f 100644 --- a/src/Controller/TaskController.php +++ b/src/Controller/TaskController.php @@ -141,7 +141,7 @@ public function excel(): SpreadsheetResponse { $entities = $this->getEntities('name'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('task.list.empty'); + throw $this->createTranslatedNotFoundException('task.list.empty'); } $doc = new TasksDocument($this, $entities); @@ -172,7 +172,7 @@ public function pdf(): PdfResponse { $entities = $this->getEntities('name'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('task.list.empty'); + throw $this->createTranslatedNotFoundException('task.list.empty'); } $doc = new TasksReport($this, $entities); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index c21bbf51b..af57b1c5c 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -115,7 +115,7 @@ public function excel(StorageInterface $storage): SpreadsheetResponse { $entities = $this->getEntities('username'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('user.list.empty'); + throw $this->createTranslatedNotFoundException('user.list.empty'); } $doc = new UsersDocument($this, $entities, $storage); @@ -204,7 +204,7 @@ public function pdf(StorageInterface $storage): PdfResponse { $entities = $this->getEntities('username'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('user.list.empty'); + throw $this->createTranslatedNotFoundException('user.list.empty'); } $doc = new UsersReport($this, $entities, $storage); @@ -329,7 +329,7 @@ public function rightsExcel(RoleBuilderService $builder): SpreadsheetResponse { $entities = $this->getEntities('username'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('user.list.empty'); + throw $this->createTranslatedNotFoundException('user.list.empty'); } $doc = new UserRightsDocument($this, $entities, $builder); @@ -347,7 +347,7 @@ public function rightsPdf(RoleBuilderService $builder): PdfResponse { $entities = $this->getEntities('username'); if ([] === $entities) { - throw $this->createTranslateNotFoundException('user.list.empty'); + throw $this->createTranslatedNotFoundException('user.list.empty'); } $doc = new UsersRightsReport($this, $entities, $builder); diff --git a/templates/navigation/vertical/navigation_test.html.twig b/templates/navigation/vertical/navigation_test.html.twig index 1c8de5c5b..b145c661e 100644 --- a/templates/navigation/vertical/navigation_test.html.twig +++ b/templates/navigation/vertical/navigation_test.html.twig @@ -62,6 +62,7 @@
diff --git a/templates/test/diagram.html.twig b/templates/test/diagram.html.twig index 4b35cef9b..063ec7fd4 100644 --- a/templates/test/diagram.html.twig +++ b/templates/test/diagram.html.twig @@ -19,11 +19,11 @@ {% endfor %} - 100 % + 100 % -