diff --git a/assets/app.js b/assets/app.js index 6fbe59d..4092a03 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,12 +1,18 @@ //region CSS import './styles/app.scss'; //endregion -//region Bootstrap -import 'bootstrap'; -//endregion //region jQuery import 'jqueryLocal'; // Declare $ as a global variable, accessible in all files //endregion +//region Bootstrap +import '@popperjs/core'; +import {Tooltip} from 'bootstrap'; +//region Tooltips +const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); +[...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl)); +//endregion +//endregion //region FontAwesome import 'fontawesome'; -//endregion \ No newline at end of file +//endregion +import 'modalDynamic'; \ No newline at end of file diff --git a/assets/controllers.json b/assets/controllers.json index bde96a5..29ea244 100644 --- a/assets/controllers.json +++ b/assets/controllers.json @@ -1,4 +1,15 @@ { - "controllers": {}, + "controllers": { + "@symfony/ux-turbo": { + "turbo-core": { + "enabled": true, + "fetch": "eager" + }, + "mercure-turbo-stream": { + "enabled": false, + "fetch": "eager" + } + } + }, "entrypoints": [] } diff --git a/assets/datatables.js b/assets/datatables.js deleted file mode 100644 index 9bfbf48..0000000 --- a/assets/datatables.js +++ /dev/null @@ -1,148 +0,0 @@ -//region Datatables.net -import 'datatables.net'; -import 'datatables.net-dt/css/dataTables.dataTables.min.css'; -//endregion -//region Bootstrap 5 styling for Datatables.net -import 'datatables.net-bs5'; -import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css'; -//endregion -//region Custom style for Datatables.net -import './styles/datatables.scss'; -//endregion -import 'jqueryLocal'; - -$(function () { - $('table.table-datatable').each(function () { - const self = $(this); - - //region Options du datatable - let options = { - paging: self.data('sortPaging', false), - pageLength: self.data('sortPerPage', 50), - processing: true, - layout: self.data( - 'sortLayout', - { - topStart: null, - topEnd: null, - bottomStart: null, - bottomEnd: 'paging', - }, - ), - }; - //endregion - //region Gestion tri initial - let initialSort = []; - $('> thead > tr > th[data-sort-onLoad]', this).each(function () { - let self = $(this); - initialSort.push( - { - index: self.index(), - priority: self.data('sortOnload'), - direction: self.data('sortOrder', 'asc').toLowerCase(), - }, - ); - }); - initialSort.sort(function (sort1, sort2) { - if (sort1.priority === sort2.priority) { - return sort1.index - sort2.index; - } - - return sort1.priority - sort2.priority; - }); - - if (initialSort.length > 0) { - options.order = []; - initialSort.forEach(function (sortOrder) { - options.order.push([sortOrder.index, sortOrder.direction]); - }); - } - //endregion - //region Gestion sens de tri (1er clic ou initial si concerné) - let descSort = []; - $('> thead > tr > th[data-sort-order]', this).each(function () { - const self = $(this); - if (self.data('sortOrder', 'asc').toLowerCase() === 'desc') { - descSort.push(self.index()); - } - }); - if (descSort.length > 0) { - if (!options.hasOwnProperty('columnDefs')) { - options.columnDefs = []; - } - options.columnDefs.push( - { - targets: descSort, - orderSequence: ['desc', 'asc'], - }, - ); - } - //endregion - //region Gestion désactivation tri - let disabledSort = []; - $('> thead > tr > th[data-sort]', this).each(function () { - const self = $(this); - if (!self.data('sort', true)) { - disabledSort.push(self.index()); - } - }); - if (disabledSort.length > 0) { - if (!options.hasOwnProperty('columnDefs')) { - options.columnDefs = []; - } - options.columnDefs.push( - { - targets: disabledSort, - orderable: false, - }, - ); - } - //endregion - //region Gestion nom des colonnes - const columnsName = $('> thead > tr > th[data-sort-name]', this); - if (columnsName.length > 0) { - if (!options.hasOwnProperty('columnDefs')) { - options.columnDefs = []; - } - - columnsName.each(function () { - const self = $(this); - const name = self.data('sortName', ''); - if (name !== '') { - options.columnDefs.push( - { - targets: self.index(), - name: name, - }, - ); - } - }); - } - //endregion - //region Gestion de AJAX - const ajaxUrl = self.data('sortAjax', false); - if (ajaxUrl !== false) { - options.serverSide = true; - options.ajax = { - url: ajaxUrl, - type: 'POST', - //createCDATASection: '', - cache: self.data('sortAjaxCache', false), - }; - - options.columns = []; - columnsName.each(function () { - const self = $(this); - const name = self.data('sortName', ''); - if (name !== '') { - options.columns[self.index()] = { - data: name, - }; - } - }); - } - //endregion - - self.DataTable(options); - }); -}); \ No newline at end of file diff --git a/assets/datatables2.js b/assets/datatables2.js new file mode 100644 index 0000000..8b407c3 --- /dev/null +++ b/assets/datatables2.js @@ -0,0 +1,329 @@ +//region Datatables.net +import 'datatables.net'; +import 'datatables.net-dt/css/dataTables.dataTables.min.css'; +//endregion +//region Bootstrap 5 styling for Datatables.net +import 'datatables.net-bs5'; +import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css'; +//endregion +//region Custom style for Datatables.net +import './styles/datatables2.scss'; +//endregion +import 'jqueryLocal'; +import Utils from 'utils'; + +/** + * Chargement automatique des éléments : tableaux, {@link https://datatables.net/ DataTables (v2)} + * + * Valide seulement si le JS "DataTable" est bien présent\ + * S'instancie automatiquement sur les <table class="table-datatable2"> + * + * Options : + * * table : + * * data-sort-paging : boolean\ + * Pagination gérée ?\ + * Défaut : faux + * * data-sort-per-page : integer\ + * Nombre d'éléments par défaut par page\ + * Défaut : 50 + * * data-sort-layout : string (JSON)\ + * Le JSON du modèle du tableau (https://datatables.net/reference/option/layout)\ + * Défaut : pagination (si active) en bas à droite + * * data-sort-ajax : string|false\ + * Faux si pas de requête AJAX géré\ + * Sinon l'URL AJAX appelée\ + * Bien penser à définir les "data-sort-name" sur les <th>\ + * Défaut : false + * * data-sort-ajax-type : string\ + * Le type de la requête AJAX (GET, POST, etc.)\ + * Défaut : POST + * * data-sort-ajax-cache : boolean\ + * Mise en cache du résultat des requêtes AJAX ?\ + * Défaut : faux + * * data-sort-lang : string (JSON)\ + * Le JSON des traductions + * * data-sort-lang-url : string (url)\ + * L'URL renvoyant le JSON des traductions\ + * Ignore si "data-sort-lang" renseigné + * * data-sort-lang-code : string\ + * Code langue (plugin, voir https://datatables.net/plug-ins/i18n/French.html#Browser-loading-/-CDN)\ + * Exemple avec "fr-FR" : chargera "//cdn.datatables.net/plug-ins/2.2.2/i18n/fr-FR.json"\ + * Ignoré si "data-sort-lang" ou "data-sort-lang-url" est renseigné + * * data-sort-child : string (HTML)|false\ + * Faux si pas de lignes enfants gérées\ + * Sinon le contenu HTML de la ligne enfant\ + * Ignoré si supplanté par la version sur les <tr>\ + * Défaut : false + * * data-sort-child-ajax : string (url)|false\ + * Faux si pas de lignes enfants gérées\ + * L'URL des requêtes AJAX pour les lignes enfants\ + * Ignoré si "data-sort-child" est renseigné ou supplanté par la version sur les <tr>\ + * Défaut : false + * * data-sort-child-ajax-type : string\ + * Le type de la requête AJAX pour les lignes enfants (GET, POST, etc.)\ + * Ignoré si supplanté par la version sur les <tr>\ + * Défaut : POST + * * data-sort-child-ajax-cache : boolean\ + * Mise en cache du résultat des requêtes AJAX pour les lignes enfants ?\ + * Ignoré si supplanté par la version sur les <tr>\ + * Défaut : faux + * * thead th : + * * data-sort-onLoad : integer\ + * Ordre de tri de la colonne pour le tri initial/au chargement + * * data-sort-order : string ("asc", "desc")\ + * Sens du tri de la colonne lors du 1er clic (et lors du tri initial/au chargement si "data-sort-onLoad" également renseigné) + * * data-sort : boolean\ + * Activer le tri sur la colonne ?\ + * Défaut : vrai + * * data-sort-name : string\ + * Le nom de la colonne dans la source de données (AJAX : cf. data-sort-ajax) + * * data-sort-name-value : boolean\ + * La colonne à une valeur de tri différente de son affichage ?\ + * Si vrai attends une paire display/sort dans le JSON (exemple : https://datatables.net/examples/ajax/orthogonal-data.html)\ + * Cf. SolulogSAS\BaseSite\Web\DataTables2_ColumnWithSortValue\ + * Défaut : faux + * * data-sort-child : string (HTML)|false\ + * Faux si pas de lignes enfants gérées\ + * Sinon le contenu HTML de la ligne enfant\ + * Défaut : false + * * data-sort-child-ajax : string (url)|false\ + * Faux si pas de lignes enfants gérées\ + * L'URL des requêtes AJAX pour les lignes enfants\ + * Ignoré si "data-sort-child" est renseigné\ + * Défaut : false + * * data-sort-child-ajax-id : any\ + * L'ID ajax de la ligne enfant (transmis dans "ajaxId")\ + * Défaut : {néant} + * * data-sort-child-ajax-type : string\ + * Le type de la requête AJAX pour les lignes enfants (GET, POST, etc.)\ + * Défaut : POST + * * data-sort-child-ajax-cache : boolean\ + * Mise en cache du résultat des requêtes AJAX pour les lignes enfants ?\ + * Défaut : faux + * * tbody td : + * * data-order : any\ + * La valeur utilisée pour le tri + * * data-filter : any\ + * La valeur utilisée pour le filtrage + * + * @param {Document|jQuery} page + */ +function datatables2 (page) { + $('table.table-datatable2', page).each(function () { + const self = $(this); + + //region Options du datatable + let options = { + paging: self.dataDefault('sortPaging', false), + pageLength: self.dataDefault('sortPerPage', 50), + processing: true, + layout: + self.dataDefault('sortLayout', + { + topStart: null, + topEnd: null, + bottomStart: null, + bottomEnd: 'paging', + }, + ), + }; + //endregion + //region Gestion traduction + let lang = self.dataDefault('sortLang', undefined); + if (Utils.isUndefined(lang)) { + let langUrl = self.dataDefault('sortLangUrl', undefined); + if (Utils.isUndefined(langUrl)) { + const langCode = self.dataDefault('sortLangCode', undefined); + if (!Utils.isUndefined(langCode)) { + langUrl = '//cdn.datatables.net/plug-ins/2.2.2/i18n/' + langCode + '.json'; + } + } + if (!Utils.isUndefined(langUrl)) { + lang = {url: langUrl}; + } + } + if (!Utils.isUndefined(lang)) { + options.language = lang; + } + //endregion + //region Gestion tri initial + let initialSort = []; + $('> thead > tr > th[data-sort-onLoad]', self).each(function () { + let self = $(this); + initialSort.push( + { + index: self.index(), + priority: self.dataDefault('sortOnload', 0), + direction: self.dataDefault('sortOrder', 'asc').toLowerCase(), + }, + ); + }); + initialSort.sort(function (sort1, sort2) { + if (sort1.priority === sort2.priority) { + return sort1.index - sort2.index; + } + + return sort1.priority - sort2.priority; + }); + + if (initialSort.length > 0) { + options.order = []; + initialSort.forEach(function (sortOrder) { + options.order.push([sortOrder.index, sortOrder.direction]); + }); + } + //endregion + //region Gestion sens de tri (1er clic ou initial si concerné) + let descSort = []; + $('> thead > tr > th[data-sort-order]', self).each(function () { + const self = $(this); + if (self.dataDefault('sortOrder', 'asc').toLowerCase() === 'desc') { + descSort.push(self.index()); + } + }); + if (descSort.length > 0) { + if (!options.hasOwnProperty('columnDefs')) { + options.columnDefs = []; + } + options.columnDefs.push( + { + targets: descSort, + orderSequence: ['desc', 'asc'], + }, + ); + } + //endregion + //region Gestion désactivation tri + let disabledSort = []; + $('> thead > tr > th[data-sort]', self).each(function () { + const self = $(this); + if (!self.dataDefault('sort', true)) { + disabledSort.push(self.index()); + } + }); + if (disabledSort.length > 0) { + if (!options.hasOwnProperty('columnDefs')) { + options.columnDefs = []; + } + options.columnDefs.push( + { + targets: disabledSort, + orderable: false, + }, + ); + } + //endregion + //region Gestion nom des colonnes + const columnsName = $('> thead > tr > th[data-sort-name]', self); + if (columnsName.length > 0) { + if (!options.hasOwnProperty('columnDefs')) { + options.columnDefs = []; + } + + columnsName.each(function () { + const self = $(this); + const name = self.dataDefault('sortName', ''); + if (name !== '') { + options.columnDefs.push( + { + targets: self.index(), + name: name, + }, + ); + } + }); + } + //endregion + //region Gestion de AJAX + const ajaxUrl = self.dataDefault('sortAjax', false); + if (ajaxUrl !== false) { + options.serverSide = true; + options.ajax = { + url: ajaxUrl, + type: self.dataDefault('sortAjaxType', 'POST'), + cache: self.dataDefault('sortAjaxCache', false), + }; + + options.columns = []; + columnsName.each(function () { + const self = $(this); + const name = self.dataDefault('sortName', ''); + const nameValue = self.dataDefault('sortNameValue', false); + if (name !== '') { + options.columns[self.index()] = { + data: nameValue + ? {_: name + '.display', sort: name + '.sort'} + : name, + }; + } + }); + } + //endregion + + const datatable = self.DataTable(options); + + //region Gestion lignes enfants + const child = self.dataDefault('sortChild', false); + const childAjaxUrl = self.dataDefault('sortChildAjax', false); + const childAjaxType = self.dataDefault('sortChildAjaxType', 'POST'); + const childAjaxCache = self.dataDefault('sortChildAjaxCache', false); + + if (child === true || childAjaxUrl !== false) { + const showData = function (dtRow, childData) { + const parentHtml = '
'; + const childJquery = $(parentHtml).append(childData); + datatables2(childJquery); + + //noinspection JSUnresolvedReference + dtRow.child(childJquery).show(); + }; + self.on('click', '> tbody > tr:not([data-dt-row!=""][data-dt-row]) > td', function () { + //region Obtention ligne + const row = $(this).parents('tr').first(); + const dtRow = datatable.row(row); + //endregion + //region Lignes enfants déjà affichées ? Les masques + //noinspection JSUnresolvedReference + if (dtRow.child.isShown()) { + //noinspection JSUnresolvedReference + dtRow.child.hide(); + return; + } + //endregion + + //region Données enfants directement spécifiées + const childData = row.dataDefault('sortChild', false); + if (childData !== false) { + showData(dtRow, childData); + return; + } + //endregion + //region Données enfants via AJAX + const rowChildAjaxUrl = row.dataDefault('sortChildAjax', childAjaxUrl); + if (Utils.isUndefined(rowChildAjaxUrl) || rowChildAjaxUrl === false) { + return; + } + + $.ajax( + { + type: row.dataDefault('sortChildAjaxType', childAjaxType), + url: rowChildAjaxUrl, + data: { + ajaxId: row.dataDefault('sortChildAjaxId', undefined), + }, + cache: row.dataDefault('sortChildAjaxCache', childAjaxCache), + }, + ) + .done(childData => { + showData(dtRow, childData); + }); + //endregion + }); + } + //endregion + }); +} + +$(function () { + datatables2(document); +}); \ No newline at end of file diff --git a/assets/jqueryLocal.js b/assets/jqueryLocal.js index 2391323..d50c408 100644 --- a/assets/jqueryLocal.js +++ b/assets/jqueryLocal.js @@ -8,18 +8,16 @@ window.jQuery = $; // Ensure global “jQuery” property available: necess (function ($) { //region .data - const jqueryDataOrig = $.fn.data; - /** - * Get a DOM "data" value + * Get a DOM “data” value, with a default value * - * @param {string} key The "data" key + * @param {string} key The “data” key * @param {any} defaultValue The default value * - * @return {any} The "data" value + * @return {any} The “data” value */ - $.fn.data = function (key, defaultValue = undefined) { - const value = jqueryDataOrig.apply(this, [key]); + $.fn.dataDefault = function (key, defaultValue = undefined) { + const value = this.data(key); if (Utils.isUndefined(value)) { return defaultValue; } diff --git a/assets/modalDynamic.js b/assets/modalDynamic.js new file mode 100644 index 0000000..5159aa8 --- /dev/null +++ b/assets/modalDynamic.js @@ -0,0 +1,26 @@ +import 'bootstrap'; +import 'jqueryLocal'; +import Utils from 'utils'; + +$(function () { + $('.modal-dynamic').on('show.bs.modal', function (event) { + const dialog = $(this); + const sender = $(event.relatedTarget); + + const title = sender.dataDefault('modalDynamicTitle'); + const body = sender.dataDefault('modalDynamicBody'); + const linkUrl = sender.dataDefault('modalDynamicLinkUrl', sender.matchTag('a') ? sender.attr('href') : undefined); + + if (!Utils.isUndefined(title)) { + $('.modal-title', dialog).html(title); + } + if (!Utils.isUndefined(body)) { + $('.modal-body', dialog).html(body); + } + if (!Utils.isUndefined(linkUrl)) { + $('a.modal-confirm-link', dialog).attr('href', linkUrl); + } + + return true; + }); +}); \ No newline at end of file diff --git a/assets/styles/_bootstrap.scss b/assets/styles/_bootstrap.scss index ed1e834..a487912 100644 --- a/assets/styles/_bootstrap.scss +++ b/assets/styles/_bootstrap.scss @@ -16,7 +16,56 @@ $alert-margin-bottom : 0.5rem; @import '../../vendor/twbs/bootstrap/scss/variables-dark'; //endregion //region Maps personnalisés - +$utilities : ( + //region Largeur max + 'max-width': ( + property: max-width, + class: mw, + values: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + ) + ), + //endregion + //region Hauteur max + 'max-height': ( + property: max-height, + class: mh, + values: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + ) + ), + //endregion + //region Largeur min + 'min-width': ( + property: min-width, + class: mnw, + values: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + ) + ), + //endregion + //region Hauteur min + 'min-height': ( + property: min-height, + class: mnh, + values: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + ) + ), + //endregion +); //endregion //region Reste de la configuration de Bootstrap @import '../../vendor/twbs/bootstrap/scss/maps'; diff --git a/assets/styles/app.scss b/assets/styles/app.scss index b3c388e..e15f70c 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -4,4 +4,9 @@ .required:not(.form-check-label) { color : var(--bs-red); +} + +.fit-content { + white-space : nowrap; + width : 1px; } \ No newline at end of file diff --git a/assets/styles/datatables.scss b/assets/styles/datatables.scss deleted file mode 100644 index 11fffeb..0000000 --- a/assets/styles/datatables.scss +++ /dev/null @@ -1,36 +0,0 @@ -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order::after, -table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order::after, -table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order::after, -table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order::after, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order::after, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order::after, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order::after, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order::after { - margin-top : 3px; -} - -table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, -table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, -table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, -table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, -table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, -table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, -table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, -table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { - position : relative; - left : 10px; - top : none; - bottom : none; -} - -div.dt-container .dt-paging .dt-paging-button { - padding : 0; - margin : 0; -} - -table.dataTable th.dt-type-numeric, -table.dataTable th.dt-type-date, -table.dataTable td.dt-type-numeric, -table.dataTable td.dt-type-date { - text-align : left; -} \ No newline at end of file diff --git a/assets/styles/datatables2.scss b/assets/styles/datatables2.scss new file mode 100644 index 0000000..09a29ff --- /dev/null +++ b/assets/styles/datatables2.scss @@ -0,0 +1,35 @@ +table.table-datatable2 { + & > thead > tr { + & > th.dt-orderable-asc span.dt-column-title { + margin-right : 5px; + } + + & > th.dt-orderable-asc span.dt-column-order, + & > th.dt-orderable-desc span.dt-column-order { + position : relative; + left : 0; + top : 0; + bottom : 0; + + &::after { + margin-top : 3px; + } + } + } + + & > tbody > tr[data-dt-row]:not([data-dt-row='']) > td { + padding : 0.75rem; + } + + div.dt-container .dt-paging .dt-paging-button { + padding : 0; + margin : 0; + } + + th.dt-type-numeric, + th.dt-type-date, + td.dt-type-numeric, + td.dt-type-date { + text-align : left; + } +} \ No newline at end of file diff --git a/config/packages/symfonycasts_sass.yaml b/config/packages/symfonycasts_sass.yaml new file mode 100644 index 0000000..160cee2 --- /dev/null +++ b/config/packages/symfonycasts_sass.yaml @@ -0,0 +1,5 @@ +symfonycasts_sass: + # Path to your Sass root file + root_sass: + - '%kernel.project_dir%/assets/styles/app.scss' + - '%kernel.project_dir%/assets/styles/datatables2.scss' diff --git a/importmap.php b/importmap.php index d121143..5aec558 100644 --- a/importmap.php +++ b/importmap.php @@ -12,58 +12,70 @@ * The "importmap:require" command can be used to add new entries to this file. */ return [ - 'app' => [ - 'path' => './assets/app.js', + 'app' => [ + 'path' => './assets/app.js', 'entrypoint' => true, ], - 'datatables' => [ - 'path' => './assets/datatables.js', + 'datatables2' => [ + 'path' => './assets/datatables2.js', 'entrypoint' => true, ], - 'jqueryLocal' => [ + 'modalDynamic' => [ + 'path' => './assets/modalDynamic.js', + ], + 'jqueryLocal' => [ 'path' => './assets/jqueryLocal.js', ], - 'utils' => [ + 'utils' => [ 'path' => './assets/utils.js', ], - 'fontawesome' => [ + 'fontawesome' => [ 'path' => './assets/fontawesome.js', ], - 'bootstrap' => [ + 'bootstrap' => [ 'version' => '5.3.6', ], - '@popperjs/core' => [ + '@popperjs/core' => [ 'version' => '2.11.8', ], - 'jquery' => [ + 'jquery' => [ 'version' => '3.7.1', ], - 'datatables.net' => [ + 'datatables.net' => [ 'version' => '2.3.1', ], - 'datatables.net-dt/css/dataTables.dataTables.min.css' => [ + 'datatables.net-dt/css/dataTables.dataTables.min.css' => [ 'version' => '2.3.1', - 'type' => 'css', + 'type' => 'css', ], - 'datatables.net-bs5' => [ + 'datatables.net-bs5' => [ 'version' => '2.3.1', ], 'datatables.net-bs5/css/dataTables.bootstrap5.min.css' => [ 'version' => '2.3.1', - 'type' => 'css', + 'type' => 'css', ], - '@fortawesome/fontawesome-svg-core' => [ + '@fortawesome/fontawesome-svg-core' => [ 'version' => '6.7.2', ], - '@fortawesome/free-solid-svg-icons' => [ + '@fortawesome/free-solid-svg-icons' => [ 'version' => '6.7.2', ], - '@fortawesome/fontawesome-svg-core/styles.min.css' => [ + '@fortawesome/fontawesome-svg-core/styles.min.css' => [ 'version' => '6.7.2', - 'type' => 'css', + 'type' => 'css', ], - 'bootstrap/dist/css/bootstrap.min.css' => [ + 'bootstrap/dist/css/bootstrap.min.css' => [ 'version' => '5.3.6', - 'type' => 'css', + 'type' => 'css', + ], + '@hotwired/stimulus' => [ + 'version' => '3.2.2', + ], + '@symfony/stimulus-bundle' => [ + 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', + ], + '@hotwired/turbo' => [ + 'version' => '7.3.0', ], ];