From c65902f784195529d3f11657e955d1c473e5f244 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa Date: Tue, 20 Sep 2022 09:36:06 +0200 Subject: [PATCH 01/81] Remove Github workflow specific code and config to allow tests to be executed on drupal.org. Update .info.yml files. --- .env | 1 - .github/workflows/ci.yml | 30 -- .github_changelog_generator | 5 - LICENSE.txt | 339 ------------------ composer.json | 87 +---- docker-compose.yml | 70 ---- grumphp.drupal8.yml | 16 - grumphp.yml | 16 - .../ui_patterns_ds_test.info.yml | 17 +- .../ui_patterns_ds/ui_patterns_ds.info.yml | 7 +- .../ui_patterns_field_group_test.info.yml | 23 +- .../ui_patterns_field_group.info.yml | 7 +- .../ui_patterns_layouts_test.info.yml | 25 +- .../ui_patterns_layouts.info.yml | 7 +- ...terns_library_bad_definition_test.info.yml | 3 +- .../ui_patterns_library_module_test.info.yml | 3 +- .../ui_patterns_library_theme_test.info.yml | 3 +- .../ui_patterns_library.info.yml | 7 +- .../ui_patterns_views_test.info.yml | 17 +- .../ui_patterns_views.info.yml | 7 +- phpunit.xml.dist | 17 - runner.yml.dist | 34 -- .../ui_patterns_field_source_test.info.yml | 4 +- .../ui_patterns_render_test.info.yml | 4 +- ui_patterns.info.yml | 7 +- 25 files changed, 73 insertions(+), 683 deletions(-) delete mode 100644 .env delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github_changelog_generator delete mode 100644 LICENSE.txt delete mode 100644 docker-compose.yml delete mode 100644 grumphp.drupal8.yml delete mode 100644 grumphp.yml delete mode 100644 phpunit.xml.dist delete mode 100644 runner.yml.dist diff --git a/.env b/.env deleted file mode 100644 index 0782604c..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -PHP_VERSION=8.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 04f028a6..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: ci -on: [push, pull_request_target] -jobs: - automated-tests: - runs-on: ubuntu-latest - strategy: - matrix: - PHP_VERSION: ["7.4", "8.0", "8.1"] - env: - PHP_VERSION: ${{ matrix.php_version }} - steps: - - name: clone - uses: actions/checkout@v2 - - run: docker-compose up -d - - name: composer install - run: docker-compose exec -T php composer install - - name: phpunit - run: docker-compose exec -T php ./vendor/bin/phpunit - code-sniffer: - runs-on: ubuntu-latest - env: - PHP_VERSION: "8.0" - steps: - - name: clone - uses: actions/checkout@v2 - - run: docker-compose up -d - - name: composer install - run: docker-compose exec -T php composer install - - name: grumphp - run: docker-compose exec -T php ./vendor/bin/grumphp run diff --git a/.github_changelog_generator b/.github_changelog_generator deleted file mode 100644 index 6d437180..00000000 --- a/.github_changelog_generator +++ /dev/null @@ -1,5 +0,0 @@ -merge_prefix=**Merged Pull Requests** -tag1='API change' -include-labels='API change,Bug,New feature' -enhancement_prefix=**New features** -enhancement-labels='New feature' diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index d159169d..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/composer.json b/composer.json index fc932066..a81284b8 100644 --- a/composer.json +++ b/composer.json @@ -1,77 +1,16 @@ { - "name": "drupal/ui_patterns", - "type": "drupal-module", - "description": "UI Patterns.", - "keywords": ["drupal", "web", "ui"], - "license": "GPL-2.0+", - "minimum-stability": "dev", - "prefer-stable": true, - "authors": [ - { - "name": "Nuvole Web", - "email": "info@nuvole.org" + "name": "drupal/ui_patterns", + "description": "UI Patterns.", + "license": "GPL-2.0-or-later", + "type": "drupal-module", + "authors": [ + { + "name": "Nuvole Web", + "email": "info@nuvole.org" + } + ], + "require-dev": { + "drupal/ds": "~3", + "drupal/field_group": "~3" } - ], - "require-dev": { - "composer/installers": "^1 || ^2", - "cweagans/composer-patches": "~1.4", - "drupal/core-composer-scaffold": "^8.8 || ^9", - "drupal/core-dev": "^8.8 || ^9", - "drupal/core-recommended": "^8.8 || ^9", - "drupal/ds": "~3", - "drupal/field_group": "~3", - "drupal/page_manager": "*", - "drupal/panels": "~4", - "drupal/paragraphs": "~1", - "drupal/token": "~1", - "drush/drush": "~10", - "openeuropa/task-runner-drupal-project-symlink": "^1.0-beta5", - "phpro/grumphp": "^1.5", - "phpspec/prophecy-phpunit": "^2" - }, - "repositories": [ - { - "type": "composer", - "url": "https://packages.drupal.org/8" - } - ], - "autoload": { - "psr-4": { - "Drupal\\ui_patterns\\": "./src" - } - }, - "autoload-dev": { - "psr-4": { - "Drupal\\Tests\\ui_patterns\\": "./tests/src" - } - }, - "scripts": { - "post-install-cmd": "./vendor/bin/run drupal:site-setup", - "post-update-cmd": "./vendor/bin/run drupal:site-setup" - }, - "extra": { - "composer-exit-on-patch-failure": true, - "enable-patching": true, - "drupal-scaffold": { - "locations": { - "web-root": "build/" - } - }, - "installer-paths": { - "build/core": ["type:drupal-core"], - "build/modules/contrib/{$name}": ["type:drupal-module"], - "build/profiles/contrib/{$name}": ["type:drupal-profile"], - "build/themes/contrib/{$name}": ["type:drupal-theme"] - } - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "composer/installers": true, - "cweagans/composer-patches": true, - "drupal/core-composer-scaffold": true, - "phpro/grumphp": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - } } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f64c80d0..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,70 +0,0 @@ -version: "2" - -services: - - mariadb: - image: wodby/mariadb:10.3-3.8.4 - stop_grace_period: 30s - environment: - MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: drupal - MYSQL_USER: drupal - MYSQL_PASSWORD: drupal - - php: - image: wodby/drupal-php:${PHP_VERSION} - environment: - DB_HOST: mariadb - DB_USER: drupal - DB_PASSWORD: drupal - DB_NAME: drupal - DB_DRIVER: mysql - PHP_XDEBUG: 1 - PHP_FPM_USER: wodby - PHP_FPM_GROUP: wodby - PHP_OPCACHE_PRELOAD_USER: wodby - PHP_XDEBUG_DEFAULT_ENABLE: 1 - PHP_XDEBUG_REMOTE_CONNECT_BACK: 1 - PHP_XDEBUG_REMOTE_HOST: "10.254.254.254" - PHP_XDEBUG_IDEKEY: "PHPSTORM" - PHP_IDE_CONFIG: "serverName=ui_patterns" - volumes: - - ./:/var/www/html - - nginx: - image: wodby/nginx:1.15-5.0.0 - depends_on: - - php - environment: - NGINX_STATIC_OPEN_FILE_CACHE: "off" - NGINX_ERROR_LOG_LEVEL: debug - NGINX_BACKEND_HOST: php - NGINX_SERVER_ROOT: /var/www/html/build - NGINX_VHOST_PRESET: drupal8 - volumes: - - ./:/var/www/html - ports: - - "8080:80" - - # If you would like to see what is going on you can run the following on your host: - # docker run --rm -p 4444:4444 -p 5900:5900 --network="host" selenium/standalone-chrome-debug:latest - # Newer version of this image might run into this issue: - # @link https://github.com/elgalu/docker-selenium/issues/20 - selenium: - image: selenium/standalone-chrome-debug:3.11 - expose: - - '4444' - environment: - - DISPLAY=:99 - - SE_OPTS=-debug - - SCREEN_WIDTH=1280 - - SCREEN_HEIGHT=800 - - VNC_NO_PASSWORD=1 - ports: - - '4444:4444' - - "5900:5900" - volumes: - - /dev/shm:/dev/shm - -volumes: - codebase: diff --git a/grumphp.drupal8.yml b/grumphp.drupal8.yml deleted file mode 100644 index 7d9c0de2..00000000 --- a/grumphp.drupal8.yml +++ /dev/null @@ -1,16 +0,0 @@ -parameters: - ascii: - failed: ~ - succeeded: ~ - tasks: - phpcs: - standard: vendor/drupal/coder/coder_sniffer/Drupal/ - ignore_patterns: - - build/ - - vendor/ - triggered_by: - - php - - module - - install - - inc - - theme diff --git a/grumphp.yml b/grumphp.yml deleted file mode 100644 index 79311e22..00000000 --- a/grumphp.yml +++ /dev/null @@ -1,16 +0,0 @@ -grumphp: - ascii: - failed: ~ - succeeded: ~ - tasks: - phpcs: - standard: vendor/drupal/coder/coder_sniffer/Drupal/ - ignore_patterns: - - build/ - - vendor/ - triggered_by: - - php - - module - - install - - inc - - theme diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml index 2142c359..b60f3ca5 100644 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml +++ b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml @@ -1,17 +1,14 @@ name: 'UI Patterns Display Suite Test' type: module description: 'Test module for UI Patterns.' -core: 8.x -core_version_requirement: ^8 || ^9 -hidden: true -package: 'User interface' +package: 'Testing' dependencies: - - ui_patterns - - ui_patterns_ds - - ui_patterns_library + - ui_patterns:ui_patterns + - ui_patterns:ui_patterns_ds + - ui_patterns:ui_patterns_library config_devel: install: - - core.entity_view_display.node.article.default - - field.field.node.article.body - - node.type.article + - core.entity_view_display.node.article.default + - field.field.node.article.body + - node.type.article diff --git a/modules/ui_patterns_ds/ui_patterns_ds.info.yml b/modules/ui_patterns_ds/ui_patterns_ds.info.yml index a37275e2..8e0a3969 100644 --- a/modules/ui_patterns_ds/ui_patterns_ds.info.yml +++ b/modules/ui_patterns_ds/ui_patterns_ds.info.yml @@ -1,9 +1,8 @@ -name: UI Patterns Display Suite +name: 'UI Patterns Display Suite' type: module -description: Use patterns as Display Suite field templates. It also provides Display Suite pattern sources. -core: 8.x +description: 'Use patterns as Display Suite field templates. It also provides Display Suite pattern sources.' core_version_requirement: ^8 || ^9 -package: User interface +package: 'User interface' dependencies: - ds:ds - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml index a6226496..7b0906a0 100644 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml +++ b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml @@ -1,20 +1,17 @@ name: 'UI Patterns Field Group Test' type: module description: 'Test module for UI Patterns.' -core: 8.x -core_version_requirement: ^8 || ^9 -hidden: true -package: 'User interface' +package: 'Testing' dependencies: -- ui_patterns -- ui_patterns_field_group -- ui_patterns_library + - ui_patterns:ui_patterns + - ui_patterns:ui_patterns_field_group + - ui_patterns:ui_patterns_library config_devel: install: - - core.entity_form_display.node.article.default - - core.entity_view_display.node.article.default - - field.field.node.article.body - - field.field.node.article.field_text - - field.storage.node.field_text - - node.type.article + - core.entity_form_display.node.article.default + - core.entity_view_display.node.article.default + - field.field.node.article.body + - field.field.node.article.field_text + - field.storage.node.field_text + - node.type.article diff --git a/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml b/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml index 2c5a4b0b..22b9ee9e 100644 --- a/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml +++ b/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml @@ -1,9 +1,8 @@ -name: UI Patterns Field Group +name: 'UI Patterns Field Group' type: module -description: Use patterns as field groups templates. -core: 8.x +description: 'Use patterns as field groups templates.' core_version_requirement: ^8 || ^9 -package: User interface +package: 'User interface' dependencies: - field_group:field_group - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml index 9f2d26c6..e88167c8 100644 --- a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml +++ b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml @@ -1,21 +1,18 @@ name: 'UI Patterns Layouts Test' type: module description: 'Test module for UI Patterns.' -core: 8.x -core_version_requirement: ^8 || ^9 -hidden: true -package: 'User interface' +package: 'Testing' dependencies: -- ui_patterns -- ui_patterns_layouts -- ui_patterns_library -- node -- text -- field_layout -- field_ui + - drupal:field_layout + - drupal:field_ui + - drupal:node + - drupal:text + - ui_patterns:ui_patterns + - ui_patterns:ui_patterns_layouts + - ui_patterns:ui_patterns_library config_devel: install: - - field.field.node.article.body - - node.type.article - - core.entity_view_display.node.article.default + - field.field.node.article.body + - node.type.article + - core.entity_view_display.node.article.default diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml index d0f06b18..6b9235d4 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml @@ -1,9 +1,8 @@ -name: UI Patterns Layouts +name: 'UI Patterns Layouts' type: module -description: Use patterns as layouts via the Layout Discovery module. -core: 8.x +description: 'Use patterns as layouts via the Layout Discovery module.' core_version_requirement: ^8 || ^9 -package: User interface +package: 'User interface' dependencies: - drupal:layout_discovery - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml index 5c6900d0..dd9a49ca 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml @@ -1,4 +1,3 @@ name: 'UI Patterns bad definition test' type: module -core: 8.x -core_version_requirement: ^8 || ^9 +package: 'Testing' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml index 8fbd6cc8..623cd455 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml @@ -1,4 +1,3 @@ name: 'UI Patterns library module test' type: module -core: 8.x -core_version_requirement: ^8 || ^9 +package: 'Testing' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml index d0069a21..b2bfc772 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml @@ -1,5 +1,4 @@ name: 'UI Patterns library theme test' type: theme -core: 8.x -core_version_requirement: ^8 || ^9 +package: 'Testing' base theme: stark diff --git a/modules/ui_patterns_library/ui_patterns_library.info.yml b/modules/ui_patterns_library/ui_patterns_library.info.yml index 23ad2ff1..cf659140 100644 --- a/modules/ui_patterns_library/ui_patterns_library.info.yml +++ b/modules/ui_patterns_library/ui_patterns_library.info.yml @@ -1,8 +1,7 @@ -name: UI Patterns Library +name: 'UI Patterns Library' type: module -description: Exposed patterns in you modules and themes and display them in a pattern library page. -core: 8.x +description: 'Exposed patterns in you modules and themes and display them in a pattern library page.' core_version_requirement: ^8 || ^9 -package: User interface +package: 'User interface' dependencies: - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml index e6954e14..957b666f 100644 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml +++ b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml @@ -1,17 +1,14 @@ name: 'UI Patterns Views Test' type: module description: 'Test module for UI Patterns.' -core: 8.x -core_version_requirement: ^8 || ^9 -hidden: true -package: 'User interface' +package: 'Testing' dependencies: -- ui_patterns -- ui_patterns_views -- ui_patterns_library -- views_ui -- node -- text + - drupal:node + - drupal:text + - drupal:views_ui + - ui_patterns:ui_patterns + - ui_patterns:ui_patterns_library + - ui_patterns:ui_patterns_views config_devel: install: diff --git a/modules/ui_patterns_views/ui_patterns_views.info.yml b/modules/ui_patterns_views/ui_patterns_views.info.yml index f1297fdd..670f2a46 100644 --- a/modules/ui_patterns_views/ui_patterns_views.info.yml +++ b/modules/ui_patterns_views/ui_patterns_views.info.yml @@ -1,9 +1,8 @@ -name: UI Patterns Views +name: 'UI Patterns Views' type: module -description: Use patterns as Views templates. -core: 8.x +description: 'Use patterns as Views templates.' core_version_requirement: ^8 || ^9 -package: User interface +package: 'User interface' dependencies: - drupal:views - ui_patterns:ui_patterns diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 5322204e..00000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - ./tests/ - ./modules/*/tests/ - - - diff --git a/runner.yml.dist b/runner.yml.dist deleted file mode 100644 index 465d1e8c..00000000 --- a/runner.yml.dist +++ /dev/null @@ -1,34 +0,0 @@ -drupal: - root: "build" - base_url: "http://nginx" - database: - host: "mariadb" - port: "3306" - name: "drupal" - user: "drupal" - password: "drupal" - post_install: - - "./vendor/bin/drush en -y ui_patterns ui_patterns_library ui_patterns_ds ui_patterns_field_group ui_patterns_layouts ui_patterns_views" - - "./vendor/bin/drush en -y coffee config_devel" - - "./vendor/bin/drush en -y page_manager paragraphs panels" - - "./vendor/bin/drush cr" - settings: - settings: - file_scan_ignore_directories: - - "vendor" - - "${drupal.root}" - -selenium: - host: "http://selenium:4444" - browser: "chrome" - -commands: - drupal:site-setup: - - { task: "run", command: "drupal:symlink-project" } - # Generate settings.testing.php, it will be used when running functional tests. - - { task: "process-php", type: "write", config: "drupal.settings", source: "${drupal.root}/sites/default/default.settings.php", destination: "${drupal.root}/sites/default/settings.testing.php", override: true } - - { task: "run", command: "drupal:drush-setup" } - - { task: "run", command: "drupal:settings-setup" } - - { task: "run", command: "setup:phpunit" } - setup:phpunit: - - { task: "process", source: "phpunit.xml.dist", destination: "phpunit.xml" } diff --git a/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml b/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml index 7f770e42..60fb9b9a 100644 --- a/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml +++ b/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml @@ -1,4 +1,4 @@ name: 'UI Patterns field source test' +description: 'Provides test plugin.' type: module -core: 8.x -core_version_requirement: ^8 || ^9 +package: 'Testing' diff --git a/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml b/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml index df40108b..868c9314 100644 --- a/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml +++ b/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml @@ -1,4 +1,4 @@ name: 'UI Patterns Render Test' type: module -core: 8.x -core_version_requirement: ^8 || ^9 +description: 'Provides test patterns.' +package: 'Testing' diff --git a/ui_patterns.info.yml b/ui_patterns.info.yml index fdd0b2e2..09625bdd 100644 --- a/ui_patterns.info.yml +++ b/ui_patterns.info.yml @@ -1,6 +1,5 @@ -name: UI Patterns +name: 'UI Patterns' type: module -description: UI patterns -core: 8.x +description: 'UI patterns.' core_version_requirement: ^8 || ^9 -package: User interface +package: 'User interface' From ffd5bc6e24297a8619a3223dad429a05e107d23c Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Wed, 21 Sep 2022 11:49:32 +0200 Subject: [PATCH 02/81] Issue #3310657 by Grimreaper: Fix hardcoded module path in tests. --- .../UiPatternsLibraryOverviewTest.php | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php index 3188b00b..9e8ad47d 100644 --- a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php +++ b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php @@ -12,6 +12,15 @@ */ class UiPatternsLibraryOverviewTest extends WebDriverTestBase { + /** + * Module extension list. + * + * Currently no interface to rely on. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleExtensionList; + /** * Default theme. * @@ -34,6 +43,8 @@ class UiPatternsLibraryOverviewTest extends WebDriverTestBase { protected function setUp(): void { parent::setUp(); + $this->moduleExtensionList = $this->container->get('extension.list.module'); + $user = $this->drupalCreateUser(['access patterns page']); $this->drupalLogin($user); } @@ -86,10 +97,13 @@ public function testLocalLibraries() { $session = $this->assertSession(); $this->drupalGet('/patterns/with_local_libraries'); - $session->responseContains('href="/modules/custom/ui_patterns/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css'); - $session->responseContains('href="/modules/custom/ui_patterns/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css'); - $session->responseContains('src="/modules/custom/ui_patterns/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_1.js'); - $session->responseContains('src="/modules/custom/ui_patterns/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_2.js'); + + $ui_patterns_library_path = $this->moduleExtensionList->getPath('ui_patterns_library'); + + $session->responseContains('href="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css'); + $session->responseContains('href="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css'); + $session->responseContains('src="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_1.js'); + $session->responseContains('src="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_2.js'); $session->responseContains('src="/core/misc/tabledrag.js'); } From 0aae8d8fd5364ee0465426c0f5eece46da8c6316 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Wed, 21 Sep 2022 11:57:52 +0200 Subject: [PATCH 03/81] Issue #3310657 by Grimreaper, GoZ: Config structure change after upgrading Field Group to 8.x-3.3 --- composer.json | 2 +- .../ui_patterns_field_group.module | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a81284b8..f5b99b5c 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,6 @@ ], "require-dev": { "drupal/ds": "~3", - "drupal/field_group": "~3" + "drupal/field_group": "~3.0" } } diff --git a/modules/ui_patterns_field_group/ui_patterns_field_group.module b/modules/ui_patterns_field_group/ui_patterns_field_group.module index 130ed17e..8b4b0dc2 100644 --- a/modules/ui_patterns_field_group/ui_patterns_field_group.module +++ b/modules/ui_patterns_field_group/ui_patterns_field_group.module @@ -9,7 +9,24 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\ui_patterns\Element\PatternContext; /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_module_implements_alter(). + */ +function ui_patterns_field_group_module_implements_alter(&$implementations, $hook) { + switch ($hook) { + // Ensure ui_patterns_field_group_form_entity_view_display_edit_form_alter, + // is executed after field_group_form_entity_view_display_edit_form_alter. + case 'form_alter': + if (isset($implementations['ui_patterns_field_group'])) { + $group = $implementations['ui_patterns_field_group']; + unset($implementations['ui_patterns_field_group']); + $implementations['ui_patterns_field_group'] = $group; + } + break; + } +} + +/** + * Implements hook_form_FORM_ID_alter() for 'entity_view_display_edit_form'. */ function ui_patterns_field_group_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) { array_unshift($form['actions']['submit']['#submit'], 'ui_patterns_field_group_field_group_field_overview_submit'); From f1bd143b86c79ca1763b51553b7f6c7228c5e60b Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Wed, 21 Sep 2022 12:32:14 +0200 Subject: [PATCH 04/81] Issue #3310657 by Grimreaper: Handle subdirectory for links in tests. --- .../UiPatternsLibraryOverviewTest.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php index 9e8ad47d..a649920e 100644 --- a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php +++ b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\ui_patterns_library\FunctionalJavascript; use Drupal\Core\Serialization\Yaml; +use Drupal\Core\Url; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; /** @@ -98,13 +99,13 @@ public function testLocalLibraries() { $this->drupalGet('/patterns/with_local_libraries'); - $ui_patterns_library_path = $this->moduleExtensionList->getPath('ui_patterns_library'); + $ui_patterns_library_module_test_path = $this->moduleExtensionList->getPath('ui_patterns_library_module_test'); - $session->responseContains('href="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css'); - $session->responseContains('href="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css'); - $session->responseContains('src="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_1.js'); - $session->responseContains('src="/' . $ui_patterns_library_path . '/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_2.js'); - $session->responseContains('src="/core/misc/tabledrag.js'); + $session->responseContains('href="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/css/library_one.css')->toString()); + $session->responseContains('href="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/css/library_two.css')->toString()); + $session->responseContains('src="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/js/library_two_1.js')->toString()); + $session->responseContains('src="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/js/library_two_2.js')->toString()); + $session->responseContains('src="' . Url::fromUserInput('/core/misc/tabledrag.js')->toString()); } /** From a37ba1d8b954d0aa9aa4fcfae0dfce315d353a3d Mon Sep 17 00:00:00 2001 From: Project Update Bot Date: Wed, 21 Sep 2022 12:52:09 +0200 Subject: [PATCH 05/81] Issue #3290197 by Project Update Bot, Grimreaper: Automated Drupal 10 compatibility fixes --- .../tests/src/Unit/EntityFinderTest.php | 4 +--- tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php | 2 +- tests/src/Kernel/UiPatternsManagerTest.php | 2 +- tests/src/Kernel/UiPatternsPreviewTest.php | 2 +- tests/src/Kernel/UiPatternsSourceManagerTest.php | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php b/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php index 8259468d..75a60c93 100644 --- a/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php +++ b/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php @@ -31,9 +31,7 @@ public function testFindEntityFromFields($fields, $expected) { * Test data. */ public function fieldsDataProvider() { - $good = $this->getMockBuilder(ContentEntityBase::class) - ->disableOriginalConstructor() - ->getMock(); + $good = $this->createMock(ContentEntityBase::class); $bad = new \stdClass(); return [ diff --git a/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php b/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php index a7bb4053..87884105 100644 --- a/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php +++ b/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php @@ -15,7 +15,7 @@ class YamlDeriverTest extends AbstractUiPatternsTest { /** * {@inheritdoc} */ - public static $modules = [ + protected static $modules = [ 'system', 'ui_patterns', 'ui_patterns_library', diff --git a/tests/src/Kernel/UiPatternsManagerTest.php b/tests/src/Kernel/UiPatternsManagerTest.php index a7f3198f..57fd076a 100644 --- a/tests/src/Kernel/UiPatternsManagerTest.php +++ b/tests/src/Kernel/UiPatternsManagerTest.php @@ -14,7 +14,7 @@ class UiPatternsManagerTest extends AbstractUiPatternsTest { /** * {@inheritdoc} */ - public static $modules = [ + protected static $modules = [ 'system', 'ui_patterns', 'ui_patterns_library', diff --git a/tests/src/Kernel/UiPatternsPreviewTest.php b/tests/src/Kernel/UiPatternsPreviewTest.php index affbfae1..eb3cb347 100644 --- a/tests/src/Kernel/UiPatternsPreviewTest.php +++ b/tests/src/Kernel/UiPatternsPreviewTest.php @@ -17,7 +17,7 @@ class UiPatternsPreviewTest extends AbstractUiPatternsTest { /** * {@inheritdoc} */ - public static $modules = [ + protected static $modules = [ 'system', 'ui_patterns', 'ui_patterns_library', diff --git a/tests/src/Kernel/UiPatternsSourceManagerTest.php b/tests/src/Kernel/UiPatternsSourceManagerTest.php index dd218a56..eb74c319 100644 --- a/tests/src/Kernel/UiPatternsSourceManagerTest.php +++ b/tests/src/Kernel/UiPatternsSourceManagerTest.php @@ -12,7 +12,7 @@ class UiPatternsSourceManagerTest extends AbstractUiPatternsTest { /** * {@inheritdoc} */ - public static $modules = [ + protected static $modules = [ 'ui_patterns', 'ui_patterns_field_source_test', ]; From fb976377806f35be36608656deb4f64697b83465 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Wed, 21 Sep 2022 13:05:10 +0200 Subject: [PATCH 06/81] Issue #3290197 by Grimreaper: Mark modules as D10 compatible. Fix quality: CSS, composer normalize, some PHPCS, PHPMD. --- composer.json | 4 ++-- modules/ui_patterns_ds/src/FieldTemplateProcessor.php | 2 +- .../src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php | 2 +- modules/ui_patterns_ds/ui_patterns_ds.info.yml | 2 +- .../ui_patterns_field_group/ui_patterns_field_group.info.yml | 2 +- .../ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php | 2 +- modules/ui_patterns_layouts/ui_patterns_layouts.info.yml | 2 +- .../ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php | 4 ++-- .../templates/patterns-meta-information.html.twig | 2 +- .../templates/patterns-overview-page.html.twig | 2 +- .../ui_patterns_library_bad_definition_test.info.yml | 1 + .../templates/with_local_libraries/css/library_one.css | 1 + .../templates/with_local_libraries/css/library_two.css | 1 + .../ui_patterns_library_module_test.info.yml | 1 + .../ui_patterns_library_theme_test.info.yml | 1 + modules/ui_patterns_library/ui_patterns_library.info.yml | 2 +- .../config/install/views.view.articles.yml | 2 -- modules/ui_patterns_views/ui_patterns_views.info.yml | 2 +- src/UiPatternsSourceManager.php | 2 +- ui_patterns.info.yml | 2 +- 20 files changed, 21 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index f5b99b5c..216a9be3 100644 --- a/composer.json +++ b/composer.json @@ -5,8 +5,8 @@ "type": "drupal-module", "authors": [ { - "name": "Nuvole Web", - "email": "info@nuvole.org" + "name": "Nuvole Web", + "email": "info@nuvole.org" } ], "require-dev": { diff --git a/modules/ui_patterns_ds/src/FieldTemplateProcessor.php b/modules/ui_patterns_ds/src/FieldTemplateProcessor.php index cf54f7cd..54c8f353 100644 --- a/modules/ui_patterns_ds/src/FieldTemplateProcessor.php +++ b/modules/ui_patterns_ds/src/FieldTemplateProcessor.php @@ -25,7 +25,7 @@ public function process(array &$variables) { $this->variables = $variables; $content = []; - foreach ($variables['items'] as $delta => $item) { + foreach (array_keys($variables['items']) as $delta) { $fields = []; foreach ($this->getMapping() as $mapping) { $fields[$mapping['destination']][] = $this->getSourceValue($mapping, $delta); diff --git a/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php b/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php index f391db1a..2a02e629 100644 --- a/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php +++ b/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php @@ -62,7 +62,7 @@ public function getSourceFields() { $label = $field->getLabel(); $sources[] = $this->getSourceField($field_name, $label); - foreach ($field->getFieldStorageDefinition()->getColumns() as $column_name => $column) { + foreach (array_keys($field->getFieldStorageDefinition()->getColumns()) as $column_name) { $sources[] = $this->getSourceField($field_name . '__' . $column_name, $label . ': ' . $column_name); } return $sources; diff --git a/modules/ui_patterns_ds/ui_patterns_ds.info.yml b/modules/ui_patterns_ds/ui_patterns_ds.info.yml index 8e0a3969..2bbdfa21 100644 --- a/modules/ui_patterns_ds/ui_patterns_ds.info.yml +++ b/modules/ui_patterns_ds/ui_patterns_ds.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Display Suite' type: module description: 'Use patterns as Display Suite field templates. It also provides Display Suite pattern sources.' -core_version_requirement: ^8 || ^9 +core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - ds:ds diff --git a/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml b/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml index 22b9ee9e..d2a376eb 100644 --- a/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml +++ b/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Field Group' type: module description: 'Use patterns as field groups templates.' -core_version_requirement: ^8 || ^9 +core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - field_group:field_group diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 12ce3b14..71327000 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -90,7 +90,7 @@ public function build(array $regions) { // Patterns expect regions to be passed along in a render array fashion. $fields = []; - foreach ($regions as $region_name => $region) { + foreach (array_keys($regions) as $region_name) { $fields[$region_name] = $regions[$region_name]; } diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml index 6b9235d4..8cd17ba8 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Layouts' type: module description: 'Use patterns as layouts via the Layout Discovery module.' -core_version_requirement: ^8 || ^9 +core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - drupal:layout_discovery diff --git a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php index 520ca076..ef35d0bf 100644 --- a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php +++ b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php @@ -133,7 +133,7 @@ public function getFileExtensions() { public function getPatterns() { $patterns = []; foreach ($this->getDirectories() as $provider => $directory) { - foreach ($this->fileScanDirectory($directory) as $file_path => $file) { + foreach (array_keys($this->fileScanDirectory($directory)) as $file_path) { $host_extension = $this->getHostExtension($file_path); if ($host_extension == FALSE || $host_extension == $provider) { $content = file_get_contents($file_path); @@ -168,7 +168,7 @@ protected function getDirectories() { $directories = []; if (isset($theme_directories[$default_theme])) { $directories[$default_theme] = $theme_directories[$default_theme]; - foreach ($base_themes as $name => $theme) { + foreach (array_keys($base_themes) as $name) { $directories[$name] = $theme_directories[$name]; } } diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig index 95dba04a..acf5e98d 100644 --- a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig +++ b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig @@ -7,7 +7,7 @@ {% if pattern is not empty %} - {# Pattern name and desciption. #} + {# Pattern name and description. #}

{{ pattern.label }}

{{ pattern.description }}

diff --git a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig index 261398f0..4c1efa22 100644 --- a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig +++ b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig @@ -22,7 +22,7 @@ {% for pattern_name, pattern in patterns %}
- {# Pattern name and desciption. #} + {# Pattern name and description. #} {{ pattern.meta }} diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml index dd9a49ca..583c552c 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml @@ -1,3 +1,4 @@ name: 'UI Patterns bad definition test' type: module +description: 'Test module for UI Patterns.' package: 'Testing' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css index e69de29b..b301b382 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css @@ -0,0 +1 @@ +/* Avoid empty file. */ diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css index e69de29b..b301b382 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css @@ -0,0 +1 @@ +/* Avoid empty file. */ diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml index 623cd455..2f127881 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml @@ -1,3 +1,4 @@ name: 'UI Patterns library module test' type: module +description: 'Test module for UI Patterns.' package: 'Testing' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml index b2bfc772..2926e0c6 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml @@ -1,4 +1,5 @@ name: 'UI Patterns library theme test' type: theme +description: 'Test theme for UI Patterns.' package: 'Testing' base theme: stark diff --git a/modules/ui_patterns_library/ui_patterns_library.info.yml b/modules/ui_patterns_library/ui_patterns_library.info.yml index cf659140..627d19d4 100644 --- a/modules/ui_patterns_library/ui_patterns_library.info.yml +++ b/modules/ui_patterns_library/ui_patterns_library.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Library' type: module description: 'Exposed patterns in you modules and themes and display them in a pattern library page.' -core_version_requirement: ^8 || ^9 +core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml index 59f8ca52..f31f1228 100644 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml +++ b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml @@ -14,8 +14,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x -core_version_requirement: ^8 || ^9 display: default: display_plugin: default diff --git a/modules/ui_patterns_views/ui_patterns_views.info.yml b/modules/ui_patterns_views/ui_patterns_views.info.yml index 670f2a46..957fc855 100644 --- a/modules/ui_patterns_views/ui_patterns_views.info.yml +++ b/modules/ui_patterns_views/ui_patterns_views.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Views' type: module description: 'Use patterns as Views templates.' -core_version_requirement: ^8 || ^9 +core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - drupal:views diff --git a/src/UiPatternsSourceManager.php b/src/UiPatternsSourceManager.php index d9cccc6a..990fda29 100644 --- a/src/UiPatternsSourceManager.php +++ b/src/UiPatternsSourceManager.php @@ -49,7 +49,7 @@ public function getDefinitionsByTag($tag) { public function getFieldsByTag($tag, array $context) { /** @var \Drupal\ui_patterns\Plugin\PatternSourceInterface $plugin */ $fields = []; - foreach ($this->getDefinitionsByTag($tag) as $id => $definition) { + foreach (array_keys($this->getDefinitionsByTag($tag)) as $id) { $plugin = $this->createInstance($id, ['context' => $context]); foreach ($plugin->getSourceFields() as $field) { $fields[$field->getFieldKey()] = $field; diff --git a/ui_patterns.info.yml b/ui_patterns.info.yml index 09625bdd..cf1fbdb0 100644 --- a/ui_patterns.info.yml +++ b/ui_patterns.info.yml @@ -1,5 +1,5 @@ name: 'UI Patterns' type: module description: 'UI patterns.' -core_version_requirement: ^8 || ^9 +core_version_requirement: ^9 || ^10 package: 'User interface' From 9615d77f721ad8532dd6a51b17d32c4308f5481e Mon Sep 17 00:00:00 2001 From: DuaelFr Date: Tue, 11 Oct 2022 09:06:02 +0000 Subject: [PATCH 07/81] Issue #3314293 by DuaelFr, Grimreaper: Replace "app.root" service for D10 compatibility --- .../ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php | 2 +- .../src/Plugin/UiPatterns/Pattern/LibraryPattern.php | 2 +- src/Plugin/PatternBase.php | 2 +- tests/src/Kernel/Plugin/PatternBaseTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php index ef35d0bf..a0da46e0 100644 --- a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php +++ b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php @@ -113,7 +113,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) { $container->get('typed_data_manager'), $container->get('messenger'), $container->get('file_system'), - $container->get('app.root'), + $container->getParameter('app.root'), $container->getParameter('ui_patterns_library.file_extensions'), $container->get('module_handler'), $container->get('theme_handler') diff --git a/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php b/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php index f57aad21..b9cd22e8 100644 --- a/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php +++ b/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php @@ -45,7 +45,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('app.root'), + $container->getParameter('app.root'), $container->get('module_handler'), $container->get('theme_handler') ); diff --git a/src/Plugin/PatternBase.php b/src/Plugin/PatternBase.php index 90993cf1..7a8d5b20 100644 --- a/src/Plugin/PatternBase.php +++ b/src/Plugin/PatternBase.php @@ -46,7 +46,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('app.root'), + $container->getParameter('app.root'), $container->get('module_handler') ); } diff --git a/tests/src/Kernel/Plugin/PatternBaseTest.php b/tests/src/Kernel/Plugin/PatternBaseTest.php index 5298b389..e68c3b11 100644 --- a/tests/src/Kernel/Plugin/PatternBaseTest.php +++ b/tests/src/Kernel/Plugin/PatternBaseTest.php @@ -54,7 +54,7 @@ protected function getUiPatternBaseMock(array $plugin_definition = [], array $me [], 'plugin_id', $plugin_definition, - \Drupal::service('app.root'), + \Drupal::getContainer()->getParameter('app.root'), \Drupal::service('module_handler'), ], '', TRUE, TRUE, TRUE, $methods); } From 64ad88963a536e45e80ea80ad8874b6c3102a861 Mon Sep 17 00:00:00 2001 From: DuaelFr Date: Tue, 11 Oct 2022 09:32:07 +0000 Subject: [PATCH 08/81] Issue #3313689 by DuaelFr, Grimreaper, Sharique: \Drupal\ui_patterns\Template\TwigExtension use classes deprecated in D10 --- src/Template/TwigExtension.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Template/TwigExtension.php b/src/Template/TwigExtension.php index b98cf4cb..298ff276 100644 --- a/src/Template/TwigExtension.php +++ b/src/Template/TwigExtension.php @@ -2,12 +2,15 @@ namespace Drupal\ui_patterns\Template; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + /** * Twig extension providing UI Patterns-specific functionalities. * * @package Drupal\ui_patterns\Template */ -class TwigExtension extends \Twig_Extension { +class TwigExtension extends AbstractExtension { /** * {@inheritdoc} @@ -21,11 +24,11 @@ public function getName() { */ public function getFunctions() { return [ - new \Twig_SimpleFunction('pattern', [ + new TwigFunction('pattern', [ $this, 'renderPattern', ]), - new \Twig_SimpleFunction('pattern_preview', [ + new TwigFunction('pattern_preview', [ $this, 'renderPatternPreview', ]), From df91310e348d95765558477265fcd13bf14e3e52 Mon Sep 17 00:00:00 2001 From: DuaelFr Date: Wed, 12 Oct 2022 16:30:06 +0000 Subject: [PATCH 09/81] Issue #3311088 by DuaelFr, Grimreaper, yannickoo: Add extra field support --- .../UiPatterns/Source/ExtraFieldSource.php | 81 +++++++++++++++++++ .../Kernel/UiPatternsExtraFieldSourceTest.php | 52 ++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/Plugin/UiPatterns/Source/ExtraFieldSource.php create mode 100644 tests/src/Kernel/UiPatternsExtraFieldSourceTest.php diff --git a/src/Plugin/UiPatterns/Source/ExtraFieldSource.php b/src/Plugin/UiPatterns/Source/ExtraFieldSource.php new file mode 100644 index 00000000..05128d44 --- /dev/null +++ b/src/Plugin/UiPatterns/Source/ExtraFieldSource.php @@ -0,0 +1,81 @@ +entityFieldManager = $entity_field_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_field.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getSourceFields() { + $sources = []; + $entity_type_id = $this->getContextProperty('entity_type'); + $bundle = $this->getContextProperty('entity_bundle'); + $extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle); + + if (!isset($extra_fields['display'])) { + return $sources; + } + + try { + $limit = $this->getContextProperty('limit'); + } + catch (PluginException $e) { + $limit = array_keys($extra_fields['display']); + } + + foreach ($extra_fields['display'] as $extra_field_name => $field) { + if (in_array($extra_field_name, $limit)) { + $sources[] = $this->getSourceField($extra_field_name, $field['label']); + } + } + + return $sources; + } + +} diff --git a/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php b/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php new file mode 100644 index 00000000..71f1b0be --- /dev/null +++ b/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php @@ -0,0 +1,52 @@ +installEntitySchema('entity_test'); + } + + /** + * Test getSourceFields. + * + * @covers ::getSourceFields + */ + public function testGetSourceFields() { + /** @var \Drupal\ui_patterns\UiPatternsSourceManager $manager */ + $manager = UiPatterns::getSourceManager(); + + /** @var \Drupal\ui_patterns\Plugin\UiPatterns\Source\ExtraFieldSource $source */ + $fields = $manager->getFieldsByTag('entity_display', [ + 'entity_type' => 'entity_test', + 'entity_bundle' => 'bundle_with_extra_fields', + ]); + + $this->assertArrayHasKey('extra_fields:display_extra_field', $fields); + $this->assertArrayHasKey('extra_fields:display_extra_field_hidden', $fields); + } + +} From 685ec747654a541233b76e65351db497bb497282 Mon Sep 17 00:00:00 2001 From: Edouard Cunibil <11996-DuaelFr@users.noreply.drupalcode.org> Date: Wed, 12 Oct 2022 18:30:42 +0200 Subject: [PATCH 10/81] Issue #3311088 by DuaelFr, Grimreaper, yannickoo: Add extra field support From 2e0ac0494a480185f8e46bbd00b3ce906717822a Mon Sep 17 00:00:00 2001 From: Yannick <45533-yan_nick@users.noreply.drupalcode.org> Date: Wed, 12 Oct 2022 18:33:20 +0200 Subject: [PATCH 11/81] Issue #3311088 by DuaelFr, Grimreaper, yannickoo: Add extra field support From d92f574a50ad41b6b1015cb3655d4093799e986e Mon Sep 17 00:00:00 2001 From: DuaelFr Date: Sat, 22 Oct 2022 12:39:18 +0000 Subject: [PATCH 12/81] Issue #3316487 by DuaelFr: Add pattern ID in preview wrapper in ui_patterns_library --- .../src/Controller/PatternsLibraryController.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php index e6494cb9..bbec1838 100644 --- a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php +++ b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php @@ -123,7 +123,13 @@ protected function getPatternRenderArray(PatternDefinition $definition) { '#variant' => $variant->getName(), '#theme_wrappers' => [ 'container' => [ - '#attributes' => ['class' => 'pattern-preview__markup pattern-preview__markup--variant_' . $variant->getName()], + '#attributes' => [ + 'class' => [ + 'pattern-preview__markup', + 'pattern-preview__markup--' . $definition->id(), + 'pattern-preview__markup--variant_' . $variant->getName(), + ], + ], ], ], ], @@ -137,7 +143,12 @@ protected function getPatternRenderArray(PatternDefinition $definition) { '#id' => $definition->id(), '#theme_wrappers' => [ 'container' => [ - '#attributes' => ['class' => 'pattern-preview__markup'], + '#attributes' => [ + 'class' => [ + 'pattern-preview__markup', + 'pattern-preview__markup--' . $definition->id(), + ], + ], ], ], ], From c780a2629015491b499903ef13d9d192f8fa8f2d Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Mon, 21 Nov 2022 13:50:40 +0100 Subject: [PATCH 13/81] Issue #3315533 by G4MBINI, Grimreaper: Move documentation from Github pages to Drupal.org --- .gitignore | 10 - README.md | 63 ++--- docs/Makefile | 195 -------------- docs/conf.py | 291 -------------------- docs/content/developer-documentation.rst | 322 ----------------------- docs/content/field-group.rst | 28 -- docs/content/field-templates.rst | 47 ---- docs/content/layout-plugin.rst | 56 ---- docs/content/patterns-definition.rst | 203 -------------- docs/content/tests.rst | 55 ---- docs/content/views.rst | 18 -- docs/images/blockquote-preview.png | Bin 27477 -> 0 bytes docs/images/developer-1.png | Bin 8597 -> 0 bytes docs/images/developer-2.png | Bin 10028 -> 0 bytes docs/images/developer-3.png | Bin 129535 -> 0 bytes docs/images/field-template-1.png | Bin 6624 -> 0 bytes docs/images/field-template-2.png | Bin 39499 -> 0 bytes docs/images/field-template-3.png | Bin 2369 -> 0 bytes docs/images/fieldgroup-1.png | Bin 23383 -> 0 bytes docs/images/fieldgroup-2.png | Bin 33618 -> 0 bytes docs/images/fieldgroup-3.png | Bin 62561 -> 0 bytes docs/images/layouts-1.png | Bin 63164 -> 0 bytes docs/images/layouts-2.png | Bin 14052 -> 0 bytes docs/images/layouts-3.png | Bin 24159 -> 0 bytes docs/images/layouts-4.png | Bin 13734 -> 0 bytes docs/images/pattern-library.png | Bin 60660 -> 0 bytes docs/images/patterns-overview.png | Bin 22338 -> 0 bytes docs/images/views-1.png | Bin 14940 -> 0 bytes docs/images/views-2.png | Bin 35658 -> 0 bytes docs/index.rst | 49 ---- modules/ui_patterns_layouts/README.md | 7 +- modules/ui_patterns_library/README.md | 8 +- tests/README.md | 1 - 33 files changed, 21 insertions(+), 1332 deletions(-) delete mode 100644 .gitignore delete mode 100644 docs/Makefile delete mode 100644 docs/conf.py delete mode 100644 docs/content/developer-documentation.rst delete mode 100644 docs/content/field-group.rst delete mode 100644 docs/content/field-templates.rst delete mode 100644 docs/content/layout-plugin.rst delete mode 100644 docs/content/patterns-definition.rst delete mode 100644 docs/content/tests.rst delete mode 100644 docs/content/views.rst delete mode 100644 docs/images/blockquote-preview.png delete mode 100644 docs/images/developer-1.png delete mode 100644 docs/images/developer-2.png delete mode 100644 docs/images/developer-3.png delete mode 100644 docs/images/field-template-1.png delete mode 100644 docs/images/field-template-2.png delete mode 100644 docs/images/field-template-3.png delete mode 100644 docs/images/fieldgroup-1.png delete mode 100644 docs/images/fieldgroup-2.png delete mode 100644 docs/images/fieldgroup-3.png delete mode 100644 docs/images/layouts-1.png delete mode 100644 docs/images/layouts-2.png delete mode 100644 docs/images/layouts-3.png delete mode 100644 docs/images/layouts-4.png delete mode 100644 docs/images/pattern-library.png delete mode 100644 docs/images/patterns-overview.png delete mode 100644 docs/images/views-1.png delete mode 100644 docs/images/views-2.png delete mode 100644 docs/index.rst delete mode 100644 tests/README.md diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d8633ada..00000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -/.idea -/vendor -/composer.lock -/docs/_build -/build/ -/phpunit.xml -/docker-compose.override.yml -/runner.yml -/.editorconfig -/.gitattributes diff --git a/README.md b/README.md index 92f5baab..183444d1 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,13 @@ # UI Patterns -[![Join the chat at https://gitter.im/nuvoleweb/ui_patterns](https://badges.gitter.im/nuvoleweb/ui_patterns.svg)](https://gitter.im/nuvoleweb/ui_patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/nuvoleweb/ui_patterns.svg?branch=8.x-1.x)](https://travis-ci.org/nuvoleweb/ui_patterns) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nuvoleweb/ui_patterns/badges/quality-score.png?b=8.x-1.x)](https://scrutinizer-ci.com/g/nuvoleweb/ui_patterns/?branch=8.x-1.x) -[![Documentation Status](https://readthedocs.org/projects/ui-patterns/badge/?version=8.x-1.x)](http://ui-patterns.readthedocs.io/en/8.x-1.x/?badge=8.x-1.x) +Define and expose self-contained UI patterns as Drupal plugins and use them +seamlessly as drop-in templates for [panels](https://www.drupal.org/project/panels), +[field groups](https://www.drupal.org/project/field_group), views, +[Display Suite](https://www.drupal.org/project/ds) view modes and field templates. -Define and expose self-contained UI patterns as Drupal plugins and use them seamlessly as drop-in templates for -[panels](https://www.drupal.org/project/panels), [field groups](https://www.drupal.org/project/field_group), views, -[Display Suite](https://www.drupal.org/project/ds) view modes and field templates. - -The UI Patterns module also integrates with with tools like [PatternLab](http://patternlab.io/) or modules like -[Component Libraries](https://www.drupal.org/project/components) thanks to -[definition overrides](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/patterns-definition.html#override-patterns-behavior). - -![Overview](https://raw.githubusercontent.com/nuvoleweb/ui_patterns/8.x-1.x/docs/images/patterns-overview.png) +The UI Patterns module also integrates with with tools like [PatternLab](http://patternlab.io/) +or modules like [Component Libraries](https://www.drupal.org/project/components) +thanks to [definition overrides](https://www.drupal.org/docs/contributed-modules/ui-patterns/define-your-patterns#s-override-patterns-behavior). ## Project overview @@ -22,51 +16,24 @@ The UI Patterns project provides 6 modules: - **UI Patterns**: the main module, it exposes the UI Patterns system APIs and it does not do much more than that. - **UI Patterns Library**: allows to define patterns via YAML and generates a pattern library page available at `/patterns` to be used as documentation for content editors or as a showcase for business. Use this module if you don't plan to - use more advanced component library systems such as PatternLab or Fractal. - [Learn more](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/patterns-definition.html) + use more advanced component library systems such as PatternLab or Fractal. + [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/define-your-patterns) - **UI Patterns Field Group**: allows to use patterns to format field groups provided by the [Field group](https://www.drupal.org/project/field_group) module. - [Learn more](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/field-group.html) + [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-with-field-groups) - **UI Patterns Layouts**: allows to use patterns as layouts. This allows patterns to be used on - [Display Suite](https://www.drupal.org/project/ds) view modes or on [panels](https://www.drupal.org/project/panels) - out of the box. [Learn more](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/layout-plugin.html) + [Display Suite](https://www.drupal.org/project/ds) view modes or on [panels](https://www.drupal.org/project/panels) + out of the box. [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-as-layouts) - **UI Patterns Display Suite**: allows to use patterns to format [Display Suite](https://www.drupal.org/project/ds) - field templates. [Learn more](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/field-templates.html) + field templates. [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-with-field-templates) - **UI Patterns Views**: allows to use patterns as Views row templates. - [Learn more](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/views.html) + [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-with-views) ## Try it out! Download and install the [Bootstrap Patterns](https://github.com/nuvoleweb/bootstrap_patterns) theme on a vanilla Drupal 8 installation to quickly try out the UI Patterns module. - ## Documentation -Documentation is hosted on [Read the Docs](https://readthedocs.org/) and available [here](http://ui-patterns.readthedocs.io/en/8.x-1.x). - -To build the documentation make sure you setup your environment by following -[these instructions](http://read-the-docs.readthedocs.io/en/latest/) first. - -After setting up your environment run: - -``` -$ cd docs -$ make html -``` - -The documentation is then available at ``./docs/_build/html/index.html``. - -If you want to contribute documentation you can setup and auto-compile that will watch for documentation changes by running: - -``` -$ make livehtml -``` - -You can then preview the compiled documentation at ``http://127.0.0.1:8000``. - -To build the documentation using Docker run: - -``` -$ docker run -it -v $(pwd)/docs:/docs xeizmendi/docker-sphinx make --directory=/docs html -``` +Documentation is available [here](https://www.drupal.org/docs/contributed-modules/ui-patterns). diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 7dffbb8d..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,195 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/UIPatterns.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/UIPatterns.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/UIPatterns" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/UIPatterns" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -livehtml: - sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index dc212ace..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,291 +0,0 @@ -# -*- coding: utf-8 -*- -# -# UI Patterns documentation build configuration file, created by -# sphinx-quickstart on Sun Jan 8 11:33:35 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os -import shlex - -# Check if this build is on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: - import sphinx_rtd_theme - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'UI Patterns' -copyright = u'2017, Nuvole Web' -author = u'Nuvole Web' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.x' -# The full version, including alpha/beta/rc tags. -release = '1.x' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -#html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'UIPatternsdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'UIPatterns.tex', u'UI Patterns Documentation', - u'Nuvole Web', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'uipatterns', u'UI Patterns Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'UIPatterns', u'UI Patterns Documentation', - author, 'UIPatterns', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/content/developer-documentation.rst b/docs/content/developer-documentation.rst deleted file mode 100644 index 23a01430..00000000 --- a/docs/content/developer-documentation.rst +++ /dev/null @@ -1,322 +0,0 @@ -Developer documentation -======================= - -Render patterns programmatically --------------------------------- - -Patterns can be rendered programmatically by using the following syntax: - -.. code-block:: php - - 'pattern', - '#id' => 'blockquote', - '#fields' => [ - 'quote' => 'You must do the things you think you cannot do.', - 'attribution' => 'Eleanor Roosevelt' - ] - ]; - - \Drupal::service('renderer')->render($elements); - -The code above will produce the following result: - -.. image:: ../images/developer-1.png - :align: center - :width: 650 - -To render a specific pattern variant use: - -.. code-block:: php - - 'pattern', - '#id' => 'blockquote', - '#variant' => 'highlighted', - '#fields' => [ - 'quote' => 'You must do the things you think you cannot do.', - 'attribution' => 'Eleanor Roosevelt' - ] - ]; - -It is also possible to just render a pattern preview as displayed on the patterns overview page in the following way -(since fields are already bundled within the pattern definition we don't need to re-declare them here): - -.. code-block:: php - - 'pattern_preview', - '#id' => 'blockquote', - ]; - - \Drupal::service('renderer')->render($elements); - - -Rendering the code above will produce the following output: - -.. image:: ../images/developer-2.png - :align: center - :width: 650 - -To render a specific pattern preview variant use: - -.. code-block:: php - - 'pattern_preview', - '#id' => 'blockquote', - '#variant' => 'highlighted', - ]; - - -Render patterns using Twig functions ------------------------------------- - -The UI Patterns module also exposes two Twig functions to easily render patterns into your Twig templates. - -The following two calls: - -.. code-block:: twig - - {{ pattern('button', {title: 'Link title', url: 'http://example.com'}) }} - {{ pattern_preview('modal') }} - -Will print: - -.. image:: ../images/developer-3.png - :align: center - :width: 650 - -To render pattern variants use: - -.. code-block:: twig - - {{ pattern('button', {title: 'Link title', url: 'http://example.com'}, 'highlighted') }} - {{ pattern_preview('modal', 'highlighted') }} - -Since patterns are rendered using the render element described above all libraries and preprocess hooks will be ran when -using Twig functions. - -Working with pattern suggestions --------------------------------- - -Modules that want to add theme hook suggestions to patterns can do that by implementing the following hook: - -.. code-block:: php - - isOfType('views_row')) { - $hook = $variables['theme_hook_original']; - $view_name = $context->getProperty('view_name'); - $display = $context->getProperty('display'); - - $suggestions[] = $hook . '__views_row__' . $view_name; - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $display; - } - } - -The hook above is a ``hook_theme_suggestions_alter()`` specifically designed for patterns. The hook is invoked -with a ``PatternContext`` object that describes information on where the current pattern is being used. - -Pattern suggestions can, for example, allow developers to use alternative pattern templates in specific contexts or to -"massage" data before it sent to the pattern by implementing fine-grained preprocess hooks. - -The following suggestions are automatically exposed by the project's sub-modules: - -.. code-block:: php - - getContextProperty('view'); - foreach ($view->display_handler->getFieldLabels() as $name => $label) { - $sources[] = $this->getSourceField($name, $label); - } - return $sources; - } - - } - -At the moment the available source plugin tags are the following: - -- ``entity_display``: provided by the ``ui_patterns`` module and triggered on an entity display configuration page. -- ``ds_field_template``: provided by the ``ui_patterns_ds`` module and triggered when setting up a field template - on an entity display configuration page. -- ``views_row``: provided by the ``ui_patterns_views`` module and triggered on a Views row setting pane. -- ``test``: provided by the ``ui_patterns_test`` module and used in tests. - - -Alter pattern configuration forms ---------------------------------- - -You can alter UI Patterns configuration forms by implementing ``hook_ui_patterns_display_settings_form_alter()``. - -For example, the following implementation adds a CSS class input field to the pattern configuration: - -.. code-block:: php - - 'input', - '#title' => t('Class name'), - ]; - } - -This hook alter forms that are built using the ``PatternDisplayFormTrait`` trait, meaning: - -- Display Suite field templates -- Field groups -- Views - -If you want to alter an entity layout form that uses UI Patters for its layout use -``hook_ui_patterns_layouts_display_settings_form_alter()`` instead, for example: - -.. code-block:: php - - 'input', - '#title' => t('Class name'), - ]; - } diff --git a/docs/content/field-group.rst b/docs/content/field-group.rst deleted file mode 100644 index 090fd8e8..00000000 --- a/docs/content/field-group.rst +++ /dev/null @@ -1,28 +0,0 @@ -Use patterns with Field Groups -============================== - -Patterns can be used to style entities' `field groups `_ thanks to the -``ui_patterns_field_group`` module. - -For example, say we want to show some metadata associated with an article, such as author, post date and tags. - -After enabling the module we create a new field group of type **Pattern** and drag all fields you want to use in that -group, as shown below: - -.. image:: ../images/fieldgroup-1.png - :align: center - :width: 450 - -Once all fields are in place we access the field group settings and choose the **Metadata** pattern. At this point we -map the fields to the pattern destination fields and save our settings: - -.. image:: ../images/fieldgroup-2.png - :align: center - :width: 450 - -Articles will now always use the **Metadata** pattern to style that field group, as shown below: - -.. image:: ../images/fieldgroup-3.png - :align: center - :width: 550 - diff --git a/docs/content/field-templates.rst b/docs/content/field-templates.rst deleted file mode 100644 index 755925bb..00000000 --- a/docs/content/field-templates.rst +++ /dev/null @@ -1,47 +0,0 @@ -Use patterns with Field templates -================================= - -Patterns can be used as Display Suite field templates by enabling the ``ui_patterns_ds`` module. This opens the -following interesting possibilities: - -- Link fields can be styled as buttons by mapping their URL and link titles to specific pattern destinations. -- Image fields can be styled as an "image with caption" by mapping a formatted image and title to specific pattern - destinations. - -Let's see how to implement the first example having the following pattern definition: - -.. code-block:: yaml - - button: - label: Button - description: A simple button. - fields: - title: - type: text - label: Label - description: The button label - preview: Submit - url: - type: text - label: URL - description: The button URL - preview: http://example.com - -On the entity display setting page we access the link field setting by clicking on the gear icon: - -.. image:: ../images/field-template-1.png - :align: center - :width: 700 - -Then, after selecting the **Pattern** field template and the **Button** pattern, we map the link field columns to the -pattern's fields defined above: - -.. image:: ../images/field-template-2.png - :align: center - :width: 700 - -Our multi-valued link field will then be formatted as follow: - -.. image:: ../images/field-template-3.png - :align: center - :width: 300 diff --git a/docs/content/layout-plugin.rst b/docs/content/layout-plugin.rst deleted file mode 100644 index 188bfad3..00000000 --- a/docs/content/layout-plugin.rst +++ /dev/null @@ -1,56 +0,0 @@ -Use patterns as layouts -======================= - -Patterns can be used as layouts thanks to the ``ui_patterns_layouts`` module. - -Once exposed as layouts patterns can be used to arrange fields on entities like nodes, -`paragraphs `_, etc. or to place blocks on a page using -`Panels `_. - -In the example below we will style a **Jumbotron** paragraph using the Jumbotron paragraph. - -Once on the paragraph **Manage display** page we choose the **Jumbotron** pattern as layout: - -.. image:: ../images/layouts-1.png - :align: center - :width: 450 - -After doing that the pattern fields will be exposed as layout regions, so given the following definition: - -.. code-block:: yaml - - jumbotron: - label: Jumbotron - description: A lightweight, flexible component that can optionally extend the entire viewport to showcase key content on your site. - fields: - title: - type: text - label: Title - description: Jumbotron title. - preview: Hello, world! - subtitle: - type: text - label: Description - description: Jumbotron description. - preview: This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information. - -We will get the following layout regions: - -.. image:: ../images/layouts-2.png - :align: center - :width: 450 - -We can now arrange the paragraph fields on the layout and save our settings. - -The paragraph below: - -.. image:: ../images/layouts-3.png - :align: center - :width: 450 - -will be now styled using the **Jumbotron** pattern as follows: - -.. image:: ../images/layouts-4.png - :align: center - :width: 550 - diff --git a/docs/content/patterns-definition.rst b/docs/content/patterns-definition.rst deleted file mode 100644 index 93562195..00000000 --- a/docs/content/patterns-definition.rst +++ /dev/null @@ -1,203 +0,0 @@ -Define your patterns -==================== - -Patterns can be exposed by both modules and themes by enabling the ``ui_patterns_library`` module. - -Defined patterns will be available at ``/patterns``, only accessible by roles having the ``access patterns page`` -permission. Below an example of a pattern library page styled using the `Bootstrap Patterns `_ -theme: - -.. image:: ../images/pattern-library.png - :align: center - -Pattern definitions -------------------- - -To define your patterns simply create a YAML file named ``MY_MODULE.ui_patterns.yml`` or ``MY_THEME.ui_patterns.yml`` -and list them using the following format: - -.. code-block:: yaml - - blockquote: - label: Blockquote - description: Display a quote with attribution information. - variants: - default: - label: Default - description: An ordinary quote. - highlighted: - label: Highlighted - description: A special quote. - fields: - quote: - type: text - label: Quote - description: Quote text. - preview: Life is like riding a bicycle. To keep your balance, you must keep moving. - attribution: - type: text - label: Attribution - description: Quote attribution. - preview: Albert Einstein - libraries: - - MY_MODULE/module_library_one - - MY_MODULE/module_library_two - - pattern_library_one: - css: - component: - css/my_component.css: {} - http://example.com/external.min.css: { type: external, minified: true } - - pattern_library_two: - js: - js/library_two.js: {} - dependencies: - - core/jquery - -Let's break this down: - -``id`` - The root of a new pattern definition (``blockquote`` in the example above). It must contain only lowercase - characters, numbers, underscores and hyphens (i.e. it should validate against ``[^a-z0-9_-]+``). -``label`` - Pattern label, used on pattern library page. -``description`` - Pattern description (optional), used on pattern library page. -``fields`` - Hash defining the pattern fields (optional). Each field can have the following properties: - - ``type`` - Field type, can be ``text``, ``numeric``, etc. at the moment only used for documentation purposes. Optional. - ``label`` - Field label, used on pattern library page. - ``description`` - Field description, used on pattern library page. Optional. - ``preview`` - Preview content, used on pattern library page. It can be either a string or a Drupal render array, in which case - we can use keys like ``type: processed_text`` or ``theme: image``. Optional. - -``variants`` - Hash defining the pattern variants (optional). Each variant can have the following properties: - - ``label`` - Variant label, used on pattern library page. - ``description`` - Variant description, used on pattern library page. Optional. - -``libraries`` - List of libraries to be loaded when rendering the pattern (optional). UI patterns are supposed to be self-contained so they - should define along all needed libraries. - -Once the pattern is defined the module will expose them as standard Drupal theme definitions. - -For example, given the ``my_pattern`` pattern ID then a theme function ``pattern_my_pattern`` is created and, -consequently, the module will look for a template file called ``pattern-my-pattern.html.twig``. - -The Twig template can be placed it either under ``MY_MODULE/templates``, if the pattern is exposed by a module, -or under ``MY_THEME/templates``, if it is exposed by a theme. As expected themes will override templates exposed by modules. - -For example, consider the following Twig template file ``pattern-blockquote.html.twig`` for the ``blockquote`` pattern -defined above: - -.. code-block:: twig - -
-

{{ quote }}

-
{{ attribution }}
-
- - -The pattern will be rendered as shown below (styled using the `Bootstrap `_ theme): - -.. image:: ../images/blockquote-preview.png - :align: center - -**Remember**: we can always visit the ``/pattern`` page in order to have access to a full preview of all our patterns. - -Organize your patterns in sub-folders -------------------------------------- - -Patterns can be defined using a single ``NAME.ui_patterns.yml`` file. However, in case of sites with a large number of -patterns, this might quickly becomes difficult to manage. - -If that's the case pattern definitions can also be organised in sub-folders, as shown below: - -.. code-block:: bash - - . - ├── templates - │   └── patterns - │   ├── button - │   │   ├── button.ui_patterns.yml - │   │   └── pattern-button.html.twig - │   ├── media - │   │   ├── media.ui_patterns.yml - │   │   └── pattern-media.html.twig - ... - │   └── pattern-jumbotron.html.twig - ├── ui_patterns_test_theme.info.yml - └── ui_patterns_test_theme.ui_patterns.yml - -**Note:** the example above is taken by the actual test target site that is used to test the module itself: have a look -at ``./tests/README.md`` and at ``./tests/target/custom`` for working examples on how to use the UI Patterns module. - -Expose pattern assets as libraries ----------------------------------- - -In case you wish to bundle your assets within the pattern directory you can define libraries with the alternative syntax -below: - -.. code-block:: yaml - - blockquote: - label: Blockquote - ... - libraries: - ... - - pattern_library_one: - css: - component: - css/my_component.css: {} - http://example.com/external.min.css: { type: external, minified: true } - - pattern_library_two: - js: - js/library_two.js: {} - -Libraries defined as above will be automatically loaded when the pattern is rendered. They are also exposed as ordinary -Drupal libraries as follows: ``ui_patterns/PATTERN_ID.LIBRARY_NAME`` - -For example, the two local libraries above can be attached to your render arrays in the following way: - -.. code-block:: php - - `_, so the following notations - are valid examples: - -.. code-block:: yaml - - use: "@my_module/templates/my-template.html.twig" - -.. code-block:: yaml - - use: "@molecules/media/media-block.twig" - -The possibility of using stand-alone Twig templates allows for a swift integration with tools like -`PatternLab `_ or modules like `Component Libraries `_. - -**Attention:** always remember to double-quote ``use:`` values or some YAML parsers (including PatternLab's) will -complain. diff --git a/docs/content/tests.rst b/docs/content/tests.rst deleted file mode 100644 index 2d5a7352..00000000 --- a/docs/content/tests.rst +++ /dev/null @@ -1,55 +0,0 @@ -Working with tests -================== - -UI Patterns is tested using `PHPUnit in Drupal 8 `_. - -To build the test site just run: - -.. code-block:: bash - - $ composer install - -After that you'll find a fully functional Drupal 8 site under ``./build``, thanks to the integration with the -`OpenEuropa Task Runner `_. - -Tu run tests use: - -.. code-block:: bash - - $ ./vendor/bin/phpunit - -Working with coding standards -============================= - -UI Patterns coding standards checks are ran using `GrumPHP `_. - -.. code-block:: bash - - $ ./vendor/bin/grumphp run - -Docker Compose -============== - -UI Patterns ships with a ``docker-compose.yml`` file which can be used to streamline local development and tests execution. - -Setup Docker Compose by copying ``docker-compose.yml`` to ``docker-compose.override.yml`` and replace ``${TRAVIS_PHP_VERSION}`` -with the desired PHP version (either "5.6" or "7.1"). - -After that run: - -.. code-block:: bash - - $ docker-compose up -d - $ docker-compose exec -u www-data php composer install - $ docker-compose exec -u www-data php ./vendor/bin/run drupal:site-setup - $ docker-compose exec -u www-data php ./vendor/bin/run drupal:site-install - $ docker-compose exec -u www-data php chown -R www-data:www-data build - -You'll then have a fully functional test site at `http://localhost:8080 `_. - -To run all tests use: - -.. code-block:: bash - - $ docker-compose exec -u www-data php ./vendor/bin/grumphp run - $ docker-compose exec -u www-data php ./vendor/bin/phpunit diff --git a/docs/content/views.rst b/docs/content/views.rst deleted file mode 100644 index a0ac1db2..00000000 --- a/docs/content/views.rst +++ /dev/null @@ -1,18 +0,0 @@ -Use patterns with Views ------------------------ - -Patterns can be used as Views row templates thanks to the ``ui_patterns_views`` module, which exposes a **Patterns** row -style plugin. - -.. image:: ../images/views-1.png - :align: center - :width: 550 - -After choosing the **Pattern** row style plugin we can map the current Views display fields to the chosen pattern -destinations, as shown below: - -.. image:: ../images/views-2.png - :align: center - :width: 550 - -Views rows will now be styled using the selected pattern. diff --git a/docs/images/blockquote-preview.png b/docs/images/blockquote-preview.png deleted file mode 100644 index 0312bfabe81e7aaff99cc1bc194b44a8e7c0b1c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27477 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 diff --git a/docs/images/developer-1.png b/docs/images/developer-1.png deleted file mode 100644 index 35398edb45424fa3da1bf048eb65428203083140..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8597 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 diff --git a/docs/images/developer-2.png b/docs/images/developer-2.png deleted file mode 100644 index f59da9faa56257c9308f5122e502f2df828c543c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10028 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

Sli*>o( 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 diff --git a/docs/images/field-template-1.png b/docs/images/field-template-1.png deleted file mode 100644 index 13285d96d8d18dc27ecc0df1eb0942ed85b79a41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6624 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 diff --git a/docs/images/field-template-2.png b/docs/images/field-template-2.png deleted file mode 100644 index 9e6a7e239120621cce87c1d78ce5d09e4af57c6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39499 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 diff --git a/docs/images/field-template-3.png b/docs/images/field-template-3.png deleted file mode 100644 index 05dfda9f4457644d11f3dfbe122680bd254f44af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2369 zcmV-H3BLA;P)Px&08mU+MgRZ*D{ZOz>gFtU)c^neA)D&o&eJP*x+iL< z`TzfP$o9CWxZL~y)Z*?dcDX5XvR;tC9Hr{4=l|9F|9p44`ThSZcFFqa;{X5o|NZjo z|Nm!qx+Rb0@&Er=ce?-j@bdfr|NH%3#PlJX=KuThEOyTS`}*Yi|M~d#q~-rCb=c$o z|9Ga;bItbu?dJOC;j`)gU%>ORow$Q{y8rg;-^$P=kFUn+|Ns5{ z>-_%l{r}$E;UtjP^W@>R((0b${`vd)|Nr&o+1vU1{SdeC|M>1Kb>Cv9)4SB?fswhO z&FiAB#@6!w`StLv)$!lT%K7*DO~m&4=;re7^|Fz<|LW!W_Ve@V@0WMFxTncY!1Uen z{w#Fi`t9<#rqUdx<;V8_*~ZAlv$y5y@Uf@3|NQv*{rV-4-q76gWSq_AZ&AAx{%WJx$OV!1Rh$m8_T;qv79`mw6siIKT@rq;c<%h22Bm65rv-1Vrv;jYQ*<>czn;q<}n|1EUn z*wx&&#oCw*B^W@&kv$tQs@BjDnP_gM4t?K62)me4d^7ZzpxZJJO>fYPupvmlf z$?|!d>YT3We0IrrrRsW*<-^e6xUaYC_x`Jp(=Bx7v7F5E?Ck6H^U}h_rgyrR;r!m( z@YvMoC6C~y(CUY+>66+0*8l(o-$_J4RCwC$ns;0jM-;~&cZ<(G4&gY;(T*mZ+))KZ z0kMly7ibjVC*x}4shb-5A>M}mGOjKj#>e;= zALC38?zD1<5%&h&(tyxoz{ZxGVDECkf90IS$3GYsTj=J~2&WM!aR`)uP=LGsGcR0sNGQkXcu)#j z4zm#Fn_41Htb(Ltf6EPMgh7)%K1`LEVt#0?B$Btuz@xhQ3d18)2XUfa5ydo`b-7W z)jEJdi{!@Pxlp{T3c!vJDqf0!S%I=fObLgm4L_VoF}3zArf)F^nqk}%b48Xic#R`lqE&DD-bfK?^%@!|eenkxK}%pe;(Kz! zQ8^(G2Z*Mq9-q@V7Y<`CxsdE;3*i^)kJ5=4YNA(SdM|`dh^wHj4tgWDmwp^7Ek)e# zz&BoCD?2LiQOx$i+ZR0wHT4d@sQX2SSu=p9pw*^d*TWs^1gBB)QAoJ}Nh7XRvK+S% z=ShPVd`1oGsH6xxQz^PS`Wl%yr>G;VI($xJ>*O*dmLl73mIFzpr7|3m)#WNo42kj> zjZQFLFAdh0(X^TDz$cRZWUEna!=l5Lm^=yvc?aL!R$JBs((H$*{v}YbIw9N*pIFr% zc`Om2lM|olg*ti%!dGr{;?u!W@uuo~1lEkt#rQYrq%B*`SIxqtKI_)4o3U2Djk30E zP49TSFz%-l#DsM|O_oa>_?juUUkTfT;Kb)q$V>P%m=2I{7WG^8X-d2<_{e#BAPc)6P5vU#t?7N1-6^eD`Va9vSXa>t7D`DZ&+B z7!*Fb0tjE)Gf%2Z$M)y<(0hl$C!dxOzD&4eoI$Sm<^w&v_<{xAH`EHBn{nhaPv774r{df#S40hyd3FBH#H{r zLf*g^N6TMo5jAGAsNW!_VdW#QNF(ME1;VKARH))k+^c?}X^lw{biEnhEdkh6l8qBB zkd?X&qVtAW=)tj6rGl@ZdVKE2nPQq3a+rNaYyRR%IBFVQ7RKUDWGStrnNcCp6n9Ye z4Wf4mr(Wta^OU>-d|QSVBBs*{dHtVv0G#pXp?Ssx_y4x1wkpY(F&+ zqunerBSX2|(q)6aYO`BN*KcsWP;8rznQ&S>X5VcYq|Atj&hy8X!Rs?1>-^twNhulm zv|n~ylA*_D-mv2RKE}uR7$4*Nuj1oF8udD)!AFGZbwr2{sMYI$8Xu3W*YQXei^XEG nSS%Kc#bU8oEEbE!`akI(ROtdQs*B&^00000NkvXXu0mjfCr(|V diff --git a/docs/images/fieldgroup-1.png b/docs/images/fieldgroup-1.png deleted file mode 100644 index 386bf0ce2461df2d3be010bd67b68aeea44db099..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23383 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

  • 9&fp`6j&-!NMjeo8NV)S<{B+_xnw9*@)(a8L62 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%0D1cy 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 W1nD4rdO*kk0000K00004XF*Lt006O% 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 Lf>^DnVbK2oC0sPG diff --git a/docs/images/pattern-library.png b/docs/images/pattern-library.png deleted file mode 100644 index 994dd76bf82fb0ac78d4d8dea543c4effc1b07bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60660 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
    ^&7RS@E^%7b}xDig&^@ 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
    With variants pattern field
    ' - - meta: - name: 'two' - label: 'Two' - description: 'Second variant' - preview: '
    With variants pattern field
    ' - - name: 'with_custom_theme_hook' theme hook: 'custom_theme_hook' label: 'With custom theme hook' @@ -43,39 +54,6 @@ label: 'Field' description: 'Field description' -- name: 'button' - label: 'Button' - description: 'A simple button.' - has_variants: true - preview: ~ - fields: - - name: 'title' - type: 'text' - label: 'Label' - description: 'The button label' - preview: 'Submit' - - name: 'url' - type: 'text' - label: 'URL' - description: 'The button URL' - preview: 'http://example.com' - variants: - - meta: - name: 'default' - label: 'Default' - description: 'A default button, nothing to see here.' - preview: 'Submit' - - meta: - name: 'primary' - label: 'Primary' - description: 'A primary button.' - preview: 'Submit' - - meta: - name: 'danger' - label: 'Danger' - description: 'A button for dangerous operations.' - preview: 'Delete' - - name: 'with_local_libraries' label: 'With local libraries' description: 'Pattern defining local libraries' @@ -97,3 +75,25 @@ type: 'string' label: 'Field' description: 'Field description' + +- name: 'with_variants' + label: 'With variants' + description: 'Pattern with variants' + has_variants: true + preview: ~ + fields: + - name: 'field' + type: 'string' + label: 'Field' + description: 'Field description' + variants: + - meta: + name: 'one' + label: 'One' + description: 'First variant' + preview: '
    With variants pattern field
    ' + - meta: + name: 'two' + label: 'Two' + description: 'Second variant' + preview: '
    With variants pattern field
    ' diff --git a/src/UiPatternsManager.php b/src/UiPatternsManager.php index 08d4ff6b..d2ddd30c 100644 --- a/src/UiPatternsManager.php +++ b/src/UiPatternsManager.php @@ -73,6 +73,7 @@ public function getDefinitions() { $definitions[$definition['id']] = $definition; unset($definitions[$id]); } + $definitions = $this->getSortedDefinitions($definitions); $this->setCachedDefinitions($definitions); } return $definitions; @@ -107,4 +108,38 @@ protected function providerExists($provider) { return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider); } + /** + * Sort pattern definitions by label then ID. + * + * @param array $definitions + * The patterns plugin definitions. + * + * @return array + * The sorted definitions. + */ + protected function getSortedDefinitions(array $definitions) { + // Sort by label ignoring parenthesis. + uasort($definitions, function ($item1, $item2) { + $sort_result = 0; + + if (isset($item1['label'], $item2['label'])) { + // Ignore parenthesis. + $label1 = str_replace(['(', ')'], '', $item1['label']); + $label2 = str_replace(['(', ')'], '', $item2['label']); + $sort_result = $label1 <=> $label2; + } + + // Fallback to pattern ID. + if ($sort_result === 0) { + // In case the pattern ID starts with an underscore. + $id1 = str_replace('_', '', $item1['id']); + $id2 = str_replace('_', '', $item2['id']); + $sort_result = $id1 <=> $id2; + } + return $sort_result; + }); + + return $definitions; + } + } diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/aaa.ui_patterns.yml b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/aaa.ui_patterns.yml new file mode 100644 index 00000000..806c65a9 --- /dev/null +++ b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/aaa.ui_patterns.yml @@ -0,0 +1,2 @@ +aaa: + label: '(Label 2)' diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/pattern-aaa.html.twig b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/pattern-aaa.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/bbb.ui_patterns.yml b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/bbb.ui_patterns.yml new file mode 100644 index 00000000..c0c0b3a5 --- /dev/null +++ b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/bbb.ui_patterns.yml @@ -0,0 +1,2 @@ +bbb: + label: 'Label 2' diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/pattern-bbb.html.twig b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/pattern-bbb.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/ccc.ui_patterns.yml b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/ccc.ui_patterns.yml new file mode 100644 index 00000000..a6db5759 --- /dev/null +++ b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/ccc.ui_patterns.yml @@ -0,0 +1,2 @@ +ccc: + label: 'Label 1' diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/pattern-ccc.html.twig b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/pattern-ccc.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/lll.ui_patterns.yml b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/lll.ui_patterns.yml new file mode 100644 index 00000000..2544b985 --- /dev/null +++ b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/lll.ui_patterns.yml @@ -0,0 +1,2 @@ +lll: + label: 'Label 3' diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/pattern-lll.html.twig b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/pattern-lll.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/pattern-zzz.html.twig b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/pattern-zzz.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/zzz.ui_patterns.yml b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/zzz.ui_patterns.yml new file mode 100644 index 00000000..bb9ef7da --- /dev/null +++ b/tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/zzz.ui_patterns.yml @@ -0,0 +1,2 @@ +zzz: + label: 'Label 1' diff --git a/tests/modules/ui_patterns_definitions_sort_test/ui_patterns_definitions_sort_test.info.yml b/tests/modules/ui_patterns_definitions_sort_test/ui_patterns_definitions_sort_test.info.yml new file mode 100644 index 00000000..66fed3bf --- /dev/null +++ b/tests/modules/ui_patterns_definitions_sort_test/ui_patterns_definitions_sort_test.info.yml @@ -0,0 +1,4 @@ +name: 'UI Patterns Definitions Sort Test' +description: 'Provides test plugins.' +type: module +package: 'Testing' diff --git a/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml b/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml index 60fb9b9a..7e18e664 100644 --- a/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml +++ b/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml @@ -1,4 +1,4 @@ -name: 'UI Patterns field source test' +name: 'UI Patterns Field Source Test' description: 'Provides test plugin.' type: module package: 'Testing' diff --git a/tests/src/Kernel/UiPatternsManagerSortingTest.php b/tests/src/Kernel/UiPatternsManagerSortingTest.php new file mode 100644 index 00000000..850ba71e --- /dev/null +++ b/tests/src/Kernel/UiPatternsManagerSortingTest.php @@ -0,0 +1,59 @@ +getDefinitions(); + + $keys = array_keys($definitions); + $expected_keys_order = [ + 'ccc', + 'zzz', + 'aaa', + 'bbb', + 'lll', + ]; + + $this->assertEquals($expected_keys_order, $keys); + } + +} From 4bbd13f4a66416c9a16d7d124e445b850d9654bf Mon Sep 17 00:00:00 2001 From: Edouard Cunibil <11996-DuaelFr@users.noreply.drupalcode.org> Date: Mon, 16 Jan 2023 10:55:21 +0000 Subject: [PATCH 17/81] Issue #3311471 by DuaelFr, herved, G4MBINI, Grimreaper, donquixote, msnassar, keszthelyi, vlad.dancer, claudiu.cristea: Detect all theme patterns --- .../src/Plugin/Deriver/LibraryDeriver.php | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php index a0da46e0..c047315d 100644 --- a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php +++ b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php @@ -154,26 +154,13 @@ public function getPatterns() { /** * Create a list of all directories to scan. * - * This includes all module directories and directories of the default theme - * and all of its possible base themes. + * This includes all module and theme directories. * * @return array * An array containing directory paths keyed by their extension name. */ protected function getDirectories() { - $default_theme = $this->themeHandler->getDefault(); - $base_themes = $this->themeHandler->getBaseThemes($this->themeHandler->listInfo(), $default_theme); - $theme_directories = $this->themeHandler->getThemeDirectories(); - - $directories = []; - if (isset($theme_directories[$default_theme])) { - $directories[$default_theme] = $theme_directories[$default_theme]; - foreach (array_keys($base_themes) as $name) { - $directories[$name] = $theme_directories[$name]; - } - } - - return $directories + $this->moduleHandler->getModuleDirectories(); + return $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories(); } /** From 7d9b5b44e8b01d3b60fa41743e92cb7f4925a4c2 Mon Sep 17 00:00:00 2001 From: Edouard Cunibil <11996-DuaelFr@users.noreply.drupalcode.org> Date: Mon, 16 Jan 2023 16:35:54 +0000 Subject: [PATCH 18/81] Issue #3311471 by herved, DuaelFr: fix pattern overriding in sub-themes. --- .../src/Plugin/Deriver/LibraryDeriver.php | 31 ++++++++++++++++++- .../tests/fixtures/overview-page-patterns.yml | 11 +++++++ .../pattern-subtheme-override.html.twig | 1 + ...ui_patterns_library_subtheme_test.info.yml | 5 +++ ...erns_library_subtheme_test.ui_patterns.yml | 9 ++++++ .../pattern-subtheme-override.html.twig | 1 + ...atterns_library_theme_test.ui_patterns.yml | 10 ++++++ .../UiPatternsLibraryOverviewTest.php | 2 +- 8 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig create mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml create mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml create mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig diff --git a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php index c047315d..57f757c9 100644 --- a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php +++ b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php @@ -160,7 +160,36 @@ public function getPatterns() { * An array containing directory paths keyed by their extension name. */ protected function getDirectories() { - return $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories(); + // Sort modules list. + $module_list = $this->moduleHandler->getModuleList(); + $module_list = $this->moduleHandler->buildModuleDependencies($module_list); + $module_list = $this->sortExtensionList($module_list); + + // Sort themes list. + $theme_list = $this->themeHandler->listInfo(); + $theme_list = $this->sortExtensionList($theme_list); + + $module_dirs = array_replace($module_list, $this->moduleHandler->getModuleDirectories()); + $theme_dirs = array_replace($theme_list, $this->themeHandler->getThemeDirectories()); + + return $module_dirs + $theme_dirs; + } + + /** + * Sort an extension list. + * + * @param \Drupal\Core\Extension\Extension[] $extensions + * The extension list. + * + * @return string[] + * + */ + protected function sortExtensionList(array $extensions) { + $extensions_sort = array_map(function ($extension) { + return $extension->sort; + }, $extensions); + arsort($extensions_sort); + return array_replace($extensions_sort, $extensions); } /** diff --git a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml index 8443c4df..b7de9789 100644 --- a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml +++ b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml @@ -97,3 +97,14 @@ label: 'Two' description: 'Second variant' preview: '
    With variants pattern field
    ' + +- name: 'subtheme_override' + label: '[Overridden] Sub-theme override' + description: '[Overridden] Sub-theme override description' + has_variants: false + preview: '
    [Overridden] Simple pattern field
    ' + fields: + - name: 'field' + type: 'string' + label: '[Overridden] Field' + description: '[Overridden] Field description' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig new file mode 100644 index 00000000..e9ff3bb0 --- /dev/null +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig @@ -0,0 +1 @@ +
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml new file mode 100644 index 00000000..495363de --- /dev/null +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml @@ -0,0 +1,5 @@ +name: 'UI Patterns library sub-theme test' +type: theme +description: 'Test sub-theme for UI Patterns.' +package: 'Testing' +base theme: ui_patterns_library_theme_test diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml new file mode 100644 index 00000000..94f425bf --- /dev/null +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml @@ -0,0 +1,9 @@ +subtheme_override: + label: '[Overridden] Sub-theme override' + description: '[Overridden] Sub-theme override description' + fields: + field: + type: 'string' + label: '[Overridden] Field' + description: '[Overridden] Field description' + preview: '[Overridden] Simple pattern field' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig new file mode 100644 index 00000000..7a012355 --- /dev/null +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig @@ -0,0 +1 @@ +
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml index 8d2b4ab8..e40305be 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml @@ -35,3 +35,13 @@ with_custom_theme_hook: label: 'Field' description: 'Field description' preview: 'Pattern field value' + +subtheme_override: + label: 'Sub-theme override' + description: 'Sub-theme override description' + fields: + field: + type: 'string' + label: 'Field' + description: 'Field description' + preview: 'Simple pattern field' diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php index a649920e..0f3192c2 100644 --- a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php +++ b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php @@ -27,7 +27,7 @@ class UiPatternsLibraryOverviewTest extends WebDriverTestBase { * * @var string */ - protected $defaultTheme = 'ui_patterns_library_theme_test'; + protected $defaultTheme = 'ui_patterns_library_subtheme_test'; /** * {@inheritdoc} From 06a4f47de81650f261466fc6e5dd8310faad9cf3 Mon Sep 17 00:00:00 2001 From: Edouard Cunibil <11996-DuaelFr@users.noreply.drupalcode.org> Date: Tue, 17 Jan 2023 08:57:58 +0000 Subject: [PATCH 19/81] Issue #3333738 by Grimreaper, DuaelFr: UI Patterns DS no more compatible with Symfony 6 --- .../src/Plugin/DsFieldTemplate/Pattern.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/ui_patterns_ds/src/Plugin/DsFieldTemplate/Pattern.php b/modules/ui_patterns_ds/src/Plugin/DsFieldTemplate/Pattern.php index c45e649e..1100bc3d 100644 --- a/modules/ui_patterns_ds/src/Plugin/DsFieldTemplate/Pattern.php +++ b/modules/ui_patterns_ds/src/Plugin/DsFieldTemplate/Pattern.php @@ -125,7 +125,11 @@ public function alterForm(&$form) { * Context array. */ protected function getContext() { - $fields = $this->parameters->get('fields'); + $parameters = $this->parameters->all(); + if (!isset($parameters['fields']) || !is_array($parameters['fields'])) { + return []; + } + $fields = $parameters['fields']; $field_name = $this->getCurrentField(); return [ @@ -155,11 +159,14 @@ public function defaultConfiguration() { * Name of field currently being edited. */ protected function getCurrentField() { - $fields = array_filter($this->parameters->get('fields', []), function ($field) { - return isset($field['settings_edit_form']['third_party_settings']['ds']['ft']['id']) && $field['settings_edit_form']['third_party_settings']['ds']['ft']['id'] == 'pattern'; - }); - $fields = array_keys($fields); - $field = reset($fields); + $parameters = $this->parameters->all(); + if (isset($parameters['fields']) && is_array($parameters['fields'])) { + $fields = array_filter($parameters['fields'], function ($field) { + return isset($field['settings_edit_form']['third_party_settings']['ds']['ft']['id']) && $field['settings_edit_form']['third_party_settings']['ds']['ft']['id'] == 'pattern'; + }); + $fields = array_keys($fields); + $field = reset($fields); + } if (empty($field)) { $trigger_element = $this->parameters->get('_triggering_element_name'); From af379f1f3576d4d19543bfc8bad635493ba8787f Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Fri, 13 Jan 2023 18:00:49 +0100 Subject: [PATCH 20/81] Issue #3328272 by Grimreaper, DuaelFr: Group patterns in /patterns and select lists --- .../Controller/PatternsLibraryController.php | 21 +- .../patterns-overview-page.html.twig | 65 +-- src/Annotation/UiPattern.php | 18 + src/Definition/PatternDefinition.php | 314 ++++++++------- src/UiPatternsManager.php | 189 ++++++--- src/UiPatternsManagerInterface.php | 86 ++++ .../patterns/aaa/aaa.ui_patterns.yml | 2 - .../patterns/aaa/pattern-aaa.html.twig | 0 .../patterns/bbb/bbb.ui_patterns.yml | 2 - .../patterns/bbb/pattern-bbb.html.twig | 0 .../patterns/ccc/ccc.ui_patterns.yml | 2 - .../patterns/ccc/pattern-ccc.html.twig | 0 .../patterns/lll/lll.ui_patterns.yml | 2 - .../patterns/lll/pattern-lll.html.twig | 0 .../patterns/zzz/pattern-zzz.html.twig | 0 .../patterns/zzz/zzz.ui_patterns.yml | 2 - ...ui_patterns_definitions_sort_test.info.yml | 4 - .../src/DummyUiPatternsManager.php | 79 ++++ .../ui_patterns_test.info.yml | 4 + .../Kernel/UiPatternsManagerSortingTest.php | 59 --- .../Unit/Definition/PatternDefinitionTest.php | 70 ++-- tests/src/Unit/UiPatternsManagerTest.php | 373 ++++++++++++++++++ ui_patterns.module | 10 +- ui_patterns.services.yml | 6 +- 24 files changed, 971 insertions(+), 337 deletions(-) create mode 100644 src/UiPatternsManagerInterface.php delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/aaa.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/pattern-aaa.html.twig delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/bbb.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/pattern-bbb.html.twig delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/ccc.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/pattern-ccc.html.twig delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/lll.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/pattern-lll.html.twig delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/pattern-zzz.html.twig delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/zzz.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_definitions_sort_test/ui_patterns_definitions_sort_test.info.yml create mode 100644 tests/modules/ui_patterns_test/src/DummyUiPatternsManager.php create mode 100644 tests/modules/ui_patterns_test/ui_patterns_test.info.yml delete mode 100644 tests/src/Kernel/UiPatternsManagerSortingTest.php create mode 100644 tests/src/Unit/UiPatternsManagerTest.php diff --git a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php index bbec1838..cd941ca3 100644 --- a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php +++ b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php @@ -77,17 +77,18 @@ public function single($name) { * Patterns overview page render array. */ public function overview() { - $patterns = []; - foreach ($this->patternsManager->getDefinitions() as $definition) { - $patterns[$definition->id()] = $definition->toArray() + [ - 'meta' => [ - '#theme' => 'patterns_meta_information', - '#pattern' => $definition->toArray(), - ], - 'rendered' => $this->getPatternRenderArray($definition), - 'definition' => $definition->toArray(), - ]; + foreach ($this->patternsManager->getGroupedDefinitions() as $groupName => $groupedDefinitions) { + foreach ($groupedDefinitions as $definition) { + $patterns[$groupName][$definition->id()] = $definition->toArray() + [ + 'meta' => [ + '#theme' => 'patterns_meta_information', + '#pattern' => $definition->toArray(), + ], + 'rendered' => $this->getPatternRenderArray($definition), + 'definition' => $definition->toArray(), + ]; + } } return [ diff --git a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig index 4c1efa22..96cfd4b1 100644 --- a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig +++ b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig @@ -9,40 +9,41 @@

    {{ "Available patterns"|t }}

    {# List of available patterns with anchor links. #} - + {% for group_name, group_patterns in patterns %} + {% if patterns|length > 1 %} +

    {{ group_name }}

    + {% endif %} +
      + {% for pattern_name, pattern in group_patterns %} +
    • + {{ pattern.label }} +
    • + {% endfor %} +
    + {% endfor %}
    - {% for pattern_name, pattern in patterns %} - -
    - {# Pattern name and description. #} - - {{ pattern.meta }} - - {# Rendered pattern preview. #} -
    - {{ "Preview"|t }} - {{ pattern.rendered }} -
    - - {# Link to standalone pattern preview page.#} -

    - - {% trans %}View {{ pattern.label }} as stand-alone{% endtrans %} - -

    -
    - -
    + {% for group_patterns in patterns %} + {% for pattern_name, pattern in group_patterns %} +
    + {{ pattern.meta }} + + {# Rendered pattern preview. #} +
    + {{ "Preview"|t }} + {{ pattern.rendered }} +
    + + {# Link to standalone pattern preview page.#} +

    + + {% trans %}View {{ pattern.label }} as stand-alone{% endtrans %} + +

    +
    + +
    + {% endfor %} {% endfor %} {% endif %} - - - diff --git a/src/Annotation/UiPattern.php b/src/Annotation/UiPattern.php index 203cce7a..aa3b4623 100644 --- a/src/Annotation/UiPattern.php +++ b/src/Annotation/UiPattern.php @@ -29,4 +29,22 @@ class UiPattern extends Plugin { */ public $label; + /** + * The description of the plugin. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + public $description; + + /** + * The category of the plugin. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + public $category; + } diff --git a/src/Definition/PatternDefinition.php b/src/Definition/PatternDefinition.php index a9e279fb..2057ed2a 100644 --- a/src/Definition/PatternDefinition.php +++ b/src/Definition/PatternDefinition.php @@ -33,6 +33,7 @@ class PatternDefinition extends PluginDefinition implements DerivablePluginDefin 'id' => NULL, 'label' => NULL, 'description' => NULL, + 'category' => '', 'base path' => NULL, 'file name' => NULL, 'use' => NULL, @@ -43,6 +44,7 @@ class PatternDefinition extends PluginDefinition implements DerivablePluginDefin 'fields' => [], 'variants' => [], 'tags' => [], + 'weight' => 0, 'additional' => [], 'deriver' => NULL, 'provider' => NULL, @@ -77,24 +79,6 @@ public function __construct(array $definition = []) { } } - /** - * Return array definition. - * - * @return array - * Array definition. - */ - public function toArray() { - $definition = $this->definition; - foreach ($this->getFields() as $field) { - $definition['fields'][$field->getName()] = $field->toArray(); - } - foreach ($this->getVariants() as $variant) { - $definition['variants'][$variant->getName()] = $variant->toArray(); - } - - return $definition; - } - /** * Getter. * @@ -118,6 +102,52 @@ public function setLabel($label) { return $this; } + /** + * Getter. + * + * @return string + * Property value. + */ + public function getDescription() { + return $this->definition['description']; + } + + /** + * Setter. + * + * @param string $description + * Property value. + * + * @return $this + */ + public function setDescription($description) { + $this->definition['description'] = $description; + return $this; + } + + /** + * Getter. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup|string + * Property value. + */ + public function getCategory() { + return $this->definition['category']; + } + + /** + * Setter. + * + * @param \Drupal\Core\StringTranslation\TranslatableMarkup|string $category + * Property value. + * + * @return $this + */ + public function setCategory($category) { + $this->definition['category'] = $category; + return $this; + } + /** * Getter. * @@ -187,6 +217,19 @@ public function setProvider($provider) { return $this; } + /** + * Get field. + * + * @param string $name + * Field name. + * + * @return PatternDefinitionField|null + * Definition field. + */ + public function getField($name) { + return $this->hasField($name) ? $this->definition['fields'][$name] : NULL; + } + /** * Getter. * @@ -211,6 +254,21 @@ public function getFieldsAsOptions() { return $options; } + /** + * Set field. + * + * @param string $name + * Field name. + * @param string $label + * Field label. + * + * @return $this + */ + public function setField($name, $label) { + $this->definition['fields'][$name] = $this->getFieldDefinition($name, $label); + return $this; + } + /** * Setter. * @@ -228,13 +286,29 @@ public function setFields(array $fields) { } /** - * Check whereas pattern has variants. + * Check whereas field exists. + * + * @param string $name + * Field name. * * @return bool - * Whereas pattern has variants. + * Whereas field exists */ - public function hasVariants() { - return !empty($this->definition['variants']); + public function hasField($name) { + return isset($this->definition['fields'][$name]); + } + + /** + * Get variant. + * + * @param string $name + * Field name. + * + * @return PatternDefinitionField|null + * Definition field. + */ + public function getVariant($name) { + return $this->hasVariant($name) ? $this->definition['variants'][$name] : NULL; } /** @@ -255,80 +329,41 @@ public function getVariants() { */ public function getVariantsAsOptions() { $options = []; - foreach ($this->getVariants() as $field) { - $options[$field->getName()] = $field->getLabel(); + foreach ($this->getVariants() as $variant) { + $options[$variant->getName()] = $variant->getLabel(); } return $options; } /** - * Setter. - * - * @param array $variants - * Property value. - * - * @return $this - */ - public function setVariants(array $variants) { - foreach ($variants as $name => $value) { - $variant = $this->getVariantDefinition($name, $value); - $this->definition['variants'][$variant->getName()] = $variant; - } - return $this; - } - - /** - * Get field. - * - * @param string $name - * Field name. - * - * @return PatternDefinitionField|null - * Definition field. - */ - public function getField($name) { - return $this->hasField($name) ? $this->definition['fields'][$name] : NULL; - } - - /** - * Check whereas field exists. - * - * @param string $name - * Field name. - * - * @return bool - * Whereas field exists - */ - public function hasField($name) { - return isset($this->definition['fields'][$name]); - } - - /** - * Set field. + * Set variant. * * @param string $name - * Field name. + * Variant name. * @param string $label - * Field label. + * Variant label. * * @return $this */ - public function setField($name, $label) { - $this->definition['fields'][$name] = $this->getFieldDefinition($name, $label); + public function setVariant($name, $label) { + $this->definition['variants'][$name] = $this->getVariantDefinition($name, $label); return $this; } /** - * Get variant. + * Setter. * - * @param string $name - * Field name. + * @param array $variants + * Property value. * - * @return PatternDefinitionField|null - * Definition field. + * @return $this */ - public function getVariant($name) { - return $this->hasVariant($name) ? $this->definition['variants'][$name] : NULL; + public function setVariants(array $variants) { + foreach ($variants as $name => $value) { + $variant = $this->getVariantDefinition($name, $value); + $this->definition['variants'][$variant->getName()] = $variant; + } + return $this; } /** @@ -345,18 +380,13 @@ public function hasVariant($name) { } /** - * Set variant. - * - * @param string $name - * Variant name. - * @param string $label - * Variant label. + * Check whereas pattern has variants. * - * @return $this + * @return bool + * Whereas pattern has variants. */ - public function setVariant($name, $label) { - $this->definition['variants'][$name] = $this->getVariantDefinition($name, $label); - return $this; + public function hasVariants() { + return !empty($this->definition['variants']); } /** @@ -388,20 +418,20 @@ public function setThemeHook($theme_hook) { * @return string * Property value. */ - public function getDescription() { - return $this->definition['description']; + public function getUse() { + return $this->definition['use']; } /** * Setter. * - * @param string $description + * @param string $use * Property value. * * @return $this */ - public function setDescription($description) { - $this->definition['description'] = $description; + public function setUse($use) { + $this->definition['use'] = $use; return $this; } @@ -415,29 +445,6 @@ public function hasUse() { return !empty($this->definition['use']); } - /** - * Getter. - * - * @return string - * Property value. - */ - public function getUse() { - return $this->definition['use']; - } - - /** - * Setter. - * - * @param string $use - * Property value. - * - * @return $this - */ - public function setUse($use) { - $this->definition['use'] = $use; - return $this; - } - /** * Getter. * @@ -547,49 +554,62 @@ public function setLibraries($libraries) { } /** - * Get Deriver property. + * Get Class property. * - * @return mixed + * @return string * Property value. */ - public function getDeriver() { - return $this->definition['deriver']; + public function getClass() { + return $this->definition['class']; } /** - * Get Additional property. + * Set Class property. * - * @return array + * @param string $class * Property value. + * + * @return $this */ - public function getAdditional() { - return $this->definition['additional']; + public function setClass($class) { + parent::setClass($class); + $this->definition['class'] = $class; + return $this; } /** - * Get Class property. + * Getter. * - * @return string + * @return int * Property value. */ - public function getClass() { - return $this->definition['class']; + public function getWeight(): int { + return $this->definition['weight']; } /** - * Set Class property. + * Setter. * - * @param string $class + * @param int $weight * Property value. * * @return $this */ - public function setClass($class) { - parent::setClass($class); - $this->definition['class'] = $class; + public function setWeight(int $weight) { + $this->definition['weight'] = $weight; return $this; } + /** + * Get Additional property. + * + * @return array + * Property value. + */ + public function getAdditional() { + return $this->definition['additional']; + } + /** * Set Additional property. * @@ -603,6 +623,16 @@ public function setAdditional(array $additional) { return $this; } + /** + * Get Deriver property. + * + * @return mixed + * Property value. + */ + public function getDeriver() { + return $this->definition['deriver']; + } + /** * Set Deriver property. * @@ -646,4 +676,22 @@ public function getVariantDefinition($name, $value) { return new PatternDefinitionVariant($name, $value); } + /** + * Return array definition. + * + * @return array + * Array definition. + */ + public function toArray() { + $definition = $this->definition; + foreach ($this->getFields() as $field) { + $definition['fields'][$field->getName()] = $field->toArray(); + } + foreach ($this->getVariants() as $variant) { + $definition['variants'][$variant->getName()] = $variant->toArray(); + } + + return $definition; + } + } diff --git a/src/UiPatternsManager.php b/src/UiPatternsManager.php index d2ddd30c..ade00c0b 100644 --- a/src/UiPatternsManager.php +++ b/src/UiPatternsManager.php @@ -2,19 +2,20 @@ namespace Drupal\ui_patterns; -use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Definition\PatternDefinition; /** - * Provides the default ui_patterns manager. + * Provides the default ui patterns manager. * * @method \Drupal\ui_patterns\Definition\PatternDefinition getDefinition($plugin_id, $exception_on_invalid = TRUE) */ -class UiPatternsManager extends DefaultPluginManager implements PluginManagerInterface { +class UiPatternsManager extends DefaultPluginManager implements UiPatternsManagerInterface { use StringTranslationTrait; @@ -35,26 +36,111 @@ class UiPatternsManager extends DefaultPluginManager implements PluginManagerInt /** * UiPatternsManager constructor. */ - public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache_backend) { + public function __construct( + \Traversable $namespaces, + CacheBackendInterface $cache_backend, + ModuleHandlerInterface $module_handler, + ThemeHandlerInterface $theme_handler + ) { parent::__construct('Plugin/UiPatterns/Pattern', $namespaces, $module_handler, 'Drupal\ui_patterns\Plugin\PatternInterface', 'Drupal\ui_patterns\Annotation\UiPattern'); - $this->moduleHandler = $module_handler; - $this->themeHandler = $theme_handler; - $this->alterInfo('ui_patterns_info'); $this->setCacheBackend($cache_backend, 'ui_patterns', ['ui_patterns']); + $this->alterInfo('ui_patterns_info'); + $this->themeHandler = $theme_handler; } /** - * Get pattern objects. + * {@inheritdoc} + */ + public function processDefinition(&$definition, $plugin_id) { + parent::processDefinition($definition, $plugin_id); + + if (is_array($definition)) { + $definition = new PatternDefinition($definition); + } + + // Add default category. + if ($definition instanceof PatternDefinition && empty($definition->getCategory())) { + $definition->setCategory($this->t('Other')); + } + } + + /** + * {@inheritdoc} + */ + public function getCategories() { + // Fetch all categories from definitions and remove duplicates. + $categories = \array_unique(\array_values(\array_map(static function (PatternDefinition $definition) { + return $definition->getCategory(); + }, $this->getDefinitions()))); + \natcasesort($categories); + // @phpstan-ignore-next-line + return $categories; + } + + /** + * {@inheritdoc} * - * @return \Drupal\ui_patterns\Plugin\PatternBase[] - * Pattern objects. + * @phpstan-ignore-next-line */ - public function getPatterns() { - $patterns = []; - foreach ($this->getDefinitions() as $definition) { - $patterns[] = $this->getFactory()->createInstance($definition->id()); + public function getSortedDefinitions(?array $definitions = NULL): array { + $definitions = $definitions ?? $this->getDefinitions(); + + \uasort($definitions, static function (PatternDefinition $item1, PatternDefinition $item2) { + // Sort by category. + $category1 = $item1->getCategory(); + if ($category1 instanceof TranslatableMarkup) { + $category1 = $category1->render(); + } + $category2 = $item2->getCategory(); + if ($category2 instanceof TranslatableMarkup) { + $category2 = $category2->render(); + } + if ($category1 != $category2) { + return \strnatcasecmp($category1, $category2); + } + + // Sort by weight. + $weight = $item1->getWeight() <=> $item2->getWeight(); + if ($weight != 0) { + return $weight; + } + + // Sort by label ignoring parenthesis. + $label1 = $item1->getLabel(); + if ($label1 instanceof TranslatableMarkup) { + $label1 = $label1->render(); + } + $label2 = $item2->getLabel(); + if ($label2 instanceof TranslatableMarkup) { + $label2 = $label2->render(); + } + // Ignore parenthesis. + $label1 = \str_replace(['(', ')'], '', $label1); + $label2 = \str_replace(['(', ')'], '', $label2); + if ($label1 != $label2) { + return \strnatcasecmp($label1, $label2); + } + + // Sort by plugin ID. + // In case the plugin ID starts with an underscore. + $id1 = \str_replace('_', '', $item1->id()); + $id2 = \str_replace('_', '', $item2->id()); + return \strnatcasecmp($id1, $id2); + }); + + return $definitions; + } + + /** + * {@inheritdoc} + */ + public function getGroupedDefinitions(?array $definitions = NULL): array { + $definitions = $this->getSortedDefinitions($definitions ?? $this->getDefinitions()); + $grouped_definitions = []; + foreach ($definitions as $id => $definition) { + $grouped_definitions[(string) $definition->getCategory()][$id] = $definition; } - return $patterns; + return $grouped_definitions; } /** @@ -73,7 +159,6 @@ public function getDefinitions() { $definitions[$definition['id']] = $definition; unset($definitions[$id]); } - $definitions = $this->getSortedDefinitions($definitions); $this->setCachedDefinitions($definitions); } return $definitions; @@ -82,64 +167,52 @@ public function getDefinitions() { /** * {@inheritdoc} */ - public function getPatternsOptions() { - return array_map(function ($option) { - return $option['label']; - }, $this->getDefinitions()); + protected function providerExists($provider) { + return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider); } /** * {@inheritdoc} */ - public function isPatternHook($hook) { - // Improve performance on not cached pages. - if (empty($this->patternHooks)) { - foreach ($this->getDefinitions() as $definition) { - $this->patternHooks[$definition->getThemeHook()] = $definition->getThemeHook(); - } + public function getPatterns(): array { + $patterns = []; + foreach ($this->getDefinitions() as $definition) { + $patterns[] = $this->getFactory()->createInstance($definition->id()); } - return !empty($this->patternHooks[$hook]); + return $patterns; } /** * {@inheritdoc} */ - protected function providerExists($provider) { - return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider); + public function getPatternsOptions(): array { + $options = []; + $grouped_definitions = $this->getGroupedDefinitions(); + foreach ($grouped_definitions as $group_name => $group_definitions) { + foreach ($group_definitions as $definition) { + $options[$group_name][$definition->id()] = $definition->getLabel(); + } + } + + // If there is only one category, do not put in optgroup. + if (count(array_keys($options)) == 1) { + $options = array_shift($options); + } + + return $options; } /** - * Sort pattern definitions by label then ID. - * - * @param array $definitions - * The patterns plugin definitions. - * - * @return array - * The sorted definitions. + * {@inheritdoc} */ - protected function getSortedDefinitions(array $definitions) { - // Sort by label ignoring parenthesis. - uasort($definitions, function ($item1, $item2) { - $sort_result = 0; - - if (isset($item1['label'], $item2['label'])) { - // Ignore parenthesis. - $label1 = str_replace(['(', ')'], '', $item1['label']); - $label2 = str_replace(['(', ')'], '', $item2['label']); - $sort_result = $label1 <=> $label2; - } - - // Fallback to pattern ID. - if ($sort_result === 0) { - // In case the pattern ID starts with an underscore. - $id1 = str_replace('_', '', $item1['id']); - $id2 = str_replace('_', '', $item2['id']); - $sort_result = $id1 <=> $id2; + public function isPatternHook(string $hook): bool { + // Improve performance on not cached pages. + if (empty($this->patternHooks)) { + foreach ($this->getDefinitions() as $definition) { + $this->patternHooks[$definition->getThemeHook()] = $definition->getThemeHook(); } - return $sort_result; - }); - - return $definitions; + } + return !empty($this->patternHooks[$hook]); } } diff --git a/src/UiPatternsManagerInterface.php b/src/UiPatternsManagerInterface.php new file mode 100644 index 00000000..9206b800 --- /dev/null +++ b/src/UiPatternsManagerInterface.php @@ -0,0 +1,86 @@ +stringTranslation = $translation; + parent::__construct($namespaces, $cache_backend, $module_handler, $theme_handler); + } + + /** + * {@inheritdoc} + */ + public function getDefinitions(): array { + $definitions = $this->patterns; + foreach ($definitions as $plugin_id => &$definition) { + $this->processDefinition($definition, $plugin_id); + } + return $definitions; + } + + /** + * Getter. + * + * getPatterns is already a method in the real plugin manager. + * + * @return array + * Property value. + */ + public function getDummyPatterns(): array { + return $this->patterns; + } + + /** + * Setter. + * + * @param array $patterns + * Property value. + * + * @return $this + */ + public function setPatterns(array $patterns) { + $this->patterns = $patterns; + return $this; + } + +} diff --git a/tests/modules/ui_patterns_test/ui_patterns_test.info.yml b/tests/modules/ui_patterns_test/ui_patterns_test.info.yml new file mode 100644 index 00000000..f58ce5ef --- /dev/null +++ b/tests/modules/ui_patterns_test/ui_patterns_test.info.yml @@ -0,0 +1,4 @@ +name: 'UI Patterns Test' +type: module +description: 'Provides test plugin.' +package: 'Testing' diff --git a/tests/src/Kernel/UiPatternsManagerSortingTest.php b/tests/src/Kernel/UiPatternsManagerSortingTest.php deleted file mode 100644 index 850ba71e..00000000 --- a/tests/src/Kernel/UiPatternsManagerSortingTest.php +++ /dev/null @@ -1,59 +0,0 @@ -getDefinitions(); - - $keys = array_keys($definitions); - $expected_keys_order = [ - 'ccc', - 'zzz', - 'aaa', - 'bbb', - 'lll', - ]; - - $this->assertEquals($expected_keys_order, $keys); - } - -} diff --git a/tests/src/Unit/Definition/PatternDefinitionTest.php b/tests/src/Unit/Definition/PatternDefinitionTest.php index dfaca61c..6d56c560 100644 --- a/tests/src/Unit/Definition/PatternDefinitionTest.php +++ b/tests/src/Unit/Definition/PatternDefinitionTest.php @@ -17,16 +17,57 @@ class PatternDefinitionTest extends AbstractUiPatternsTest { * Test getters. * * @dataProvider definitionGettersProvider + * + * @covers ::getCategory + * @covers ::getDescription + * @covers ::getLabel + * @covers ::getProvider + * @covers ::getUse + * @covers ::getWeight + * @covers ::getTags + * @covers ::getBasePath + * @covers ::getClass + * @covers ::getFileName + * @covers ::getTemplate + * @covers ::getThemeHook + * @covers ::hasCustomThemeHook + * @covers ::id */ public function testGettersSetters($getter, $name, $value) { $pattern_definition = new PatternDefinition([$name => $value]); $this->assertEquals(call_user_func([$pattern_definition, $getter]), $value); } + /** + * Provider. + * + * @return array + * Data. + */ + public function definitionGettersProvider() { + return [ + ['getProvider', 'provider', 'my_module'], + ['id', 'id', 'pattern_id'], + ['getLabel', 'label', 'Pattern label'], + ['getDescription', 'description', 'Pattern description.'], + ['getCategory', 'category', 'Pattern category'], + ['getUse', 'use', 'template.twig'], + ['hasCustomThemeHook', 'custom theme hook', FALSE], + ['getThemeHook', 'theme hook', 'eme hook: custom_my_theme_hook'], + ['getTemplate', 'template', 'my-template.html.twig'], + ['getFileName', 'file name', '/path/to/filename.ui_patterns.yml'], + ['getClass', 'class', '\Drupal\ui_patterns\MyClass'], + ['getBasePath', 'base path', '/path/to'], + ['getTags', 'tags', ['a', 'b']], + ['getWeight', 'weight', 10], + ]; + } + /** * Test field singleton. * - * @dataProvider definitionGettersProvider + * @covers ::getField + * @covers ::setFields */ public function testFields() { $fields = [ @@ -74,6 +115,8 @@ public function testFields() { * Test fields processing. * * @dataProvider fieldsProcessingProvider + * + * @covers ::setFields */ public function testFieldsProcessing($actual, $expected) { $pattern_definition = new PatternDefinition(); @@ -95,6 +138,8 @@ public function fieldsProcessingProvider() { * Test fields processing. * * @dataProvider variantsProcessingProvider + * + * @covers ::setVariants */ public function testVariantsProcessing($actual, $expected) { $pattern_definition = new PatternDefinition(); @@ -112,27 +157,4 @@ public function variantsProcessingProvider() { return Yaml::decode(file_get_contents($this->getFixturePath() . '/definition/variants_processing.yml')); } - /** - * Provider. - * - * @return array - * Data. - */ - public function definitionGettersProvider() { - return [ - ['getProvider', 'provider', 'my_module'], - ['id', 'id', 'pattern_id'], - ['getLabel', 'label', 'Pattern label'], - ['getDescription', 'description', 'Pattern description.'], - ['getUse', 'use', 'template.twig'], - ['hasCustomThemeHook', 'custom theme hook', FALSE], - ['getThemeHook', 'theme hook', 'eme hook: custom_my_theme_hook'], - ['getTemplate', 'template', 'my-template.html.twig'], - ['getFileName', 'file name', '/path/to/filename.ui_patterns.yml'], - ['getClass', 'class', '\Drupal\ui_patterns\MyClass'], - ['getBasePath', 'base path', '/path/to'], - ['getTags', 'tags', ['a', 'b']], - ]; - } - } diff --git a/tests/src/Unit/UiPatternsManagerTest.php b/tests/src/Unit/UiPatternsManagerTest.php new file mode 100644 index 00000000..2b07775b --- /dev/null +++ b/tests/src/Unit/UiPatternsManagerTest.php @@ -0,0 +1,373 @@ +container = new ContainerBuilder(); + $this->container->set('string_translation', $this->getStringTranslationStub()); + + // Set up for this class. + $namespaces = $this->createMock(\Traversable::class); + + /** @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject $moduleHandler */ + $moduleHandler = $this->createMock(ModuleHandlerInterface::class); + $moduleHandler->expects($this->any()) + ->method('getModuleDirectories') + ->willReturn([]); + + /** @var \Drupal\Core\Extension\ThemeHandlerInterface|\PHPUnit\Framework\MockObject\MockObject $themeHandler */ + $themeHandler = $this->createMock(ThemeHandlerInterface::class); + $themeHandler->expects($this->any()) + ->method('getThemeDirectories') + ->willReturn([]); + + $cache = $this->createMock(CacheBackendInterface::class); + $this->stringTranslation = $this->getStringTranslationStub(); + + $this->uiPatternsManager = new DummyUiPatternsManager($namespaces, $cache, $moduleHandler, $themeHandler, $this->stringTranslation); + } + + /** + * Tests the constructor. + * + * @covers ::__construct + */ + public function testConstructor(): void { + $this->assertInstanceOf( + UiPatternsManager::class, + $this->uiPatternsManager + ); + } + + /** + * Tests the processDefinition(). + * + * @covers ::processDefinition + */ + public function testProcessDefinition(): void { + $plugin_id = 'test'; + $definition = ['id' => $plugin_id]; + + $expected = new PatternDefinition($definition); + $expected->setCategory($this->stringTranslation->translate('Other')); + + /** @var \Drupal\ui_patterns\Definition\PatternDefinition $definition */ + $this->uiPatternsManager->processDefinition($definition, $plugin_id); + $this->assertInstanceOf(PatternDefinition::class, $definition); + $this->assertEquals($definition->toArray(), $expected->toArray()); + } + + /** + * @covers ::getCategories + */ + public function testGetCategories(): void { + $this->uiPatternsManager->setPatterns([ + 'id_1' => [ + 'id' => 'id_1', + 'category' => 'Cat 1', + ], + 'id_2' => [ + 'id' => 'id_2', + 'category' => 'Cat 2', + ], + 'id_3' => [ + 'id' => 'id_3', + ], + ]); + $expected = [ + 'Cat 1', + 'Cat 2', + 'Other', + ]; + $categories = $this->uiPatternsManager->getCategories(); + $this->assertEquals($expected, $categories); + } + + /** + * @covers ::getSortedDefinitions + */ + public function testGetSortedDefinitions(): void { + $this->uiPatternsManager->setPatterns([ + 'id_z1z2' => [ + 'category' => 'Z', + 'weight' => 1, + 'label' => '(Z)', + 'id' => 'id_z1z2', + ], + 'id_z1z1' => [ + 'category' => 'Z', + 'weight' => 1, + 'label' => 'Z', + 'id' => 'id_z1z1', + ], + 'id_z1a2' => [ + 'category' => 'Z', + 'weight' => 1, + 'label' => '(A)', + 'id' => 'id_z1a2', + ], + 'id_z1a1' => [ + 'category' => 'Z', + 'weight' => 1, + 'label' => 'A', + 'id' => 'id_z1a1', + ], + 'id_z0z2' => [ + 'category' => 'Z', + 'weight' => 0, + 'label' => '(Z)', + 'id' => 'id_z0z2', + ], + 'id_z0z1' => [ + 'category' => 'Z', + 'weight' => 0, + 'label' => 'Z', + 'id' => 'id_z0z1', + ], + 'id_z0a2' => [ + 'category' => 'Z', + 'weight' => 0, + 'label' => '(A)', + 'id' => 'id_z0a2', + ], + 'id_z0a1' => [ + 'category' => 'Z', + 'weight' => 0, + 'label' => 'A', + 'id' => 'id_z0a1', + ], + 'id_a1z2' => [ + 'category' => 'A', + 'weight' => 1, + 'label' => '(Z)', + 'id' => 'id_a1z2', + ], + 'id_a1z1' => [ + 'category' => 'A', + 'weight' => 1, + 'label' => 'Z', + 'id' => 'id_a1z1', + ], + 'id_a1a2' => [ + 'category' => 'A', + 'weight' => 1, + 'label' => '(A)', + 'id' => 'id_a1a2', + ], + 'id_a1a1' => [ + 'category' => 'A', + 'weight' => 1, + 'label' => 'A', + 'id' => 'id_a1a1', + ], + 'id_a0z2' => [ + 'category' => 'A', + 'weight' => 0, + 'label' => '(Z)', + 'id' => 'id_a0z2', + ], + 'id_a0z1' => [ + 'category' => 'A', + 'weight' => 0, + 'label' => 'Z', + 'id' => 'id_a0z1', + ], + 'id_a0a2' => [ + 'category' => 'A', + 'weight' => 0, + 'label' => '(A)', + 'id' => 'id_a0a2', + ], + 'id_a0a1' => [ + 'category' => 'A', + 'weight' => 0, + 'label' => 'A', + 'id' => 'id_a0a1', + ], + ]); + + $expected = [ + 'id_a0a1', + 'id_a0a2', + 'id_a0z1', + 'id_a0z2', + 'id_a1a1', + 'id_a1a2', + 'id_a1z1', + 'id_a1z2', + 'id_z0a1', + 'id_z0a2', + 'id_z0z1', + 'id_z0z2', + 'id_z1a1', + 'id_z1a2', + 'id_z1z1', + 'id_z1z2', + ]; + + $sorted_definitions = $this->uiPatternsManager->getSortedDefinitions(); + $this->assertEquals($expected, \array_keys($sorted_definitions)); + $this->assertContainsOnlyInstancesOf(PatternDefinition::class, $sorted_definitions); + } + + /** + * @covers ::getGroupedDefinitions + */ + public function testGetGroupedDefinitions(): void { + $this->uiPatternsManager->setPatterns([ + 'cat_1_1_b' => [ + 'id' => 'cat_1_1_b', + 'category' => 'Cat 1', + 'label' => 'B', + 'weight' => 1, + ], + 'cat_1_1_a' => [ + 'id' => 'cat_1_1_a', + 'category' => 'Cat 1', + 'label' => 'A', + 'weight' => 1, + ], + 'cat_1_0_a' => [ + 'id' => 'cat_1_0_a', + 'category' => 'Cat 1', + 'label' => 'A', + 'weight' => 0, + ], + 'cat_2_0_a' => [ + 'id' => 'cat_1_0_a', + 'category' => 'Cat 2', + 'label' => 'A', + 'weight' => 0, + ], + 'no_category' => [ + 'id' => 'no_category', + 'label' => 'B', + 'weight' => 0, + ], + ]); + + $category_expected = [ + 'Cat 1' => [ + 'cat_1_0_a', + 'cat_1_1_a', + 'cat_1_1_b', + ], + 'Cat 2' => [ + 'cat_2_0_a', + ], + 'Other' => [ + 'no_category', + ], + ]; + + $definitions = $this->uiPatternsManager->getGroupedDefinitions(); + $this->assertEquals(\array_keys($category_expected), \array_keys($definitions)); + foreach ($category_expected as $category => $expected) { + $this->assertArrayHasKey($category, $definitions); + $this->assertEquals($expected, \array_keys($definitions[$category])); + $this->assertContainsOnlyInstancesOf(PatternDefinition::class, $definitions[$category]); + } + } + + /** + * @covers ::getPatternsOptions + */ + public function testGetPatternsOptions(): void { + $this->uiPatternsManager->setPatterns([ + 'id_1' => [ + 'id' => 'id_1', + 'label' => 'Label 1', + 'category' => 'Cat 1', + ], + 'id_2' => [ + 'id' => 'id_2', + 'label' => 'Label 2', + 'category' => 'Cat 2', + ], + 'id_3' => [ + 'label' => 'Label 3', + 'id' => 'id_3', + ], + ]); + $expected = [ + 'Cat 1' => [ + 'id_1' => 'Label 1', + ], + 'Cat 2' => [ + 'id_2' => 'Label 2', + ], + 'Other' => [ + 'id_3' => 'Label 3', + ], + ]; + $options = $this->uiPatternsManager->getPatternsOptions(); + $this->assertEquals($expected, $options); + + // Only one category. + $this->uiPatternsManager->setPatterns([ + 'id_1' => [ + 'id' => 'id_1', + 'label' => 'Label 1', + 'category' => 'Cat 1', + ], + 'id_2' => [ + 'id' => 'id_2', + 'label' => 'Label 2', + 'category' => 'Cat 1', + ], + ]); + $expected = [ + 'id_1' => 'Label 1', + 'id_2' => 'Label 2', + ]; + $options = $this->uiPatternsManager->getPatternsOptions(); + $this->assertEquals($expected, $options); + } + +} diff --git a/ui_patterns.module b/ui_patterns.module index d4d61cd7..3b5568c7 100644 --- a/ui_patterns.module +++ b/ui_patterns.module @@ -31,9 +31,7 @@ function ui_patterns_theme() { /** * Implements hook_library_info_build(). */ -function ui_patterns_library_info_build() { - /** @var \Drupal\ui_patterns\Plugin\PatternBase $pattern */ - +function ui_patterns_library_info_build(): array { $definitions = []; foreach (UiPatterns::getManager()->getPatterns() as $pattern) { $definitions += $pattern->getLibraryDefinitions(); @@ -42,9 +40,9 @@ function ui_patterns_library_info_build() { } /** - * Implements hook_theme_suggestions_HOOK_alter(). + * Implements hook_theme_suggestions_alter(). */ -function ui_patterns_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { +function ui_patterns_theme_suggestions_alter(array &$suggestions, array $variables, string $hook): void { if (UiPatterns::getManager()->isPatternHook($hook)) { \Drupal::moduleHandler()->alter('ui_patterns_suggestions', $suggestions, $variables, $variables['context']); \Drupal::theme()->alter('ui_patterns_suggestions', $suggestions, $variables, $variables['context']); @@ -59,7 +57,7 @@ function ui_patterns_theme_suggestions_alter(array &$suggestions, array $variabl /** * Implements hook_ui_patterns_suggestions_alter(). */ -function ui_patterns_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { +function ui_patterns_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context): void { // Add preview theme suggestion. if ($context->isOfType('preview')) { $suggestions[] = $variables['theme_hook_original'] . '__preview'; diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index cb420c61..96f39be4 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -1,7 +1,11 @@ services: plugin.manager.ui_patterns: class: Drupal\ui_patterns\UiPatternsManager - arguments: ['@container.namespaces', '@module_handler', '@theme_handler', '@cache.discovery'] + arguments: + - '@container.namespaces' + - '@cache.discovery' + - '@module_handler' + - '@theme_handler' plugin.manager.ui_patterns_source: class: Drupal\ui_patterns\UiPatternsSourceManager parent: default_plugin_manager From 8e38f1d43dac079280357ba6f0e7fe3c5f50ed7c Mon Sep 17 00:00:00 2001 From: Pierre Dureau <31905-pdureau@users.noreply.drupalcode.org> Date: Tue, 16 May 2023 09:07:12 +0000 Subject: [PATCH 21/81] Issue #3347891 by pdureau, Grimreaper: UI Patterns Library: print tags by default --- .../templates/patterns-meta-information.html.twig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig index acf5e98d..0a76add4 100644 --- a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig +++ b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig @@ -10,6 +10,16 @@ {# Pattern name and description. #}

    {{ pattern.label }}

    {{ pattern.description }}

    + {% if pattern.tags %} +
    + {{ "Tags:"|t }} +
      + {% for tag in pattern.tags %} +
    • {{ tag }}
    • + {% endfor %} +
    +
    + {% endif %} {# Pattern fields descriptions. #} From 99e98c7355d80a7c168b452fd90c72e4a33aab5e Mon Sep 17 00:00:00 2001 From: Pierre Dureau <31905-pdureau@users.noreply.drupalcode.org> Date: Mon, 29 May 2023 08:05:58 +0000 Subject: [PATCH 22/81] Issue #3311480 by pdureau, Grimreaper, DuaelFr: Reduce preprocess hooks usage by adding add_class() & set_attributes() filters --- src/Template/AttributesFilterTrait.php | 117 ++++++++++ src/Template/TwigExtension.php | 13 ++ tests/src/Unit/Template/TwigExtensionTest.php | 206 ++++++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 src/Template/AttributesFilterTrait.php create mode 100644 tests/src/Unit/Template/TwigExtensionTest.php diff --git a/src/Template/AttributesFilterTrait.php b/src/Template/AttributesFilterTrait.php new file mode 100644 index 00000000..49c2668c --- /dev/null +++ b/src/Template/AttributesFilterTrait.php @@ -0,0 +1,117 @@ +arrayIsList($element)) { + foreach ($element as $index => $item) { + if (!\is_array($item)) { + continue; + } + $element[$index] = $this->addClass($item, ...$classes); + } + return $element; + } + $attributes = new Attribute($element['#attributes'] ?? []); + $attributes->addClass(...$classes); + $element['#attributes'] = $attributes->toArray(); + + // Make sure element gets rendered again. + unset($element['#printed']); + + return $element; + } + + /** + * Set attribute on a given element. + * + * @param mixed $element + * A render array. + * @param string $name + * The attribute name. + * @param mixed $value + * (optional) The attribute value. + * + * @return mixed + * The element with the given sanitized attribute's value or the unchanged + * element if passed value is not an array. + * + * @see Drupal\Core\Template\TwigExtension::setAttribute() + */ + public function setAttribute(mixed $element, string $name, mixed $value = NULL): mixed { + if (!\is_array($element)) { + return $element; + } + if ($this->arrayIsList($element)) { + foreach ($element as $index => $item) { + if (!\is_array($item)) { + continue; + } + $element[$index] = $this->setAttribute($item, $name, $value); + } + return $element; + } + $element['#attributes'] = AttributeHelper::mergeCollections( + $element['#attributes'] ?? [], + new Attribute([$name => $value]) + ); + + // Make sure element gets rendered again. + unset($element['#printed']); + + return $element; + } + + /** + * Checks whether a given array is a list. + * + * Same as array_is_list() but compatible with PHP8. + * + * @param array $array + * The array being evaluated. + * + * @return bool + * Returns true if array is a list, false otherwise. + * + * @see https://www.php.net/manual/en/function.array-is-list.php#126794 + */ + private function arrayIsList(array $array): bool { + $i = -1; + foreach ($array as $k => $v) { + ++$i; + if ($k !== $i) { + return FALSE; + } + } + return TRUE; + } + +} diff --git a/src/Template/TwigExtension.php b/src/Template/TwigExtension.php index 298ff276..e12a1efc 100644 --- a/src/Template/TwigExtension.php +++ b/src/Template/TwigExtension.php @@ -4,6 +4,7 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; +use Twig\TwigFilter; /** * Twig extension providing UI Patterns-specific functionalities. @@ -12,6 +13,8 @@ */ class TwigExtension extends AbstractExtension { + use AttributesFilterTrait; + /** * {@inheritdoc} */ @@ -35,6 +38,16 @@ public function getFunctions() { ]; } + /** + * {@inheritdoc} + */ + public function getFilters() { + return [ + new TwigFilter('add_class', [$this, 'addClass']), + new TwigFilter('set_attribute', [$this, 'setAttribute']), + ]; + } + /** * Render given pattern. * diff --git a/tests/src/Unit/Template/TwigExtensionTest.php b/tests/src/Unit/Template/TwigExtensionTest.php new file mode 100644 index 00000000..1ceac10f --- /dev/null +++ b/tests/src/Unit/Template/TwigExtensionTest.php @@ -0,0 +1,206 @@ +createMock('\Drupal\Core\Render\RendererInterface'); + $urlGenerator = $this->createMock('\Drupal\Core\Routing\UrlGeneratorInterface'); + $themeManager = $this->createMock('\Drupal\Core\Theme\ThemeManagerInterface'); + $dateFormatter = $this->createMock('\Drupal\Core\Datetime\DateFormatterInterface'); + $fileUrlGenerator = $this->createMock(FileUrlGeneratorInterface::class); + + $this->systemUnderTest = new TwigExtension($renderer, $urlGenerator, $themeManager, $dateFormatter, $fileUrlGenerator); + } + + /** + * Tests Twig 'add_class' filter. + * + * @covers ::addClass + * @dataProvider providerTestTwigAddClass + */ + public function testTwigAddClass($element, $classes, $expected_result) { + $processed = $this->systemUnderTest->addClass($element, $classes); + $this->assertEquals($expected_result, $processed); + } + + /** + * A data provider for ::testTwigAddClass(). + * + * @return \Iterator + * An iterator. + */ + public function providerTestTwigAddClass(): \Iterator { + yield 'should add a class on element' => [ + ['#type' => 'container'], + 'my-class', + ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], + ]; + + yield 'should add a class from a array of string keys on element' => [ + ['#type' => 'container'], + ['my-class'], + ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], + ]; + + yield 'should add a class from a Markup value' => [ + ['#type' => 'container'], + [Markup::create('my-class')], + ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], + ]; + + yield 'should add a class when an attributes array is already present' => [ + [ + '#type' => + 'container', + '#attributes' => [ + 'foo' => 'bar', + ], + ], + [Markup::create('my-class')], + [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['my-class'], + 'foo' => 'bar', + ], + ], + ]; + + yield 'should add a class when an attributes object is already present' => [ + [ + '#type' => + 'container', + '#attributes' => new Attribute([ + 'foo' => 'bar', + ]), + ], + [Markup::create('my-class')], + [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['my-class'], + 'foo' => 'bar', + ], + ], + ]; + + yield '#printed should be removed after class(es) added' => [ + [ + '#markup' => 'This content is already is rendered', + '#printed' => TRUE, + ], + '', + [ + '#markup' => 'This content is already is rendered', + '#attributes' => [ + 'class' => [''], + ], + ], + ]; + } + + /** + * Tests Twig 'set_attribute' filter. + * + * @covers ::setAttribute + * @dataProvider providerTestTwigSetAttribute + */ + public function testTwigSetAttribute($element, $key, $value, $expected_result) { + $processed = $this->systemUnderTest->setAttribute($element, $key, $value); + $this->assertEquals($expected_result, $processed); + } + + /** + * A data provider for ::testTwigSetAttribute(). + * + * @return \Iterator + * An iterator. + */ + public function providerTestTwigSetAttribute(): \Iterator { + yield 'should add attributes on element' => [ + ['#theme' => 'image'], + 'title', + 'Aloha', + [ + '#theme' => 'image', + '#attributes' => [ + 'title' => 'Aloha', + ], + ], + ]; + + yield 'should merge existing attributes on element' => [ + [ + '#theme' => 'image', + '#attributes' => [ + 'title' => 'Aloha', + ], + ], + 'title', + 'Bonjour', + [ + '#theme' => 'image', + '#attributes' => [ + 'title' => 'Bonjour', + ], + ], + ]; + + yield 'should add JSON attribute value correctly on element' => [ + ['#type' => 'container'], + 'data-slider', + Json::encode(['autoplay' => TRUE]), + [ + '#type' => 'container', + '#attributes' => [ + 'data-slider' => '{"autoplay":true}', + ], + ], + ]; + + yield '#printed should be removed after setting attribute' => [ + [ + '#markup' => 'This content is already is rendered', + '#printed' => TRUE, + ], + 'title', + NULL, + [ + '#markup' => 'This content is already is rendered', + '#attributes' => [ + 'title' => NULL, + ], + ], + ]; + } + +} From d09dea14f0fc50f9af0f9b6a5d96e56d039f0816 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Thu, 1 Jun 2023 13:55:28 +0000 Subject: [PATCH 23/81] Issue #3311340 by Grimreaper, pdureau: UI Patterns Library: support UI Patterns Settings --- .../patterns-meta-information.html.twig | 61 +++++++++++++------ .../UiPatternsLibraryOverviewTest.php | 11 ++-- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig index 0a76add4..27a3b7a4 100644 --- a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig +++ b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig @@ -22,25 +22,46 @@ {% endif %} {# Pattern fields descriptions. #} -
    - - - - - - - - - - {% for field in pattern.fields %} - - - - - - - {% endfor %} - -
    {{ "Field"|t }}{{ "Label"|t }}{{ "Type"|t }}{{ "Description"|t }}
    {{ field.name }}{{ field.label }}{{ field.type }}{{ field.description }}
    + {% if pattern.fields or pattern.additional.settings %} + + + + + + + + + + + + {% for field in pattern.fields %} + + + + + + + + {% endfor %} + {% for name, setting in pattern.additional.settings %} + + + + + + + + {% endfor %} + +
    {{ "Type"|t }}{{ "Name"|t }}{{ "Label"|t }}{{ "Type"|t }}{{ "Description"|t }} / {{ "Options"|t }}
    {{ "Field"|t }}{{ field.name }}{{ field.label }}{{ field.type }}{{ field.description }}
    {{ "Setting"|t }}{{ name }}{{ setting.label }}{{ setting.type }}{{ setting.description }} + {% if setting.options %} +
      + {% for key, label in setting.options %} +
    • {{ key }}: {{ label }}
    • + {% endfor %} +
    + {% endif %} +
    + {% endif %} {% endif %} diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php index 0f3192c2..947f9474 100644 --- a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php +++ b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php @@ -158,7 +158,7 @@ protected function assertPatternFields($root, array $pattern) { $session = $this->assertSession(); // Assert table header. - foreach (['Field', 'Label', 'Type', 'Description'] as $index => $item) { + foreach (['Type', 'Name', 'Label', 'Type', 'Description / Options'] as $index => $item) { $child = $index + 1; $session->elementContains('css', "$root > table.pattern-preview__fields > thead > tr > th:nth-child($child)", $item); } @@ -167,10 +167,11 @@ protected function assertPatternFields($root, array $pattern) { foreach ($pattern['fields'] as $index => $field) { $child = $index + 1; $row_root = "$root > table.pattern-preview__fields > tbody > tr:nth-child($child)"; - $session->elementContains('css', "$row_root > td:nth-child(1)", $field['name']); - $session->elementContains('css', "$row_root > td:nth-child(2)", $field['label']); - $session->elementContains('css', "$row_root > td:nth-child(3)", $field['type']); - $session->elementContains('css', "$row_root > td:nth-child(4)", $field['description']); + $session->elementContains('css', "$row_root > td:nth-child(1)", 'Field'); + $session->elementContains('css', "$row_root > td:nth-child(2)", $field['name']); + $session->elementContains('css', "$row_root > td:nth-child(3)", $field['label']); + $session->elementContains('css', "$row_root > td:nth-child(4)", $field['type']); + $session->elementContains('css', "$row_root > td:nth-child(5)", $field['description']); } } From 90fba88e1e7394050d71d45b464dafd532950a6b Mon Sep 17 00:00:00 2001 From: Edouard Cunibil <11996-DuaelFr@users.noreply.drupalcode.org> Date: Thu, 1 Jun 2023 18:14:11 +0200 Subject: [PATCH 24/81] Issue #3353287 by DuaelFr, Grimreaper: Mark PatternPreview::getPreviewMarkup() as deprecated. --- src/Element/PatternPreview.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Element/PatternPreview.php b/src/Element/PatternPreview.php index cbd15d9e..c87545d3 100644 --- a/src/Element/PatternPreview.php +++ b/src/Element/PatternPreview.php @@ -52,8 +52,14 @@ public static function processFields(array $element) { * * @return array|\Drupal\Component\Render\MarkupInterface|string * Preview safe markup. + * + * @deprecated in ui_patterns:1.6.0 and is removed from ui_patterns:2.0.0 + * without replacement. + * + * @see https://www.drupal.org/node/3353287 */ public static function getPreviewMarkup($preview) { + @trigger_error(__METHOD__ . '() is deprecated in ui_patterns:1.6.0 and is removed from ui_patterns:2.0.0. No replacement provided. See https://www.drupal.org/node/3353287', E_USER_DEPRECATED); if (is_array($preview)) { $rendered = []; // If preview is a render array add hashes to keys. From 33ac834948e48f1aa961448c1ca1ea8453794edf Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Mon, 19 Jun 2023 18:09:14 +0200 Subject: [PATCH 25/81] Initial sdc work --- .../src/FieldTemplateProcessor.php | 170 ------------- .../src/FieldTemplateProcessorInterface.php | 22 -- .../src/Plugin/DsFieldTemplate/Pattern.php | 197 --------------- .../UiPatterns/Source/DsFieldSource.php | 69 ----- .../Source/DsFieldTemplateSource.php | 71 ------ .../pattern-ds-field-template.html.twig | 9 - ...tity_view_display.node.article.default.yml | 60 ----- .../install/field.field.node.article.body.yml | 21 -- .../config/install/node.type.article.yml | 10 - .../templates/field.ui_patterns.yml | 16 -- .../templates/pattern-field.html.twig | 8 - .../ui_patterns_ds_test.info.yml | 14 -- .../ui_patterns_ds_test.install | 17 -- .../UiPatternsFieldRenderTest.php | 93 ------- .../UiPatternsFieldSettingsTest.php | 113 --------- .../ui_patterns_ds/ui_patterns_ds.info.yml | 8 - modules/ui_patterns_ds/ui_patterns_ds.module | 94 ------- .../ui_patterns_ds.services.yml | 3 - modules/ui_patterns_field_group/README.md | 3 - .../FieldGroupFormatter/PatternFormatter.php | 186 -------------- .../src/Utility/EntityFinder.php | 35 --- ...tity_form_display.node.article.default.yml | 76 ------ ...tity_view_display.node.article.default.yml | 56 ----- .../install/field.field.node.article.body.yml | 21 -- .../field.field.node.article.field_text.yml | 18 -- .../install/field.storage.node.field_text.yml | 20 -- .../config/install/node.type.article.yml | 10 - .../templates/metadata.ui_patterns.yml | 20 -- .../templates/pattern-metadata.html.twig | 9 - .../ui_patterns_field_group_test.info.yml | 17 -- .../UiPatternsFieldGroupRenderTest.php | 91 ------- .../UiPatternsFieldGroupSettingsTest.php | 120 --------- .../tests/src/Unit/EntityFinderTest.php | 111 -------- .../ui_patterns_field_group.module | 129 ---------- .../src/Plugin/Layout/PatternLayout.php | 8 +- .../Discovery/UIPatternsSdcDiscovery.php | 103 ++++++++ .../UiPatternsSdcPluginDiscovery.php | 36 +++ .../src/UiPatternsSdcPluginManager.php | 67 +++++ .../src/UiPatternsSdcServiceProvider.php | 20 ++ .../ui_patterns_sdc.info.yml} | 5 +- src/Element/Pattern.php | 238 ------------------ src/Element/PatternContext.php | 87 ------- src/Element/PatternPreview.php | 91 ------- .../components/foo-bar.twig | 1 + .../components/foo-bar.ui_patterns.yml | 5 + .../components/foo.twig | 2 + .../components/foo.ui_patterns.yml | 9 + .../templates/foo.ui_patterns.yml | 4 + ui_patterns.module | 66 ----- 49 files changed, 253 insertions(+), 2406 deletions(-) delete mode 100644 modules/ui_patterns_ds/src/FieldTemplateProcessor.php delete mode 100644 modules/ui_patterns_ds/src/FieldTemplateProcessorInterface.php delete mode 100644 modules/ui_patterns_ds/src/Plugin/DsFieldTemplate/Pattern.php delete mode 100644 modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldSource.php delete mode 100644 modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php delete mode 100644 modules/ui_patterns_ds/templates/pattern-ds-field-template.html.twig delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/core.entity_view_display.node.article.default.yml delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/field.field.node.article.body.yml delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/node.type.article.yml delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/field.ui_patterns.yml delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/pattern-field.html.twig delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml delete mode 100644 modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.install delete mode 100644 modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldRenderTest.php delete mode 100644 modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldSettingsTest.php delete mode 100644 modules/ui_patterns_ds/ui_patterns_ds.info.yml delete mode 100644 modules/ui_patterns_ds/ui_patterns_ds.module delete mode 100644 modules/ui_patterns_ds/ui_patterns_ds.services.yml delete mode 100644 modules/ui_patterns_field_group/README.md delete mode 100644 modules/ui_patterns_field_group/src/Plugin/field_group/FieldGroupFormatter/PatternFormatter.php delete mode 100644 modules/ui_patterns_field_group/src/Utility/EntityFinder.php delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_form_display.node.article.default.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_view_display.node.article.default.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.body.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.field_text.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.storage.node.field_text.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/node.type.article.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/metadata.ui_patterns.yml delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/pattern-metadata.html.twig delete mode 100644 modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml delete mode 100644 modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupRenderTest.php delete mode 100644 modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupSettingsTest.php delete mode 100644 modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php delete mode 100644 modules/ui_patterns_field_group/ui_patterns_field_group.module create mode 100644 modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php create mode 100644 modules/ui_patterns_sdc/src/Plugin/Discovery/UiPatternsSdcPluginDiscovery.php create mode 100644 modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php create mode 100644 modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php rename modules/{ui_patterns_field_group/ui_patterns_field_group.info.yml => ui_patterns_sdc/ui_patterns_sdc.info.yml} (50%) delete mode 100644 src/Element/Pattern.php delete mode 100644 src/Element/PatternContext.php delete mode 100644 src/Element/PatternPreview.php create mode 100644 tests/modules/ui_patterns_render_test/components/foo-bar.twig create mode 100644 tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml create mode 100644 tests/modules/ui_patterns_render_test/components/foo.twig create mode 100644 tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml diff --git a/modules/ui_patterns_ds/src/FieldTemplateProcessor.php b/modules/ui_patterns_ds/src/FieldTemplateProcessor.php deleted file mode 100644 index 54c8f353..00000000 --- a/modules/ui_patterns_ds/src/FieldTemplateProcessor.php +++ /dev/null @@ -1,170 +0,0 @@ -variables = $variables; - - $content = []; - foreach (array_keys($variables['items']) as $delta) { - $fields = []; - foreach ($this->getMapping() as $mapping) { - $fields[$mapping['destination']][] = $this->getSourceValue($mapping, $delta); - } - - $content['pattern_' . $delta] = [ - '#type' => 'pattern', - '#id' => $this->getPatternId(), - '#variant' => $this->getVariant(), - '#fields' => $fields, - '#context' => $this->getContext(), - '#multiple_sources' => TRUE, - ]; - } - - $variables['pattern'] = $content; - } - - /** - * Get source value. - * - * @param array $mapping - * Mapping array. - * @param int $delta - * Field delta. - * - * @return mixed - * Source value. - */ - public function getSourceValue(array $mapping, $delta) { - $value = $this->variables['items'][$delta]['content']; - if ($mapping['source'] != $this->getFieldName()) { - $column = $this->getColumnName($mapping['source']); - $value = $this->getEntity()->get($this->getFieldName())->getValue(); - $value = $value[$delta][$column]; - } - return $value; - } - - /** - * Get field parent entity. - * - * @return \Drupal\Core\Entity\ContentEntityBase - * Parent entity. - */ - protected function getEntity() { - return $this->variables['element']['#object']; - } - - /** - * Get Pattern ID. - * - * @return string - * Pattern ID. - */ - protected function getPatternId() { - return $this->getSetting('pattern'); - } - - /** - * Get mapping settings. - * - * @return array - * Mapping settings. - */ - protected function getMapping() { - return $this->getSetting('pattern_mapping', []); - } - - /** - * Get mapping settings. - * - * @return string - * Mapping settings. - */ - protected function getVariant() { - return $this->getSetting('pattern_variant'); - } - - /** - * Get setting value or default to given value if none set. - * - * @param string $name - * Setting name. - * @param string $default - * Setting default value. - * - * @return mixed - * Setting value. - */ - protected function getSetting($name, $default = '') { - return $this->variables['ds-config']['settings'][$name] ?? $default; - } - - /** - * Get field name. - * - * @return string - * Field name. - */ - protected function getFieldName() { - return $this->variables['field_name']; - } - - /** - * Extract column name from a source name. - * - * @param string $source - * Source name. - * - * @return string - * Column name. - */ - protected function getColumnName($source) { - return str_replace($this->getFieldName() . '__', '', $source); - } - - /** - * Get pattern context. - * - * @return array - * Pattern context. - */ - protected function getContext() { - $element = $this->variables['element']; - $context = [ - 'type' => 'ds_field_template', - 'field_name' => $this->getFieldName(), - 'entity_type' => $element['#entity_type'], - 'bundle' => $element['#bundle'], - 'view_mode' => $element['#view_mode'], - 'entity' => NULL, - ]; - - if (isset($element['#object']) && is_object($element['#object']) && $element['#object'] instanceof ContentEntityBase) { - $context['entity'] = $element['#object']; - } - - return $context; - } - -} diff --git a/modules/ui_patterns_ds/src/FieldTemplateProcessorInterface.php b/modules/ui_patterns_ds/src/FieldTemplateProcessorInterface.php deleted file mode 100644 index e5f7f552..00000000 --- a/modules/ui_patterns_ds/src/FieldTemplateProcessorInterface.php +++ /dev/null @@ -1,22 +0,0 @@ -patternsManager = $patterns_manager; - $this->sourceManager = $source_manager; - $this->parameters = $parameters->getCurrentRequest()->request; - $this->fieldManager = $field_manager; - $this->moduleHandler = $module_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('plugin.manager.ui_patterns'), - $container->get('plugin.manager.ui_patterns_source'), - $container->get('request_stack'), - $container->get('entity_field.manager'), - $container->get('module_handler') - ); - } - - /** - * {@inheritdoc} - */ - public function alterForm(&$form) { - $context = $this->getContext(); - if ($this->isSupportedField($context)) { - $this->buildPatternDisplayForm($form, 'ds_field_template', $context, $this->getConfiguration()); - } - else { - $form['#markup'] = $this->t("The current field is not supported."); - } - } - - /** - * Get source field plugin context. - * - * @return array - * Context array. - */ - protected function getContext() { - $parameters = $this->parameters->all(); - if (!isset($parameters['fields']) || !is_array($parameters['fields'])) { - return []; - } - $fields = $parameters['fields']; - $field_name = $this->getCurrentField(); - - return [ - 'field_name' => $field_name, - 'field_settings' => $fields[$field_name], - 'entity_type' => $this->parameters->get('ds_entity_type'), - 'bundle' => $this->parameters->get('ds_bundle'), - 'view_mode' => $this->parameters->get('ds_view_mode'), - ]; - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - return [ - 'pattern' => '', - 'pattern_variant' => '', - 'pattern_mapping' => [], - ]; - } - - /** - * Get name of field currently being edited. - * - * @return string - * Name of field currently being edited. - */ - protected function getCurrentField() { - $parameters = $this->parameters->all(); - if (isset($parameters['fields']) && is_array($parameters['fields'])) { - $fields = array_filter($parameters['fields'], function ($field) { - return isset($field['settings_edit_form']['third_party_settings']['ds']['ft']['id']) && $field['settings_edit_form']['third_party_settings']['ds']['ft']['id'] == 'pattern'; - }); - $fields = array_keys($fields); - $field = reset($fields); - } - - if (empty($field)) { - $trigger_element = $this->parameters->get('_triggering_element_name'); - $field = str_replace('_plugin_settings_edit', '', $trigger_element); - } - - return $field; - } - - /** - * Pattern Display Suite field template plugin only supports actual fields. - * - * @param array $context - * Current context. - * - * @return bool - * TRUE if supported, FALSE otherwise. - */ - protected function isSupportedField(array $context) { - /** @var \Drupal\field\Entity\FieldConfig $field */ - if ($context['entity_type'] && $context['bundle']) { - $field = $this->fieldManager->getFieldDefinitions($context['entity_type'], $context['bundle']); - return isset($field[$context['field_name']]); - } - return FALSE; - } - -} diff --git a/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldSource.php b/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldSource.php deleted file mode 100644 index 54ebd5f2..00000000 --- a/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldSource.php +++ /dev/null @@ -1,69 +0,0 @@ -dsManager = $ds_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('plugin.manager.ds') - ); - } - - /** - * {@inheritdoc} - */ - public function getSourceFields() { - $sources = []; - $fields = $this->dsManager->getDefinitions(); - - foreach ($fields as $field) { - if (!$this->getContextProperty('limit')) { - $sources[] = $this->getSourceField($field['id'], $field['title']); - } - elseif (in_array($field['id'], $this->getContextProperty('limit'))) { - $sources[] = $this->getSourceField($field['id'], $field['title']); - } - } - return $sources; - } - -} diff --git a/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php b/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php deleted file mode 100644 index 2a02e629..00000000 --- a/modules/ui_patterns_ds/src/Plugin/UiPatterns/Source/DsFieldTemplateSource.php +++ /dev/null @@ -1,71 +0,0 @@ -fieldManager = $field_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_field.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function getSourceFields() { - $sources = []; - $field_name = $this->getContextProperty('field_name'); - $entity_type = $this->getContextProperty('entity_type'); - $bundle = $this->getContextProperty('bundle'); - - /** @var \Drupal\field\Entity\FieldConfig $field */ - $field = $this->fieldManager->getFieldDefinitions($entity_type, $bundle)[$field_name]; - $label = $field->getLabel(); - - $sources[] = $this->getSourceField($field_name, $label); - foreach (array_keys($field->getFieldStorageDefinition()->getColumns()) as $column_name) { - $sources[] = $this->getSourceField($field_name . '__' . $column_name, $label . ': ' . $column_name); - } - return $sources; - } - -} diff --git a/modules/ui_patterns_ds/templates/pattern-ds-field-template.html.twig b/modules/ui_patterns_ds/templates/pattern-ds-field-template.html.twig deleted file mode 100644 index c46be2fd..00000000 --- a/modules/ui_patterns_ds/templates/pattern-ds-field-template.html.twig +++ /dev/null @@ -1,9 +0,0 @@ -{# -/** - * @file - * Default UI Patterns for Display Suite field templates. - * - * @ingroup themeable - */ -#} -{{ pattern }} diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/core.entity_view_display.node.article.default.yml b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/core.entity_view_display.node.article.default.yml deleted file mode 100644 index 3188b7ba..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/core.entity_view_display.node.article.default.yml +++ /dev/null @@ -1,60 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.field.node.article.body - - node.type.article - module: - - ds - - text - - user -third_party_settings: - ds: - layout: - id: ds_1col - library: null - disable_css: false - entity_classes: all_classes - settings: - wrappers: - ds_content: div - outer_wrapper: div - attributes: '' - link_attribute: '' - link_custom: '' - classes: - layout_class: { } - regions: - ds_content: - - links - - body -id: node.article.default -targetEntityType: node -bundle: article -mode: default -content: - body: - label: above - type: text_default - weight: 101 - settings: { } - third_party_settings: - ds: - ft: - id: pattern - settings: - pattern: field - pattern_variant: default - pattern_mapping: - 'ds_field_template:body': - destination: value - weight: 0 - plugin: ds_field_template - source: body - region: ds_content - links: - weight: 100 - region: ds_content - settings: { } - third_party_settings: { } -hidden: { } diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/field.field.node.article.body.yml b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/field.field.node.article.body.yml deleted file mode 100644 index 8f3681d9..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/field.field.node.article.body.yml +++ /dev/null @@ -1,21 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.storage.node.body - - node.type.article - module: - - text -id: node.article.body -field_name: body -entity_type: node -bundle: article -label: Body -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - display_summary: true -field_type: text_with_summary diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/node.type.article.yml b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/node.type.article.yml deleted file mode 100644 index f47f2add..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/config/install/node.type.article.yml +++ /dev/null @@ -1,10 +0,0 @@ -langcode: en -status: true -dependencies: { } -name: Article -type: article -description: '' -help: '' -new_revision: true -preview_mode: 1 -display_submitted: true diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/field.ui_patterns.yml b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/field.ui_patterns.yml deleted file mode 100644 index af8aeeb4..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/field.ui_patterns.yml +++ /dev/null @@ -1,16 +0,0 @@ -field: - label: "Field" - variants: - default: - label: "Default" - overridden: - label: "Overridden" - fields: - value: - type: "text" - label: "Value" - preview: "value" - format: - type: "text" - label: "Format" - preview: "format" diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/pattern-field.html.twig b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/pattern-field.html.twig deleted file mode 100644 index 17aa29ea..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/templates/pattern-field.html.twig +++ /dev/null @@ -1,8 +0,0 @@ -{# -/** - * @file - * Test field pattern. - */ -#} -

    {{ "Value:"|t }} {{ value }}

    -

    {{ "Format:"|t }} {{ format }}

    diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml deleted file mode 100644 index b60f3ca5..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.info.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: 'UI Patterns Display Suite Test' -type: module -description: 'Test module for UI Patterns.' -package: 'Testing' -dependencies: - - ui_patterns:ui_patterns - - ui_patterns:ui_patterns_ds - - ui_patterns:ui_patterns_library - -config_devel: - install: - - core.entity_view_display.node.article.default - - field.field.node.article.body - - node.type.article diff --git a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.install b/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.install deleted file mode 100644 index e1ebeb86..00000000 --- a/modules/ui_patterns_ds/tests/modules/ui_patterns_ds_test/ui_patterns_ds_test.install +++ /dev/null @@ -1,17 +0,0 @@ -getEditable('ds.settings') - ->set('field_template', TRUE) - ->save(); -} diff --git a/modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldRenderTest.php b/modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldRenderTest.php deleted file mode 100644 index 72e931ed..00000000 --- a/modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldRenderTest.php +++ /dev/null @@ -1,93 +0,0 @@ -assertSession(); - - $this->enableTwigDebugMode(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - $node = $this->drupalCreateNode([ - 'title' => 'Test article', - 'body' => 'Test body', - 'type' => 'article', - ]); - - $this->drupalGet($node->toUrl()); - - // Assert correct variant suggestions. - $suggestions = [ - 'pattern-field--variant-default--ds-field-template--body--node--article--full.html.twig', - 'pattern-field--variant-default--ds-field-template--body--node--full.html.twig', - 'pattern-field--variant-default--ds-field-template--body--node--article.html.twig', - 'pattern-field--variant-default--ds-field-template--body--node.html.twig', - 'pattern-field--variant-default--ds-field-template--body.html.twig', - 'pattern-field--variant-default--ds-field-template.html.twig', - - 'pattern-field--ds-field-template--body--node--article--full.html.twig', - 'pattern-field--ds-field-template--body--node--full.html.twig', - 'pattern-field--ds-field-template--body--node--article.html.twig', - 'pattern-field--ds-field-template--body--node.html.twig', - 'pattern-field--ds-field-template--body.html.twig', - 'pattern-field--ds-field-template.html.twig', - - 'pattern-field--variant-default.html.twig', - 'pattern-field.html.twig', - ]; - foreach ($suggestions as $suggestion) { - $assert_session->responseContains($suggestion); - } - - // Test content is rendered in the pattern. - $assert_session->pageTextContains('Value: Test body'); - } - -} diff --git a/modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldSettingsTest.php b/modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldSettingsTest.php deleted file mode 100644 index cf9c1d1c..00000000 --- a/modules/ui_patterns_ds/tests/src/FunctionalJavascript/UiPatternsFieldSettingsTest.php +++ /dev/null @@ -1,113 +0,0 @@ -getSession(); - $page = $session->getPage(); - $assert_session = $this->assertSession(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - // Visit Article's default display settings page. - $this->drupalGet('/admin/structure/types/manage/article/display'); - - // Click on Body field display settings. - $page->pressButton('body_plugin_settings_edit'); - $assert_session->assertWaitOnAjaxRequest(); - - // Select "Pattern" field template. - $page->selectFieldOption('Choose a Field Template', 'Pattern'); - $assert_session->assertWaitOnAjaxRequest(); - - // Choose test pattern. - $page->selectFieldOption('fields[body][settings_edit_form][third_party_settings][ds][ft][settings][pattern]', 'Field'); - $assert_session->assertWaitOnAjaxRequest(); - - // Choose test variant. - $page->selectFieldOption('Variant', 'Overridden'); - $assert_session->assertWaitOnAjaxRequest(); - - // Map pattern fields. - $page->selectFieldOption('Destination for Body', '- Hidden -'); - $page->selectFieldOption('Destination for Body: value', 'Value'); - $page->selectFieldOption('Destination for Body: format', 'Format'); - - // Submit field settings. - // @todo Make sure values are persisted when re-editing the field settings. - $page->pressButton('Update'); - $assert_session->assertWaitOnAjaxRequest(); - - // Save view mode setting page. - $page->pressButton('Save'); - - // Get default view mode for Article node bundle. - $display = EntityViewDisplay::load("node.article.default"); - - // Assert existence of third party settings. - $third_party_settings = $display->getComponent('body')['third_party_settings']; - $this->assertNotEmpty($third_party_settings['ds']['ft'], "Field template settings not found."); - - // Assert settings value. - $settings = $third_party_settings['ds']['ft']; - $this->assertEquals($settings['id'], 'pattern'); - $this->assertEquals($settings['settings']['pattern'], 'field'); - $this->assertEquals($settings['settings']['pattern_variant'], 'overridden'); - - // Assert mappings. - $this->assertNotEmpty($settings['settings']['pattern_mapping'], "Pattern mapping is empty."); - - $mapping = $settings['settings']['pattern_mapping']; - $this->assertArrayNotHasKey('ds_field_template:body', $mapping, "Body mapping found."); - $this->assertArrayHasKey('ds_field_template:body__value', $mapping, "Body value mapping not found."); - $this->assertArrayHasKey('ds_field_template:body__format', $mapping, "Body format mapping not found."); - - $this->assertEquals($mapping['ds_field_template:body__value']['destination'], 'value', "Body value mapping not valid."); - $this->assertEquals($mapping['ds_field_template:body__format']['destination'], 'format', "Body format mapping not valid."); - } - -} diff --git a/modules/ui_patterns_ds/ui_patterns_ds.info.yml b/modules/ui_patterns_ds/ui_patterns_ds.info.yml deleted file mode 100644 index 2bbdfa21..00000000 --- a/modules/ui_patterns_ds/ui_patterns_ds.info.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: 'UI Patterns Display Suite' -type: module -description: 'Use patterns as Display Suite field templates. It also provides Display Suite pattern sources.' -core_version_requirement: ^9 || ^10 -package: 'User interface' -dependencies: - - ds:ds - - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_ds/ui_patterns_ds.module b/modules/ui_patterns_ds/ui_patterns_ds.module deleted file mode 100644 index 07b22913..00000000 --- a/modules/ui_patterns_ds/ui_patterns_ds.module +++ /dev/null @@ -1,94 +0,0 @@ - [ - 'variables' => ['pattern' => NULL], - ], - ]; -} - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function ui_patterns_ds_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) { - array_unshift($form['actions']['submit']['#submit'], 'ui_patterns_ds_field_overview_submit'); -} - -/** - * Form submit callback to fix the field_group configuration. - * - * @param array $form - * The form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. - */ -function ui_patterns_ds_field_overview_submit(array $form, FormStateInterface $form_state) { - /** @var \Drupal\Core\Entity\EntityFormInterface $entity_form */ - /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */ - - $entity_form = $form_state->getFormObject(); - $display = $entity_form->getEntity(); - $components = array_filter($display->getComponents(), function ($component) { - return isset($component['third_party_settings']['ds']['ft']['settings']['pattern']); - }); - - foreach ($components as $name => $component) { - PatternDisplayFormTrait::processFormStateValues($component['third_party_settings']['ds']['ft']['settings']); - $display->setComponent($name, $component); - } - - $display->save(); -} - -/** - * Preprocess hook. - * - * @param array $variables - * Theme variables. - */ -function template_preprocess_field__pattern_ds_field_template(array &$variables) { - \Drupal::service('ui_patterns_ds.field_template_processor')->process($variables); -} - -/** - * Implements hook_ui_patterns_suggestions_alter(). - */ -function ui_patterns_ds_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('ds_field_template')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $field_name = $context->getProperty('field_name'); - $entity_type = $context->getProperty('entity_type'); - $bundle = $context->getProperty('bundle'); - $view_mode = $context->getProperty('view_mode'); - - $suggestions[] = $hook . '__ds_field_template'; - $suggestions[] = $hook . '__ds_field_template__' . $field_name; - $suggestions[] = $hook . '__ds_field_template__' . $field_name . '__' . $entity_type; - $suggestions[] = $hook . '__ds_field_template__' . $field_name . '__' . $entity_type . '__' . $bundle; - $suggestions[] = $hook . '__ds_field_template__' . $field_name . '__' . $entity_type . '__' . $view_mode; - $suggestions[] = $hook . '__ds_field_template__' . $field_name . '__' . $entity_type . '__' . $bundle . '__' . $view_mode; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__ds_field_template'; - $suggestions[] = $hook . '__variant_' . $variant . '__ds_field_template__' . $field_name; - $suggestions[] = $hook . '__variant_' . $variant . '__ds_field_template__' . $field_name . '__' . $entity_type; - $suggestions[] = $hook . '__variant_' . $variant . '__ds_field_template__' . $field_name . '__' . $entity_type . '__' . $bundle; - $suggestions[] = $hook . '__variant_' . $variant . '__ds_field_template__' . $field_name . '__' . $entity_type . '__' . $view_mode; - $suggestions[] = $hook . '__variant_' . $variant . '__ds_field_template__' . $field_name . '__' . $entity_type . '__' . $bundle . '__' . $view_mode; - } - } -} diff --git a/modules/ui_patterns_ds/ui_patterns_ds.services.yml b/modules/ui_patterns_ds/ui_patterns_ds.services.yml deleted file mode 100644 index 1cea2e17..00000000 --- a/modules/ui_patterns_ds/ui_patterns_ds.services.yml +++ /dev/null @@ -1,3 +0,0 @@ -services: - ui_patterns_ds.field_template_processor: - class: Drupal\ui_patterns_ds\FieldTemplateProcessor diff --git a/modules/ui_patterns_field_group/README.md b/modules/ui_patterns_field_group/README.md deleted file mode 100644 index 6e427526..00000000 --- a/modules/ui_patterns_field_group/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# UI Patterns Field Group - -Integrates UI Patterns with the [Field Group](https://www.drupal.org/project/field_group) module. diff --git a/modules/ui_patterns_field_group/src/Plugin/field_group/FieldGroupFormatter/PatternFormatter.php b/modules/ui_patterns_field_group/src/Plugin/field_group/FieldGroupFormatter/PatternFormatter.php deleted file mode 100644 index 20970b0c..00000000 --- a/modules/ui_patterns_field_group/src/Plugin/field_group/FieldGroupFormatter/PatternFormatter.php +++ /dev/null @@ -1,186 +0,0 @@ -configuration = $configuration; - $this->patternsManager = $patterns_manager; - $this->sourceManager = $source_manager; - $this->entityFinder = new EntityFinder(); - $this->moduleHandler = $module_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('plugin.manager.ui_patterns'), - $container->get('plugin.manager.ui_patterns_source'), - $container->get('module_handler') - ); - } - - /** - * {@inheritdoc} - */ - public function preRender(&$element, $rendering_object) { - - $fields = []; - $mapping = $this->getSetting('pattern_mapping'); - foreach ($mapping as $field) { - $fields[$field['destination']][] = $element[$field['source']]; - } - - $element['#type'] = 'pattern'; - $element['#id'] = $this->getSetting('pattern'); - $element['#fields'] = $fields; - $element['#multiple_sources'] = TRUE; - $element['#variant'] = $this->getSetting('pattern_variant'); - - // Allow default context values to not override those exposed elsewhere. - $element['#context']['type'] = 'field_group'; - $element['#context']['group_name'] = $this->configuration['group']->group_name; - $element['#context']['entity_type'] = $this->configuration['group']->entity_type; - $element['#context']['bundle'] = $this->configuration['group']->bundle; - $element['#context']['view_mode'] = $this->configuration['group']->mode; - - // Pass current entity to pattern context, if any. - $element['#context']['entity'] = $this->entityFinder->findEntityFromFields($element['#fields']); - } - - /** - * Get field group name. - * - * @return string - * Field group name. - */ - protected function getFieldGroupName() { - return $this->configuration['group']->group_name; - } - - /** - * {@inheritdoc} - */ - public function settingsForm() { - $form = parent::settingsForm(); - unset($form['id']); - unset($form['classes']); - - if (isset($this->configuration['group']->children) && !empty($this->configuration['group']->children)) { - $context = [ - 'entity_type' => $this->configuration['group']->entity_type, - 'entity_bundle' => $this->configuration['group']->bundle, - 'limit' => $this->configuration['group']->children, - ]; - - $this->buildPatternDisplayForm($form, 'entity_display', $context, $this->configuration['settings']); - } - else { - $form['message'] = [ - '#markup' => $this->t('Attention: you have to add fields to this field group and save the whole entity display before being able to to access the pattern display configuration.'), - ]; - } - - return $form; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $label = $this->t('None'); - if (!empty($this->getSetting('pattern'))) { - $label = $this->patternsManager->getDefinition($this->getSetting('pattern'))->getLabel(); - } - - return [ - $this->t('Pattern: @pattern', ['@pattern' => $label]), - ]; - } - - /** - * {@inheritdoc} - */ - public static function defaultContextSettings($context) { - return [ - 'pattern' => '', - 'pattern_mapping' => [], - 'pattern_variant' => '', - ] + parent::defaultContextSettings($context); - } - -} diff --git a/modules/ui_patterns_field_group/src/Utility/EntityFinder.php b/modules/ui_patterns_field_group/src/Utility/EntityFinder.php deleted file mode 100644 index 4e06b65a..00000000 --- a/modules/ui_patterns_field_group/src/Utility/EntityFinder.php +++ /dev/null @@ -1,35 +0,0 @@ - $value) { - if ($key === '#object' && $value instanceof ContentEntityBase) { - return $value; - } - } - } - -} diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_form_display.node.article.default.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_form_display.node.article.default.yml deleted file mode 100644 index 4032847a..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_form_display.node.article.default.yml +++ /dev/null @@ -1,76 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.field.node.article.body - - field.field.node.article.field_text - - node.type.article - module: - - text -id: node.article.default -targetEntityType: node -bundle: article -mode: default -content: - body: - type: text_textarea_with_summary - weight: 121 - settings: - rows: 9 - summary_rows: 3 - placeholder: '' - third_party_settings: { } - region: content - created: - type: datetime_timestamp - weight: 10 - region: content - settings: { } - third_party_settings: { } - field_text: - weight: 122 - settings: - size: 60 - placeholder: '' - third_party_settings: { } - type: string_textfield - region: content - promote: - type: boolean_checkbox - settings: - display_label: true - weight: 15 - region: content - third_party_settings: { } - status: - type: boolean_checkbox - settings: - display_label: true - weight: 120 - region: content - third_party_settings: { } - sticky: - type: boolean_checkbox - settings: - display_label: true - weight: 16 - region: content - third_party_settings: { } - title: - type: string_textfield - weight: -5 - region: content - settings: - size: 60 - placeholder: '' - third_party_settings: { } - uid: - type: entity_reference_autocomplete - weight: 5 - settings: - match_operator: CONTAINS - size: 60 - placeholder: '' - region: content - third_party_settings: { } -hidden: { } diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_view_display.node.article.default.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_view_display.node.article.default.yml deleted file mode 100644 index b7461a28..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/core.entity_view_display.node.article.default.yml +++ /dev/null @@ -1,56 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.field.node.article.body - - field.field.node.article.field_text - - node.type.article - module: - - field_group - - text - - user -third_party_settings: - field_group: - group_pattern_group: - children: - - field_text - parent_name: '' - weight: 20 - format_type: pattern_formatter - format_settings: - label: 'Pattern group' - pattern_variant: first - pattern: metadata - pattern_mapping: - 'fields:field_text': - destination: field_1 - weight: 0 - plugin: fields - source: field_text - label: 'Pattern group' -id: node.article.default -targetEntityType: node -bundle: article -mode: default -content: - body: - label: hidden - type: text_default - weight: 101 - settings: { } - third_party_settings: { } - region: content - field_text: - weight: 102 - label: above - settings: - link_to_entity: false - third_party_settings: { } - type: string - region: content - links: - weight: 100 - region: content - settings: { } - third_party_settings: { } -hidden: { } diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.body.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.body.yml deleted file mode 100644 index 8f3681d9..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.body.yml +++ /dev/null @@ -1,21 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.storage.node.body - - node.type.article - module: - - text -id: node.article.body -field_name: body -entity_type: node -bundle: article -label: Body -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - display_summary: true -field_type: text_with_summary diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.field_text.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.field_text.yml deleted file mode 100644 index b4ea928b..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.field.node.article.field_text.yml +++ /dev/null @@ -1,18 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.storage.node.field_text - - node.type.article -id: node.article.field_text -field_name: field_text -entity_type: node -bundle: article -label: Text -description: '' -required: false -translatable: false -default_value: { } -default_value_callback: '' -settings: { } -field_type: string diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.storage.node.field_text.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.storage.node.field_text.yml deleted file mode 100644 index 1aeeadc2..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/field.storage.node.field_text.yml +++ /dev/null @@ -1,20 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - node -id: node.field_text -field_name: field_text -entity_type: node -type: string -settings: - max_length: 255 - is_ascii: false - case_sensitive: false -module: core -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/node.type.article.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/node.type.article.yml deleted file mode 100644 index f47f2add..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/config/install/node.type.article.yml +++ /dev/null @@ -1,10 +0,0 @@ -langcode: en -status: true -dependencies: { } -name: Article -type: article -description: '' -help: '' -new_revision: true -preview_mode: 1 -display_submitted: true diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/metadata.ui_patterns.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/metadata.ui_patterns.yml deleted file mode 100644 index 4905e1bc..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/metadata.ui_patterns.yml +++ /dev/null @@ -1,20 +0,0 @@ -metadata: - label: "Metadata" - variants: - first: - label: "First" - second: - label: "Second" - fields: - field_1: - type: "text" - label: "Field 1" - preview: "Field 1" - field_2: - type: "text" - label: "Field 2" - preview: "Field 3" - field_3: - type: "text" - label: "Field 3" - preview: "Field 3" diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/pattern-metadata.html.twig b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/pattern-metadata.html.twig deleted file mode 100644 index 6866bf69..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/templates/pattern-metadata.html.twig +++ /dev/null @@ -1,9 +0,0 @@ -{# -/** - * @file - * Test metadata pattern. - */ -#} -

    {{ "Field 1:"|t }} {{ field_1 }}

    -

    {{ "Field 2:"|t }} {{ field_2 }}

    -

    {{ "Field 3:"|t }} {{ field_3 }}

    diff --git a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml b/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml deleted file mode 100644 index 7b0906a0..00000000 --- a/modules/ui_patterns_field_group/tests/modules/ui_patterns_field_group_test/ui_patterns_field_group_test.info.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'UI Patterns Field Group Test' -type: module -description: 'Test module for UI Patterns.' -package: 'Testing' -dependencies: - - ui_patterns:ui_patterns - - ui_patterns:ui_patterns_field_group - - ui_patterns:ui_patterns_library - -config_devel: - install: - - core.entity_form_display.node.article.default - - core.entity_view_display.node.article.default - - field.field.node.article.body - - field.field.node.article.field_text - - field.storage.node.field_text - - node.type.article diff --git a/modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupRenderTest.php b/modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupRenderTest.php deleted file mode 100644 index 066c1e16..00000000 --- a/modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupRenderTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertSession(); - - $this->enableTwigDebugMode(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - $node = $this->drupalCreateNode([ - 'title' => 'Test article', - 'field_text' => 'Test text field', - 'type' => 'article', - ]); - - $this->drupalGet($node->toUrl()); - - // Assert correct variant suggestions. - $suggestions = [ - 'pattern-metadata--variant-first--field-group--group-pattern-group--node--article--default.html.twig', - 'pattern-metadata--variant-first--field-group--group-pattern-group--node--default.html.twig', - 'pattern-metadata--variant-first--field-group--group-pattern-group--node--article.html.twig', - 'pattern-metadata--variant-first--field-group--group-pattern-group--node.html.twig', - 'pattern-metadata--variant-first--field-group--group-pattern-group.html.twig', - 'pattern-metadata--variant-first--field-group.html.twig', - - 'pattern-metadata--field-group--group-pattern-group--node--article--default.html.twig', - 'pattern-metadata--field-group--group-pattern-group--node--default.html.twig', - 'pattern-metadata--field-group--group-pattern-group--node--article.html.twig', - 'pattern-metadata--field-group--group-pattern-group--node.html.twig', - 'pattern-metadata--field-group--group-pattern-group.html.twig', - 'pattern-metadata--field-group.html.twig', - - 'pattern-metadata--variant-first.html.twig', - 'pattern-metadata.html.twig', - ]; - foreach ($suggestions as $suggestion) { - $assert_session->responseContains($suggestion); - } - - // Test field content is rendered in field group pattern. - $assert_session->pageTextContains('Field 1: Text Test text field'); - } - -} diff --git a/modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupSettingsTest.php b/modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupSettingsTest.php deleted file mode 100644 index b6f4fedc..00000000 --- a/modules/ui_patterns_field_group/tests/src/FunctionalJavascript/UiPatternsFieldGroupSettingsTest.php +++ /dev/null @@ -1,120 +0,0 @@ -getSession()->getPage(); - $assert_session = $this->assertSession(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - // Visit Article's field group creation page. - $this->drupalGet('/admin/structure/types/manage/article/display/add-group'); - - // Add new Pattern field group. - $page->selectFieldOption('Add a new group', 'Pattern'); - $assert_session->assertWaitOnAjaxRequest(); - - // Select pattern and save. - $page->fillField('Label', 'Metadata'); - $page->waitFor(10, function (DocumentElement $page) { - return $page->hasContent('Machine name: group_metadata'); - }); - $page->pressButton('Save and continue'); - - // Assert warning message. - $assert_session->pageTextContains("Attention: you have to add fields to this field group and save the whole entity display before being able to to access the pattern display configuration."); - } - - /** - * Test that pattern field group settings are correctly saved. - */ - public function testUiPatternsFieldGroupSettings() { - $page = $this->getSession()->getPage(); - $assert_session = $this->assertSession(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - // Visit Article's default view mode page. - $this->drupalGet('/admin/structure/types/manage/article/display'); - - // Click on field group settings button. - $page->pressButton('group_pattern_group_group_settings_edit'); - $assert_session->assertWaitOnAjaxRequest(); - - // Choose variant. - $page->selectFieldOption('Variant', 'Second'); - $page->selectFieldOption('Destination for Text', 'Field 2'); - $assert_session->assertWaitOnAjaxRequest(); - - // Submit field group settings. - $page->pressButton('Update'); - $assert_session->assertWaitOnAjaxRequest(); - - // Save view mode setting page. - $page->pressButton('Save'); - - // Get default view mode for Article node bundle. - $display = EntityViewDisplay::load("node.article.default"); - - // Assert existence of third party settings. - $settings = $display->getThirdPartySetting('field_group', 'group_pattern_group'); - - // Assert settings value. - $this->assertEquals($settings['format_type'], 'pattern_formatter'); - $this->assertEquals($settings['format_settings']['pattern'], 'metadata'); - $this->assertEquals($settings['format_settings']['pattern_variant'], 'second'); - - // Assert mappings. - $this->assertNotEmpty($settings['format_settings']['pattern_mapping'], "Pattern mapping is empty."); - - $mapping = $settings['format_settings']['pattern_mapping']; - $this->assertArrayHasKey('fields:field_text', $mapping, 'Mapping not found.'); - $this->assertEquals($mapping['fields:field_text']['destination'], 'field_2', "Mapping not valid."); - } - -} diff --git a/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php b/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php deleted file mode 100644 index 75a60c93..00000000 --- a/modules/ui_patterns_field_group/tests/src/Unit/EntityFinderTest.php +++ /dev/null @@ -1,111 +0,0 @@ -findEntityFromFields($fields); - $this->assertEquals($expected, $entity); - } - - /** - * Test data provider. - * - * @return array - * Test data. - */ - public function fieldsDataProvider() { - $good = $this->createMock(ContentEntityBase::class); - $bad = new \stdClass(); - - return [ - // Found with singe value per field. - [ - 'fields' => [ - 'foo' => ['#object' => $good], - 'bar' => ['#object' => $bad], - ], - 'expected' => $good, - ], - - // Found with singe value per field. - [ - 'fields' => [ - 'bar' => ['#object' => $bad], - 'foo' => ['#object' => $good], - ], - 'expected' => $good, - ], - - // Found with multiple values per field. - [ - 'fields' => [ - 'foo' => [['#object' => $good]], - 'bar' => [['#object' => $bad]], - ], - 'expected' => $good, - ], - - // Found with multiple values per field. - [ - 'fields' => [ - 'bar' => [ - ['#object' => $bad], - ['#object' => $good], - ], - 'foo' => [ - ['#object' => $bad], - ['#object' => $bad], - ], - ], - 'expected' => $good, - ], - - // Found with one empty array field and multiple values per field. - [ - 'fields' => [ - 'foo' => [ - [], - ['#object' => $good], - ], - ], - 'expected' => $good, - ], - - // Found with one empty null field and multiple values per field. - [ - 'fields' => [ - 'foo' => [NULL, ['#object' => $good], - ], - ], - 'expected' => $good, - ], - - // Not found with one empty null field and multiple values per field. - [ - 'fields' => [ - 'foo' => [NULL, ['#object' => $bad], - ], - ], - 'expected' => NULL, - ], - ]; - } - -} diff --git a/modules/ui_patterns_field_group/ui_patterns_field_group.module b/modules/ui_patterns_field_group/ui_patterns_field_group.module deleted file mode 100644 index 8b4b0dc2..00000000 --- a/modules/ui_patterns_field_group/ui_patterns_field_group.module +++ /dev/null @@ -1,129 +0,0 @@ -get('field_group'); - if (!empty($field_group_form_state)) { - foreach ($form['#fieldgroups'] as $group_name) { - // Only save updated groups. - if (!isset($field_group_form_state[$group_name])) { - continue; - } - - if (isset($field_group_form_state[$group_name]->format_settings)) { - // Call static processFormStateValues if the plugin implements it. - $plugin_definition = \Drupal::service('plugin.manager.field_group.formatters')->getDefinition($field_group_form_state[$group_name]->format_type, FALSE); - if (method_exists($plugin_definition['class'], 'processFormStateValues')) { - call_user_func_array([ - $plugin_definition['class'], - 'processFormStateValues', - ], - [&$field_group_form_state[$group_name]->format_settings]); - } - } - } - - // Set the form_state so that the submit hook of field_groups can work. - $form_state->set('field_group', $field_group_form_state); - } -} - -/** - * Implements hook_ui_patterns_suggestions_alter(). - */ -function ui_patterns_field_group_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('field_group')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $group_name = $context->getProperty('group_name'); - $entity_type = $context->getProperty('entity_type'); - $bundle = $context->getProperty('bundle'); - $view_mode = $context->getProperty('view_mode'); - - $suggestions[] = $hook . '__field_group'; - $suggestions[] = $hook . '__field_group__' . $group_name; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $view_mode; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle . '__' . $view_mode; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__field_group'; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $view_mode; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle . '__' . $view_mode; - } - } -} - -/** - * Implements hook_ui_patterns_destination_suggestions_alter(). - */ -function ui_patterns_field_group_ui_patterns_destination_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('field_group')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $group_name = $context->getProperty('group_name'); - $entity_type = $context->getProperty('entity_type'); - $bundle = $context->getProperty('bundle'); - $view_mode = $context->getProperty('view_mode'); - $pattern = $context->getProperty('pattern'); - $field = $context->getProperty('field'); - - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $view_mode . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle . '__' . $view_mode . '__' . $pattern . '__' . $field; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $view_mode . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__field_group__' . $group_name . '__' . $entity_type . '__' . $bundle . '__' . $view_mode . '__' . $pattern . '__' . $field; - } - } -} diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 71327000..51da08b4 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -95,11 +95,11 @@ public function build(array $regions) { } return [ - '#type' => 'pattern', - '#id' => $this->getPluginDefinition()->get('additional')['pattern'], - '#fields' => $fields, + '#type' => 'component', + '#component' => $this->getPluginDefinition()->getProvider() . ':' . $this->getPluginDefinition()->get('additional')['pattern'], + '#slots' => $fields, '#variant' => $configuration['pattern']['variant'], - ] + $this->elementInfo->getInfo('pattern'); + ]; } /** diff --git a/modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php b/modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php new file mode 100644 index 00000000..f325caa3 --- /dev/null +++ b/modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php @@ -0,0 +1,103 @@ +fileSystem->basename($file, '.ui_patterns.yml'); + $provider_paths = array_flip($this->directories); + $provider = $this->findProvider($file, $provider_paths); + // We use the provider to dedupe components because it does not make sense + // for a single provider to fork itself. + return sprintf('%s:%s', $provider, $id); + } + + /** + * Finds the provider of the discovered file. + * + * The approach here is suboptimal because the provider is actually set in + * the plugin definition after the getIdentifier is called. So we either do + * this, or we forego the base class. + * + * @param string $file + * The discovered file. + * @param array $provider_paths + * The associative array of the path to the provider. + * + * @return string + * The provider + */ + private function findProvider(string $file, array $provider_paths): string { + $parts = explode(DIRECTORY_SEPARATOR, $file); + array_pop($parts); + if (empty($parts)) { + return ''; + } + $provider = $provider_paths[implode(DIRECTORY_SEPARATOR, $parts)] ?? ''; + return empty($provider) + ? $this->findProvider(implode(DIRECTORY_SEPARATOR, $parts), $provider_paths) + : $provider; + } + +} diff --git a/modules/ui_patterns_sdc/src/Plugin/Discovery/UiPatternsSdcPluginDiscovery.php b/modules/ui_patterns_sdc/src/Plugin/Discovery/UiPatternsSdcPluginDiscovery.php new file mode 100644 index 00000000..68c63414 --- /dev/null +++ b/modules/ui_patterns_sdc/src/Plugin/Discovery/UiPatternsSdcPluginDiscovery.php @@ -0,0 +1,36 @@ +discovery = new UIPatternsSdcDiscovery($directories, $file_cache_key_suffix, $file_system); + } + +} diff --git a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php new file mode 100644 index 00000000..0151a249 --- /dev/null +++ b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php @@ -0,0 +1,67 @@ +discovery)) { + $directories = $this->getScanDirectories(); + $decorated = new DirectoryWithMetadataPluginDiscovery($directories, 'sdc', $this->fileSystem); + $this->discovery = new UiPatternsSdcPluginDiscovery( + $decorated, + $directories, + 'sdc', + $this->fileSystem + ); + } + return $this->discovery; + } + + /** + * {@inheritdoc} + */ + protected function alterDefinitions(&$definitions) { + $ui_patterns_definitions = UiPatterns::getPatternDefinitions(); + foreach ($definitions as & $definition) { + $id = $definition['id'] ?? NULL; + if ($id) { + $ui_patterns_id = explode(':', $id)[1] ?? NULL; + if ($pattern = $ui_patterns_definitions[$ui_patterns_id] ?? NULL) { + //$definition['$schema'] = 'https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json'; + $definition['props'] = ['type' => 'object', 'properties' => []]; + foreach ($pattern->getFields() as $field_name => $field) { + $definition['slots'][$field_name] = [ + 'title' => $field->getLabel(), + 'description' => $field->getDescription(), + 'examples' => $field->getPreview() + ]; + } + } + } + + } + return parent::alterDefinitions($definitions); + } + +} diff --git a/modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php b/modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php new file mode 100644 index 00000000..8f4ee012 --- /dev/null +++ b/modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php @@ -0,0 +1,20 @@ +hasDefinition('plugin.manager.sdc')) { + $definition = $container->getDefinition('plugin.manager.sdc'); + $definition->setClass( + 'Drupal\ui_patterns_sdc\UiPatternsSdcPluginManager' + ); + } + } + +} diff --git a/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml b/modules/ui_patterns_sdc/ui_patterns_sdc.info.yml similarity index 50% rename from modules/ui_patterns_field_group/ui_patterns_field_group.info.yml rename to modules/ui_patterns_sdc/ui_patterns_sdc.info.yml index d2a376eb..a9671368 100644 --- a/modules/ui_patterns_field_group/ui_patterns_field_group.info.yml +++ b/modules/ui_patterns_sdc/ui_patterns_sdc.info.yml @@ -1,8 +1,7 @@ -name: 'UI Patterns Field Group' +name: 'UI Patterns SDC' type: module -description: 'Use patterns as field groups templates.' +description: 'Use patterns with Single Directory Components.' core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - - field_group:field_group - ui_patterns:ui_patterns diff --git a/src/Element/Pattern.php b/src/Element/Pattern.php deleted file mode 100644 index e04ea99a..00000000 --- a/src/Element/Pattern.php +++ /dev/null @@ -1,238 +0,0 @@ - FALSE, - '#multiple_sources' => FALSE, - '#pre_render' => [ - [$class, 'processContext'], - [$class, 'processRenderArray'], - [$class, 'processLibraries'], - [$class, 'processMultipleSources'], - [$class, 'processFields'], - [$class, 'ensureVariant'], - [$class, 'processUse'], - ], - ]; - } - - /** - * Process render array. - * - * @param array $element - * Render array. - * - * @return array - * Render array. - */ - public static function processRenderArray(array $element) { - $element['#theme'] = UiPatterns::getPatternDefinition($element['#id'])->getThemeHook(); - - if (isset($element['#attributes']) && !empty($element['#attributes']) && is_array($element['#attributes'])) { - $element['#attributes'] = new Attribute($element['#attributes']); - } - else { - $element['#attributes'] = new Attribute(); - } - - unset($element['#type']); - return $element; - } - - /** - * Process libraries. - * - * @param array $element - * Render array. - * - * @return array - * Render array. - */ - public static function processLibraries(array $element) { - foreach (UiPatterns::getPatternDefinition($element['#id'])->getLibrariesNames() as $library) { - $element['#attached']['library'][] = $library; - } - - return $element; - } - - /** - * Process fields. - * - * @param array $element - * Render array. - * - * @return array - * Render array. - */ - public static function processFields(array $element) { - // Make sure we don't render anything in case fields are empty. - if (self::hasFields($element)) { - $fields = $element['#fields']; - unset($element['#fields']); - - foreach ($fields as $name => $field) { - $key = '#' . $name; - $element[$key] = $field; - } - } - else { - $element['#markup'] = ''; - } - - return $element; - } - - /** - * Make sure that we never pass through a value that is not a string. - * - * This would prevent accidental assignments of a render array as variant - * which would break hook_ui_patterns_suggestions_alter(). - * - * @param array $element - * Render array. - * - * @return array - * Render array. - */ - public static function ensureVariant(array $element) { - if (!isset($element['#variant']) || !is_string($element['#variant'])) { - $element['#variant'] = ''; - } - - return $element; - } - - /** - * Process use property. - * - * @param array $element - * Render array. - * - * @return array - * Render array. - */ - public static function processUse(array $element) { - $definition = UiPatterns::getPatternDefinition($element['#id']); - if ($definition->hasUse()) { - $element['#use'] = $definition->getUse(); - } - - return $element; - } - - /** - * Process fields. - * - * @param array $element - * Render array. - * - * @return array - * Render array. - */ - public static function processMultipleSources(array $element) { - // Make sure we don't render anything in case fields are empty. - if (self::hasFields($element) && self::hasMultipleSources($element)) { - foreach ($element['#fields'] as $name => $field) { - // This guarantees backward compatibility: single sources be simple. - $element['#fields'][$name] = reset($field); - if (count($field) > 1) { - /** @var \Drupal\ui_patterns\Element\PatternContext $context */ - $context = $element['#context']; - $context->setProperty('pattern', $element['#id']); - $context->setProperty('field', $name); - - // Render multiple sources with "patterns_destination" template. - $element['#fields'][$name] = [ - '#sources' => $field, - '#context' => $context, - '#theme' => 'patterns_destination', - ]; - } - } - } - return $element; - } - - /** - * Process context. - * - * @param array $element - * Render array. - * - * @return array - * Render array. - * - * @throws \Drupal\ui_patterns\Exception\PatternRenderException - * Throws an exception if no context type is specified. - */ - public static function processContext(array $element) { - - if (self::hasValidContext($element)) { - $context = $element['#context']; - $element['#context'] = new PatternContext($context['type'], $element['#context']); - } - else { - $element['#context'] = new PatternContext('empty'); - } - - return $element; - } - - /** - * Whereas pattern has field or not. - * - * @param array $element - * Render array. - * - * @return bool - * TRUE or FALSE. - */ - public static function hasFields(array $element) { - return isset($element['#fields']) && !empty($element['#fields']) && is_array($element['#fields']); - } - - /** - * Whereas pattern fields can accept multiple sources. - * - * @param array $element - * Render array. - * - * @return bool - * TRUE or FALSE. - */ - public static function hasMultipleSources(array $element) { - return isset($element['#multiple_sources']) && $element['#multiple_sources'] === TRUE; - } - - /** - * Whereas pattern has a valid context, i.e. context "type" is set. - * - * @param array $element - * Render array. - * - * @return bool - * TRUE or FALSE. - */ - public static function hasValidContext(array $element) { - return isset($element['#context']) && is_array($element['#context']) && !empty($element['#context']['type']); - } - -} diff --git a/src/Element/PatternContext.php b/src/Element/PatternContext.php deleted file mode 100644 index aa987501..00000000 --- a/src/Element/PatternContext.php +++ /dev/null @@ -1,87 +0,0 @@ -type = $type; - unset($values['type']); - foreach ($values as $name => $value) { - $this->setProperty($name, $value); - } - } - - /** - * Get pattern context property. - * - * @return mixed - * Property value. - */ - public function getProperty($name) { - return $this->properties[$name] ?? NULL; - } - - /** - * Set pattern context property. - * - * @param string $name - * Property name. - * @param mixed $value - * Property value. - */ - public function setProperty($name, $value) { - $this->properties[$name] = $value; - } - - /** - * Check whereas the current context is of a given type. - * - * @param string $type - * Type string. - * - * @return bool - * Whereas the current context is of a given type. - */ - public function isOfType($type) { - return $this->type == $type; - } - - /** - * Get context type. - * - * @return string - * Context type. - */ - public function getType() { - return $this->type; - } - -} diff --git a/src/Element/PatternPreview.php b/src/Element/PatternPreview.php deleted file mode 100644 index c87545d3..00000000 --- a/src/Element/PatternPreview.php +++ /dev/null @@ -1,91 +0,0 @@ -getFields() as $field) { - $preview = $field->getPreview(); - // Some fields are used as Twig array keys and don't need escaping. - if ($field->getEscape()) { - // The examples are not user submitted and are safe markup. - $preview = self::getPreviewMarkup($preview); - } - - $fields[$field->getName()] = $preview; - } - - if (isset($definition['additional']['attributes'])) { - $fields['attributes'] = $definition['extra']['attributes']; - } - $element['#fields'] = $fields; - - return parent::processFields($element); - } - - /** - * Make previews markup safe. - * - * @param string|string[] $preview - * The preview, may be a string or an array. - * - * @return array|\Drupal\Component\Render\MarkupInterface|string - * Preview safe markup. - * - * @deprecated in ui_patterns:1.6.0 and is removed from ui_patterns:2.0.0 - * without replacement. - * - * @see https://www.drupal.org/node/3353287 - */ - public static function getPreviewMarkup($preview) { - @trigger_error(__METHOD__ . '() is deprecated in ui_patterns:1.6.0 and is removed from ui_patterns:2.0.0. No replacement provided. See https://www.drupal.org/node/3353287', E_USER_DEPRECATED); - if (is_array($preview)) { - $rendered = []; - // If preview is a render array add hashes to keys. - $hash_keys = array_key_exists('theme', $preview) || array_key_exists('type', $preview); - foreach ($preview as $key => $value) { - $key = $hash_keys ? '#' . $key : $key; - if (is_array($value)) { - // Process array values recursively. - $value = self::getPreviewMarkup($value); - } - $rendered[$key] = $value; - } - - return $rendered; - } - - return Markup::create($preview); - } - - /** - * {@inheritdoc} - */ - public static function processContext(array $element) { - $element['#context'] = new PatternContext('preview'); - - return $element; - } - -} diff --git a/tests/modules/ui_patterns_render_test/components/foo-bar.twig b/tests/modules/ui_patterns_render_test/components/foo-bar.twig new file mode 100644 index 00000000..76c7ac2d --- /dev/null +++ b/tests/modules/ui_patterns_render_test/components/foo-bar.twig @@ -0,0 +1 @@ +Foo Bar diff --git a/tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml b/tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml new file mode 100644 index 00000000..94addb38 --- /dev/null +++ b/tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml @@ -0,0 +1,5 @@ +foo-bar: + label: Foo Bar + variants: + default: + label: Default diff --git a/tests/modules/ui_patterns_render_test/components/foo.twig b/tests/modules/ui_patterns_render_test/components/foo.twig new file mode 100644 index 00000000..ba7ad791 --- /dev/null +++ b/tests/modules/ui_patterns_render_test/components/foo.twig @@ -0,0 +1,2 @@ +JUHUUUUUUUUUUUUUU +{{ test }} diff --git a/tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml b/tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml new file mode 100644 index 00000000..0ec96efa --- /dev/null +++ b/tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml @@ -0,0 +1,9 @@ +foo: + label: Foo + fields: + test: + type: test + label: Test + variants: + default: + label: Default diff --git a/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml b/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml index c699a913..0ec96efa 100644 --- a/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml +++ b/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml @@ -1,5 +1,9 @@ foo: label: Foo + fields: + test: + type: test + label: Test variants: default: label: Default diff --git a/ui_patterns.module b/ui_patterns.module index 3b5568c7..26abd513 100644 --- a/ui_patterns.module +++ b/ui_patterns.module @@ -5,70 +5,4 @@ * Contains ui_patterns.module. */ -use Drupal\ui_patterns\UiPatterns; -use Drupal\ui_patterns\Element\PatternContext; -/** - * Implements hook_theme(). - */ -function ui_patterns_theme() { - /** @var \Drupal\ui_patterns\Plugin\PatternBase $pattern */ - $items = [ - 'patterns_destination' => [ - 'variables' => ['sources' => NULL, 'context' => NULL], - ], - 'patterns_use_wrapper' => [ - 'variables' => ['use' => NULL], - ], - ]; - - foreach (UiPatterns::getManager()->getPatterns() as $pattern) { - $items += $pattern->getThemeImplementation(); - } - return $items; -} - -/** - * Implements hook_library_info_build(). - */ -function ui_patterns_library_info_build(): array { - $definitions = []; - foreach (UiPatterns::getManager()->getPatterns() as $pattern) { - $definitions += $pattern->getLibraryDefinitions(); - } - return $definitions; -} - -/** - * Implements hook_theme_suggestions_alter(). - */ -function ui_patterns_theme_suggestions_alter(array &$suggestions, array $variables, string $hook): void { - if (UiPatterns::getManager()->isPatternHook($hook)) { - \Drupal::moduleHandler()->alter('ui_patterns_suggestions', $suggestions, $variables, $variables['context']); - \Drupal::theme()->alter('ui_patterns_suggestions', $suggestions, $variables, $variables['context']); - } - - if ($hook == 'patterns_destination') { - \Drupal::moduleHandler()->alter('ui_patterns_destination_suggestions', $suggestions, $variables, $variables['context']); - \Drupal::theme()->alter('ui_patterns_destination_suggestions', $suggestions, $variables, $variables['context']); - } -} - -/** - * Implements hook_ui_patterns_suggestions_alter(). - */ -function ui_patterns_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context): void { - // Add preview theme suggestion. - if ($context->isOfType('preview')) { - $suggestions[] = $variables['theme_hook_original'] . '__preview'; - } - - if (!empty($variables['variant'])) { - $suggestions[] = $variables['theme_hook_original'] . '__variant_' . $variables['variant']; - - // Add variant preview theme suggestion. - if ($context->isOfType('preview')) { - $suggestions[] = $variables['theme_hook_original'] . '__variant_' . $variables['variant'] . '__preview'; - } - } -} From f2105474e22ca1663cfe72777ad05d3aa3a25663 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Mon, 19 Jun 2023 19:18:33 +0200 Subject: [PATCH 26/81] Add Component discovery --- .../ui_patterns_layouts.module | 1 + .../Plugin/Deriver/SdcComponentDeriver.php | 250 ++++++++++++++++++ .../Plugin/UiPatterns/Pattern/SdcPattern.php | 118 +++++++++ .../src/UiPatternsSdcPluginManager.php | 5 + .../ui_patterns_sdc.services.yml | 3 + 5 files changed, 377 insertions(+) create mode 100644 modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php create mode 100644 modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php create mode 100644 modules/ui_patterns_sdc/ui_patterns_sdc.services.yml diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.module b/modules/ui_patterns_layouts/ui_patterns_layouts.module index 9d5e2786..12d3578d 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.module +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.module @@ -18,6 +18,7 @@ use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface; function ui_patterns_layouts_layout_alter(&$definitions) { /** @var \Drupal\ui_patterns\Definition\PatternDefinition[] $pattern_definitions */ + $defs = UiPatterns::getPatternDefinitions(); // @todo Use layout deriver instead. // @link https://github.com/nuvoleweb/ui_patterns/issues/94 foreach (UiPatterns::getPatternDefinitions() as $pattern_definition) { diff --git a/modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php b/modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php new file mode 100644 index 00000000..e3a13afa --- /dev/null +++ b/modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php @@ -0,0 +1,250 @@ +root = $root; + $this->fileExtensions = $extensions; + $this->moduleHandler = $module_handler; + $this->themeHandler = $theme_handler; + $this->extensionDiscovery = new ExtensionDiscovery($root); + } + + /** + * {@inheritdoc} + */ + public static function create( + ContainerInterface $container, + $base_plugin_id + ) { + return new static( + $base_plugin_id, + $container->get('typed_data_manager'), + $container->get('messenger'), + $container->get('file_system'), + $container->getParameter('app.root'), + $container->getParameter('ui_patterns_sdc.file_extensions'), + $container->get('module_handler'), + $container->get('theme_handler'), + $container->get('config.factory') + ); + } + + /** + * {@inheritdoc} + */ + public function getFileExtensions() { + return $this->fileExtensions; + } + + /** + * {@inheritdoc} + */ + public function getPatterns() { + $patterns = []; + foreach ($this->getDirectories() as $provider => $directory) { + if (!empty($directory) && is_dir($directory)) { + foreach ($this->fileScanDirectory($directory) as $file_path => $file) { + $content = Yaml::decode(file_get_contents($file_path)); + $fields = []; + if (isset($content['slots']) && is_array($content['slots'])) { + foreach ($content['slots'] as $key => $slot) { + $fields[$key] = [ + 'label' => $slot['title'] ?? $key, + 'description' => $slot['description'] ?? NULL, + 'preview' => $slot['examples'] ?? NULL, + ]; + } + } + $definition = [ + 'id' => basename($file_path, '.component.yml'), + 'label' => $content['name'], + 'description' => $content['description'] ?? NULL, + 'fields' => $fields, + 'base path' => dirname($file_path), + 'file name' => basename($file_path), + 'provider' => $provider + ]; + $patterns[] = $this->getPatternDefinition($definition); + } + } + } + + return $patterns; + } + + /** + * Create a list of all directories to scan. + * + * This includes all module directories and directories of the default theme + * and all of its possible base themes. + * + * @return array + * An array containing directory paths keyed by their extension name. + */ + protected function getDirectories() { + $extension_directories = [ + ...$this->moduleHandler->getModuleDirectories(), + ...$this->themeHandler->getThemeDirectories(), + ]; + return array_map( + static fn(string $path) => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'components', + $extension_directories + ); + } + + /** + * Get extension name that hosts the given YAML definition file. + * + * @param string $pathname + * YAML definition file full path. + * + * @return bool|string + * Either extension machine name or FALSE if not found. + */ + protected function getHostExtension($pathname) { + $extensions = $this->getExtensionLocations(); + $parts = explode(DIRECTORY_SEPARATOR, $pathname); + while (!empty($parts)) { + $path = implode(DIRECTORY_SEPARATOR, $parts); + if (isset($extensions[$path])) { + return $extensions[$path]; + } + array_pop($parts); + } + return FALSE; + } + + /** + * Get extension locations. + * + * @return array + * Array of extensions keyed by their path location. + */ + protected function getExtensionLocations() { + /** @var \Drupal\Core\Extension\Extension[] $extensions */ + if (empty($this->extensionLocations)) { + $extensions = $this->extensionDiscovery->scan( + 'theme' + ) + $this->extensionDiscovery->scan('module'); + foreach ($extensions as $name => $extension) { + $this->extensionLocations[$this->root . DIRECTORY_SEPARATOR . $extension->getPath( + )] = $name; + } + } + return $this->extensionLocations; + } + +} diff --git a/modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php b/modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php new file mode 100644 index 00000000..094347fc --- /dev/null +++ b/modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php @@ -0,0 +1,118 @@ +themeHandler = $theme_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->getParameter('app.root'), + $container->get('module_handler'), + $container->get('theme_handler') + ); + } + + /** + * {@inheritdoc} + */ + public function getThemeImplementation() { + $item = parent::getThemeImplementation(); + $definition = $this->getPluginDefinition(); + $item[$definition['theme hook']] += $this->processTemplateProperty($definition); + $item[$definition['theme hook']] += $this->processCustomThemeHookProperty($definition); + return $item; + } + + /** + * Process 'custom hook theme' definition property. + * + * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition + * Pattern definition array. + * + * @return array + * Processed hook definition portion. + */ + protected function processCustomThemeHookProperty(PatternDefinition $definition) { + /** @var \Drupal\Core\Extension\Extension $module */ + $return = []; + if (!$definition->hasCustomThemeHook() && $this->moduleHandler->moduleExists($definition->getProvider())) { + $module = $this->moduleHandler->getModule($definition->getProvider()); + $return['path'] = $module->getPath() . '/templates'; + if ($this->templateExists($definition->getBasePath(), $definition->getTemplate())) { + $return['path'] = str_replace($this->root, '', $definition->getBasePath()); + } + } + return $return; + } + + /** + * Weather template exists in given directory. + * + * @param string $directory + * Directory full path. + * @param string $template + * Template name, without default Twig extension. + * + * @return bool + * Weather template exists in given directory. + */ + protected function templateExists($directory, $template) { + return file_exists($directory . DIRECTORY_SEPARATOR . $template . '.html.twig'); + } + + /** + * Process 'template' definition property. + * + * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition + * Pattern definition array. + * + * @return array + * Processed hook definition portion. + */ + protected function processTemplateProperty(PatternDefinition $definition) { + $return = []; + + if ($definition->hasTemplate()) { + $return = ['template' => $definition->getTemplate()]; + } + return $return; + } + +} diff --git a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php index 0151a249..750f6843 100644 --- a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php +++ b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php @@ -48,6 +48,11 @@ protected function alterDefinitions(&$definitions) { if ($id) { $ui_patterns_id = explode(':', $id)[1] ?? NULL; if ($pattern = $ui_patterns_definitions[$ui_patterns_id] ?? NULL) { + + // Check if the pattern is created by the sdc discover. + if (isset($pattern->getAdditional()['sdc'])) { + continue; + } //$definition['$schema'] = 'https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json'; $definition['props'] = ['type' => 'object', 'properties' => []]; foreach ($pattern->getFields() as $field_name => $field) { diff --git a/modules/ui_patterns_sdc/ui_patterns_sdc.services.yml b/modules/ui_patterns_sdc/ui_patterns_sdc.services.yml new file mode 100644 index 00000000..70b22c06 --- /dev/null +++ b/modules/ui_patterns_sdc/ui_patterns_sdc.services.yml @@ -0,0 +1,3 @@ +parameters: + ui_patterns_sdc.file_extensions: + - ".component.yml" From 60e63ce55eba40b3eefff79eaf19bf454cae463b Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Tue, 20 Jun 2023 17:15:47 +0200 Subject: [PATCH 27/81] chore: Remove UI Patterns definitions --- .../src/Plugin/Layout/PatternLayout.php | 55 +- .../ui_patterns_layouts.module | 120 +-- modules/ui_patterns_library/README.md | 8 - .../Controller/PatternsLibraryController.php | 5 +- .../patterns-meta-information.html.twig | 67 -- .../patterns-overview-page.html.twig | 49 -- .../templates/patterns-single-page.html.twig | 18 - ...atterns-variant-meta-information.html.twig | 25 - .../tests/fixtures/overview-page-patterns.yml | 110 --- ...terns_library_bad_definition_test.info.yml | 4 - ...ibrary_bad_definition_test.ui_patterns.yml | 2 - .../templates/button/button.ui_patterns.yml | 24 - .../templates/button/pattern-button.html.twig | 1 - .../with_local_libraries/css/library_one.css | 1 - .../with_local_libraries/css/library_two.css | 1 - .../pattern-with-local-libraries.html.twig | 1 - .../with_local_libraries.ui_patterns.yml | 23 - .../ui_patterns_library_module_test.info.yml | 4 - ...tterns_library_module_test.ui_patterns.yml | 10 - .../pattern-subtheme-override.html.twig | 1 - ...ui_patterns_library_subtheme_test.info.yml | 5 - ...erns_library_subtheme_test.ui_patterns.yml | 9 - .../templates/custom-theme-hook.html.twig | 1 - .../pattern-button--variant-danger.html.twig | 1 - .../templates/pattern-simple.html.twig | 1 - .../pattern-subtheme-override.html.twig | 1 - ...ttern-with-variants--variant-one.html.twig | 1 - ...ttern-with-variants--variant-two.html.twig | 1 - .../templates/raw/raw-template.twig | 1 - .../ui_patterns_library_theme_test.info.yml | 5 - ...atterns_library_theme_test.ui_patterns.yml | 47 -- .../UiPatternsLibraryOverviewTest.php | 230 ------ .../Plugin/Deriver/SdcComponentDeriver.php | 250 ------- .../src/UiPatternsSdcPluginManager.php | 58 +- .../UiPatterns/Source/ViewsRowSource.php | 34 - .../src/Plugin/views/row/Pattern.php | 152 ---- .../templates/pattern-views-row.html.twig | 16 - .../install/field.field.node.article.body.yml | 21 - .../config/install/node.type.article.yml | 10 - .../config/install/views.view.articles.yml | 187 ----- .../templates/pattern-teaser.html.twig | 8 - .../templates/teaser.ui_patterns.yml | 16 - .../ui_patterns_views_test.info.yml | 17 - .../UiPatternsViewsRenderTest.php | 80 -- .../UiPatternsViewsSettingsTest.php | 82 --- .../ui_patterns_views.info.yml | 8 - .../ui_patterns_views.module | 113 --- src/Definition/ArrayAccessDefinitionTrait.php | 46 -- src/Definition/PatternDefinition.php | 697 ------------------ src/Definition/PatternDefinitionField.php | 189 ----- src/Definition/PatternDefinitionVariant.php | 93 --- src/Definition/PatternSourceField.php | 154 ---- src/Exception/PatternDefinitionException.php | 14 - src/Exception/PatternRenderException.php | 12 - .../Deriver/AbstractPatternsDeriver.php | 112 --- .../Deriver/AbstractYamlPatternsDeriver.php | 91 --- .../Deriver/PatternsDeriverInterface.php | 20 - .../Deriver/YamlPatternsDeriverInterface.php | 27 - src/Plugin/PatternBase.php | 165 ----- src/Plugin/PatternInterface.php | 32 - src/TypedData/PatternDataDefinition.php | 96 --- src/UiPatterns.php | 55 -- src/UiPatternsManager.php | 218 ------ src/UiPatternsManagerInterface.php | 86 --- templates/patterns-use-wrapper.html.twig | 9 - ui_patterns.services.yml | 7 - 66 files changed, 62 insertions(+), 3945 deletions(-) delete mode 100644 modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php delete mode 100644 modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsRowSource.php delete mode 100644 modules/ui_patterns_views/src/Plugin/views/row/Pattern.php delete mode 100644 modules/ui_patterns_views/templates/pattern-views-row.html.twig delete mode 100644 modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/field.field.node.article.body.yml delete mode 100644 modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/node.type.article.yml delete mode 100644 modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml delete mode 100644 modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/pattern-teaser.html.twig delete mode 100644 modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/teaser.ui_patterns.yml delete mode 100644 modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml delete mode 100644 modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsRenderTest.php delete mode 100644 modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsSettingsTest.php delete mode 100644 modules/ui_patterns_views/ui_patterns_views.info.yml delete mode 100644 modules/ui_patterns_views/ui_patterns_views.module delete mode 100644 src/Definition/ArrayAccessDefinitionTrait.php delete mode 100644 src/Definition/PatternDefinition.php delete mode 100644 src/Definition/PatternDefinitionField.php delete mode 100644 src/Definition/PatternDefinitionVariant.php delete mode 100644 src/Definition/PatternSourceField.php delete mode 100644 src/Exception/PatternDefinitionException.php delete mode 100644 src/Exception/PatternRenderException.php delete mode 100644 src/Plugin/Deriver/AbstractPatternsDeriver.php delete mode 100644 src/Plugin/Deriver/AbstractYamlPatternsDeriver.php delete mode 100644 src/Plugin/Deriver/PatternsDeriverInterface.php delete mode 100644 src/Plugin/Deriver/YamlPatternsDeriverInterface.php delete mode 100644 src/Plugin/PatternBase.php delete mode 100644 src/Plugin/PatternInterface.php delete mode 100644 src/TypedData/PatternDataDefinition.php delete mode 100644 src/UiPatterns.php delete mode 100644 src/UiPatternsManager.php delete mode 100644 src/UiPatternsManagerInterface.php delete mode 100644 templates/patterns-use-wrapper.html.twig diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 51da08b4..24df63f2 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -26,13 +26,6 @@ class PatternLayout extends LayoutDefault implements PluginFormInterface, Contai */ protected $moduleHandler = NULL; - /** - * Pattern manager service. - * - * @var \Drupal\ui_patterns\UiPatternsManager - */ - protected $patternManager = NULL; - /** * The element info. * @@ -51,15 +44,12 @@ class PatternLayout extends LayoutDefault implements PluginFormInterface, Contai * The plugin implementation definition. * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info * Element info object. - * @param \Drupal\ui_patterns\UiPatternsManager $pattern_manager - * Pattern manager service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * Module handler. */ - public function __construct(array $configuration, $plugin_id, LayoutDefinition $plugin_definition, ElementInfoManagerInterface $element_info, UiPatternsManager $pattern_manager, ModuleHandlerInterface $module_handler) { + public function __construct(array $configuration, $plugin_id, LayoutDefinition $plugin_definition, ElementInfoManagerInterface $element_info, ModuleHandlerInterface $module_handler) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->elementInfo = $element_info; - $this->patternManager = $pattern_manager; $this->moduleHandler = $module_handler; } @@ -72,7 +62,6 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('plugin.manager.element_info'), - $container->get('plugin.manager.ui_patterns'), $container->get('module_handler') ); } @@ -83,21 +72,16 @@ public static function create(ContainerInterface $container, array $configuratio public function build(array $regions) { $configuration = $this->getConfiguration(); - // Remove default field template if "Only content" option has been selected. - if ($configuration['pattern']['field_templates'] == 'only_content') { - $this->processOnlyContentFields($regions); - } - // Patterns expect regions to be passed along in a render array fashion. - $fields = []; + $slots = []; foreach (array_keys($regions) as $region_name) { - $fields[$region_name] = $regions[$region_name]; + $slots[$region_name] = $regions[$region_name]; } return [ '#type' => 'component', - '#component' => $this->getPluginDefinition()->getProvider() . ':' . $this->getPluginDefinition()->get('additional')['pattern'], - '#slots' => $fields, + '#component' => $this->getPluginDefinition()->id(), + '#slots' => $slots, '#variant' => $configuration['pattern']['variant'], ]; } @@ -141,17 +125,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $configuration['pattern']['field_templates'], ]; - $pattern_id = $this->getPluginDefinition()->get('additional')['pattern']; - $definition = $this->patternManager->getDefinition($pattern_id); - if ($definition->hasVariants()) { - $form['pattern']['variant'] = [ - '#type' => 'select', - '#title' => $this->t('Variant'), - '#options' => $definition->getVariantsAsOptions(), - '#default_value' => $configuration['pattern']['variant'], - ]; - } - $this->moduleHandler->alter('ui_patterns_layouts_display_settings_form', $form['pattern'], $definition, $configuration); return $form; } @@ -168,22 +141,4 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s $this->configuration = $form_state->getValues(); } - /** - * Remove default field template if "Only content" option has been selected. - * - * @param array $regions - * Layout regions. - */ - protected function processOnlyContentFields(array &$regions) { - foreach ($regions as $region_name => $region) { - if (is_array($region)) { - foreach ($regions[$region_name] as $field_name => $field) { - if (is_array($field) && isset($field['#theme']) && $field['#theme'] == 'field') { - $regions[$region_name][$field_name]['#theme'] = NULL; - } - } - } - } - } - } diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.module b/modules/ui_patterns_layouts/ui_patterns_layouts.module index 12d3578d..8ade92e3 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.module +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.module @@ -18,117 +18,21 @@ use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface; function ui_patterns_layouts_layout_alter(&$definitions) { /** @var \Drupal\ui_patterns\Definition\PatternDefinition[] $pattern_definitions */ - $defs = UiPatterns::getPatternDefinitions(); - // @todo Use layout deriver instead. - // @link https://github.com/nuvoleweb/ui_patterns/issues/94 - foreach (UiPatterns::getPatternDefinitions() as $pattern_definition) { + /** @var \Drupal\sdc\ComponentPluginManager $plugin_manager */ + $plugin_manager = \Drupal::service('plugin.manager.sdc'); + /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ + $components = $plugin_manager->getDefinitions(); + foreach ($components as $component) { $definition = [ - 'label' => $pattern_definition->getLabel(), - 'theme' => $pattern_definition->getThemeHook(), - 'provider' => $pattern_definition->getProvider(), - 'category' => 'Patterns', + 'label' => $component['name'] ?? $component['id'], + 'category' => 'sdc', + 'provider' => $component['provider'], 'class' => '\Drupal\ui_patterns_layouts\Plugin\Layout\PatternLayout', - 'pattern' => $pattern_definition->id(), - 'template' => 'pattern-' . $pattern_definition->id(), + 'id' => $component['id'], ]; - foreach ($pattern_definition->getFields() as $field) { - $definition['regions'][$field->getName()]['label'] = $field->getLabel(); - } - $definitions['pattern_' . $pattern_definition->id()] = new LayoutDefinition($definition); - } -} - -/** - * Implements hook_preprocess_HOOK(). - */ -function ui_patterns_layouts_preprocess_ds_entity_view(&$variables) { - if (isset($variables['content']['#type']) && $variables['content']['#type'] == 'pattern') { - - /** @var \Drupal\Core\Entity\EntityInterface $entity */ - $entity = $variables['content']['#entity']; - - // Allow default context values to not override those exposed elsewhere. - $variables['content']['#context']['type'] = 'layout'; - $variables['content']['#context']['entity_type'] = $variables['content']['#entity_type']; - $variables['content']['#context']['bundle'] = $variables['content']['#bundle']; - $variables['content']['#context']['view_mode'] = $variables['content']['#view_mode']; - $variables['content']['#context']['entity_id'] = $entity->id(); - $variables['content']['#context']['entity'] = $entity; - } -} - -/** - * Implements hook_entity_view_alter(). - */ -function ui_patterns_layouts_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) { - if ($display instanceof EntityDisplayWithLayoutInterface && isset($build['_field_layout']['#type']) && $build['_field_layout']['#type'] == 'pattern') { - $build['_field_layout']['#context']['type'] = 'layout'; - $build['_field_layout']['#context']['entity_type'] = $build['#entity_type']; - $build['_field_layout']['#context']['bundle'] = $entity->bundle(); - $build['_field_layout']['#context']['view_mode'] = $build['#view_mode']; - $build['_field_layout']['#context']['entity_id'] = $entity->id(); - $build['_field_layout']['#context']['entity'] = $entity; - } -} - -/** - * Implements hook_ui_patterns_suggestions_alter(). - */ -function ui_patterns_layouts_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('layout')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $entity_type = $context->getProperty('entity_type'); - $bundle = $context->getProperty('bundle'); - $view_mode = $context->getProperty('view_mode'); - $entity_id = $context->getProperty('entity_id'); - - $suggestions[] = $hook . '__layout'; - $suggestions[] = $hook . '__layout__' . $entity_type; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $bundle; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $view_mode; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $bundle . '__' . $view_mode; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $entity_id; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__layout'; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $bundle; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $view_mode; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $bundle . '__' . $view_mode; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $entity_id; - } - } -} - -/** - * Implements hook_ui_patterns_destination_suggestions_alter(). - */ -function ui_patterns_layouts_ui_patterns_destination_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('layout')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $entity_type = $context->getProperty('entity_type'); - $bundle = $context->getProperty('bundle'); - $view_mode = $context->getProperty('view_mode'); - $entity_id = $context->getProperty('entity_id'); - $pattern = $context->getProperty('pattern'); - $field = $context->getProperty('field'); - - $suggestions[] = $hook . '__layout__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $bundle . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $view_mode . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $bundle . '__' . $view_mode . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__layout__' . $entity_type . '__' . $entity_id . '__' . $pattern . '__' . $field; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $bundle . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $view_mode . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $bundle . '__' . $view_mode . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__layout__' . $entity_type . '__' . $entity_id . '__' . $pattern . '__' . $field; + foreach ($component['slots'] as $slot_id => $slot) { + $definition['regions'][$slot_id]['label'] = $slot['title']; } + $definitions['sdc_' . $component['id']] = new LayoutDefinition($definition); } } diff --git a/modules/ui_patterns_library/README.md b/modules/ui_patterns_library/README.md index df884983..e69de29b 100644 --- a/modules/ui_patterns_library/README.md +++ b/modules/ui_patterns_library/README.md @@ -1,8 +0,0 @@ -# UI Patterns Library - -The UI Patterns Library module allows developers to expose patterns via YAML -definitions and to display them via a pattern library page to be used as -documentation for content editors or as a showcase for business, available at -`/patterns`. - -For more information please refer to the [official documentation](https://www.drupal.org/docs/contributed-modules/ui-patterns). diff --git a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php index cd941ca3..8d121a80 100644 --- a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php +++ b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php @@ -24,15 +24,14 @@ class PatternsLibraryController extends ControllerBase { /** * {@inheritdoc} */ - public function __construct(UiPatternsManager $ui_patterns_manager) { - $this->patternsManager = $ui_patterns_manager; + public function __construct() { } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('plugin.manager.ui_patterns')); + return new static(); } /** diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig index 27a3b7a4..e69de29b 100644 --- a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig +++ b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig @@ -1,67 +0,0 @@ -{# -/** - * @file - * UI Pattern meta information. - */ -#} - -{% if pattern is not empty %} - - {# Pattern name and description. #} -

    {{ pattern.label }}

    -

    {{ pattern.description }}

    - {% if pattern.tags %} -
    - {{ "Tags:"|t }} -
      - {% for tag in pattern.tags %} -
    • {{ tag }}
    • - {% endfor %} -
    -
    - {% endif %} - - {# Pattern fields descriptions. #} - {% if pattern.fields or pattern.additional.settings %} - - - - - - - - - - - - {% for field in pattern.fields %} - - - - - - - - {% endfor %} - {% for name, setting in pattern.additional.settings %} - - - - - - - - {% endfor %} - -
    {{ "Type"|t }}{{ "Name"|t }}{{ "Label"|t }}{{ "Type"|t }}{{ "Description"|t }} / {{ "Options"|t }}
    {{ "Field"|t }}{{ field.name }}{{ field.label }}{{ field.type }}{{ field.description }}
    {{ "Setting"|t }}{{ name }}{{ setting.label }}{{ setting.type }}{{ setting.description }} - {% if setting.options %} -
      - {% for key, label in setting.options %} -
    • {{ key }}: {{ label }}
    • - {% endfor %} -
    - {% endif %} -
    - {% endif %} - -{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig index 96cfd4b1..e69de29b 100644 --- a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig +++ b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig @@ -1,49 +0,0 @@ -{# -/** - * @file - * UI Pattern library page template, override this in your theme. - */ -#} - -{% if patterns is not empty %} -

    {{ "Available patterns"|t }}

    - - {# List of available patterns with anchor links. #} - {% for group_name, group_patterns in patterns %} - {% if patterns|length > 1 %} -

    {{ group_name }}

    - {% endif %} -
      - {% for pattern_name, pattern in group_patterns %} -
    • - {{ pattern.label }} -
    • - {% endfor %} -
    - {% endfor %} - -
    - - {% for group_patterns in patterns %} - {% for pattern_name, pattern in group_patterns %} -
    - {{ pattern.meta }} - - {# Rendered pattern preview. #} -
    - {{ "Preview"|t }} - {{ pattern.rendered }} -
    - - {# Link to standalone pattern preview page.#} -

    - - {% trans %}View {{ pattern.label }} as stand-alone{% endtrans %} - -

    -
    - -
    - {% endfor %} - {% endfor %} -{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-single-page.html.twig b/modules/ui_patterns_library/templates/patterns-single-page.html.twig index 6d6294ce..e69de29b 100644 --- a/modules/ui_patterns_library/templates/patterns-single-page.html.twig +++ b/modules/ui_patterns_library/templates/patterns-single-page.html.twig @@ -1,18 +0,0 @@ -{# -/** - * @file - * UI Pattern library standalone page, override this in your theme. - */ -#} - -{% if pattern is not empty %} -
    - {{ pattern.meta }} - - {# Rendered pattern preview. #} -
    - {{ "Preview"|t }} - {{ pattern.rendered }} -
    -
    -{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig index f1f692d0..e69de29b 100644 --- a/modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig +++ b/modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig @@ -1,25 +0,0 @@ -{# -/** - * @file - * UI Pattern variant meta information. - */ -#} - -{% if variant is not empty %} - - - - - - - - - - - - - - - -
    {{ "Variant"|t }}{{ "Name"|t }}{{ "Description"|t }}
    {{ variant.label }}{{ variant.name }}{{ variant.description }}
    -{% endif %} diff --git a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml index b7de9789..e69de29b 100644 --- a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml +++ b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml @@ -1,110 +0,0 @@ -- name: 'button' - label: 'Button' - description: 'A simple button.' - has_variants: true - preview: ~ - fields: - - name: 'title' - type: 'text' - label: 'Label' - description: 'The button label' - preview: 'Submit' - - name: 'url' - type: 'text' - label: 'URL' - description: 'The button URL' - preview: 'http://example.com' - variants: - - meta: - name: 'default' - label: 'Default' - description: 'A default button, nothing to see here.' - preview: 'Submit' - - meta: - name: 'primary' - label: 'Primary' - description: 'A primary button.' - preview: 'Submit' - - meta: - name: 'danger' - label: 'Danger' - description: 'A button for dangerous operations.' - preview: 'Delete' - -- name: 'simple' - label: 'Simple' - description: 'A simple pattern' - has_variants: false - preview: '
    Simple pattern field
    ' - fields: - - name: 'field' - type: 'string' - label: 'Field' - description: 'Field description' - -- name: 'with_custom_theme_hook' - theme hook: 'custom_theme_hook' - label: 'With custom theme hook' - description: 'Pattern with custom theme hook.' - has_variants: false - preview: 'With custom theme hook: Pattern field value' - fields: - - name: 'field' - type: 'string' - label: 'Field' - description: 'Field description' - -- name: 'with_local_libraries' - label: 'With local libraries' - description: 'Pattern defining local libraries' - has_variants: false - preview: 'With local libraries: Pattern field value' - fields: - - name: 'field' - type: 'string' - label: 'Field' - description: 'Field description' - -- name: 'with_raw_template' - label: 'With raw template' - description: 'Pattern using raw Twig template.' - has_variants: false - preview: 'With raw template: Pattern field value' - fields: - - name: 'field' - type: 'string' - label: 'Field' - description: 'Field description' - -- name: 'with_variants' - label: 'With variants' - description: 'Pattern with variants' - has_variants: true - preview: ~ - fields: - - name: 'field' - type: 'string' - label: 'Field' - description: 'Field description' - variants: - - meta: - name: 'one' - label: 'One' - description: 'First variant' - preview: '
    With variants pattern field
    ' - - meta: - name: 'two' - label: 'Two' - description: 'Second variant' - preview: '
    With variants pattern field
    ' - -- name: 'subtheme_override' - label: '[Overridden] Sub-theme override' - description: '[Overridden] Sub-theme override description' - has_variants: false - preview: '
    [Overridden] Simple pattern field
    ' - fields: - - name: 'field' - type: 'string' - label: '[Overridden] Field' - description: '[Overridden] Field description' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml index 583c552c..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml @@ -1,4 +0,0 @@ -name: 'UI Patterns bad definition test' -type: module -description: 'Test module for UI Patterns.' -package: 'Testing' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml index b411c1e6..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml @@ -1,2 +0,0 @@ -bad_definition: - foo: 'bar' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml index 987c56a5..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml @@ -1,24 +0,0 @@ -button: - label: Button - description: A simple button. - variants: - default: - label: Default - description: A default button, nothing to see here. - primary: - label: Primary - description: A primary button. - danger: - label: Danger - description: A button for dangerous operations. - fields: - title: - type: text - label: Label - description: The button label - preview: Submit - url: - type: text - label: URL - description: The button URL - preview: http://example.com diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig index 656e5544..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig @@ -1 +0,0 @@ -{{ title }} diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css index b301b382..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css @@ -1 +0,0 @@ -/* Avoid empty file. */ diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css index b301b382..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css @@ -1 +0,0 @@ -/* Avoid empty file. */ diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig index 90dac666..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig @@ -1 +0,0 @@ -With local libraries: {{ field }} \ No newline at end of file diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml index 2ac48716..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml @@ -1,23 +0,0 @@ -with_local_libraries: - label: 'With local libraries' - description: 'Pattern defining local libraries' - fields: - field: - type: 'string' - label: 'Field' - description: 'Field description' - preview: 'Pattern field value' - libraries: - - library_one: - css: - component: - css/library_one.css: {} - - library_two: - css: - theme: - css/library_two.css: {} - js: - js/library_two_1.js: {} - js/library_two_2.js: {} - dependencies: - - core/drupal.tabledrag diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml index 2f127881..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml @@ -1,4 +0,0 @@ -name: 'UI Patterns library module test' -type: module -description: 'Test module for UI Patterns.' -package: 'Testing' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml index b2d176df..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml @@ -1,10 +0,0 @@ -with_raw_template: - label: 'With raw template' - description: 'Pattern using raw Twig template.' - use: "@ui_patterns_library_theme_test/raw/raw-template.twig" - fields: - field: - type: 'string' - label: 'Field' - description: 'Field description' - preview: 'Pattern field value' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig index e9ff3bb0..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig @@ -1 +0,0 @@ -
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml index 495363de..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml @@ -1,5 +0,0 @@ -name: 'UI Patterns library sub-theme test' -type: theme -description: 'Test sub-theme for UI Patterns.' -package: 'Testing' -base theme: ui_patterns_library_theme_test diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml index 94f425bf..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml @@ -1,9 +0,0 @@ -subtheme_override: - label: '[Overridden] Sub-theme override' - description: '[Overridden] Sub-theme override description' - fields: - field: - type: 'string' - label: '[Overridden] Field' - description: '[Overridden] Field description' - preview: '[Overridden] Simple pattern field' diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig index 9b863bdf..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig @@ -1 +0,0 @@ -With custom theme hook: {{ field }} \ No newline at end of file diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig index a35eda97..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig @@ -1 +0,0 @@ -{{ 'Delete'|t }} diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig index 5a03b485..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig @@ -1 +0,0 @@ -
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig index 7a012355..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig @@ -1 +0,0 @@ -
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig index 5ee19a3f..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig @@ -1 +0,0 @@ -
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig index e1853a3a..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig @@ -1 +0,0 @@ -
    {{ field }}
    diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig index 8fb4bf38..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig @@ -1 +0,0 @@ -With raw template: {{ field }} \ No newline at end of file diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml index 2926e0c6..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml @@ -1,5 +0,0 @@ -name: 'UI Patterns library theme test' -type: theme -description: 'Test theme for UI Patterns.' -package: 'Testing' -base theme: stark diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml index e40305be..e69de29b 100644 --- a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml +++ b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml @@ -1,47 +0,0 @@ -simple: - label: 'Simple' - description: 'A simple pattern' - fields: - field: - type: 'string' - label: 'Field' - description: 'Field description' - preview: 'Simple pattern field' - -with_variants: - label: 'With variants' - description: 'Pattern with variants' - variants: - one: - label: 'One' - description: 'First variant' - two: - label: 'Two' - description: 'Second variant' - fields: - field: - type: 'string' - label: 'Field' - description: 'Field description' - preview: 'With variants pattern field' - -with_custom_theme_hook: - theme hook: 'custom_theme_hook' - label: 'With custom theme hook' - description: 'Pattern with custom theme hook.' - fields: - field: - type: 'string' - label: 'Field' - description: 'Field description' - preview: 'Pattern field value' - -subtheme_override: - label: 'Sub-theme override' - description: 'Sub-theme override description' - fields: - field: - type: 'string' - label: 'Field' - description: 'Field description' - preview: 'Simple pattern field' diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php index 947f9474..e69de29b 100644 --- a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php +++ b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php @@ -1,230 +0,0 @@ -moduleExtensionList = $this->container->get('extension.list.module'); - - $user = $this->drupalCreateUser(['access patterns page']); - $this->drupalLogin($user); - } - - /** - * Tests overview page. - */ - public function testOverviewPage() { - $session = $this->assertSession(); - - $this->drupalGet('/patterns'); - - $session->elementContains('css', 'h1', 'Pattern library'); - $session->elementContains('css', 'h2', 'Available patterns'); - - foreach ($this->getExpectedPatterns() as $index => $pattern) { - - // Assert pattern anchor link. - $this->assertListLink($index + 1, $pattern['label'], $pattern['name']); - - // Assert pattern preview. - $this->assertPatternPreview($pattern); - - // Test view single page link. - $session->linkExists("View {$pattern['label']} as stand-alone"); - $link = $this->getSession()->getPage()->findLink("View {$pattern['label']} as stand-alone"); - $this->assertStringContainsString('/patterns/' . $pattern['name'], $link->getAttribute('href')); - } - } - - /** - * Tests overview page. - */ - public function testSinglePages() { - $session = $this->assertSession(); - - foreach ($this->getExpectedPatterns() as $pattern) { - $this->drupalGet('/patterns/' . $pattern['name']); - $session->elementContains('css', 'h1', $pattern['label']); - - // Assert pattern preview. - $this->assertPatternPreview($pattern); - } - } - - /** - * Test that libraries defined locally are loaded correctly. - */ - public function testLocalLibraries() { - $session = $this->assertSession(); - - $this->drupalGet('/patterns/with_local_libraries'); - - $ui_patterns_library_module_test_path = $this->moduleExtensionList->getPath('ui_patterns_library_module_test'); - - $session->responseContains('href="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/css/library_one.css')->toString()); - $session->responseContains('href="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/css/library_two.css')->toString()); - $session->responseContains('src="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/js/library_two_1.js')->toString()); - $session->responseContains('src="' . Url::fromUserInput('/' . $ui_patterns_library_module_test_path . '/templates/with_local_libraries/js/library_two_2.js')->toString()); - $session->responseContains('src="' . Url::fromUserInput('/core/misc/tabledrag.js')->toString()); - } - - /** - * Assert pattern preview display. - * - * @param array $pattern - * Expected pattern. - */ - protected function assertPatternPreview(array $pattern) { - $session = $this->assertSession(); - - // Assert pattern title and description. - $root = '.pattern-preview__' . $pattern['name']; - $session->elementExists('css', $root); - $session->elementContains('css', "$root > h3.pattern-preview__label", $pattern['label']); - $session->elementContains('css', "$root > p.pattern-preview__description", $pattern['description']); - - // Assert metadata block. - $this->assertPatternFields($root, $pattern); - - if (!$pattern['has_variants']) { - // Make sure no variant markup exists. - $session->elementNotExists('css', "$root > fieldset.pattern-preview__preview > .pattern-preview__variants"); - - // Assert preview content when without variants. - $session->elementContains('css', "$root > fieldset.pattern-preview__preview > .pattern-preview__markup", $pattern['preview']); - } - else { - // Assert that variant markup exists. - $session->elementExists('css', "$root > fieldset.pattern-preview__preview > .pattern-preview__variants"); - - // Assert variant meta information and preview. - foreach ($pattern['variants'] as $variant) { - $this->assertPatternVariant($root, $variant); - } - } - } - - /** - * Assert pattern table fields. - * - * @param string $root - * CSS selector of element containing the table. - * @param array $pattern - * Expected pattern. - * - * @throws \Behat\Mink\Exception\ElementHtmlException - */ - protected function assertPatternFields($root, array $pattern) { - $session = $this->assertSession(); - - // Assert table header. - foreach (['Type', 'Name', 'Label', 'Type', 'Description / Options'] as $index => $item) { - $child = $index + 1; - $session->elementContains('css', "$root > table.pattern-preview__fields > thead > tr > th:nth-child($child)", $item); - } - - // Assert field table rows. - foreach ($pattern['fields'] as $index => $field) { - $child = $index + 1; - $row_root = "$root > table.pattern-preview__fields > tbody > tr:nth-child($child)"; - $session->elementContains('css', "$row_root > td:nth-child(1)", 'Field'); - $session->elementContains('css', "$row_root > td:nth-child(2)", $field['name']); - $session->elementContains('css', "$row_root > td:nth-child(3)", $field['label']); - $session->elementContains('css', "$row_root > td:nth-child(4)", $field['type']); - $session->elementContains('css', "$row_root > td:nth-child(5)", $field['description']); - } - } - - /** - * Assert pattern variant metadata and preview. - * - * @param string $root - * CSS selector of element containing the table. - * @param array $variant - * Variant expected values. - */ - protected function assertPatternVariant($root, array $variant) { - $session = $this->assertSession(); - $name = $variant['meta']['name']; - - // Assert table header. - foreach (['Variant', 'Name', 'Description'] as $index => $item) { - $child = $index + 1; - $session->elementContains('css', "$root table.pattern-preview__variants--$name > thead > tr > th:nth-child($child)", $item); - } - - // Assert variant meta table rows. - $row_root = "$root table.pattern-preview__variants--$name > tbody > tr"; - $session->elementContains('css', "$row_root > td:nth-child(1)", $variant['meta']['name']); - $session->elementContains('css', "$row_root > td:nth-child(2)", $variant['meta']['label']); - $session->elementContains('css', "$row_root > td:nth-child(3)", $variant['meta']['description']); - - // Assert variant preview. - $session->elementContains('css', "$root .pattern-preview__markup--variant_$name", $variant['preview']); - } - - /** - * Assert pattern overview list link. - * - * @param int $index - * Position on list. - * @param string $label - * Pattern label. - * @param string $name - * Pattern machine name. - * - * @throws \Behat\Mink\Exception\ElementHtmlException - */ - protected function assertListLink($index, $label, $name) { - $this->assertSession()->elementContains('css', "ul > li:nth-child($index) > a", $label); - $this->assertSession()->elementAttributeContains('css', "ul > li:nth-child($index) > a", 'href', '#' . $name); - } - - /** - * Get expected patterns. - */ - protected function getExpectedPatterns() { - return Yaml::decode(file_get_contents(__DIR__ . '/../../fixtures/overview-page-patterns.yml')); - } - -} diff --git a/modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php b/modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php deleted file mode 100644 index e3a13afa..00000000 --- a/modules/ui_patterns_sdc/src/Plugin/Deriver/SdcComponentDeriver.php +++ /dev/null @@ -1,250 +0,0 @@ -root = $root; - $this->fileExtensions = $extensions; - $this->moduleHandler = $module_handler; - $this->themeHandler = $theme_handler; - $this->extensionDiscovery = new ExtensionDiscovery($root); - } - - /** - * {@inheritdoc} - */ - public static function create( - ContainerInterface $container, - $base_plugin_id - ) { - return new static( - $base_plugin_id, - $container->get('typed_data_manager'), - $container->get('messenger'), - $container->get('file_system'), - $container->getParameter('app.root'), - $container->getParameter('ui_patterns_sdc.file_extensions'), - $container->get('module_handler'), - $container->get('theme_handler'), - $container->get('config.factory') - ); - } - - /** - * {@inheritdoc} - */ - public function getFileExtensions() { - return $this->fileExtensions; - } - - /** - * {@inheritdoc} - */ - public function getPatterns() { - $patterns = []; - foreach ($this->getDirectories() as $provider => $directory) { - if (!empty($directory) && is_dir($directory)) { - foreach ($this->fileScanDirectory($directory) as $file_path => $file) { - $content = Yaml::decode(file_get_contents($file_path)); - $fields = []; - if (isset($content['slots']) && is_array($content['slots'])) { - foreach ($content['slots'] as $key => $slot) { - $fields[$key] = [ - 'label' => $slot['title'] ?? $key, - 'description' => $slot['description'] ?? NULL, - 'preview' => $slot['examples'] ?? NULL, - ]; - } - } - $definition = [ - 'id' => basename($file_path, '.component.yml'), - 'label' => $content['name'], - 'description' => $content['description'] ?? NULL, - 'fields' => $fields, - 'base path' => dirname($file_path), - 'file name' => basename($file_path), - 'provider' => $provider - ]; - $patterns[] = $this->getPatternDefinition($definition); - } - } - } - - return $patterns; - } - - /** - * Create a list of all directories to scan. - * - * This includes all module directories and directories of the default theme - * and all of its possible base themes. - * - * @return array - * An array containing directory paths keyed by their extension name. - */ - protected function getDirectories() { - $extension_directories = [ - ...$this->moduleHandler->getModuleDirectories(), - ...$this->themeHandler->getThemeDirectories(), - ]; - return array_map( - static fn(string $path) => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'components', - $extension_directories - ); - } - - /** - * Get extension name that hosts the given YAML definition file. - * - * @param string $pathname - * YAML definition file full path. - * - * @return bool|string - * Either extension machine name or FALSE if not found. - */ - protected function getHostExtension($pathname) { - $extensions = $this->getExtensionLocations(); - $parts = explode(DIRECTORY_SEPARATOR, $pathname); - while (!empty($parts)) { - $path = implode(DIRECTORY_SEPARATOR, $parts); - if (isset($extensions[$path])) { - return $extensions[$path]; - } - array_pop($parts); - } - return FALSE; - } - - /** - * Get extension locations. - * - * @return array - * Array of extensions keyed by their path location. - */ - protected function getExtensionLocations() { - /** @var \Drupal\Core\Extension\Extension[] $extensions */ - if (empty($this->extensionLocations)) { - $extensions = $this->extensionDiscovery->scan( - 'theme' - ) + $this->extensionDiscovery->scan('module'); - foreach ($extensions as $name => $extension) { - $this->extensionLocations[$this->root . DIRECTORY_SEPARATOR . $extension->getPath( - )] = $name; - } - } - return $this->extensionLocations; - } - -} diff --git a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php index 750f6843..36f51ac4 100644 --- a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php +++ b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php @@ -38,29 +38,57 @@ protected function getDiscovery() { return $this->discovery; } + + protected function isUiPatternFile($definition){ + return isset($definition['_discovered_file_path']) && str_ends_with($definition['_discovered_file_path'], 'ui_patterns.yml'); + } + + protected function mapPatternToComponent($pattern, $component) { + $component['props'] = ['type' => 'object', 'properties' => []]; + if (isset($pattern['fields'])) { + foreach ($pattern['fields'] as $field_id => $field) { + $component['slots'][$field_id] = [ + 'title' => $field['label'], + 'description' => $field['description'] ?? NULL, + 'examples' => $field['preview'] ?? NULL + ]; + } + } + return $component; + } + /** * {@inheritdoc} */ protected function alterDefinitions(&$definitions) { - $ui_patterns_definitions = UiPatterns::getPatternDefinitions(); foreach ($definitions as & $definition) { $id = $definition['id'] ?? NULL; if ($id) { - $ui_patterns_id = explode(':', $id)[1] ?? NULL; - if ($pattern = $ui_patterns_definitions[$ui_patterns_id] ?? NULL) { - - // Check if the pattern is created by the sdc discover. - if (isset($pattern->getAdditional()['sdc'])) { - continue; + $definition_patterns_id = explode(':', $id)[1] ?? NULL; + if ($this->isUiPatternFile($definition)) { + $patterns = []; + // First detect UI Patterns definitions. + foreach ($definition as $key => $ui_pattern_definition) { + // Simple check for sub key label? + // Not sure if there is something better. + if (isset($ui_pattern_definition['label'])) { + $patterns[$key] = $definition[$key]; + unset($definition[$key]); + } } - //$definition['$schema'] = 'https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json'; - $definition['props'] = ['type' => 'object', 'properties' => []]; - foreach ($pattern->getFields() as $field_name => $field) { - $definition['slots'][$field_name] = [ - 'title' => $field->getLabel(), - 'description' => $field->getDescription(), - 'examples' => $field->getPreview() - ]; + + // Components only accept one component for one file + // To support multiple components for one file we clone them. + foreach ($patterns as $pattern_id => $pattern) { + if ($definition_patterns_id !== $pattern_id) { + $cloned_definition = $definition; + $cloned_definition[] = ''; + $cloned_definition_id = $cloned_definition['provider'] . ':' . $pattern_id; + $cloned_definition['id'] = $cloned_definition_id; + $definitions[$cloned_definition_id] = $this->mapPatternToComponent($pattern, $cloned_definition); + } else { + $definitions[$id] = $this->mapPatternToComponent($pattern, $definition); + } } } } diff --git a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsRowSource.php b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsRowSource.php deleted file mode 100644 index 0ee4011c..00000000 --- a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewsRowSource.php +++ /dev/null @@ -1,34 +0,0 @@ -getContextProperty('view'); - foreach ($view->display_handler->getFieldLabels() as $name => $label) { - $sources[] = $this->getSourceField($name, $label); - } - return $sources; - } - -} diff --git a/modules/ui_patterns_views/src/Plugin/views/row/Pattern.php b/modules/ui_patterns_views/src/Plugin/views/row/Pattern.php deleted file mode 100644 index 2a1a2993..00000000 --- a/modules/ui_patterns_views/src/Plugin/views/row/Pattern.php +++ /dev/null @@ -1,152 +0,0 @@ -patternsManager = $patterns_manager; - $this->sourceManager = $source_manager; - $this->moduleHandler = $module_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('plugin.manager.ui_patterns'), - $container->get('plugin.manager.ui_patterns_source'), - $container->get('module_handler') - ); - } - - /** - * {@inheritdoc} - */ - protected function defineOptions() { - $options = parent::defineOptions(); - $options['hide_empty'] = ['default' => FALSE]; - $options['default_field_elements'] = ['default' => FALSE]; - return $options; - } - - /** - * {@inheritdoc} - */ - public function buildOptionsForm(&$form, FormStateInterface $form_state) { - parent::buildOptionsForm($form, $form_state); - - $form['default_field_elements'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Provide default field wrapper elements'), - '#default_value' => $this->options['default_field_elements'], - '#description' => $this->t('If not checked, fields that are not configured to customize their HTML elements will get no wrappers at all for their field, label and field + label wrappers. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.'), - ]; - - $form['hide_empty'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Hide empty fields'), - '#default_value' => $this->options['hide_empty'], - '#description' => $this->t('Do not display fields, labels or markup for fields that are empty.'), - ]; - - $context = ['view' => $this->view]; - $this->buildPatternDisplayForm($form, 'views_row', $context, $this->options); - } - - /** - * {@inheritdoc} - */ - public function submitOptionsForm(&$form, FormStateInterface $form_state) { - $settings = $form_state->getValue('row_options'); - self::processFormStateValues($settings); - $form_state->setValue('row_options', $settings); - } - - /** - * Helper function: check for all conditions that make a field visible. - * - * @param \Drupal\views\Plugin\views\field\FieldPluginBase $field - * Field object. - * @param \Drupal\Component\Render\MarkupInterface|null $field_output - * Field output. - * - * @return bool - * TRUE if a field should be visible, FALSE otherwise. - * - * @see template_preprocess_pattern_views_row() - */ - public function isFieldVisible(FieldPluginBase $field, $field_output) { - $empty_value = $field->isValueEmpty($field_output, $field->options['empty_zero']); - $hide_field = !$empty_value || (empty($field->options['hide_empty']) && empty($this->options['hide_empty'])); - $empty = empty($field->options['exclude']) && $hide_field; - return $empty && $this->hasMappingDestination('views_row', $field->field, $this->options); - } - -} diff --git a/modules/ui_patterns_views/templates/pattern-views-row.html.twig b/modules/ui_patterns_views/templates/pattern-views-row.html.twig deleted file mode 100644 index 0fbe3c40..00000000 --- a/modules/ui_patterns_views/templates/pattern-views-row.html.twig +++ /dev/null @@ -1,16 +0,0 @@ -{# -/** - * @file - * Default UI Patterns Views row template. - * - * Available variables: - * - view: The view in use. - * - pattern: pattern element to be rendered. - * - row: The raw result from the query, with all data it fetched. - * - * @see template_preprocess_pattern_views_row() - * - * @ingroup themeable - */ -#} -{{ pattern }} diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/field.field.node.article.body.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/field.field.node.article.body.yml deleted file mode 100644 index 8f3681d9..00000000 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/field.field.node.article.body.yml +++ /dev/null @@ -1,21 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.storage.node.body - - node.type.article - module: - - text -id: node.article.body -field_name: body -entity_type: node -bundle: article -label: Body -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - display_summary: true -field_type: text_with_summary diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/node.type.article.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/node.type.article.yml deleted file mode 100644 index f47f2add..00000000 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/node.type.article.yml +++ /dev/null @@ -1,10 +0,0 @@ -langcode: en -status: true -dependencies: { } -name: Article -type: article -description: '' -help: '' -new_revision: true -preview_mode: 1 -display_submitted: true diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml deleted file mode 100644 index f31f1228..00000000 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/config/install/views.view.articles.yml +++ /dev/null @@ -1,187 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - node.type.article - module: - - node - - ui_patterns_views - - user -id: articles -label: Articles -module: views -description: '' -tag: '' -base_table: node_field_data -base_field: nid -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: perm - options: - perm: 'access content' - cache: - type: tag - options: { } - query: - type: views_query - options: - disable_sql_rewrite: false - distinct: false - replica: false - query_comment: '' - query_tags: { } - exposed_form: - type: basic - options: - submit_button: Apply - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: some - options: - items_per_page: 10 - offset: 0 - style: - type: default - row: - type: ui_patterns - options: - default_field_elements: 0 - inline: - title: 0 - separator: '' - hide_empty: 0 - pattern: teaser - pattern_variant: default - pattern_mapping: - 'views_row:title': - destination: title - weight: 0 - plugin: views_row - source: title - fields: - title: - id: title - table: node_field_data - field: title - entity_type: node - entity_field: title - label: '' - alter: - alter_text: false - make_link: false - absolute: false - trim: false - word_boundary: false - ellipsis: false - strip_tags: false - html: false - hide_empty: false - empty_zero: false - settings: - link_to_entity: true - plugin_id: field - relationship: none - group_type: group - admin_label: '' - exclude: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_alter_empty: true - click_sort_column: value - type: string - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - filters: - status: - value: '1' - table: node_field_data - field: status - plugin_id: boolean - entity_type: node - entity_field: status - id: status - expose: - operator: '' - group: 1 - type: - id: type - table: node_field_data - field: type - value: - article: article - entity_type: node - entity_field: type - plugin_id: bundle - sorts: - created: - id: created - table: node_field_data - field: created - order: DESC - entity_type: node - entity_field: created - plugin_id: date - relationship: none - group_type: group - admin_label: '' - exposed: false - expose: - label: '' - granularity: second - title: Articles - header: { } - footer: { } - empty: { } - relationships: { } - arguments: { } - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - 'user.node_grants:view' - - user.permissions - tags: { } - page_1: - display_plugin: page - id: page_1 - display_title: Page - position: 1 - display_options: - display_extenders: { } - path: articles - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - 'user.node_grants:view' - - user.permissions - tags: { } diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/pattern-teaser.html.twig b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/pattern-teaser.html.twig deleted file mode 100644 index 519b873c..00000000 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/pattern-teaser.html.twig +++ /dev/null @@ -1,8 +0,0 @@ -{# -/** - * @file - * Test teaser pattern. - */ -#} -

    {{ title }}

    -

    {{ description }}

    \ No newline at end of file diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/teaser.ui_patterns.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/teaser.ui_patterns.yml deleted file mode 100644 index 86f7e8a5..00000000 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/templates/teaser.ui_patterns.yml +++ /dev/null @@ -1,16 +0,0 @@ -teaser: - label: "Teaser" - variants: - default: - label: "Default" - highlighted: - label: "Highlighted" - fields: - title: - type: "text" - label: "Title" - preview: "Title" - description: - type: "text" - label: "Description" - preview: "Description" diff --git a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml b/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml deleted file mode 100644 index 957b666f..00000000 --- a/modules/ui_patterns_views/tests/modules/ui_patterns_views_test/ui_patterns_views_test.info.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'UI Patterns Views Test' -type: module -description: 'Test module for UI Patterns.' -package: 'Testing' -dependencies: - - drupal:node - - drupal:text - - drupal:views_ui - - ui_patterns:ui_patterns - - ui_patterns:ui_patterns_library - - ui_patterns:ui_patterns_views - -config_devel: - install: - - field.field.node.article.body - - node.type.article - - views.view.articles diff --git a/modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsRenderTest.php b/modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsRenderTest.php deleted file mode 100644 index cea28e8f..00000000 --- a/modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsRenderTest.php +++ /dev/null @@ -1,80 +0,0 @@ -assertSession(); - - $this->enableTwigDebugMode(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - $this->drupalCreateNode([ - 'title' => 'Test article', - 'type' => 'article', - ]); - - $this->drupalGet('/articles'); - - // Assert correct variant suggestions. - $suggestions = [ - 'pattern-teaser--variant-default--views-row--articles--page-1.html.twig', - 'pattern-teaser--variant-default--views-row--articles.html.twig', - 'pattern-teaser--variant-default--views-row.html.twig', - - 'pattern-teaser--views-row--articles--page-1.html.twig', - 'pattern-teaser--views-row--articles.html.twig', - 'pattern-teaser--views-row.html.twig', - - 'pattern-teaser--variant-default.html.twig', - 'pattern-teaser.html.twig', - ]; - foreach ($suggestions as $suggestion) { - $assert_session->responseContains($suggestion); - } - - // Test field content is rendered in field group pattern. - $assert_session->elementContains('css', 'h3', 'Test article'); - } - -} diff --git a/modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsSettingsTest.php b/modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsSettingsTest.php deleted file mode 100644 index c0162029..00000000 --- a/modules/ui_patterns_views/tests/src/FunctionalJavascript/UiPatternsViewsSettingsTest.php +++ /dev/null @@ -1,82 +0,0 @@ -getSession()->getPage(); - $assert_session = $this->assertSession(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - // Visit Articles views setting page. - $this->drupalGet('/admin/structure/views/view/articles'); - - // Access row style settings. - $page->clickLink('Change settings for this style'); - $assert_session->assertWaitOnAjaxRequest(); - - // Configure row style. - $page->selectFieldOption('Variant', 'Highlighted'); - $page->selectFieldOption('Destination for Content: Title', 'Description'); - - // Submit row style settings. - $page->find('css', '.ui-dialog-buttonpane .form-actions')->pressButton('Apply'); - $assert_session->assertWaitOnAjaxRequest(); - - // Save view. - $page->find('css', '#edit-actions')->pressButton('Save'); - - $view = View::load('articles'); - $settings = $view->getDisplay('default')['display_options']['row']['options']; - - // Assert settings values. - $this->assertEquals($settings['pattern'], 'teaser'); - $this->assertEquals($settings['pattern_variant'], 'highlighted'); - - // Assert mappings. - $this->assertNotEmpty($settings['pattern_mapping'], "Pattern mapping is empty."); - - $mapping = $settings['pattern_mapping']; - $this->assertArrayHasKey('views_row:title', $mapping, 'Mapping not found.'); - $this->assertEquals($mapping['views_row:title']['destination'], 'description', "Mapping not valid."); - } - -} diff --git a/modules/ui_patterns_views/ui_patterns_views.info.yml b/modules/ui_patterns_views/ui_patterns_views.info.yml deleted file mode 100644 index 957fc855..00000000 --- a/modules/ui_patterns_views/ui_patterns_views.info.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: 'UI Patterns Views' -type: module -description: 'Use patterns as Views templates.' -core_version_requirement: ^9 || ^10 -package: 'User interface' -dependencies: - - drupal:views - - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_views/ui_patterns_views.module b/modules/ui_patterns_views/ui_patterns_views.module deleted file mode 100644 index 58f0b042..00000000 --- a/modules/ui_patterns_views/ui_patterns_views.module +++ /dev/null @@ -1,113 +0,0 @@ - [ - 'variables' => ['view' => NULL, 'options' => [], 'row' => NULL], - ], - ]; -} - -/** - * Preprocess hook. - * - * @param array $variables - * Theme variables. - */ -function template_preprocess_pattern_views_row(array &$variables) { - /** @var \Drupal\views\ResultRow $row */ - /** @var \Drupal\views\ViewExecutable $view */ - /** @var \Drupal\ui_patterns_views\Plugin\views\row\Pattern $row_plugin */ - - $fields = []; - $view = $variables['view']; - $row_plugin = $view->rowPlugin; - $options = $variables['options']; - $row = $variables['row']; - - foreach ($options['pattern_mapping'] as $mapping) { - $field_name = $mapping['source']; - $field = $view->field[$field_name]; - $field_output = $view->style_plugin->getField($row->index, $field_name); - if ($row_plugin->isFieldVisible($field, $field_output)) { - $destination = $row_plugin->getMappingDestination('views_row', $field_name, $options); - $fields[$destination][] = $field_output; - } - } - - $variables['pattern'] = []; - if ($view->preview && !isset($view->element['#embed'])) { - \Drupal::messenger()->addWarning(t("Pattern Views row plugin does not support preview.")); - $variables['pattern'] = ['#type' => 'status_messages']; - } - elseif (!empty($fields)) { - $variables['pattern'] = [ - '#type' => 'pattern', - '#id' => $options['pattern'], - '#fields' => $fields, - '#multiple_sources' => TRUE, - '#variant' => $options['pattern_variant'] ?? '', - ]; - - // Allow default context values to not override those exposed elsewhere. - $variables['pattern']['#context']['type'] = 'views_row'; - $variables['pattern']['#context']['view_name'] = $view->storage->id(); - $variables['pattern']['#context']['display'] = $view->current_display; - $variables['pattern']['#context']['view'] = $view; - $variables['pattern']['#context']['row'] = $row; - } -} - -/** - * Implements hook_ui_patterns_suggestions_alter(). - */ -function ui_patterns_views_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('views_row')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $view_name = $context->getProperty('view_name'); - $display = $context->getProperty('display'); - - $suggestions[] = $hook . '__views_row'; - $suggestions[] = $hook . '__views_row__' . $view_name; - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $display; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__views_row'; - $suggestions[] = $hook . '__variant_' . $variant . '__views_row__' . $view_name; - $suggestions[] = $hook . '__variant_' . $variant . '__views_row__' . $view_name . '__' . $display; - } - } -} - -/** - * Implements hook_ui_patterns_destination_suggestions_alter(). - */ -function ui_patterns_views_ui_patterns_destination_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('views_row')) { - $hook = $variables['theme_hook_original']; - $variant = $variables["variant"] ?? ''; - $view_name = $context->getProperty('view_name'); - $display = $context->getProperty('display'); - $pattern = $context->getProperty('pattern'); - $field = $context->getProperty('field'); - - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $display . '__' . $pattern . '__' . $field; - - if (!empty($variant)) { - $suggestions[] = $hook . '__variant_' . $variant . '__views_row__' . $view_name . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__variant_' . $variant . '__views_row__' . $view_name . '__' . $display . '__' . $pattern . '__' . $field; - } - } -} diff --git a/src/Definition/ArrayAccessDefinitionTrait.php b/src/Definition/ArrayAccessDefinitionTrait.php deleted file mode 100644 index db733336..00000000 --- a/src/Definition/ArrayAccessDefinitionTrait.php +++ /dev/null @@ -1,46 +0,0 @@ -definition); - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function offsetGet($offset) { - return $this->definition[$offset] ?? NULL; - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function offsetSet($offset, $value) { - $this->definition[$offset] = $value; - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function offsetUnset($offset) { - unset($this->definition[$offset]); - } - -} diff --git a/src/Definition/PatternDefinition.php b/src/Definition/PatternDefinition.php deleted file mode 100644 index 2057ed2a..00000000 --- a/src/Definition/PatternDefinition.php +++ /dev/null @@ -1,697 +0,0 @@ - NULL, - 'label' => NULL, - 'description' => NULL, - 'category' => '', - 'base path' => NULL, - 'file name' => NULL, - 'use' => NULL, - 'theme hook' => NULL, - 'custom theme hook' => FALSE, - 'template' => NULL, - 'libraries' => [], - 'fields' => [], - 'variants' => [], - 'tags' => [], - 'weight' => 0, - 'additional' => [], - 'deriver' => NULL, - 'provider' => NULL, - 'class' => NULL, - ]; - - /** - * PatternDefinition constructor. - */ - public function __construct(array $definition = []) { - foreach ($definition as $name => $value) { - if (array_key_exists($name, $this->definition)) { - $this->definition[$name] = $value; - } - else { - $this->definition['additional'][$name] = $value; - } - } - - $this->id = $this->definition['id']; - $this->setFields($this->definition['fields']); - $this->setVariants($this->definition['variants']); - $this->setThemeHook(self::PATTERN_PREFIX . $this->id()); - - if (!empty($definition['theme hook'])) { - $this->setThemeHook($definition['theme hook']); - $this->definition['custom theme hook'] = TRUE; - } - - if (!$this->hasTemplate()) { - $this->setTemplate(str_replace('_', '-', $this->getThemeHook())); - } - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function getLabel() { - return $this->definition['label']; - } - - /** - * Setter. - * - * @param mixed $label - * Property value. - * - * @return $this - */ - public function setLabel($label) { - $this->definition['label'] = $label; - return $this; - } - - /** - * Getter. - * - * @return string - * Property value. - */ - public function getDescription() { - return $this->definition['description']; - } - - /** - * Setter. - * - * @param string $description - * Property value. - * - * @return $this - */ - public function setDescription($description) { - $this->definition['description'] = $description; - return $this; - } - - /** - * Getter. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup|string - * Property value. - */ - public function getCategory() { - return $this->definition['category']; - } - - /** - * Setter. - * - * @param \Drupal\Core\StringTranslation\TranslatableMarkup|string $category - * Property value. - * - * @return $this - */ - public function setCategory($category) { - $this->definition['category'] = $category; - return $this; - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function getBasePath() { - return $this->definition['base path']; - } - - /** - * Setter. - * - * @param mixed $basePath - * Property value. - * - * @return $this - */ - public function setBasePath($basePath) { - $this->definition['base path'] = $basePath; - return $this; - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function getFileName() { - return $this->definition['file name']; - } - - /** - * Setter. - * - * @param mixed $fileName - * Property value. - * - * @return $this - */ - public function setFileName($fileName) { - $this->definition['file name'] = $fileName; - return $this; - } - - /** - * Get Provider property. - * - * @return string - * Property value. - */ - public function getProvider() { - return $this->definition['provider']; - } - - /** - * Setter. - * - * @param mixed $provider - * Property value. - * - * @return $this - */ - public function setProvider($provider) { - $this->definition['provider'] = $provider; - return $this; - } - - /** - * Get field. - * - * @param string $name - * Field name. - * - * @return PatternDefinitionField|null - * Definition field. - */ - public function getField($name) { - return $this->hasField($name) ? $this->definition['fields'][$name] : NULL; - } - - /** - * Getter. - * - * @return PatternDefinitionField[] - * Property value. - */ - public function getFields() { - return $this->definition['fields']; - } - - /** - * Get field as options. - * - * @return array - * Fields as select options. - */ - public function getFieldsAsOptions() { - $options = []; - foreach ($this->getFields() as $field) { - $options[$field->getName()] = $field->getLabel(); - } - return $options; - } - - /** - * Set field. - * - * @param string $name - * Field name. - * @param string $label - * Field label. - * - * @return $this - */ - public function setField($name, $label) { - $this->definition['fields'][$name] = $this->getFieldDefinition($name, $label); - return $this; - } - - /** - * Setter. - * - * @param array $fields - * Property value. - * - * @return $this - */ - public function setFields(array $fields) { - foreach ($fields as $name => $value) { - $field = $this->getFieldDefinition($name, $value); - $this->definition['fields'][$field->getName()] = $field; - } - return $this; - } - - /** - * Check whereas field exists. - * - * @param string $name - * Field name. - * - * @return bool - * Whereas field exists - */ - public function hasField($name) { - return isset($this->definition['fields'][$name]); - } - - /** - * Get variant. - * - * @param string $name - * Field name. - * - * @return PatternDefinitionField|null - * Definition field. - */ - public function getVariant($name) { - return $this->hasVariant($name) ? $this->definition['variants'][$name] : NULL; - } - - /** - * Getter. - * - * @return \Drupal\ui_patterns\Definition\PatternDefinitionVariant[] - * Property value. - */ - public function getVariants() { - return $this->definition['variants']; - } - - /** - * Get field as options. - * - * @return array - * Variants as select options. - */ - public function getVariantsAsOptions() { - $options = []; - foreach ($this->getVariants() as $variant) { - $options[$variant->getName()] = $variant->getLabel(); - } - return $options; - } - - /** - * Set variant. - * - * @param string $name - * Variant name. - * @param string $label - * Variant label. - * - * @return $this - */ - public function setVariant($name, $label) { - $this->definition['variants'][$name] = $this->getVariantDefinition($name, $label); - return $this; - } - - /** - * Setter. - * - * @param array $variants - * Property value. - * - * @return $this - */ - public function setVariants(array $variants) { - foreach ($variants as $name => $value) { - $variant = $this->getVariantDefinition($name, $value); - $this->definition['variants'][$variant->getName()] = $variant; - } - return $this; - } - - /** - * Check whereas variant exists. - * - * @param string $name - * Variant name. - * - * @return bool - * Whereas variant exists - */ - public function hasVariant($name) { - return isset($this->definition['variants'][$name]); - } - - /** - * Check whereas pattern has variants. - * - * @return bool - * Whereas pattern has variants. - */ - public function hasVariants() { - return !empty($this->definition['variants']); - } - - /** - * Getter. - * - * @return string - * Property value. - */ - public function getThemeHook() { - return $this->definition['theme hook']; - } - - /** - * Setter. - * - * @param string $theme_hook - * Property value. - * - * @return $this - */ - public function setThemeHook($theme_hook) { - $this->definition['theme hook'] = $theme_hook; - return $this; - } - - /** - * Getter. - * - * @return string - * Property value. - */ - public function getUse() { - return $this->definition['use']; - } - - /** - * Setter. - * - * @param string $use - * Property value. - * - * @return $this - */ - public function setUse($use) { - $this->definition['use'] = $use; - return $this; - } - - /** - * Getter. - * - * @return bool - * Whereas definition uses the "use:" property. - */ - public function hasUse() { - return !empty($this->definition['use']); - } - - /** - * Getter. - * - * @return array - * Property value. - */ - public function getTags() { - return $this->definition['tags']; - } - - /** - * Setter. - * - * @param array $tags - * Property value. - * - * @return $this - */ - public function setTags(array $tags) { - $this->definition['tags'] = $tags; - return $this; - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function hasCustomThemeHook() { - return $this->definition['custom theme hook']; - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function getTemplate() { - return $this->definition['template']; - } - - /** - * Setter. - * - * @param mixed $template - * Property value. - * - * @return $this - */ - public function setTemplate($template) { - $this->definition['template'] = $template; - return $this; - } - - /** - * Getter. - * - * @return bool - * Whereas has template. - */ - public function hasTemplate() { - return !empty($this->definition['template']); - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function getLibraries() { - return $this->definition['libraries']; - } - - /** - * Getter. - * - * @return mixed - * Property value. - */ - public function getLibrariesNames() { - $libraries = []; - foreach ($this->getLibraries() as $library) { - if (is_array($library)) { - $libraries[] = self::LIBRARY_PREFIX . '/' . $this->id() . '.' . key($library); - } - else { - $libraries[] = $library; - } - } - return $libraries; - } - - /** - * Setter. - * - * @param mixed $libraries - * Property value. - * - * @return $this - */ - public function setLibraries($libraries) { - $this->definition['libraries'] = $libraries; - return $this; - } - - /** - * Get Class property. - * - * @return string - * Property value. - */ - public function getClass() { - return $this->definition['class']; - } - - /** - * Set Class property. - * - * @param string $class - * Property value. - * - * @return $this - */ - public function setClass($class) { - parent::setClass($class); - $this->definition['class'] = $class; - return $this; - } - - /** - * Getter. - * - * @return int - * Property value. - */ - public function getWeight(): int { - return $this->definition['weight']; - } - - /** - * Setter. - * - * @param int $weight - * Property value. - * - * @return $this - */ - public function setWeight(int $weight) { - $this->definition['weight'] = $weight; - return $this; - } - - /** - * Get Additional property. - * - * @return array - * Property value. - */ - public function getAdditional() { - return $this->definition['additional']; - } - - /** - * Set Additional property. - * - * @param array $additional - * Property value. - * - * @return $this - */ - public function setAdditional(array $additional) { - $this->definition['additional'] = $additional; - return $this; - } - - /** - * Get Deriver property. - * - * @return mixed - * Property value. - */ - public function getDeriver() { - return $this->definition['deriver']; - } - - /** - * Set Deriver property. - * - * @param mixed $deriver - * Property value. - * - * @return $this - */ - public function setDeriver($deriver) { - $this->definition['deriver'] = $deriver; - return $this; - } - - /** - * Factory method: create a new field definition. - * - * @param string $name - * Field name. - * @param string $value - * Field value. - * - * @return \Drupal\ui_patterns\Definition\PatternDefinitionField - * Definition instance. - */ - public function getFieldDefinition($name, $value) { - return new PatternDefinitionField($name, $value); - } - - /** - * Factory method: create a new variant definition. - * - * @param string $name - * Variant name. - * @param string $value - * Variant value. - * - * @return \Drupal\ui_patterns\Definition\PatternDefinitionVariant - * Definition instance. - */ - public function getVariantDefinition($name, $value) { - return new PatternDefinitionVariant($name, $value); - } - - /** - * Return array definition. - * - * @return array - * Array definition. - */ - public function toArray() { - $definition = $this->definition; - foreach ($this->getFields() as $field) { - $definition['fields'][$field->getName()] = $field->toArray(); - } - foreach ($this->getVariants() as $variant) { - $definition['variants'][$variant->getName()] = $variant->toArray(); - } - - return $definition; - } - -} diff --git a/src/Definition/PatternDefinitionField.php b/src/Definition/PatternDefinitionField.php deleted file mode 100644 index ec3b17e7..00000000 --- a/src/Definition/PatternDefinitionField.php +++ /dev/null @@ -1,189 +0,0 @@ - NULL, - 'label' => NULL, - 'description' => NULL, - 'type' => NULL, - 'preview' => NULL, - 'escape' => TRUE, - 'additional' => [], - ]; - - /** - * PatternDefinitionField constructor. - */ - public function __construct($name, $value) { - if (is_scalar($value)) { - $this->definition['name'] = is_numeric($name) ? $value : $name; - $this->definition['label'] = $value; - } - else { - $this->definition['name'] = !isset($value['name']) ? $name : $value['name']; - $this->definition['label'] = $value['label']; - $this->definition = $value + $this->definition; - } - } - - /** - * Return array definition. - * - * @return array - * Array definition. - */ - public function toArray() { - return $this->definition; - } - - /** - * Get Name property. - * - * @return mixed - * Property value. - */ - public function getName() { - return $this->definition['name']; - } - - /** - * Get Label property. - * - * @return mixed - * Property value. - */ - public function getLabel() { - return $this->definition['label']; - } - - /** - * Get Description property. - * - * @return string - * Property value. - */ - public function getDescription() { - return $this->definition['description']; - } - - /** - * Set Description property. - * - * @param string $description - * Property value. - * - * @return $this - */ - public function setDescription($description) { - $this->definition['description'] = $description; - return $this; - } - - /** - * Get Type property. - * - * @return string - * Property value. - */ - public function getType() { - return $this->definition['type']; - } - - /** - * Set Type property. - * - * @param string $type - * Property value. - * - * @return $this - */ - public function setType($type) { - $this->definition['type'] = $type; - return $this; - } - - /** - * Get Preview property. - * - * @return mixed - * Property value. - */ - public function getPreview() { - return $this->definition['preview']; - } - - /** - * Set Preview property. - * - * @param mixed $preview - * Property value. - * - * @return $this - */ - public function setPreview($preview) { - $this->definition['preview'] = $preview; - return $this; - } - - /** - * Get Escape property. - * - * @return bool - * Property value. - */ - public function getEscape() { - return $this->definition['escape']; - } - - /** - * Set Escape property. - * - * @param bool $escape - * Property value. - * - * @return $this - */ - public function setEscape($escape) { - $this->definition['escape'] = $escape; - return $this; - } - - /** - * Get Additional property. - * - * @return array - * Property value. - */ - public function getAdditional() { - return $this->definition['additional']; - } - - /** - * Set Additional property. - * - * @param array $additional - * Property value. - * - * @return $this - */ - public function setAdditional(array $additional) { - $this->definition['additional'] = $additional; - return $this; - } - -} diff --git a/src/Definition/PatternDefinitionVariant.php b/src/Definition/PatternDefinitionVariant.php deleted file mode 100644 index b17cabda..00000000 --- a/src/Definition/PatternDefinitionVariant.php +++ /dev/null @@ -1,93 +0,0 @@ - NULL, - 'label' => NULL, - 'description' => NULL, - ]; - - /** - * PatternDefinitionVariant constructor. - */ - public function __construct($name, $value) { - if (is_scalar($value)) { - $this->definition['name'] = is_numeric($name) ? $value : $name; - $this->definition['label'] = $value; - } - else { - $this->definition['name'] = !isset($value['name']) ? $name : $value['name']; - $this->definition['label'] = $value['label']; - $this->definition = $value + $this->definition; - } - } - - /** - * Return array definition. - * - * @return array - * Array definition. - */ - public function toArray() { - return $this->definition; - } - - /** - * Get Name property. - * - * @return mixed - * Property value. - */ - public function getName() { - return $this->definition['name']; - } - - /** - * Get Label property. - * - * @return mixed - * Property value. - */ - public function getLabel() { - return $this->definition['label']; - } - - /** - * Get Description property. - * - * @return string - * Property value. - */ - public function getDescription() { - return $this->definition['description']; - } - - /** - * Set Description property. - * - * @param string $description - * Property value. - * - * @return $this - */ - public function setDescription($description) { - $this->definition['description'] = $description; - return $this; - } - -} diff --git a/src/Definition/PatternSourceField.php b/src/Definition/PatternSourceField.php deleted file mode 100644 index 16cfd825..00000000 --- a/src/Definition/PatternSourceField.php +++ /dev/null @@ -1,154 +0,0 @@ -fieldName = $field_name; - $this->fieldLabel = $field_label; - $this->pluginId = $plugin_id; - $this->pluginLabel = $plugin_label; - } - - /** - * Get FieldName property. - * - * @return string - * Property value. - */ - public function getFieldName() { - return $this->fieldName; - } - - /** - * Set FieldName property. - * - * @param string $fieldName - * Property value. - * - * @return $this - */ - public function setFieldName($fieldName) { - $this->fieldName = $fieldName; - return $this; - } - - /** - * Get FieldLabel property. - * - * @return string - * Property value. - */ - public function getFieldLabel() { - return $this->fieldLabel; - } - - /** - * Set FieldLabel property. - * - * @param string $fieldLabel - * Property value. - * - * @return $this - */ - public function setFieldLabel($fieldLabel) { - $this->fieldLabel = $fieldLabel; - return $this; - } - - /** - * Get Plugin property. - * - * @return string - * Property value. - */ - public function getPluginId() { - return $this->pluginId; - } - - /** - * Set Plugin property. - * - * @param string $pluginId - * Property value. - * - * @return $this - */ - public function setPluginId($pluginId) { - $this->pluginId = $pluginId; - return $this; - } - - /** - * Get PluginLabel property. - * - * @return string - * Property value. - */ - public function getPluginLabel() { - return $this->pluginLabel; - } - - /** - * Set PluginLabel property. - * - * @param string $pluginLabel - * Property value. - * - * @return $this - */ - public function setPluginLabel($pluginLabel) { - $this->pluginLabel = $pluginLabel; - return $this; - } - - /** - * Get unique field key. - * - * @return string - * Field key. - */ - public function getFieldKey() { - return $this->getPluginId() . self::FIELD_KEY_SEPARATOR . $this->getFieldName(); - } - -} diff --git a/src/Exception/PatternDefinitionException.php b/src/Exception/PatternDefinitionException.php deleted file mode 100644 index 33f77978..00000000 --- a/src/Exception/PatternDefinitionException.php +++ /dev/null @@ -1,14 +0,0 @@ -basePluginId = $base_plugin_id; - $this->typedDataManager = $typed_data_manager; - $this->messenger = $messenger; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, $base_plugin_id) { - return new static( - $base_plugin_id, - $container->get('typed_data_manager'), - $container->get('messenger') - ); - } - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($base_plugin_definition) { - foreach ($this->getPatterns() as $pattern) { - $pattern->setDeriver($base_plugin_definition['deriver']); - $pattern->setClass($base_plugin_definition['class']); - if ($this->isValidPatternDefinition($pattern)) { - $this->derivatives[$pattern->id()] = $pattern; - } - } - return $this->derivatives; - } - - /** - * Get pattern data object. - * - * @param array $definition - * Pattern definition array. - * - * @return \Drupal\ui_patterns\Definition\PatternDefinition - * Pattern definition object. - */ - protected function getPatternDefinition(array $definition = []) { - return new PatternDefinition($definition); - } - - /** - * Validate pattern definition. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition. - * - * @return bool - * Whereas current pattern definition is valid or not. - */ - protected function isValidPatternDefinition(PatternDefinition $definition) { - $data_definition = PatternDataDefinition::create(); - $violations = $this->typedDataManager->create($data_definition, $definition->toArray())->validate(); - if ($violations->count()) { - /** @var \Symfony\Component\Validator\ConstraintViolation $violation */ - $this->messenger->addError($this->t("Pattern ':id' is skipped because of the following validation error(s):", [':id' => $definition->id()])); - foreach ($violations as $violation) { - $message = $this->t('Validation error on ":id.:property": :message', [ - ':id' => $definition->id(), - ':property' => $violation->getPropertyPath(), - ':message' => $violation->getMessage(), - ]); - $this->messenger->addError($message); - } - return FALSE; - } - return TRUE; - } - -} diff --git a/src/Plugin/Deriver/AbstractYamlPatternsDeriver.php b/src/Plugin/Deriver/AbstractYamlPatternsDeriver.php deleted file mode 100644 index 98db5032..00000000 --- a/src/Plugin/Deriver/AbstractYamlPatternsDeriver.php +++ /dev/null @@ -1,91 +0,0 @@ -fileSystem = $file_system; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, $base_plugin_id) { - return new static( - $base_plugin_id, - $container->get('typed_data_manager'), - $container->get('messenger'), - $container->get('file_system') - ); - } - - /** - * {@inheritdoc} - */ - public function fileScanDirectory($directory) { - if (!is_dir($directory)) { - return []; - } - $options = ['nomask' => $this->getNoMask()]; - $extensions = $this->getFileExtensions(); - $extensions = array_map('preg_quote', $extensions); - $extensions = implode('|', $extensions); - $files = $this->fileSystem->scanDirectory($directory, "/{$extensions}$/", $options); - // In different file systems order of files in a folder can be different - // that can break tests. So let's sort them alphabetically manually. - ksort($files); - return $files; - } - - /** - * Returns a regular expression for directories to be excluded in a file scan. - * - * @return string - * Regular expression. - */ - protected function getNoMask() { - $ignore = Settings::get('file_scan_ignore_directories', []); - // We add 'tests' directory to the ones found in settings. - $ignore[] = 'tests'; - array_walk($ignore, function (&$value) { - $value = preg_quote($value, '/'); - }); - return '/^' . implode('|', $ignore) . '$/'; - } - -} diff --git a/src/Plugin/Deriver/PatternsDeriverInterface.php b/src/Plugin/Deriver/PatternsDeriverInterface.php deleted file mode 100644 index bf3f4aff..00000000 --- a/src/Plugin/Deriver/PatternsDeriverInterface.php +++ /dev/null @@ -1,20 +0,0 @@ -root = $root; - $this->moduleHandler = $module_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->getParameter('app.root'), - $container->get('module_handler') - ); - } - - /** - * {@inheritdoc} - */ - public function getThemeImplementation() { - $definition = $this->getPluginDefinition(); - $item = []; - $item += $this->processVariables($definition); - $item += $this->processUseProperty($definition); - return [ - $definition['theme hook'] => $item, - ]; - } - - /** - * {@inheritdoc} - */ - public function getLibraryDefinitions() { - // @codingStandardsIgnoreStart - $libraries = []; - $definition = $this->getPluginDefinition(); - - // Get only locally defined libraries. - $items = array_filter($definition['libraries'], function ($library) { - return is_array($library); - }); - - // Attach pattern base path to assets. - if (!empty($definition['base path'])) { - $base_path = str_replace($this->root, '', $definition['base path']); - $this->processLibraries($items, $base_path); - } - - // Produce final libraries array. - $id = $definition['id']; - array_walk($items, function ($value) use (&$libraries, $id) { - $libraries[$id . '.' . key($value)] = reset($value); - }); - - // @codingStandardsIgnoreEnd - return $libraries; - } - - /** - * Process libraries. - * - * @param array|string $libraries - * List of dependencies or "dependencies:" root property. - * @param string $base_path - * Pattern base path. - * @param string $parent - * Item parent set in previous recursive iteration, if any. - */ - protected function processLibraries(&$libraries, $base_path, $parent = '') { - if (!is_string($libraries)) { - $parents = ['js', 'base', 'layout', 'component', 'state', 'theme']; - $_libraries = $libraries; - foreach ($_libraries as $name => $values) { - $is_asset = in_array($parent, $parents, TRUE); - $is_external = isset($values['type']) && $values['type'] == 'external'; - if ($is_asset && !$is_external) { - $libraries[$base_path . DIRECTORY_SEPARATOR . $name] = $values; - unset($libraries[$name]); - } - elseif (!$is_asset) { - $this->processLibraries($libraries[$name], $base_path, $name); - } - } - } - } - - /** - * Process 'use' definition property. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - */ - protected function processUseProperty(PatternDefinition $definition) { - $return = []; - if ($definition->hasUse()) { - $return = [ - 'path' => $this->moduleHandler->getModule('ui_patterns')->getPath() . '/templates', - 'template' => 'patterns-use-wrapper', - ]; - } - return $return; - } - - /** - * Process theme variables. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - */ - protected function processVariables(PatternDefinition $definition) { - $return = []; - foreach ($definition->getFields() as $field) { - $return['variables'][$field->getName()] = NULL; - } - $return['variables']['attributes'] = []; - $return['variables']['context'] = []; - $return['variables']['variant'] = ''; - $return['variables']['use'] = ''; - return $return; - } - -} diff --git a/src/Plugin/PatternInterface.php b/src/Plugin/PatternInterface.php deleted file mode 100644 index df5ceff8..00000000 --- a/src/Plugin/PatternInterface.php +++ /dev/null @@ -1,32 +0,0 @@ -setMainPropertyName('id') - ->setPropertyDefinition('id', $this->getMachineNameDefinition()->setRequired(TRUE)) - ->setPropertyDefinition('label', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('base path', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('file name', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('provider', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('fields', $this->getFieldsDefinition()) - ->setPropertyDefinition('variants', $this->getVariantsDefinition()) - ->setPropertyDefinition('theme hook', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('description', DataDefinition::create('string')) - ->setPropertyDefinition('use', DataDefinition::create('string')) - ->setPropertyDefinition('tags', ListDataDefinition::create('string')) - ->setPropertyDefinition('custom theme hook', DataDefinition::create('boolean')) - ->setPropertyDefinition('template', DataDefinition::create('string')) - ->setPropertyDefinition('libraries', DataDefinition::create('any')); - return $this->propertyDefinitions; - } - - /** - * Get valid machine name definition. - * - * @return \Drupal\Core\TypedData\DataDefinition - * Data definition instance. - */ - protected function getMachineNameDefinition() { - return DataDefinition::create('string') - ->addConstraint('Regex', sprintf(self::MACHINE_NAME, implode('|', $this->reserved))); - } - - /** - * Get definition for 'field' property. - * - * @return \Drupal\Core\TypedData\ListDataDefinition - * Data definition instance. - */ - protected function getFieldsDefinition() { - return new ListDataDefinition([], MapDataDefinition::create() - ->setPropertyDefinition('name', $this->getMachineNameDefinition()->setRequired(TRUE)) - ->setPropertyDefinition('label', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('type', $this->getMachineNameDefinition()) - ->setPropertyDefinition('description', DataDefinition::create('string')) - ->setPropertyDefinition('preview', DataDefinition::create('any'))); - } - - /** - * Get definition for 'variant' property. - * - * @return \Drupal\Core\TypedData\ListDataDefinition - * Data definition instance. - */ - protected function getVariantsDefinition() { - return new ListDataDefinition([], MapDataDefinition::create() - ->setPropertyDefinition('name', $this->getMachineNameDefinition()->setRequired(TRUE)) - ->setPropertyDefinition('label', DataDefinition::create('string')->setRequired(TRUE)) - ->setPropertyDefinition('description', DataDefinition::create('string'))); - } - -} diff --git a/src/UiPatterns.php b/src/UiPatterns.php deleted file mode 100644 index c480b712..00000000 --- a/src/UiPatterns.php +++ /dev/null @@ -1,55 +0,0 @@ -getDefinition($id); - } - - /** - * Get pattern definitions. - * - * @return \Drupal\ui_patterns\Definition\PatternDefinition[] - * Pattern object instance. - */ - public static function getPatternDefinitions() { - return \Drupal::service('plugin.manager.ui_patterns')->getDefinitions(); - } - -} diff --git a/src/UiPatternsManager.php b/src/UiPatternsManager.php deleted file mode 100644 index ade00c0b..00000000 --- a/src/UiPatternsManager.php +++ /dev/null @@ -1,218 +0,0 @@ -setCacheBackend($cache_backend, 'ui_patterns', ['ui_patterns']); - $this->alterInfo('ui_patterns_info'); - $this->themeHandler = $theme_handler; - } - - /** - * {@inheritdoc} - */ - public function processDefinition(&$definition, $plugin_id) { - parent::processDefinition($definition, $plugin_id); - - if (is_array($definition)) { - $definition = new PatternDefinition($definition); - } - - // Add default category. - if ($definition instanceof PatternDefinition && empty($definition->getCategory())) { - $definition->setCategory($this->t('Other')); - } - } - - /** - * {@inheritdoc} - */ - public function getCategories() { - // Fetch all categories from definitions and remove duplicates. - $categories = \array_unique(\array_values(\array_map(static function (PatternDefinition $definition) { - return $definition->getCategory(); - }, $this->getDefinitions()))); - \natcasesort($categories); - // @phpstan-ignore-next-line - return $categories; - } - - /** - * {@inheritdoc} - * - * @phpstan-ignore-next-line - */ - public function getSortedDefinitions(?array $definitions = NULL): array { - $definitions = $definitions ?? $this->getDefinitions(); - - \uasort($definitions, static function (PatternDefinition $item1, PatternDefinition $item2) { - // Sort by category. - $category1 = $item1->getCategory(); - if ($category1 instanceof TranslatableMarkup) { - $category1 = $category1->render(); - } - $category2 = $item2->getCategory(); - if ($category2 instanceof TranslatableMarkup) { - $category2 = $category2->render(); - } - if ($category1 != $category2) { - return \strnatcasecmp($category1, $category2); - } - - // Sort by weight. - $weight = $item1->getWeight() <=> $item2->getWeight(); - if ($weight != 0) { - return $weight; - } - - // Sort by label ignoring parenthesis. - $label1 = $item1->getLabel(); - if ($label1 instanceof TranslatableMarkup) { - $label1 = $label1->render(); - } - $label2 = $item2->getLabel(); - if ($label2 instanceof TranslatableMarkup) { - $label2 = $label2->render(); - } - // Ignore parenthesis. - $label1 = \str_replace(['(', ')'], '', $label1); - $label2 = \str_replace(['(', ')'], '', $label2); - if ($label1 != $label2) { - return \strnatcasecmp($label1, $label2); - } - - // Sort by plugin ID. - // In case the plugin ID starts with an underscore. - $id1 = \str_replace('_', '', $item1->id()); - $id2 = \str_replace('_', '', $item2->id()); - return \strnatcasecmp($id1, $id2); - }); - - return $definitions; - } - - /** - * {@inheritdoc} - */ - public function getGroupedDefinitions(?array $definitions = NULL): array { - $definitions = $this->getSortedDefinitions($definitions ?? $this->getDefinitions()); - $grouped_definitions = []; - foreach ($definitions as $id => $definition) { - $grouped_definitions[(string) $definition->getCategory()][$id] = $definition; - } - return $grouped_definitions; - } - - /** - * Return pattern definitions. - * - * @return \Drupal\ui_patterns\Definition\PatternDefinition[] - * Pattern definitions. - */ - public function getDefinitions() { - $definitions = $this->getCachedDefinitions(); - if (!isset($definitions)) { - // Remove derivative id from pattern definitions keys. - // @todo make sure validation takes care of ensuring ids are unique. - $definitions = []; - foreach ($this->findDefinitions() as $id => $definition) { - $definitions[$definition['id']] = $definition; - unset($definitions[$id]); - } - $this->setCachedDefinitions($definitions); - } - return $definitions; - } - - /** - * {@inheritdoc} - */ - protected function providerExists($provider) { - return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider); - } - - /** - * {@inheritdoc} - */ - public function getPatterns(): array { - $patterns = []; - foreach ($this->getDefinitions() as $definition) { - $patterns[] = $this->getFactory()->createInstance($definition->id()); - } - return $patterns; - } - - /** - * {@inheritdoc} - */ - public function getPatternsOptions(): array { - $options = []; - $grouped_definitions = $this->getGroupedDefinitions(); - foreach ($grouped_definitions as $group_name => $group_definitions) { - foreach ($group_definitions as $definition) { - $options[$group_name][$definition->id()] = $definition->getLabel(); - } - } - - // If there is only one category, do not put in optgroup. - if (count(array_keys($options)) == 1) { - $options = array_shift($options); - } - - return $options; - } - - /** - * {@inheritdoc} - */ - public function isPatternHook(string $hook): bool { - // Improve performance on not cached pages. - if (empty($this->patternHooks)) { - foreach ($this->getDefinitions() as $definition) { - $this->patternHooks[$definition->getThemeHook()] = $definition->getThemeHook(); - } - } - return !empty($this->patternHooks[$hook]); - } - -} diff --git a/src/UiPatternsManagerInterface.php b/src/UiPatternsManagerInterface.php deleted file mode 100644 index 9206b800..00000000 --- a/src/UiPatternsManagerInterface.php +++ /dev/null @@ -1,86 +0,0 @@ - Date: Tue, 20 Jun 2023 17:32:38 +0200 Subject: [PATCH 28/81] chore: Add UI Pattern Selection for pattern function --- .../src/Plugin/Layout/PatternLayout.php | 16 +--------- .../src/UiPatternsSdcPluginManager.php | 1 + src/Template/TwigExtension.php | 29 ++++++++++++++----- src/UiPatternsManager.php | 26 +++++++++++++++++ 4 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 src/UiPatternsManager.php diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 24df63f2..04ea1893 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -72,7 +72,7 @@ public static function create(ContainerInterface $container, array $configuratio public function build(array $regions) { $configuration = $this->getConfiguration(); - // Patterns expect regions to be passed along in a render array fashion. + // Components expect slots to be passed along in a render array fashion. $slots = []; foreach (array_keys($regions) as $region_name) { $slots[$region_name] = $regions[$region_name]; @@ -102,7 +102,6 @@ public function defaultConfiguration() { * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $configuration = $this->getConfiguration(); $form = []; $form['pattern'] = [ @@ -111,19 +110,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Pattern settings'), '#tree' => TRUE, ]; - $form['pattern']['field_templates'] = [ - '#type' => 'select', - '#title' => $this->t('Field templates'), - '#options' => [ - 'default' => $this->t("Default"), - 'only_content' => $this->t("Only content"), - ], - '#description' => implode('
    ', [ - $this->t("Default: use field templates to wrap field content."), - $this->t("Only content: only print field content, without field wrapping or label."), - ]), - '#default_value' => $configuration['pattern']['field_templates'], - ]; return $form; } diff --git a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php index 36f51ac4..01bca14e 100644 --- a/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php +++ b/modules/ui_patterns_sdc/src/UiPatternsSdcPluginManager.php @@ -89,6 +89,7 @@ protected function alterDefinitions(&$definitions) { } else { $definitions[$id] = $this->mapPatternToComponent($pattern, $definition); } + $definitions[$id]['ui_pattern_id'] = $pattern_id; } } } diff --git a/src/Template/TwigExtension.php b/src/Template/TwigExtension.php index e12a1efc..ff3d972c 100644 --- a/src/Template/TwigExtension.php +++ b/src/Template/TwigExtension.php @@ -2,6 +2,7 @@ namespace Drupal\ui_patterns\Template; +use Drupal\ui_patterns\UiPatternsManager; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; use Twig\TwigFilter; @@ -64,11 +65,18 @@ public function getFilters() { * @see \Drupal\ui_patterns\Element\Pattern */ public function renderPattern($id, array $fields = [], $variant = "") { + $component = UiPatternsManager::getComponentByUiPatternId($id); + if ($component) { + return [ + '#type' => 'component', + '#id' => $id, + '#slots' => $fields, + '#variant' => $variant, + ]; + } return [ - '#type' => 'pattern', - '#id' => $id, - '#fields' => $fields, - '#variant' => $variant, + '#type' => 'markup', + '#markup' => 'No component found for ' . $id, ]; } @@ -86,10 +94,17 @@ public function renderPattern($id, array $fields = [], $variant = "") { * @see \Drupal\ui_patterns\Element\Pattern */ public function renderPatternPreview($id, $variant = "") { + $component = UiPatternsManager::getComponentByUiPatternId($id); + if ($component) { + return [ + '#type' => 'component', + '#id' => $component['id'], + '#variant' => $variant, + ]; + } return [ - '#type' => 'pattern_preview', - '#id' => $id, - '#variant' => $variant, + '#type' => 'markup', + '#markup' => 'No component found for ' . $id, ]; } diff --git a/src/UiPatternsManager.php b/src/UiPatternsManager.php new file mode 100644 index 00000000..d26536cf --- /dev/null +++ b/src/UiPatternsManager.php @@ -0,0 +1,26 @@ +getDefinitions(); + foreach ($components as $component) { + if (isset($component['ui_pattern_id']) && $component['ui_pattern_id'] === $ui_pattern_id) { + return $component; + } + } + } +} From a70b178d01c79a5b263ffaf7d3c0e9a85a8df63b Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Tue, 20 Jun 2023 18:18:40 +0200 Subject: [PATCH 29/81] feat: Add ui patterns legacy module --- .../src/Plugin/Layout/PatternLayout.php | 2 +- .../ui_patterns_layouts.module | 7 +- .../Discovery/UIPatternsLegacyDiscovery.php} | 6 +- .../UiPatternsLegacyPluginDiscovery.php} | 6 +- .../src/Template/TwigExtension.php | 98 +++++++++++++++ .../src/UiPatternsLegacyManager.php | 4 +- .../src/UiPatternsLegacyPluginManager.php} | 12 +- .../src/UiPatternsLegacyServiceProvider.php} | 6 +- .../ui_patterns_legacy.info.yml} | 5 +- .../ui_patterns_legacy.services.yml | 8 ++ .../Controller/PatternsLibraryController.php | 4 +- .../Plugin/UiPatterns/Pattern/SdcPattern.php | 118 ------------------ .../ui_patterns_sdc.services.yml | 3 - src/Annotation/UiPattern.php | 50 -------- src/Form/PatternDisplayFormTrait.php | 2 +- src/Template/TwigExtension.php | 77 ------------ .../my-widget/my-widget.component.yml | 42 +++++++ .../components/my-widget/my-widget.css | 24 ++++ .../components/my-widget/my-widget.twig | 16 +++ .../ui_patterns_sdc_render_test.info.yml | 4 + ...r.php => DummyUiPatternsLegacyManager.php} | 4 +- tests/src/Kernel/UiPatternsManagerTest.php | 2 +- tests/src/Unit/UiPatternsManagerTest.php | 14 +-- ui_patterns.api.php | 2 +- 24 files changed, 232 insertions(+), 284 deletions(-) rename modules/{ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php => ui_patterns_legacy/src/Plugin/Discovery/UIPatternsLegacyDiscovery.php} (95%) rename modules/{ui_patterns_sdc/src/Plugin/Discovery/UiPatternsSdcPluginDiscovery.php => ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php} (82%) create mode 100644 modules/ui_patterns_legacy/src/Template/TwigExtension.php rename src/UiPatternsManager.php => modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php (91%) rename modules/{ui_patterns_sdc/src/UiPatternsSdcPluginManager.php => ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php} (90%) rename modules/{ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php => ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php} (67%) rename modules/{ui_patterns_sdc/ui_patterns_sdc.info.yml => ui_patterns_legacy/ui_patterns_legacy.info.yml} (52%) create mode 100644 modules/ui_patterns_legacy/ui_patterns_legacy.services.yml delete mode 100644 modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php delete mode 100644 modules/ui_patterns_sdc/ui_patterns_sdc.services.yml delete mode 100644 src/Annotation/UiPattern.php create mode 100644 tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml create mode 100644 tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css create mode 100644 tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig create mode 100644 tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml rename tests/modules/ui_patterns_test/src/{DummyUiPatternsManager.php => DummyUiPatternsLegacyManager.php} (93%) diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 04ea1893..24e761b5 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -8,7 +8,7 @@ use Drupal\Core\Layout\LayoutDefinition; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginFormInterface; -use Drupal\ui_patterns\UiPatternsManager; +use Drupal\ui_patterns\UiPatternsLegacyManager; use Drupal\Core\Render\ElementInfoManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.module b/modules/ui_patterns_layouts/ui_patterns_layouts.module index 8ade92e3..1299b023 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.module +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.module @@ -30,9 +30,12 @@ function ui_patterns_layouts_layout_alter(&$definitions) { 'class' => '\Drupal\ui_patterns_layouts\Plugin\Layout\PatternLayout', 'id' => $component['id'], ]; - foreach ($component['slots'] as $slot_id => $slot) { - $definition['regions'][$slot_id]['label'] = $slot['title']; + if (isset($component['slots'])) { + foreach ($component['slots'] as $slot_id => $slot) { + $definition['regions'][$slot_id]['label'] = $slot['title']; + } } + $definitions['sdc_' . $component['id']] = new LayoutDefinition($definition); } } diff --git a/modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php b/modules/ui_patterns_legacy/src/Plugin/Discovery/UIPatternsLegacyDiscovery.php similarity index 95% rename from modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php rename to modules/ui_patterns_legacy/src/Plugin/Discovery/UIPatternsLegacyDiscovery.php index f325caa3..162a8672 100644 --- a/modules/ui_patterns_sdc/src/Plugin/Discovery/UIPatternsSdcDiscovery.php +++ b/modules/ui_patterns_legacy/src/Plugin/Discovery/UIPatternsLegacyDiscovery.php @@ -1,17 +1,17 @@ discovery = new UIPatternsSdcDiscovery($directories, $file_cache_key_suffix, $file_system); + $this->discovery = new UIPatternsLegacyDiscovery($directories, $file_cache_key_suffix, $file_system); } } diff --git a/modules/ui_patterns_legacy/src/Template/TwigExtension.php b/modules/ui_patterns_legacy/src/Template/TwigExtension.php new file mode 100644 index 00000000..ef334562 --- /dev/null +++ b/modules/ui_patterns_legacy/src/Template/TwigExtension.php @@ -0,0 +1,98 @@ + 'component', + '#id' => $id, + '#slots' => $fields, + '#variant' => $variant, + ]; + } + return [ + '#type' => 'markup', + '#markup' => 'No component found for ' . $id, + ]; + } + + /** + * Render given pattern. + * + * @param string $id + * Pattern ID. + * @param string $variant + * Variant name. + * + * @return array + * Pattern render array. + * + * @see \Drupal\ui_patterns\Element\Pattern + */ + public function renderPatternPreview($id, $variant = "") { + $component = UiPatternsLegacyManager::getComponentByUiPatternId($id); + if ($component) { + return [ + '#type' => 'component', + '#id' => $component['id'], + '#variant' => $variant, + ]; + } + return [ + '#type' => 'markup', + '#markup' => 'No component found for ' . $id, + ]; + } + +} diff --git a/src/UiPatternsManager.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php similarity index 91% rename from src/UiPatternsManager.php rename to modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php index d26536cf..02460565 100644 --- a/src/UiPatternsManager.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php @@ -1,7 +1,7 @@ discovery)) { $directories = $this->getScanDirectories(); $decorated = new DirectoryWithMetadataPluginDiscovery($directories, 'sdc', $this->fileSystem); - $this->discovery = new UiPatternsSdcPluginDiscovery( + $this->discovery = new UiPatternsLegacyPluginDiscovery( $decorated, $directories, - 'sdc', + 'ui_patterns_legacy_sdc', $this->fileSystem ); } diff --git a/modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php similarity index 67% rename from modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php rename to modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php index 8f4ee012..9d9ffbc8 100644 --- a/modules/ui_patterns_sdc/src/UiPatternsSdcServiceProvider.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php @@ -1,18 +1,18 @@ hasDefinition('plugin.manager.sdc')) { $definition = $container->getDefinition('plugin.manager.sdc'); $definition->setClass( - 'Drupal\ui_patterns_sdc\UiPatternsSdcPluginManager' + 'Drupal\ui_patterns_legacy\UiPatternsLegacyPluginManager' ); } } diff --git a/modules/ui_patterns_sdc/ui_patterns_sdc.info.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml similarity index 52% rename from modules/ui_patterns_sdc/ui_patterns_sdc.info.yml rename to modules/ui_patterns_legacy/ui_patterns_legacy.info.yml index a9671368..0b12bcc9 100644 --- a/modules/ui_patterns_sdc/ui_patterns_sdc.info.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml @@ -1,7 +1,8 @@ -name: 'UI Patterns SDC' +name: 'UI Patterns Legacy' type: module -description: 'Use patterns with Single Directory Components.' +description: 'Legacy discovery of UI Patterns configurations.' core_version_requirement: ^9 || ^10 package: 'User interface' dependencies: - ui_patterns:ui_patterns + - drupal:sdc diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml new file mode 100644 index 00000000..3db4d733 --- /dev/null +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml @@ -0,0 +1,8 @@ +parameters: + ui_patterns_legacy.file_extensions: + - ".component.yml" +services: + ui_patterns_legacy.twig.extension: + class: Drupal\ui_patterns_legacy\Template\TwigExtension + tags: + - { name: twig.extension } diff --git a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php index 8d121a80..536f2511 100644 --- a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php +++ b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php @@ -4,7 +4,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\ui_patterns\Definition\PatternDefinition; -use Drupal\ui_patterns\UiPatternsManager; +use Drupal\ui_patterns\UiPatternsLegacyManager; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -17,7 +17,7 @@ class PatternsLibraryController extends ControllerBase { /** * Patterns manager service. * - * @var \Drupal\ui_patterns\UiPatternsManager + * @var \Drupal\ui_patterns\UiPatternsLegacyManager */ protected $patternsManager; diff --git a/modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php b/modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php deleted file mode 100644 index 094347fc..00000000 --- a/modules/ui_patterns_sdc/src/Plugin/UiPatterns/Pattern/SdcPattern.php +++ /dev/null @@ -1,118 +0,0 @@ -themeHandler = $theme_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->getParameter('app.root'), - $container->get('module_handler'), - $container->get('theme_handler') - ); - } - - /** - * {@inheritdoc} - */ - public function getThemeImplementation() { - $item = parent::getThemeImplementation(); - $definition = $this->getPluginDefinition(); - $item[$definition['theme hook']] += $this->processTemplateProperty($definition); - $item[$definition['theme hook']] += $this->processCustomThemeHookProperty($definition); - return $item; - } - - /** - * Process 'custom hook theme' definition property. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - */ - protected function processCustomThemeHookProperty(PatternDefinition $definition) { - /** @var \Drupal\Core\Extension\Extension $module */ - $return = []; - if (!$definition->hasCustomThemeHook() && $this->moduleHandler->moduleExists($definition->getProvider())) { - $module = $this->moduleHandler->getModule($definition->getProvider()); - $return['path'] = $module->getPath() . '/templates'; - if ($this->templateExists($definition->getBasePath(), $definition->getTemplate())) { - $return['path'] = str_replace($this->root, '', $definition->getBasePath()); - } - } - return $return; - } - - /** - * Weather template exists in given directory. - * - * @param string $directory - * Directory full path. - * @param string $template - * Template name, without default Twig extension. - * - * @return bool - * Weather template exists in given directory. - */ - protected function templateExists($directory, $template) { - return file_exists($directory . DIRECTORY_SEPARATOR . $template . '.html.twig'); - } - - /** - * Process 'template' definition property. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - */ - protected function processTemplateProperty(PatternDefinition $definition) { - $return = []; - - if ($definition->hasTemplate()) { - $return = ['template' => $definition->getTemplate()]; - } - return $return; - } - -} diff --git a/modules/ui_patterns_sdc/ui_patterns_sdc.services.yml b/modules/ui_patterns_sdc/ui_patterns_sdc.services.yml deleted file mode 100644 index 70b22c06..00000000 --- a/modules/ui_patterns_sdc/ui_patterns_sdc.services.yml +++ /dev/null @@ -1,3 +0,0 @@ -parameters: - ui_patterns_sdc.file_extensions: - - ".component.yml" diff --git a/src/Annotation/UiPattern.php b/src/Annotation/UiPattern.php deleted file mode 100644 index aa3b4623..00000000 --- a/src/Annotation/UiPattern.php +++ /dev/null @@ -1,50 +0,0 @@ - 'component', - '#id' => $id, - '#slots' => $fields, - '#variant' => $variant, - ]; - } - return [ - '#type' => 'markup', - '#markup' => 'No component found for ' . $id, - ]; - } - - /** - * Render given pattern. - * - * @param string $id - * Pattern ID. - * @param string $variant - * Variant name. - * - * @return array - * Pattern render array. - * - * @see \Drupal\ui_patterns\Element\Pattern - */ - public function renderPatternPreview($id, $variant = "") { - $component = UiPatternsManager::getComponentByUiPatternId($id); - if ($component) { - return [ - '#type' => 'component', - '#id' => $component['id'], - '#variant' => $variant, - ]; - } - return [ - '#type' => 'markup', - '#markup' => 'No component found for ' . $id, - ]; - } - } diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml b/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml new file mode 100644 index 00000000..0d50f669 --- /dev/null +++ b/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml @@ -0,0 +1,42 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: Banner +description: Banner with title and a CTA link +libraryOverrides: + dependencies: + - core/drupal +props: + type: object + properties: + heading: + title: Heading + description: The title for the banner text. + examples: + - Join us at The Conference + type: string + ctaText: + title: CTA Text + type: string + examples: + - Click me! + ctaHref: + title: CTA Href + type: string + examples: + - 'https://www.example.org' + ctaTarget: + title: CTA Target + type: string + enum: + - '' + - _blank + image: + title: Media Image + description: Background image for the banner. + type: string +slots: + banner_body: + title: Body + description: The contents of the banner. + examples: + -

    Foo is NOT bar.

    + diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css b/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css new file mode 100644 index 00000000..0466dec3 --- /dev/null +++ b/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css @@ -0,0 +1,24 @@ +.component--my-banner { + position: relative; + width: 80%; + max-width: 1200px; + padding: 2em; + color: white; + background: black; + background-size: cover; +} +.component--my-banner--header { + display: flex; + align-items: center; + justify-content: space-between; +} +.component--my-banner--header h3 { + margin: 0; + font-size: 2em; +} +.component--my-banner--body > * { + margin-bottom: 0; +} +.component--my-banner--body > :first-child { + margin-top: 1em; +} diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig b/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig new file mode 100644 index 00000000..c99fead7 --- /dev/null +++ b/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig @@ -0,0 +1,16 @@ +{# Prepare presentational attributes #} +{% if image is not empty %} + {% set attributes = attributes.setAttribute('style', 'background-image: linear-gradient(to right, black, black, rgba(0, 0, 0, 70%), transparent), url("' ~ image ~ '");') %} +{% endif %} + +{# Markup for the component #} +
    +
    +

    {{ heading }}

    + {% include 'sdc_test:my-cta' with { text: ctaText, href: ctaHref, target: ctaTarget } only %} +
    +
    + {% block banner_body %} + {% endblock %} +
    +
    diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml b/tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml new file mode 100644 index 00000000..daf89ca6 --- /dev/null +++ b/tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml @@ -0,0 +1,4 @@ +name: 'UI Patterns SDC Render Test' +type: module +description: 'Provides test patterns.' +package: 'Testing' diff --git a/tests/modules/ui_patterns_test/src/DummyUiPatternsManager.php b/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php similarity index 93% rename from tests/modules/ui_patterns_test/src/DummyUiPatternsManager.php rename to tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php index 6ddf7810..4648f9c8 100644 --- a/tests/modules/ui_patterns_test/src/DummyUiPatternsManager.php +++ b/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php @@ -8,14 +8,14 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\StringTranslation\TranslationInterface; -use Drupal\ui_patterns\UiPatternsManager; +use Drupal\ui_patterns\UiPatternsLegacyManager; /** * Plugin manager used for tests. * * @phpstan-ignore-next-line */ -class DummyUiPatternsManager extends UiPatternsManager { +class DummyUiPatternsLegacyManager extends UiPatternsLegacyManager { /** * The list of patterns. diff --git a/tests/src/Kernel/UiPatternsManagerTest.php b/tests/src/Kernel/UiPatternsManagerTest.php index 57fd076a..a4d103a2 100644 --- a/tests/src/Kernel/UiPatternsManagerTest.php +++ b/tests/src/Kernel/UiPatternsManagerTest.php @@ -5,7 +5,7 @@ use Drupal\ui_patterns\UiPatterns; /** - * @coversDefaultClass \Drupal\ui_patterns\UiPatternsManager + * @coversDefaultClass \Drupal\ui_patterns\UiPatternsLegacyManager * * @group ui_patterns */ diff --git a/tests/src/Unit/UiPatternsManagerTest.php b/tests/src/Unit/UiPatternsManagerTest.php index 2b07775b..90b6cca5 100644 --- a/tests/src/Unit/UiPatternsManagerTest.php +++ b/tests/src/Unit/UiPatternsManagerTest.php @@ -10,8 +10,8 @@ use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Tests\UnitTestCase; use Drupal\ui_patterns\Definition\PatternDefinition; -use Drupal\ui_patterns\UiPatternsManager; -use Drupal\ui_patterns_test\DummyUiPatternsManager; +use Drupal\ui_patterns\UiPatternsLegacyManager; +use Drupal\ui_patterns_test\DummyUiPatternsLegacyManager; use Symfony\Component\DependencyInjection\ContainerBuilder; /** @@ -19,7 +19,7 @@ * * @group ui_patterns * - * @coversDefaultClass \Drupal\ui_patterns\UiPatternsManager + * @coversDefaultClass \Drupal\ui_patterns\UiPatternsLegacyManager */ class UiPatternsManagerTest extends UnitTestCase { @@ -40,9 +40,9 @@ class UiPatternsManagerTest extends UnitTestCase { /** * The ui patterns manager. * - * @var \Drupal\ui_patterns_test\DummyUiPatternsManager + * @var \Drupal\ui_patterns_test\DummyUiPatternsLegacyManager */ - protected DummyUiPatternsManager $uiPatternsManager; + protected DummyUiPatternsLegacyManager $uiPatternsManager; /** * {@inheritdoc} @@ -70,7 +70,7 @@ protected function setUp(): void { $cache = $this->createMock(CacheBackendInterface::class); $this->stringTranslation = $this->getStringTranslationStub(); - $this->uiPatternsManager = new DummyUiPatternsManager($namespaces, $cache, $moduleHandler, $themeHandler, $this->stringTranslation); + $this->uiPatternsManager = new DummyUiPatternsLegacyManager($namespaces, $cache, $moduleHandler, $themeHandler, $this->stringTranslation); } /** @@ -80,7 +80,7 @@ protected function setUp(): void { */ public function testConstructor(): void { $this->assertInstanceOf( - UiPatternsManager::class, + UiPatternsLegacyManager::class, $this->uiPatternsManager ); } diff --git a/ui_patterns.api.php b/ui_patterns.api.php index 96b837fa..9d99b6f5 100644 --- a/ui_patterns.api.php +++ b/ui_patterns.api.php @@ -13,7 +13,7 @@ * @param \Drupal\ui_patterns\Definition\PatternDefinition[] $definitions * Pattern definitions. * - * @see \Drupal\ui_patterns\UiPatternsManager + * @see \Drupal\ui_patterns\UiPatternsLegacyManager */ function hook_ui_patterns_info_alter(array &$definitions) { $definitions['my_pattern']->setLabel('My new label'); From 0231f98df7ade05ed8698c2e20ae6d647a324e28 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Thu, 22 Jun 2023 13:22:07 +0200 Subject: [PATCH 30/81] Add initial prop widget configuration --- CHANGELOG.md | 2 +- composer.json | 6 +- .../src/Plugin/Layout/PatternLayout.php | 17 +- .../UiPatternsLayoutsRenderTest.php | 9 - .../UiPatternsLayoutsSettingsTest.php | 9 - .../ui_patterns_layouts.info.yml | 2 +- .../ui_patterns_layouts.module | 5 - .../UiPatternsLegacyPluginDiscovery.php | 2 + .../src/UiPatternsLegacyManager.php | 5 + .../src/UiPatternsLegacyPluginManager.php | 28 +- .../src/UiPatternsLegacyServiceProvider.php | 8 +- .../ui_patterns_legacy.info.yml | 2 +- modules/ui_patterns_library/README.md | 0 .../Controller/PatternsLibraryController.php | 161 ----------- .../src/Plugin/Deriver/LibraryDeriver.php | 234 ---------------- .../UiPatterns/Pattern/LibraryPattern.php | 120 -------- .../patterns-meta-information.html.twig | 0 .../patterns-overview-page.html.twig | 0 .../templates/patterns-single-page.html.twig | 0 ...atterns-variant-meta-information.html.twig | 0 .../tests/fixtures/overview-page-patterns.yml | 0 ...terns_library_bad_definition_test.info.yml | 0 ...ibrary_bad_definition_test.ui_patterns.yml | 0 .../templates/button/button.ui_patterns.yml | 0 .../templates/button/pattern-button.html.twig | 0 .../with_local_libraries/css/library_one.css | 0 .../with_local_libraries/css/library_two.css | 0 .../with_local_libraries/js/library_two_1.js | 0 .../with_local_libraries/js/library_two_2.js | 0 .../pattern-with-local-libraries.html.twig | 0 .../with_local_libraries.ui_patterns.yml | 0 .../ui_patterns_library_module_test.info.yml | 0 ...tterns_library_module_test.ui_patterns.yml | 0 .../pattern-subtheme-override.html.twig | 0 ...ui_patterns_library_subtheme_test.info.yml | 0 ...erns_library_subtheme_test.ui_patterns.yml | 0 .../templates/custom-theme-hook.html.twig | 0 .../pattern-button--variant-danger.html.twig | 0 .../templates/pattern-simple.html.twig | 0 .../pattern-subtheme-override.html.twig | 0 ...ttern-with-variants--variant-one.html.twig | 0 ...ttern-with-variants--variant-two.html.twig | 0 .../templates/raw/raw-template.twig | 0 .../ui_patterns_library_theme_test.info.yml | 0 ...atterns_library_theme_test.ui_patterns.yml | 0 .../UiPatternsLibraryBadDefinitionTest.php | 46 ---- .../UiPatternsLibraryOverviewTest.php | 0 .../ui_patterns_library.info.yml | 7 - .../ui_patterns_library.links.menu.yml | 5 - .../ui_patterns_library.module | 26 -- .../ui_patterns_library.permissions.yml | 2 - .../ui_patterns_library.routing.yml | 14 - .../ui_patterns_library.services.yml | 5 - .../ui_patterns_props_widget/css/toggler.png | Bin 0 -> 14513 bytes .../css/ui_patterns_props_widget.css | 36 +++ .../ui_pattern_props_widget.toggle_token.js | 50 ++++ .../src/Annotation/PropWidget.php | 40 +++ .../src/Definition/PropWidgetDefinition.php | 251 +++++++++++++++++ .../src/Element/ComponentPropsWidget.php | 58 ++++ .../src/Form/PropsWidgetFormBuilder.php | 193 +++++++++++++ .../src/Plugin/PropWidgetBase.php | 259 ++++++++++++++++++ .../src/Plugin/PropWidgetInterface.php | 74 +++++ .../PropWidget/TextfieldPropWidget.php | 35 +++ .../src/UiPatternsPropsWidget.php | 127 +++++++++ .../src/UiPatternsPropsWidgetManager.php | 95 +++++++ .../ui_patterns_props_widget.info.yml | 9 + .../ui_patterns_props_widget.libraries.yml | 12 + .../ui_patterns_props_widget.module | 29 ++ .../ui_patterns_props_widget.services.yml | 4 + src/Definition/ArrayAccessDefinitionTrait.php | 46 ++++ .../my-widget/my-widget.component.yml | 18 +- .../components/my-widget/my-widget.css | 10 +- .../components/my-widget/my-widget.twig | 6 +- .../ui_patterns_props_widget_test.info.yml} | 2 +- .../src/DummyUiPatternsLegacyManager.php | 2 +- ui_patterns.info.yml | 2 +- ui_patterns.module | 2 - 77 files changed, 1392 insertions(+), 683 deletions(-) delete mode 100644 modules/ui_patterns_library/README.md delete mode 100644 modules/ui_patterns_library/src/Controller/PatternsLibraryController.php delete mode 100644 modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php delete mode 100644 modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php delete mode 100644 modules/ui_patterns_library/templates/patterns-meta-information.html.twig delete mode 100644 modules/ui_patterns_library/templates/patterns-overview-page.html.twig delete mode 100644 modules/ui_patterns_library/templates/patterns-single-page.html.twig delete mode 100644 modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig delete mode 100644 modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_1.js delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_2.js delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml delete mode 100644 modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml delete mode 100644 modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryBadDefinitionTest.php delete mode 100644 modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php delete mode 100644 modules/ui_patterns_library/ui_patterns_library.info.yml delete mode 100644 modules/ui_patterns_library/ui_patterns_library.links.menu.yml delete mode 100644 modules/ui_patterns_library/ui_patterns_library.module delete mode 100644 modules/ui_patterns_library/ui_patterns_library.permissions.yml delete mode 100644 modules/ui_patterns_library/ui_patterns_library.routing.yml delete mode 100644 modules/ui_patterns_library/ui_patterns_library.services.yml create mode 100644 modules/ui_patterns_props_widget/css/toggler.png create mode 100644 modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css create mode 100644 modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js create mode 100644 modules/ui_patterns_props_widget/src/Annotation/PropWidget.php create mode 100644 modules/ui_patterns_props_widget/src/Definition/PropWidgetDefinition.php create mode 100644 modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php create mode 100644 modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php create mode 100644 modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php create mode 100644 modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php create mode 100644 modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/TextfieldPropWidget.php create mode 100644 modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php create mode 100644 modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php create mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml create mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml create mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.module create mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.services.yml create mode 100644 src/Definition/ArrayAccessDefinitionTrait.php rename tests/modules/{ui_patterns_sdc_widget_render_test => ui_patterns_props_widget_test}/components/my-widget/my-widget.component.yml (75%) rename tests/modules/{ui_patterns_sdc_widget_render_test => ui_patterns_props_widget_test}/components/my-widget/my-widget.css (63%) rename tests/modules/{ui_patterns_sdc_widget_render_test => ui_patterns_props_widget_test}/components/my-widget/my-widget.twig (79%) rename tests/modules/{ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml => ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml} (65%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea637985..728e0aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -255,4 +255,4 @@ -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* diff --git a/composer.json b/composer.json index 216a9be3..057c80dd 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,8 @@ "email": "info@nuvole.org" } ], - "require-dev": { - "drupal/ds": "~3", - "drupal/field_group": "~3.0" + "require": { + "drupal/token": "^1.0" } + } diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 24e761b5..a68ed0ba 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -8,7 +8,6 @@ use Drupal\Core\Layout\LayoutDefinition; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginFormInterface; -use Drupal\ui_patterns\UiPatternsLegacyManager; use Drupal\Core\Render\ElementInfoManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -77,11 +76,12 @@ public function build(array $regions) { foreach (array_keys($regions) as $region_name) { $slots[$region_name] = $regions[$region_name]; } - + $props_configuration = $configuration['pattern']['settings'] ?? []; return [ '#type' => 'component', '#component' => $this->getPluginDefinition()->id(), '#slots' => $slots, + '#props_configuration' => $props_configuration, '#variant' => $configuration['pattern']['variant'], ]; } @@ -103,14 +103,19 @@ public function defaultConfiguration() { */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = []; - + $configuration = $this->getConfiguration(); $form['pattern'] = [ '#group' => 'additional_settings', - '#type' => 'details', - '#title' => $this->t('Pattern settings'), + '#type' => 'container', + '#title' => $this->t('Configuration'), '#tree' => TRUE, ]; - + $component_id = $this->getPluginDefinition()->id(); + /** @var \Drupal\sdc\ComponentPluginManager $plugin_manager */ + $plugin_manager = \Drupal::service('plugin.manager.sdc'); + /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ + $component_metadata = $plugin_manager->find($component_id)?->metadata; + $this->moduleHandler->alter('ui_patterns_layouts_display_configuration_form', $form['pattern'], $component_metadata, $configuration); return $form; } diff --git a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php index 5402a1b9..80a99721 100644 --- a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php +++ b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php @@ -19,15 +19,6 @@ class UiPatternsLayoutsRenderTest extends WebDriverTestBase { */ protected $defaultTheme = 'stark'; - /** - * Disable schema validation when running tests. - * - * @var bool - * - * @todo Fix this by providing actual schema validation. - */ - protected $strictConfigSchema = FALSE; - use TwigDebugTrait; /** diff --git a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php index 1db3df03..4ea60727 100644 --- a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php +++ b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php @@ -19,15 +19,6 @@ class UiPatternsLayoutsSettingsTest extends WebDriverTestBase { */ protected $defaultTheme = 'stark'; - /** - * Disable schema validation when running tests. - * - * @var bool - * - * @todo Fix this by providing actual schema validation. - */ - protected $strictConfigSchema = FALSE; - /** * {@inheritdoc} */ diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml index 8cd17ba8..0afbc04f 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Layouts' type: module description: 'Use patterns as layouts via the Layout Discovery module.' -core_version_requirement: ^9 || ^10 +core_version_requirement: ^10 package: 'User interface' dependencies: - drupal:layout_discovery diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.module b/modules/ui_patterns_layouts/ui_patterns_layouts.module index 1299b023..b47e1f2b 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.module +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.module @@ -5,12 +5,7 @@ * Contains module file. */ -use Drupal\ui_patterns\Element\PatternContext; use Drupal\Core\Layout\LayoutDefinition; -use Drupal\ui_patterns\UiPatterns; -use Drupal\Core\Entity\Display\EntityViewDisplayInterface; -use Drupal\Core\Entity\EntityInterface; -use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface; /** * Implements hook_layout_alter(). diff --git a/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php b/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php index 4b616a86..ca20a010 100644 --- a/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php +++ b/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php @@ -16,6 +16,8 @@ final class UiPatternsLegacyPluginDiscovery extends YamlDiscoveryDecorator { /** * Constructs a YamlDirectoryDiscovery object. * + * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated + * The decorated origin SDC Discovery Service. * @param array $directories * An array of directories to scan, keyed by the provider. The value can * either be a string or an array of strings. The string values should be diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php index 02460565..2d9510ef 100644 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyManager.php @@ -1,6 +1,10 @@ discovery; } - - protected function isUiPatternFile($definition){ + /** + * + */ + protected function isUiPatternFile($definition) { return isset($definition['_discovered_file_path']) && str_ends_with($definition['_discovered_file_path'], 'ui_patterns.yml'); } + /** + * + */ protected function mapPatternToComponent($pattern, $component) { $component['props'] = ['type' => 'object', 'properties' => []]; if (isset($pattern['fields'])) { @@ -50,7 +53,7 @@ protected function mapPatternToComponent($pattern, $component) { $component['slots'][$field_id] = [ 'title' => $field['label'], 'description' => $field['description'] ?? NULL, - 'examples' => $field['preview'] ?? NULL + 'examples' => $field['preview'] ?? NULL, ]; } } @@ -62,7 +65,7 @@ protected function mapPatternToComponent($pattern, $component) { */ protected function alterDefinitions(&$definitions) { foreach ($definitions as & $definition) { - $id = $definition['id'] ?? NULL; + $id = $definition['id'] ?? NULL; if ($id) { $definition_patterns_id = explode(':', $id)[1] ?? NULL; if ($this->isUiPatternFile($definition)) { @@ -86,7 +89,8 @@ protected function alterDefinitions(&$definitions) { $cloned_definition_id = $cloned_definition['provider'] . ':' . $pattern_id; $cloned_definition['id'] = $cloned_definition_id; $definitions[$cloned_definition_id] = $this->mapPatternToComponent($pattern, $cloned_definition); - } else { + } + else { $definitions[$id] = $this->mapPatternToComponent($pattern, $definition); } $definitions[$id]['ui_pattern_id'] = $pattern_id; diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php index 9d9ffbc8..88f22486 100644 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php @@ -5,13 +5,19 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderBase; +/** + * Replace SDC Plugin Manager service with our own. + */ class UiPatternsLegacyServiceProvider extends ServiceProviderBase { + /** + * {@inheritdoc} + */ public function alter(ContainerBuilder $container) { if ($container->hasDefinition('plugin.manager.sdc')) { $definition = $container->getDefinition('plugin.manager.sdc'); - $definition->setClass( + $definition->setClass( 'Drupal\ui_patterns_legacy\UiPatternsLegacyPluginManager' ); } diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml index 0b12bcc9..685fafc9 100644 --- a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml @@ -1,7 +1,7 @@ name: 'UI Patterns Legacy' type: module description: 'Legacy discovery of UI Patterns configurations.' -core_version_requirement: ^9 || ^10 +core_version_requirement: ^10 package: 'User interface' dependencies: - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_library/README.md b/modules/ui_patterns_library/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php b/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php deleted file mode 100644 index 536f2511..00000000 --- a/modules/ui_patterns_library/src/Controller/PatternsLibraryController.php +++ /dev/null @@ -1,161 +0,0 @@ -patternsManager->getDefinition($name)->getLabel(); - } - - /** - * Render pattern library page. - * - * @param string $name - * Plugin ID. - * - * @return array - * Return render array. - */ - public function single($name) { - $definition = $this->patternsManager->getDefinition($name); - - return [ - '#theme' => 'patterns_single_page', - '#pattern' => [ - 'meta' => [ - '#theme' => 'patterns_meta_information', - '#pattern' => $definition->toArray(), - ], - 'rendered' => $this->getPatternRenderArray($definition), - 'definition' => $definition->toArray(), - ], - ]; - } - - /** - * Render pattern library page. - * - * @return array - * Patterns overview page render array. - */ - public function overview() { - $patterns = []; - foreach ($this->patternsManager->getGroupedDefinitions() as $groupName => $groupedDefinitions) { - foreach ($groupedDefinitions as $definition) { - $patterns[$groupName][$definition->id()] = $definition->toArray() + [ - 'meta' => [ - '#theme' => 'patterns_meta_information', - '#pattern' => $definition->toArray(), - ], - 'rendered' => $this->getPatternRenderArray($definition), - 'definition' => $definition->toArray(), - ]; - } - } - - return [ - '#theme' => 'patterns_overview_page', - '#patterns' => $patterns, - ]; - } - - /** - * Get pattern preview render array, handling variants. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition object. - * - * @return array - * Render array. - */ - protected function getPatternRenderArray(PatternDefinition $definition) { - $render = []; - - // If pattern has variants then render them all adding meta information - // on top of each one, or simply render pattern preview otherwise. - if ($definition->hasVariants()) { - foreach ($definition->getVariants() as $variant) { - $render[$definition->id() . '_' . $variant->getName()] = [ - 'meta' => [ - '#theme' => 'patterns_variant_meta_information', - '#variant' => $variant->toArray(), - ], - 'pattern' => [ - '#type' => 'pattern_preview', - '#id' => $definition->id(), - '#variant' => $variant->getName(), - '#theme_wrappers' => [ - 'container' => [ - '#attributes' => [ - 'class' => [ - 'pattern-preview__markup', - 'pattern-preview__markup--' . $definition->id(), - 'pattern-preview__markup--variant_' . $variant->getName(), - ], - ], - ], - ], - ], - ]; - } - } - else { - $render[$definition->id()] = [ - 'pattern' => [ - '#type' => 'pattern_preview', - '#id' => $definition->id(), - '#theme_wrappers' => [ - 'container' => [ - '#attributes' => [ - 'class' => [ - 'pattern-preview__markup', - 'pattern-preview__markup--' . $definition->id(), - ], - ], - ], - ], - ], - ]; - } - - return $render; - } - -} diff --git a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php b/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php deleted file mode 100644 index 57f757c9..00000000 --- a/modules/ui_patterns_library/src/Plugin/Deriver/LibraryDeriver.php +++ /dev/null @@ -1,234 +0,0 @@ -root = $root; - $this->fileExtensions = $extensions; - $this->moduleHandler = $module_handler; - $this->themeHandler = $theme_handler; - $this->extensionDiscovery = new ExtensionDiscovery($root); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, $base_plugin_id) { - return new static( - $base_plugin_id, - $container->get('typed_data_manager'), - $container->get('messenger'), - $container->get('file_system'), - $container->getParameter('app.root'), - $container->getParameter('ui_patterns_library.file_extensions'), - $container->get('module_handler'), - $container->get('theme_handler') - ); - } - - /** - * {@inheritdoc} - */ - public function getFileExtensions() { - return $this->fileExtensions; - } - - /** - * {@inheritdoc} - */ - public function getPatterns() { - $patterns = []; - foreach ($this->getDirectories() as $provider => $directory) { - foreach (array_keys($this->fileScanDirectory($directory)) as $file_path) { - $host_extension = $this->getHostExtension($file_path); - if ($host_extension == FALSE || $host_extension == $provider) { - $content = file_get_contents($file_path); - foreach (Yaml::decode($content) as $id => $definition) { - $definition['id'] = $id; - $definition['base path'] = dirname($file_path); - $definition['file name'] = basename($file_path); - $definition['provider'] = $provider; - $patterns[] = $this->getPatternDefinition($definition); - } - } - } - } - - return $patterns; - } - - /** - * Create a list of all directories to scan. - * - * This includes all module and theme directories. - * - * @return array - * An array containing directory paths keyed by their extension name. - */ - protected function getDirectories() { - // Sort modules list. - $module_list = $this->moduleHandler->getModuleList(); - $module_list = $this->moduleHandler->buildModuleDependencies($module_list); - $module_list = $this->sortExtensionList($module_list); - - // Sort themes list. - $theme_list = $this->themeHandler->listInfo(); - $theme_list = $this->sortExtensionList($theme_list); - - $module_dirs = array_replace($module_list, $this->moduleHandler->getModuleDirectories()); - $theme_dirs = array_replace($theme_list, $this->themeHandler->getThemeDirectories()); - - return $module_dirs + $theme_dirs; - } - - /** - * Sort an extension list. - * - * @param \Drupal\Core\Extension\Extension[] $extensions - * The extension list. - * - * @return string[] - * - */ - protected function sortExtensionList(array $extensions) { - $extensions_sort = array_map(function ($extension) { - return $extension->sort; - }, $extensions); - arsort($extensions_sort); - return array_replace($extensions_sort, $extensions); - } - - /** - * Get extension name that hosts the given YAML definition file. - * - * @param string $pathname - * YAML definition file full path. - * - * @return bool|string - * Either extension machine name or FALSE if not found. - */ - protected function getHostExtension($pathname) { - $extensions = $this->getExtensionLocations(); - $parts = explode(DIRECTORY_SEPARATOR, $pathname); - while (!empty($parts)) { - $path = implode(DIRECTORY_SEPARATOR, $parts); - if (isset($extensions[$path])) { - return $extensions[$path]; - } - array_pop($parts); - } - return FALSE; - } - - /** - * Get extension locations. - * - * @return array - * Array of extensions keyed by their path location. - */ - protected function getExtensionLocations() { - /** @var \Drupal\Core\Extension\Extension[] $extensions */ - if (empty($this->extensionLocations)) { - $extensions = $this->extensionDiscovery->scan('theme') + $this->extensionDiscovery->scan('module'); - foreach ($extensions as $name => $extension) { - $this->extensionLocations[$this->root . DIRECTORY_SEPARATOR . $extension->getPath()] = $name; - } - } - return $this->extensionLocations; - } - -} diff --git a/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php b/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php deleted file mode 100644 index b9cd22e8..00000000 --- a/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php +++ /dev/null @@ -1,120 +0,0 @@ -themeHandler = $theme_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->getParameter('app.root'), - $container->get('module_handler'), - $container->get('theme_handler') - ); - } - - /** - * {@inheritdoc} - */ - public function getThemeImplementation() { - $item = parent::getThemeImplementation(); - $definition = $this->getPluginDefinition(); - $item[$definition['theme hook']] += $this->processTemplateProperty($definition); - $item[$definition['theme hook']] += $this->processCustomThemeHookProperty($definition); - return $item; - } - - /** - * Process 'custom hook theme' definition property. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - */ - protected function processCustomThemeHookProperty(PatternDefinition $definition) { - /** @var \Drupal\Core\Extension\Extension $module */ - $return = []; - if (!$definition->hasCustomThemeHook() && $this->moduleHandler->moduleExists($definition->getProvider())) { - $module = $this->moduleHandler->getModule($definition->getProvider()); - $return['path'] = $module->getPath() . '/templates'; - if ($this->templateExists($definition->getBasePath(), $definition->getTemplate())) { - $return['path'] = str_replace($this->root, '', $definition->getBasePath()); - } - } - return $return; - } - - /** - * Weather template exists in given directory. - * - * @param string $directory - * Directory full path. - * @param string $template - * Template name, without default Twig extension. - * - * @return bool - * Weather template exists in given directory. - */ - protected function templateExists($directory, $template) { - return file_exists($directory . DIRECTORY_SEPARATOR . $template . '.html.twig'); - } - - /** - * Process 'template' definition property. - * - * @param \Drupal\ui_patterns\Definition\PatternDefinition $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - */ - protected function processTemplateProperty(PatternDefinition $definition) { - $return = []; - - if ($definition->hasTemplate()) { - $return = ['template' => $definition->getTemplate()]; - } - return $return; - } - -} diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/templates/patterns-single-page.html.twig b/modules/ui_patterns_library/templates/patterns-single-page.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-variant-meta-information.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.info.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_bad_definition_test/ui_patterns_library_bad_definition_test.ui_patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/button.ui_patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/button/pattern-button.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_one.css deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/css/library_two.css deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_1.js b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_1.js deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_2.js b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/js/library_two_2.js deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/pattern-with-local-libraries.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/templates/with_local_libraries/with_local_libraries.ui_patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.info.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_module_test/ui_patterns_library_module_test.ui_patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/templates/pattern-subtheme-override.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.info.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_subtheme_test/ui_patterns_library_subtheme_test.ui_patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/custom-theme-hook.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-button--variant-danger.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-simple.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-subtheme-override.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-one.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/pattern-with-variants--variant-two.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/templates/raw/raw-template.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.info.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml b/modules/ui_patterns_library/tests/modules/ui_patterns_library_theme_test/ui_patterns_library_theme_test.ui_patterns.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryBadDefinitionTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryBadDefinitionTest.php deleted file mode 100644 index 3f41c4fa..00000000 --- a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryBadDefinitionTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertSession(); - - $user = $this->drupalCreateUser(['access patterns page']); - $this->drupalLogin($user); - - drupal_flush_all_caches(); - $this->drupalGet('/patterns'); - - $session->pageTextContains("Pattern 'bad_definition' is skipped because of the following validation error(s):"); - $session->pageTextContains('Validation error on "bad_definition.label": This value should not be null.'); - } - -} diff --git a/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php b/modules/ui_patterns_library/tests/src/FunctionalJavascript/UiPatternsLibraryOverviewTest.php deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/ui_patterns_library/ui_patterns_library.info.yml b/modules/ui_patterns_library/ui_patterns_library.info.yml deleted file mode 100644 index 627d19d4..00000000 --- a/modules/ui_patterns_library/ui_patterns_library.info.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: 'UI Patterns Library' -type: module -description: 'Exposed patterns in you modules and themes and display them in a pattern library page.' -core_version_requirement: ^9 || ^10 -package: 'User interface' -dependencies: - - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_library/ui_patterns_library.links.menu.yml b/modules/ui_patterns_library/ui_patterns_library.links.menu.yml deleted file mode 100644 index 133cfa6f..00000000 --- a/modules/ui_patterns_library/ui_patterns_library.links.menu.yml +++ /dev/null @@ -1,5 +0,0 @@ -ui_patterns_library.overview: - title: 'UI Patterns library' - description: 'See a list of UI Patterns which exist on the site.' - parent: system.admin_reports - route_name: ui_patterns.patterns.overview diff --git a/modules/ui_patterns_library/ui_patterns_library.module b/modules/ui_patterns_library/ui_patterns_library.module deleted file mode 100644 index 79f767e9..00000000 --- a/modules/ui_patterns_library/ui_patterns_library.module +++ /dev/null @@ -1,26 +0,0 @@ - [ - 'variables' => ['patterns' => NULL], - ], - 'patterns_single_page' => [ - 'variables' => ['pattern' => NULL], - ], - 'patterns_meta_information' => [ - 'variables' => ['pattern' => NULL], - ], - 'patterns_variant_meta_information' => [ - 'variables' => ['variant' => NULL], - ], - ]; -} diff --git a/modules/ui_patterns_library/ui_patterns_library.permissions.yml b/modules/ui_patterns_library/ui_patterns_library.permissions.yml deleted file mode 100644 index 92951358..00000000 --- a/modules/ui_patterns_library/ui_patterns_library.permissions.yml +++ /dev/null @@ -1,2 +0,0 @@ -access patterns page: - title: 'Access library page' diff --git a/modules/ui_patterns_library/ui_patterns_library.routing.yml b/modules/ui_patterns_library/ui_patterns_library.routing.yml deleted file mode 100644 index 4960f572..00000000 --- a/modules/ui_patterns_library/ui_patterns_library.routing.yml +++ /dev/null @@ -1,14 +0,0 @@ -ui_patterns.patterns.overview: - path: '/patterns' - defaults: - _controller: '\Drupal\ui_patterns_library\Controller\PatternsLibraryController::overview' - _title: 'Pattern library' - requirements: - _permission: 'access patterns page' -ui_patterns.patterns.single: - path: '/patterns/{name}' - defaults: - _controller: '\Drupal\ui_patterns_library\Controller\PatternsLibraryController::single' - _title_callback: '\Drupal\ui_patterns_library\Controller\PatternsLibraryController::title' - requirements: - _permission: 'access patterns page' diff --git a/modules/ui_patterns_library/ui_patterns_library.services.yml b/modules/ui_patterns_library/ui_patterns_library.services.yml deleted file mode 100644 index 2a8c750f..00000000 --- a/modules/ui_patterns_library/ui_patterns_library.services.yml +++ /dev/null @@ -1,5 +0,0 @@ -parameters: - ui_patterns_library.file_extensions: - - ".ui_patterns.yml" - - ".patterns.yml" - - ".pattern.yml" diff --git a/modules/ui_patterns_props_widget/css/toggler.png b/modules/ui_patterns_props_widget/css/toggler.png new file mode 100644 index 0000000000000000000000000000000000000000..e8db53e8361249c25f17fdeb1911e0984679e4a3 GIT binary patch literal 14513 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 literal 0 HcmV?d00001 diff --git a/modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css b/modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css new file mode 100644 index 00000000..3f7c4f6f --- /dev/null +++ b/modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css @@ -0,0 +1,36 @@ +.js-ui-patterns-props-widget__wrapper { + position: relative; +} + +#ui-patterns-props-widget-token-link { + display: none; +} + +.ui-patterns-props-widget-show-token-a { + display: block; +} + +.js-ui-patterns-props-widget__toggler { + position: absolute; + width: 14px; + height: 14px; + right: 0; + top: 0; + display: block; + cursor: pointer; + background-image: url("toggler.png"); + background-repeat: no-repeat; + background-size: cover; +} + +.js-ui-patterns-props-widget__wrapper .js-ui-patterns-props-widget__token-wrapper > * { + display: none; +} + +.js-ui-patterns-props-widget__wrapper.js-ui-patterns-props-widget--token-has-value > .js-ui-patterns-props-widget__input-wrapper > * { + display: none; +} + +.js-ui-patterns-props-widget__wrapper.js-ui-patterns-props-widget--token-has-value > .js-ui-patterns-props-widget__token-wrapper > * { + display: block; +} diff --git a/modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js b/modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js new file mode 100644 index 00000000..fb96645b --- /dev/null +++ b/modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js @@ -0,0 +1,50 @@ +/** + * @file + * JavaScript file for the UI Pattern props widget module. + */ + +(function ($, Drupal, drupalSettings, DrupalCoffee) { + + 'use strict'; + + /** + * Attaches ui patterns props widget module behaviors. + * + * Handles enable/disable token element. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attach ui patterns props toggle functionality to the page. + * + */ + Drupal.behaviors.ups_toggle_token = { + attach: function () { + $('.js-ui-patterns-props-widget-show-token-link').once().each(function () { + $(this).after($('' + Drupal.t('Browse available token') + '').click(function (event) { + event.preventDefault(); + $('#ui-patterns-props-widget-token-link:first a').click(); + })); + }); + + $('.js-ui-patterns-props-widget__wrapper').once().each(function () { + var wrapper = $(this); + var toggler = $('
    '); + $(toggler).click(function () { + var tokenInput = $('.js-ui-patterns-props-widget__token', wrapper); + if ($(wrapper).hasClass('js-ui-patterns-props-widget--token-has-value')) { + tokenInput.attr('data-init-val', tokenInput.val()); + tokenInput.val(''); + wrapper.removeClass('js-ui-patterns-props-widget--token-has-value'); + } else { + tokenInput.val(tokenInput.attr('data-init-val')); + wrapper.addClass('js-ui-patterns-props-widget--token-has-value'); + } + }); + $('.js-ui-patterns-props-widget__input-wrapper', wrapper).append(toggler) + $('.js-ui-patterns-props-widget__token-wrapper', wrapper).append(toggler.clone(true)) + }); + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php b/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php new file mode 100644 index 00000000..e2acbf4e --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php @@ -0,0 +1,40 @@ + NULL, + 'label' => NULL, + 'description' => NULL, + 'type' => NULL, + 'required' => FALSE, + 'default_value' => NULL, + 'forced_value' => NULL, + 'options' => NULL, + 'form_visible' => TRUE, + 'allow_token' => FALSE, + ]; + + /** + * PatternDefinitionSetting constructor. + */ + public function __construct($name, $value) { + if (is_scalar($value)) { + $this->definition['name'] = is_numeric($name) ? $value : $name; + $this->definition['label'] = $value; + $this->definition['type'] = 'textfield'; + $this->definition['preview'] = NULL; + $this->definition['allow_token'] = FALSE; + } + else { + $this->definition['name'] = !isset($value['name']) ? $name : $value['name']; + $this->definition['label'] = $value['label']; + $this->definition['required'] = $value['required'] ?? FALSE; + $this->definition['default_value'] = $value['default_value'] ?? NULL; + $this->definition['preview'] = $value['preview'] ?? NULL; + $this->definition['options'] = $value['options'] ?? NULL; + $this->definition['allow_token'] = $value['allow_token'] ?? FALSE; + $this->definition = $value + $this->definition; + } + } + + /** + * Return array definition. + * + * @return array + * Array definition. + */ + public function toArray() { + return $this->definition; + } + + /** + * Get Name property. + * + * @return mixed + * Property value. + */ + public function getName() { + return $this->definition['name']; + } + + /** + * Get Label property. + * + * @return mixed + * Property value. + */ + public function getLabel() { + return $this->definition['label']; + } + + /** + * Get required property. + * + * @return mixed + * Property value. + */ + public function getRequired() { + return $this->definition['required']; + } + + /** + * Get allow token property. + * + * @return bool + * Property value. + */ + public function getAllowToken() { + return $this->definition['allow_token']; + } + + /** + * Get options array. + * + * @return mixed + * Property option. + */ + public function getOptions() { + return $this->definition['options']; + } + + /** + * Get default value property. + * + * @return mixed + * Property value. + */ + public function getDefaultValue() { + return $this->definition['default_value']; + } + + /** + * Set default value property. + * + * @return mixed + * Property value. + */ + public function setDefaultValue($defaultValue) { + $this->definition['default_value'] = $defaultValue; + return $this; + } + + /** + * Set allow token value property. + * + * @param bool $allow_token + * Property value. + * + * @return $this + */ + public function setAllowToken($allow_token) { + $this->definition['allow_token'] = $allow_token; + return $this; + } + + /** + * Get default value property. + * + * @return mixed + * Property value. + */ + public function getForcedValue() { + return $this->definition['forced_value']; + } + + /** + * Get preview property. + * + * @return mixed + * Property value. + */ + public function getPreview() { + return $this->definition['preview']; + } + + /** + * Set default value property. + * + * @return mixed + * Property value. + */ + public function setForcedValue($forcedValue) { + $this->definition['forced_value'] = $forcedValue; + return $this; + } + + /** + * Get Description property. + * + * @return string + * Property value. + */ + public function getDescription() { + return $this->definition['description']; + } + + /** + * Set Description property. + * + * @param string $description + * Property value. + * + * @return $this + */ + public function setDescription($description) { + $this->definition['description'] = $description; + return $this; + } + + /** + * Is form visible property. + * + * @return bool + * Property value. + */ + public function isFormVisible() { + return $this->definition['form_visible']; + } + + /** + * Set form visible property. + * + * @param bool $visible + * Property value. + * + * @return $this + */ + public function setFormVisible($visible) { + $this->definition['form_visible'] = $visible; + return $this; + } + + /** + * Get Type property. + * + * @return string + * Property value. + */ + public function getType() { + return $this->definition['type']; + } + + /** + * Set Type property. + * + * @param string $type + * Property value. + * + * @return $this + */ + public function setType($type) { + $this->definition['type'] = $type; + return $this; + } + +} diff --git a/modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php b/modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php new file mode 100644 index 00000000..9a992c53 --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php @@ -0,0 +1,58 @@ +find($element['#component'])->metadata; + $variant = $element['#variant'] ?? NULL; + $processed_props = UiPatternsPropsWidget::preprocess($component_metadata, $props_configuration, $variant, $preview, $entity); + unset($element['#props_configuration']); + foreach ($processed_props as $name => $prop_value) { + if (!isset($element['#props'][$name])) { + $element['#props'][$name] = $prop_value; + } + else { + if ($prop_value instanceof Attribute && $element['#props'][$name] instanceof Attribute) { + $element['#props'][$name] = new Attribute(array_merge($prop_value->toArray(), $element['#props'][$name]->toArray())); + } + elseif (is_array($element['#props'][$name]) && is_array($prop_value)) { + $element['#props'][$name] = array_merge($element['#props'][$name], $prop_value); + } + } + } + return $element; + } + + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['processPropsWidget']; + } + +} diff --git a/modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php b/modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php new file mode 100644 index 00000000..1c9d33f5 --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php @@ -0,0 +1,193 @@ +getDefinitions(); + /** @var EntityTypeInterface $definition */ + foreach ($entity_type_definations as $definition) { + if ($definition instanceof ContentEntityType) { + $content_entity_types[] = $definition->id(); + } + } + $form['token_link'] = [ + '#prefix' => '', + '#theme' => 'token_tree_link', + '#token_types' => $content_entity_types, + '#show_restricted' => TRUE, + '#weight' => 90, + ]; + } + + /** + * Build pattern props widget fieldset. + * + * @param array $form + * Form array. + * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata + * The pattern definition. + * @param array $configuration + * The pattern configuration. + */ + public static function layoutForm(array &$form, ComponentMetadata $component_metadata, array $configuration) { + $widgets = UiPatternsPropsWidget::getPatternDefinitionWidgets($component_metadata); + self::buildTokenLink($form); + + $form['#attached']['library'][] = 'ui_patterns_props_widget/widget'; + if (UiPatternsPropsWidgetManager::allowVariantToken($component_metadata)) { + $variant_token_value = $configuration['pattern']['variant_token'] ?? NULL; + $form['variant_token'] = [ + '#type' => 'textfield', + '#title' => 'Variant token', + '#attributes' => ['class' => ['js-ui-patterns-props-widget-show-token-link']], + '#default_value' => $variant_token_value, + ]; + } + + $form['variant']['#attributes']['class'][] = 'ui-patterns-variant-selector-' . $component_metadata->id; + if (!empty($widgets)) { + foreach ($widgets as $key => $widget) { + if (empty($widget->getType()) || !$widget->isFormVisible()) { + continue; + } + + if (!isset($form['settings'])) { + $form['settings'] = [ + '#type' => 'fieldset', + '#title' => t('Settings'), + ]; + } + $setting_value = $configuration['pattern']['settings'][$key] ?? NULL; + $token_value = $configuration['pattern']['settings'][$key . "_token"] ?? ""; + $widget = UiPatternsPropsWidget::createWidget($component_metadata, $widget); + $form['settings'] += $widget->buildConfigurationForm([], $setting_value, $token_value, 'layouts_display'); + } + PropsWidgetFormBuilder::buildVariantsForm(".ui-patterns-variant-selector-" . $component_metadata->id, $form['settings'], $component_metadata); + } + } + + /** + * Build widget display form. + * + * @param array $form + * Form array. + * @param array $configuration + * Configurations array. + */ + public static function displayForm(array &$form, array $configuration) { + $form['#attached']['library'][] = 'ui_patterns_props_widget/widget'; + self::buildTokenLink($form); + + /** @var \Drupal\sdc\ComponentPluginManager $plugin_manager */ + $plugin_manager = \Drupal::service('plugin.manager.sdc'); + /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ + $components = $plugin_manager->getDefinitions(); + + foreach ($components as $component_id => $component) { + $widgets = UiPatternsPropsWidget::getPatternDefinitionWidgets($component); + $form['variants'][$component_id]['#attributes']['class'][] = 'ui-patterns-variant-selector-' . $component_id; + if (UiPatternsPropsWidgetManager::allowVariantToken($component)) { + $variant_token_value = $configuration['variants_token'][$component_id] ?? NULL; + $form['variants']['#weight'] = 20; + $form['pattern_mapping']['#weight'] = 30; + $form['pattern_settings']['#weight'] = 40; + $form['variants_token'] = [ + '#type' => 'container', + '#title' => t('Pattern Variant'), + '#weight' => 25, + '#states' => [ + 'visible' => [ + 'select[id="patterns-select"]' => ['value' => $component_id], + ], + ], + ]; + $form['variants_token'][$component_id] = [ + '#type' => 'textfield', + '#title' => t('Variant token'), + '#default_value' => $variant_token_value, + '#attributes' => ['class' => ['js-ui-patterns-props-widget-show-token-link']], + '#states' => [ + 'visible' => [ + 'select[id="patterns-select"]' => ['value' => $component_id], + ], + ], + ]; + } + if (!empty($widgets)) { + foreach ($widgets as $key => $widget) { + if (empty($widget->getType()) || !$widget->isFormVisible()) { + continue; + } + if (!isset($form['pattern_widgets'][$component_id])) { + $form['pattern_widgets'][$component_id] = [ + '#type' => 'fieldset', + '#title' => t('Settings'), + '#states' => [ + 'visible' => [ + 'select[id="patterns-select"]' => ['value' => $component_id], + ], + ], + ]; + } + $fieldset = &$form['pattern_settings'][$component_id]; + $settingType = UiPatternsPropsWidget::createWidget($component, $widget); + $setting_value = $configuration['pattern_settings'][$component_id][$key] ?? NULL; + $token_value = $configuration['pattern_settings'][$component_id][$key . "_token"] ?? NULL; + $fieldset += $settingType->buildConfigurationForm([], $setting_value, $token_value, 'display'); + } + PropsWidgetFormBuilder::buildVariantsForm('.ui-patterns-variant-selector-' . $component_id, $fieldset, $component); + } + } + } + + /** + * Hide all settings which are configured by the variant. + * + * @param string $select_selector + * The id of the variant select field. + * @param array $fieldset + * The fieldset. + * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata + * The pattern definition. + */ + private static function buildVariantsForm($select_selector, array &$fieldset, ComponentMetadata $component_metadata) { + $variants = $component_metadata->variants ?? []; + foreach ($variants as $variant_ary) { + $settings = $variant_ary['settings'] ?? []; + foreach ($settings as $name => $setting) { + if (isset($fieldset[$name])) { + // Add an or before a new state begins. + if (isset($fieldset[$name]['#states']['invisible']) && count($fieldset[$name]['#states']['invisible']) != 0) { + $fieldset[$name]['#states']['invisible'][] = 'or'; + } + // Hide configured setting. + $fieldset[$name]['#states']['invisible'][][$select_selector]['value'] = $variant->getName(); + $fieldset[$name . '_token']['#states']['invisible'][][$select_selector]['value'] = $variant->getName(); + } + } + } + } + +} diff --git a/modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php b/modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php new file mode 100644 index 00000000..d2d534d3 --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php @@ -0,0 +1,259 @@ +defaultConfiguration(); + $this->propWidgetDefinition = $configuration['prop_widget_definition']; + $this->componentMetadata = $configuration['component_metadata']; + unset($configuration['prop_widget_definition']); + unset($configuration['component_metadata']); + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * Return value if set otherwise take the default value. + * + * @param mixed $value + * The provided value. + * + * @return string + * The value for this setting + */ + protected function getValue($value) { + if ($value === NULL) { + return $this->getPropWidgetDefinition()->getDefaultValue(); + } + else { + return $value ?? ""; + } + } + + /** + * Returns the widget definition. + * + * @return \Drupal\ui_patterns_settings\Definition\PropWidgetDefinition + * The widget definition. + */ + protected function getPropWidgetDefinition() { + return $this->propWidgetDefinition; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $plugin = new static($configuration, $plugin_id, $plugin_definition); + + /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ + $translation = $container->get('string_translation'); + + $plugin->setStringTranslation($translation); + + return $plugin; + } + + /** + * {@inheritdoc} + */ + public function label() { + $plugin_definition = $this->getPluginDefinition(); + return $plugin_definition['label']; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + $plugin_definition = $this->getPluginDefinition(); + return $plugin_definition['description'] ?? ''; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = $configuration + $this->defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + return []; + } + + /** + * {@inheritdoc} + */ + public function preprocess($value, array $context) { + $def = $this->getPropWidgetDefinition(); + $value = $this->propPreprocess($value, $context, $def); + return $value; + } + + /** + * {@inheritdoc} + */ + public function propPreprocess($value, array $context, PropWidgetDefinition $def) { + return $value; + } + + /** + * Returns the bind form field. + * + * @param array $form + * The fieldset definition array for the widget form. + * @param string $value + * The stored default value. + * @param \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition $def + * The widget definition. + * + * @return array + * The form. + */ + protected function tokenForm(array $form, $value, PropWidgetDefinition $def) { + $form[$def->getName() . "_token"] = [ + '#type' => 'textfield', + '#title' => $this->t("Token for %label", ['%label' => $def->getLabel()]), + '#default_value' => $this->getValue($value), + '#attributes' => ['class' => ['js-ui-patterns-props-widget-show-token-link', 'js-ui-patterns-props-widget__token']], + '#wrapper_attributes' => ['class' => ['js-ui-patterns-props-widget__token-wrapper']], + ]; + return $form; + } + + /** + * Check required input fields in layout forms. + * + * @param array $element + * The element to validate. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * @param array $form + * The form. + */ + public static function validateLayout(array $element, FormStateInterface &$form_state, array &$form) { + $parents = $element['#parents']; + $value = $form_state->getValue($parents); + $parents[count($parents) - 1] = $parents[count($parents) - 1] . '_token'; + $token_value = $form_state->getValue($parents); + if (empty($value) && empty($token_value)) { + // Check if a variant is selected and the value + // is provided by the variant. + $variant = $form_state->getValue([ + 'layout_configuration', + 'pattern', + 'variant', + ]); + if (!empty($variant)) { + $variant_def = $element['#pattern_definition']->getVariant($variant); + $variant_ary = $variant_def->toArray(); + if (!empty($variant_ary['settings'][$element['#pattern_setting_definition']->getName()])) { + return; + } + } + + $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); + } + } + + /** + * Add validation and basics classes to the raw input field. + * + * @param array $input + * The input field. + * @param \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition $def + * The widget definition. + * @param string $form_type + * The form type. Either layouts_display or display. + */ + protected function handleInput(array &$input, PropWidgetDefinition $def, $form_type) { + $input['#attributes']['class'][] = 'js-ui-patterns-props-widget__input'; + $input['#wrapper_attributes']['class'][] = 'js-ui-patterns-props-widget__input-wrapper'; + if ($def->getRequired()) { + $input['#title'] .= ' *'; + if ($form_type === 'layouts_display') { + $input['#prop_widget_definition'] = $this->propWidgetDefinition; + $input['#component_metadata'] = $this->componentMetadata; + $input['#element_validate'][] = [ + PropWidgetBase::class, + 'validateLayout', + ]; + } + } + } + + /** + * {@inheritdoc} + * + * Creates a generic configuration form for all widgets. + * Individual widgets can add elements to this form by + * overriding PatternSettingTypeBaseInterface::widgetForm(). + * Most plugins should not override this method unless they + * need to alter the generic form elements. + * + * @see \Drupal\Core\Block\BlockBase::blockForm() + */ + public function buildConfigurationForm(array $form, $value, $token_value, $form_type) { + $def = $this->getPropWidgetDefinition(); + $form = $this->widgetForm($form, $value, $def, $form_type); + $classes = 'js-ui-patterns-props-widget__wrapper'; + if ($def->getAllowToken()) { + if (!empty($token_value)) { + $classes .= ' js-ui-patterns-props-widget--token-has-value'; + } + $form[$def->getName()]['#prefix'] = '
    '; + } + if ($def->getAllowToken()) { + $form = $this->tokenForm($form, $token_value, $def); + $form[$def->getName() . '_token']['#suffix'] = '
    '; + } + + return $form; + } + +} diff --git a/modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php b/modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php new file mode 100644 index 00000000..0100a68a --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php @@ -0,0 +1,74 @@ +getName()] = [ + '#type' => 'textfield', + '#title' => $def->getLabel(), + '#description' => $def->getDescription(), + '#default_value' => $this->getValue($value), + ]; + $this->handleInput($form[$def->getName()], $def, $form_type); + return $form; + } + +} diff --git a/modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php b/modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php new file mode 100644 index 00000000..1e21699d --- /dev/null +++ b/modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php @@ -0,0 +1,127 @@ + $widget_definition) { + if ($widget_definition->getForcedValue()) { + $value = $widget_definition->getForcedValue(); + } + elseif (!empty($props_configuration[$key . '_token'])) { + $token_value = $props_configuration[$key . '_token']; + $token_data = []; + if ($entity !== NULL) { + $token_data[$entity->getEntityTypeId()] = $entity; + } + $value = \Drupal::token()->replace($token_value, $token_data, ['clear' => TRUE]); + } + elseif (isset($props_configuration[$key])) { + $value = $props_configuration[$key]; + } + elseif ($preview && !empty($widget_definition->getPreview())) { + $value = $widget_definition->getPreview(); + } + else { + $value = $widget_definition->getDefaultValue(); + } + if ($variant != 'default' && $variant != NULL) { + $variant_ob = NULL; + if ($variant_ob != NULL) { + $variant_ary = $variant_ob->toArray(); + if (isset($variant_ary['settings']) && isset($variant_ary['settings'][$key])) { + $value = $variant_ary['settings'][$key]; + } + } + } + $widget = UiPatternsPropsWidget::createWidget($component_metadata, $widget_definition); + $processed_widgets_data[$key] = $widget->preprocess($value, $context); + } + return $processed_widgets_data; + + } + + /** + * Get setting definitions for a pattern definition. + * + * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata + * The definition. + * + * @return \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition[] + * Setting pattern definitons. + */ + public static function getPatternDefinitionWidgets(ComponentMetadata $component_metadata) { + $props = $component_metadata->schema['properties']; + $widgets = []; + if (!empty($props)) { + foreach ($props as $key => $prop) { + $def = ['label' => $prop['title'], 'options' => $prop['enum'] ?? NULL]; + $widget = UiPatternsPropsWidget::getManager()->getWidgetByProp($component_metadata, $key, $prop); + if ($widget !== NULL) { + $def['type'] = $widget['id']; + $widgets[$key] = new PropWidgetDefinition($key, $def); + } + } + } + return $widgets; + } + + /** + * Create prop widget type plugin. + * + * @param \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition $widget_defintion + * The widget defintion. + * + * @return \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition + * Widget Plugin instance. + */ + public static function createWidget(ComponentMetadata $component_metadata, PropWidgetDefinition $widget_defintion) { + $configuration = []; + $configuration['prop_widget_definition'] = $widget_defintion; + $configuration['component_metadata'] = $component_metadata; + return \Drupal::service('plugin.manager.ui_patterns_props_widget_manager') + ->createInstance($widget_defintion->getType(), $configuration); + } + +} diff --git a/modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php b/modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php new file mode 100644 index 00000000..bbd2346b --- /dev/null +++ b/modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php @@ -0,0 +1,95 @@ +moduleHandler = $module_handler; + $this->alterInfo('ui_patterns_props_widget_info'); + $this->setCacheBackend($cache_backend, 'ui_patterns_props_widget', ['ui_patterns_props_widget']); + } + + /** + * + */ + public function getWidgetByProp(ComponentMetadata $component_metadata, $prop_name, array $prop) { + $definitions = $this->getDefinitions(); + $widget = NULL; + $metadata_schema = $component_metadata->schema; + // Check for an existing widget. + if (isset($prop['widget']['type'])) { + $widget_type = $prop['widget']['type']; + $widget = $this->getDefinition($widget_type); + } + + // Check for default widgets. + $schema_stub = ['name' => $prop_name, 'props' => ['properties' => []]]; + + foreach ($definitions as $definition) { + $origin_schema = $schema_stub; + $origin_schema['props']['properties'][$prop_name] = $metadata_schema['props']['properties'][$prop_name]; + $annotation_schema = $origin_schema; + $annotation_schema['props']['properties'][$prop_name] = $definition['applies']; + if ($this->compatibilityChecker->isCompatible($origin_schema, $annotation_schema)) { + $widget = $definition; + break; + } + } + return $widget; + } + + /** + * Returns TRUE if a variant token can configured. + * + * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata + * The pattern definition. + * + * @return bool + * Returns TRUE if a variant token can configured. + */ + public static function allowVariantToken(ComponentMetadata $component_metadata) { + if (isset($component_metadata->allow_variant_token) && $component_metadata->allow_variant_token) { + return TRUE; + } + else { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function createInstance($plugin_id, array $configuration = []) { + $plugin_definition = $this->getDefinition($plugin_id); + $plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition); + // If the plugin provides a factory method, pass the container to it. + if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) { + $plugin = $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition); + } + else { + $plugin = new $plugin_class($configuration, $plugin_id, $plugin_definition); + } + return $plugin; + } + +} diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml b/modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml new file mode 100644 index 00000000..37f9b96d --- /dev/null +++ b/modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml @@ -0,0 +1,9 @@ +name: UI Patterns Props Widget +type: module +description: Configure components properties with widgets +package: User interface +core_version_requirement: ^10 +dependencies: + - ui_patterns:ui_patterns + - drupal:sdc + - token:token diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml b/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml new file mode 100644 index 00000000..9a1404f7 --- /dev/null +++ b/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml @@ -0,0 +1,12 @@ +widget: + version: VERSION + js: + js/ui_patterns_props_widget.toggle_token.js: {} + css: + component: + css/ui_patterns_props_widget.css: {} + dependencies: + - core/jquery + - core/jquery.once + - core/drupal + - core/drupalSettings diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.module b/modules/ui_patterns_props_widget/ui_patterns_props_widget.module new file mode 100644 index 00000000..509108d6 --- /dev/null +++ b/modules/ui_patterns_props_widget/ui_patterns_props_widget.module @@ -0,0 +1,29 @@ +definition); + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) { + return $this->definition[$offset] ?? NULL; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) { + $this->definition[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) { + unset($this->definition[$offset]); + } + +} diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml similarity index 75% rename from tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml rename to tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml index 0d50f669..3deff0d5 100644 --- a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.component.yml +++ b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml @@ -1,21 +1,35 @@ $schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json -name: Banner -description: Banner with title and a CTA link +name: Widget +description: Widget Test with title and a CTA link libraryOverrides: dependencies: - core/drupal +variants: + default: + label: Default + other: + label: other + props: type: object + required: + - heading properties: heading: title: Heading + required: true + default_value: 'Asdf' description: The title for the banner text. + widget: + type: textfield examples: - Join us at The Conference type: string ctaText: title: CTA Text type: string + widget: + type: textfield examples: - Click me! ctaHref: diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.css similarity index 63% rename from tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css rename to tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.css index 0466dec3..ea702632 100644 --- a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.css +++ b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.css @@ -1,4 +1,4 @@ -.component--my-banner { +.component--my-widget { position: relative; width: 80%; max-width: 1200px; @@ -7,18 +7,18 @@ background: black; background-size: cover; } -.component--my-banner--header { +.component--my-widget--header { display: flex; align-items: center; justify-content: space-between; } -.component--my-banner--header h3 { +.component--my-widget--header h3 { margin: 0; font-size: 2em; } -.component--my-banner--body > * { +.component--my-widget--body > * { margin-bottom: 0; } -.component--my-banner--body > :first-child { +.component--my-widget--body > :first-child { margin-top: 1em; } diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig similarity index 79% rename from tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig rename to tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig index c99fead7..66bb01b2 100644 --- a/tests/modules/ui_patterns_sdc_widget_render_test/components/my-widget/my-widget.twig +++ b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig @@ -5,11 +5,11 @@ {# Markup for the component #}
    -
    -

    {{ heading }}

    +
    +

    Heading: {{ heading }}

    {% include 'sdc_test:my-cta' with { text: ctaText, href: ctaHref, target: ctaTarget } only %}
    -
    +
    {% block banner_body %} {% endblock %}
    diff --git a/tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml b/tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml similarity index 65% rename from tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml rename to tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml index daf89ca6..b4711bec 100644 --- a/tests/modules/ui_patterns_sdc_widget_render_test/ui_patterns_sdc_render_test.info.yml +++ b/tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml @@ -1,4 +1,4 @@ -name: 'UI Patterns SDC Render Test' +name: 'UI Patterns Props Widget Test' type: module description: 'Provides test patterns.' package: 'Testing' diff --git a/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php b/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php index 4648f9c8..3b794efc 100644 --- a/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php +++ b/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php @@ -54,7 +54,7 @@ public function getDefinitions(): array { /** * Getter. * - * getPatterns is already a method in the real plugin manager. + * GetPatterns is already a method in the real plugin manager. * * @return array * Property value. diff --git a/ui_patterns.info.yml b/ui_patterns.info.yml index cf1fbdb0..abb7f6fe 100644 --- a/ui_patterns.info.yml +++ b/ui_patterns.info.yml @@ -1,5 +1,5 @@ name: 'UI Patterns' type: module description: 'UI patterns.' -core_version_requirement: ^9 || ^10 +core_version_requirement: ^10 package: 'User interface' diff --git a/ui_patterns.module b/ui_patterns.module index 26abd513..5c049f6e 100644 --- a/ui_patterns.module +++ b/ui_patterns.module @@ -4,5 +4,3 @@ * @file * Contains ui_patterns.module. */ - - From b2a687d65d0d6150734901f9dc92134656722cb7 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Thu, 29 Jun 2023 17:31:17 +0200 Subject: [PATCH 31/81] feat: Add initial tests --- config/schema/ui_patterns.schema.yml | 8 + .../one-column/one-column.component.yml | 22 ++ .../components/one-column/one-column.twig | 4 + ...tity_view_display.node.article.default.yml | 6 +- .../templates/one_column.ui_patterns.yml | 12 - .../templates/pattern-one-column.html.twig | 7 - .../ui_patterns_layouts_test.info.yml | 3 +- .../UiPatternsLayoutsRenderTest.php | 4 +- .../UiPatternsLayoutsSettingsTest.php | 3 +- .../ui_patterns_layouts.install | 16 - .../ui_patterns_layouts.module | 4 +- .../src/UiPatternsLegacyPluginManager.php | 9 +- .../src/UiPatternsLegacyServiceProvider.php | 2 +- ... ui_patterns_props_widget.toggle_token.js} | 15 +- .../src/Annotation/PropWidget.php | 8 +- .../UiPatterns/EnumerationPropWidgetBase.php | 66 ++++ .../PropWidget/SelectPropWidget.php | 29 ++ .../PropWidget/TextfieldPropWidget.php | 3 +- .../src/UiPatternsPropsWidget.php | 3 + .../src/UiPatternsPropsWidgetManager.php | 20 +- .../ui_patterns_props_widget.libraries.yml | 2 +- src/Definition/PatternSourceField.php | 154 ++++++++ .../my-widget/my-widget.component.yml | 7 +- .../components/my-widget/my-widget.twig | 4 +- .../UiPatternsPreviewRenderTest.php | 71 ---- .../Kernel/Plugin/Deriver/YamlDeriverTest.php | 50 --- tests/src/Kernel/Plugin/PatternBaseTest.php | 62 --- .../TypedData/PatternDataDefinitionTest.php | 41 -- .../Kernel/UiPatternsExtraFieldSourceTest.php | 3 +- tests/src/Kernel/UiPatternsManagerTest.php | 49 --- tests/src/Kernel/UiPatternsPreviewTest.php | 51 --- tests/src/Unit/AbstractUiPatternsTest.php | 9 - .../Unit/Definition/PatternDefinitionTest.php | 160 -------- tests/src/Unit/Element/PatternPreviewTest.php | 29 -- tests/src/Unit/Template/TwigExtensionTest.php | 206 ---------- tests/src/Unit/UiPatternsManagerTest.php | 373 ------------------ .../fixtures/definition/fields_processing.yml | 64 --- .../definition/variants_processing.yml | 40 -- tests/src/fixtures/libraries.yml | 105 ----- .../fixtures/pattern_element_libraries.yml | 24 -- tests/src/fixtures/preview_markup.yml | 14 - tests/src/fixtures/preview_process.yml | 8 - tests/src/fixtures/validation.yml | 67 ---- ui_patterns.info.yml | 2 + 44 files changed, 338 insertions(+), 1501 deletions(-) create mode 100644 config/schema/ui_patterns.schema.yml create mode 100644 modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.component.yml create mode 100644 modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.twig delete mode 100644 modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/one_column.ui_patterns.yml delete mode 100644 modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/pattern-one-column.html.twig rename modules/ui_patterns_props_widget/js/{ui_pattern_props_widget.toggle_token.js => ui_patterns_props_widget.toggle_token.js} (68%) create mode 100644 modules/ui_patterns_props_widget/src/Plugin/UiPatterns/EnumerationPropWidgetBase.php create mode 100644 modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php create mode 100644 src/Definition/PatternSourceField.php delete mode 100644 tests/src/Functional/UiPatternsPreviewRenderTest.php delete mode 100644 tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php delete mode 100644 tests/src/Kernel/Plugin/PatternBaseTest.php delete mode 100644 tests/src/Kernel/TypedData/PatternDataDefinitionTest.php delete mode 100644 tests/src/Kernel/UiPatternsManagerTest.php delete mode 100644 tests/src/Kernel/UiPatternsPreviewTest.php delete mode 100644 tests/src/Unit/Definition/PatternDefinitionTest.php delete mode 100644 tests/src/Unit/Element/PatternPreviewTest.php delete mode 100644 tests/src/Unit/Template/TwigExtensionTest.php delete mode 100644 tests/src/Unit/UiPatternsManagerTest.php delete mode 100644 tests/src/fixtures/definition/fields_processing.yml delete mode 100644 tests/src/fixtures/definition/variants_processing.yml delete mode 100644 tests/src/fixtures/libraries.yml delete mode 100644 tests/src/fixtures/pattern_element_libraries.yml delete mode 100644 tests/src/fixtures/preview_markup.yml delete mode 100644 tests/src/fixtures/preview_process.yml delete mode 100644 tests/src/fixtures/validation.yml diff --git a/config/schema/ui_patterns.schema.yml b/config/schema/ui_patterns.schema.yml new file mode 100644 index 00000000..4e3b522c --- /dev/null +++ b/config/schema/ui_patterns.schema.yml @@ -0,0 +1,8 @@ +core.entity_view_display.*.*.*.third_party_settings.field_layout.settings.pattern: + type: mapping + label: 'Per-view-mode Layout Pattern settings' + mapping: + variant: + type: string + label: 'Variant' + diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.component.yml b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.component.yml new file mode 100644 index 00000000..c1b8e228 --- /dev/null +++ b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.component.yml @@ -0,0 +1,22 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: One columns +description: Widget Test with One Column +libraryOverrides: + dependencies: + - core/drupal +props: + type: object + properties: + heading: + title: Heading + description: The title for the banner text. + examples: + - Join us at The Conference + type: string +slots: + body: + title: Body + description: The contents of the banner. + examples: + -

    Foo is NOT bar.

    + diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.twig b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.twig new file mode 100644 index 00000000..27e50fff --- /dev/null +++ b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/components/one-column/one-column.twig @@ -0,0 +1,4 @@ +
    +{% block body %} +{% endblock %} +
    diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/config/install/core.entity_view_display.node.article.default.yml b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/config/install/core.entity_view_display.node.article.default.yml index 20cffe3b..d595c4eb 100644 --- a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/config/install/core.entity_view_display.node.article.default.yml +++ b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/config/install/core.entity_view_display.node.article.default.yml @@ -11,11 +11,9 @@ dependencies: - user third_party_settings: field_layout: - id: pattern_one_column + id: ui_patterns_layouts_test:one_column settings: - pattern: - field_templates: default - variant: default + pattern: {} id: node.article.default targetEntityType: node bundle: article diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/one_column.ui_patterns.yml b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/one_column.ui_patterns.yml deleted file mode 100644 index c838098a..00000000 --- a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/one_column.ui_patterns.yml +++ /dev/null @@ -1,12 +0,0 @@ -one_column: - label: "One column" - variants: - default: - label: "Default" - highlighted: - label: "Highlighted" - fields: - body: - type: "text" - label: "Body" - preview: "Body" diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/pattern-one-column.html.twig b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/pattern-one-column.html.twig deleted file mode 100644 index af9d618c..00000000 --- a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/templates/pattern-one-column.html.twig +++ /dev/null @@ -1,7 +0,0 @@ -{# -/** - * @file - * "One column" layout pattern. - */ -#} -

    {{ body }}

    \ No newline at end of file diff --git a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml index e88167c8..9362391d 100644 --- a/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml +++ b/modules/ui_patterns_layouts/tests/modules/ui_patterns_layouts_test/ui_patterns_layouts_test.info.yml @@ -9,8 +9,7 @@ dependencies: - drupal:text - ui_patterns:ui_patterns - ui_patterns:ui_patterns_layouts - - ui_patterns:ui_patterns_library - + - drupal:sdc config_devel: install: - field.field.node.article.body diff --git a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php index 80a99721..6d19dd4a 100644 --- a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php +++ b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php @@ -30,10 +30,11 @@ class UiPatternsLayoutsRenderTest extends WebDriverTestBase { 'field_ui', 'field_layout', 'text', + 'sdc', + 'layout_builder', 'ui_patterns', 'ui_patterns_layouts', 'ui_patterns_layouts_test', - 'ui_patterns_library', ]; /** @@ -42,7 +43,6 @@ class UiPatternsLayoutsRenderTest extends WebDriverTestBase { public function testUiPatternsLayoutsRendering() { $this->enableTwigDebugMode(); $this->drupalLogin($this->drupalCreateUser([], NULL, TRUE)); - $node = $this->drupalCreateNode([ 'title' => 'Test article', 'body' => 'Test body', diff --git a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php index 4ea60727..ee8a3633 100644 --- a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php +++ b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php @@ -28,10 +28,11 @@ class UiPatternsLayoutsSettingsTest extends WebDriverTestBase { 'field_ui', 'field_layout', 'text', + 'layout_builder', 'ui_patterns', 'ui_patterns_layouts', + 'sdc', 'ui_patterns_layouts_test', - 'ui_patterns_library', ]; /** diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.install b/modules/ui_patterns_layouts/ui_patterns_layouts.install index e510cd34..1def9606 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.install +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.install @@ -4,19 +4,3 @@ * @file * Contains install file. */ - -/** - * Uninstall layout_plugin and install layout_discovery. - */ -function ui_patterns_layouts_update_8101() { - /** @var \Drupal\Core\Extension\ModuleInstaller $installer */ - $installer = \Drupal::service('module_installer'); - - if (\Drupal::moduleHandler()->moduleExists('layout_plugin')) { - $installer->uninstall(['layout_plugin']); - } - - if (!\Drupal::moduleHandler()->moduleExists('layout_discovery')) { - $installer->install(['layout_discovery']); - } -} diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.module b/modules/ui_patterns_layouts/ui_patterns_layouts.module index b47e1f2b..5ee63f76 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.module +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.module @@ -11,8 +11,6 @@ use Drupal\Core\Layout\LayoutDefinition; * Implements hook_layout_alter(). */ function ui_patterns_layouts_layout_alter(&$definitions) { - /** @var \Drupal\ui_patterns\Definition\PatternDefinition[] $pattern_definitions */ - /** @var \Drupal\sdc\ComponentPluginManager $plugin_manager */ $plugin_manager = \Drupal::service('plugin.manager.sdc'); /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ @@ -31,6 +29,6 @@ function ui_patterns_layouts_layout_alter(&$definitions) { } } - $definitions['sdc_' . $component['id']] = new LayoutDefinition($definition); + $definitions[str_replace('-', '_', $component['id'])] = new LayoutDefinition($definition); } } diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php index 8ab52c0b..d0edd884 100644 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php @@ -10,8 +10,8 @@ * Plugin Manager for *.ui_patterns.yml configuration files. * * Plugin Manager overwrites getDiscovery() to provide a decorated - * Discovery. Maybe there is more gentle way? - * After discovery it maps UiPatterns configuration to SDC components. + * Discovery. Decoration of the service seems not possible for me. + * Probably there is more gentle way. * * @see plugin_api * @@ -47,7 +47,10 @@ protected function isUiPatternFile($definition) { * */ protected function mapPatternToComponent($pattern, $component) { - $component['props'] = ['type' => 'object', 'properties' => []]; + $component['props'] = ['type' => 'object', 'properties' => [ + 'context' => [ + ] + ]]; if (isset($pattern['fields'])) { foreach ($pattern['fields'] as $field_id => $field) { $component['slots'][$field_id] = [ diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php index 88f22486..3d30ef80 100644 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php @@ -14,7 +14,7 @@ class UiPatternsLegacyServiceProvider extends ServiceProviderBase { * {@inheritdoc} */ public function alter(ContainerBuilder $container) { - + return; if ($container->hasDefinition('plugin.manager.sdc')) { $definition = $container->getDefinition('plugin.manager.sdc'); $definition->setClass( diff --git a/modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js b/modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js similarity index 68% rename from modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js rename to modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js index fb96645b..e3c9b072 100644 --- a/modules/ui_patterns_props_widget/js/ui_pattern_props_widget.toggle_token.js +++ b/modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js @@ -1,6 +1,6 @@ /** * @file - * JavaScript file for the UI Pattern props widget module. + * JavaScript file for the UI Pattern settings module. */ (function ($, Drupal, drupalSettings, DrupalCoffee) { @@ -8,27 +8,27 @@ 'use strict'; /** - * Attaches ui patterns props widget module behaviors. + * Attaches ui patterns settings module behaviors. * * Handles enable/disable token element. * * @type {Drupal~behavior} * * @prop {Drupal~behaviorAttach} attach - * Attach ui patterns props toggle functionality to the page. + * Attach ui patterns settings toggle functionality to the page. * */ Drupal.behaviors.ups_toggle_token = { attach: function () { - $('.js-ui-patterns-props-widget-show-token-link').once().each(function () { - $(this).after($('' + Drupal.t('Browse available token') + '').click(function (event) { + once('ui-patterns-props-widget-show-token-link', '.js-ui-patterns-props-widget-show-token-link').forEach(function (elm) { + $(elm).after($('' + Drupal.t('Browse available token') + '').click(function (event) { event.preventDefault(); $('#ui-patterns-props-widget-token-link:first a').click(); })); }); - $('.js-ui-patterns-props-widget__wrapper').once().each(function () { - var wrapper = $(this); + once('ui-patterns-props-widget-wrapper', '.js-ui-patterns-props-widget__wrapper').forEach(function (el) { + var wrapper = $(el); var toggler = $('
    '); $(toggler).click(function () { var tokenInput = $('.js-ui-patterns-props-widget__token', wrapper); @@ -46,5 +46,4 @@ }); } }; - })(jQuery, Drupal, drupalSettings); diff --git a/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php b/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php index e2acbf4e..f9317b9b 100644 --- a/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php +++ b/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php @@ -30,11 +30,17 @@ class PropWidget extends Plugin { */ public $label; + /** + * The loading priority . + * + * @var int + */ + public int $priority; /** * Applies for . * * @var array */ - public $applies = []; + public $schema = []; } diff --git a/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/EnumerationPropWidgetBase.php b/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/EnumerationPropWidgetBase.php new file mode 100644 index 00000000..5d382476 --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/EnumerationPropWidgetBase.php @@ -0,0 +1,66 @@ + $this->t("Please select")]; + } + + /** + * Returns the enumeration type. + * + * @return string + * The enumeration type. + */ + abstract protected function getEnumerationType(); + + /** + * Returns the enumeration options. + * + * @param \Drupal\ui_patterns_settings\Definition\PatternDefinitionSetting $def + * The pattern definition. + * + * @return mixed + * The options. + */ + protected function getOptions(PropWidgetDefinition $def) { + return $def->getOptions(); + } + + /** + * {@inheritdoc} + */ + public function widgetForm(array $form, $value, PropWidgetDefinition $def, $form_type) { + if ($def->getRequired() == FALSE) { + $options = $this->emptyOption(); + } + else { + $options = []; + } + + $options += $this->getOptions($def); + $form[$def->getName()] = [ + '#type' => $this->getEnumerationType($def), + '#title' => $def->getLabel(), + '#description' => $def->getDescription(), + '#default_value' => $this->getValue($value), + '#options' => $options, + ]; + $this->handleInput($form[$def->getName()], $def, $form_type); + return $form; + } + +} diff --git a/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php b/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php new file mode 100644 index 00000000..95bb10af --- /dev/null +++ b/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php @@ -0,0 +1,29 @@ +getDefinitions(); @@ -43,16 +45,20 @@ public function getWidgetByProp(ComponentMetadata $component_metadata, $prop_nam } // Check for default widgets. - $schema_stub = ['name' => $prop_name, 'props' => ['properties' => []]]; - + $schema_stub = ['name' => $prop_name, 'properties' => []]; + usort($definitions, function($a, $b) { + return $a['priority'] ?? 1 > $b['priority'] ?? 1; + } ); foreach ($definitions as $definition) { $origin_schema = $schema_stub; - $origin_schema['props']['properties'][$prop_name] = $metadata_schema['props']['properties'][$prop_name]; $annotation_schema = $origin_schema; - $annotation_schema['props']['properties'][$prop_name] = $definition['applies']; - if ($this->compatibilityChecker->isCompatible($origin_schema, $annotation_schema)) { + $origin_schema['properties'][$prop_name] = $metadata_schema['properties'][$prop_name]; + $annotation_schema['properties'][$prop_name] = $definition['schema']; + try { + $this->compatibilityChecker->isCompatible($annotation_schema, $origin_schema); $widget = $definition; - break; + } catch (IncompatibleComponentSchema $exception) { + // Do nothing. } } return $widget; diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml b/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml index 9a1404f7..e9c0ac3a 100644 --- a/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml +++ b/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml @@ -7,6 +7,6 @@ widget: css/ui_patterns_props_widget.css: {} dependencies: - core/jquery - - core/jquery.once + - core/once - core/drupal - core/drupalSettings diff --git a/src/Definition/PatternSourceField.php b/src/Definition/PatternSourceField.php new file mode 100644 index 00000000..16cfd825 --- /dev/null +++ b/src/Definition/PatternSourceField.php @@ -0,0 +1,154 @@ +fieldName = $field_name; + $this->fieldLabel = $field_label; + $this->pluginId = $plugin_id; + $this->pluginLabel = $plugin_label; + } + + /** + * Get FieldName property. + * + * @return string + * Property value. + */ + public function getFieldName() { + return $this->fieldName; + } + + /** + * Set FieldName property. + * + * @param string $fieldName + * Property value. + * + * @return $this + */ + public function setFieldName($fieldName) { + $this->fieldName = $fieldName; + return $this; + } + + /** + * Get FieldLabel property. + * + * @return string + * Property value. + */ + public function getFieldLabel() { + return $this->fieldLabel; + } + + /** + * Set FieldLabel property. + * + * @param string $fieldLabel + * Property value. + * + * @return $this + */ + public function setFieldLabel($fieldLabel) { + $this->fieldLabel = $fieldLabel; + return $this; + } + + /** + * Get Plugin property. + * + * @return string + * Property value. + */ + public function getPluginId() { + return $this->pluginId; + } + + /** + * Set Plugin property. + * + * @param string $pluginId + * Property value. + * + * @return $this + */ + public function setPluginId($pluginId) { + $this->pluginId = $pluginId; + return $this; + } + + /** + * Get PluginLabel property. + * + * @return string + * Property value. + */ + public function getPluginLabel() { + return $this->pluginLabel; + } + + /** + * Set PluginLabel property. + * + * @param string $pluginLabel + * Property value. + * + * @return $this + */ + public function setPluginLabel($pluginLabel) { + $this->pluginLabel = $pluginLabel; + return $this; + } + + /** + * Get unique field key. + * + * @return string + * Field key. + */ + public function getFieldKey() { + return $this->getPluginId() . self::FIELD_KEY_SEPARATOR . $this->getFieldName(); + } + +} diff --git a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml index 3deff0d5..4a3b23c9 100644 --- a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml +++ b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml @@ -12,13 +12,10 @@ variants: props: type: object - required: - - heading properties: heading: title: Heading - required: true - default_value: 'Asdf' + default: 'Asdf' description: The title for the banner text. widget: type: textfield @@ -48,7 +45,7 @@ props: description: Background image for the banner. type: string slots: - banner_body: + widget_body: title: Body description: The contents of the banner. examples: diff --git a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig index 66bb01b2..4d44e888 100644 --- a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig +++ b/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig @@ -7,10 +7,10 @@

    Heading: {{ heading }}

    - {% include 'sdc_test:my-cta' with { text: ctaText, href: ctaHref, target: ctaTarget } only %}
    - {% block banner_body %} + {% block widget_body %} + {{ widget_body }} {% endblock %}
    diff --git a/tests/src/Functional/UiPatternsPreviewRenderTest.php b/tests/src/Functional/UiPatternsPreviewRenderTest.php deleted file mode 100644 index 038fc2f0..00000000 --- a/tests/src/Functional/UiPatternsPreviewRenderTest.php +++ /dev/null @@ -1,71 +0,0 @@ -assertSession(); - - $this->enableTwigDebugMode(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - $this->drupalGet('/patterns'); - - // Assert correct variant suggestions. - $suggestions = [ - 'pattern-foo--variant-default--preview.html.twig', - 'pattern-foo--variant-default.html.twig', - 'pattern-foo--preview.html.twig', - 'pattern-foo.html.twig', - 'pattern-foo-bar--variant-default--preview.html.twig', - 'pattern-foo-bar--variant-default.html.twig', - 'pattern-foo-bar--preview.html.twig', - 'pattern-foo-bar.html.twig', - ]; - foreach ($suggestions as $suggestion) { - $assert_session->responseContains($suggestion); - } - } - -} diff --git a/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php b/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php deleted file mode 100644 index 87884105..00000000 --- a/tests/src/Kernel/Plugin/Deriver/YamlDeriverTest.php +++ /dev/null @@ -1,50 +0,0 @@ -container->get('theme_installer')->install([$default_theme]); - $this->container->get('config.factory')->getEditable('system.theme')->set('default', $default_theme)->save(); - } - - /** - * Test get derivative definitions. - * - * @covers ::getDerivativeDefinitions - */ - public function testGetDerivativeDefinitions() { - UiPatterns::getManager()->clearCachedDefinitions(); - foreach (UiPatterns::getManager()->getDefinitions() as $definition) { - $this->assertNotEmpty($definition->id(), 'Pattern definition id is empty'); - $this->assertNotEmpty($definition->getProvider(), 'Pattern definition provider is empty'); - $this->assertNotEmpty($definition->getBasePath(), 'Pattern definition base path is empty'); - } - } - -} diff --git a/tests/src/Kernel/Plugin/PatternBaseTest.php b/tests/src/Kernel/Plugin/PatternBaseTest.php deleted file mode 100644 index e68c3b11..00000000 --- a/tests/src/Kernel/Plugin/PatternBaseTest.php +++ /dev/null @@ -1,62 +0,0 @@ -getUiPatternBaseMock($actual); - /** @var \Drupal\ui_patterns\Plugin\PatternBase $pattern */ - $libraries = $pattern->getLibraryDefinitions(); - $this->assertEquals($expected, $libraries); - } - - /** - * Data provider for rendering tests. - * - * The actual data is read from fixtures stored in a YAML configuration. - * - * @return array - * A set of dump data for testing. - */ - public function hookLibraryInfoBuildDataProvider() { - return $this->getFixtureContent('libraries.yml'); - } - - /** - * Get PatternBase mock. - * - * @param array $plugin_definition - * Plugin definition. - * @param array $methods - * List of methods to mock. - * - * @return \PHPUnit\Framework\MockObject\MockObject - * Mock object. - */ - protected function getUiPatternBaseMock(array $plugin_definition = [], array $methods = []) { - return $this->getMockForAbstractClass(PatternBase::class, [ - [], - 'plugin_id', - $plugin_definition, - \Drupal::getContainer()->getParameter('app.root'), - \Drupal::service('module_handler'), - ], '', TRUE, TRUE, TRUE, $methods); - } - -} diff --git a/tests/src/Kernel/TypedData/PatternDataDefinitionTest.php b/tests/src/Kernel/TypedData/PatternDataDefinitionTest.php deleted file mode 100644 index ebc90cc1..00000000 --- a/tests/src/Kernel/TypedData/PatternDataDefinitionTest.php +++ /dev/null @@ -1,41 +0,0 @@ -create($definition, $data)->validate(); - - $actual = []; - foreach ($violations as $violation) { - $actual[] = $violation->getPropertyPath() . ': ' . $violation->getMessage(); - } - $this->assertEquals($expected, $actual); - } - - /** - * Return validation data. - * - * @return array - * Pattern validation data. - */ - public function validationProvider() { - return $this->getFixtureContent('validation.yml'); - } - -} diff --git a/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php b/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php index 71f1b0be..9d6141a7 100644 --- a/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php +++ b/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\ui_patterns\Kernel; use Drupal\KernelTests\KernelTestBase; -use Drupal\ui_patterns\UiPatterns; /** * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\Source\ExtraFieldSource @@ -37,7 +36,7 @@ protected function setUp() : void { */ public function testGetSourceFields() { /** @var \Drupal\ui_patterns\UiPatternsSourceManager $manager */ - $manager = UiPatterns::getSourceManager(); + $manager = \Drupal::service('plugin.manager.ui_patterns_source'); /** @var \Drupal\ui_patterns\Plugin\UiPatterns\Source\ExtraFieldSource $source */ $fields = $manager->getFieldsByTag('entity_display', [ diff --git a/tests/src/Kernel/UiPatternsManagerTest.php b/tests/src/Kernel/UiPatternsManagerTest.php deleted file mode 100644 index a4d103a2..00000000 --- a/tests/src/Kernel/UiPatternsManagerTest.php +++ /dev/null @@ -1,49 +0,0 @@ -container->get('theme_installer')->install([$default_theme]); - $this->container->get('config.factory')->getEditable('system.theme')->set('default', $default_theme)->save(); - } - - /** - * Test UiPatternsManager::getPatternDefinition. - * - * @covers ::getPatterns - */ - public function testGetPattern() { - $manager = UiPatterns::getManager(); - $definitions = $manager->getDefinitions(); - - foreach ($manager->getPatterns() as $pattern) { - $this->assertEquals($definitions[$pattern->getPluginId()]->id(), $pattern->getBaseId()); - } - } - -} diff --git a/tests/src/Kernel/UiPatternsPreviewTest.php b/tests/src/Kernel/UiPatternsPreviewTest.php deleted file mode 100644 index eb3cb347..00000000 --- a/tests/src/Kernel/UiPatternsPreviewTest.php +++ /dev/null @@ -1,51 +0,0 @@ -renderRoot($render); - $this->assertEquals($expected, $render["#context"]->getType()); - } - - /** - * Data provider for Process Context tests. - * - * The actual data is read from fixtures stored in a YAML configuration. - * - * @return array - * A set of dump data for testing. - */ - public function processContextDataProvider() { - return $this->getFixtureContent('preview_process.yml'); - } - -} diff --git a/tests/src/Unit/AbstractUiPatternsTest.php b/tests/src/Unit/AbstractUiPatternsTest.php index 0667fe3f..46db7ca2 100644 --- a/tests/src/Unit/AbstractUiPatternsTest.php +++ b/tests/src/Unit/AbstractUiPatternsTest.php @@ -13,14 +13,5 @@ */ abstract class AbstractUiPatternsTest extends UnitTestCase { - /** - * Get fixtures base path. - * - * @return string - * Fixtures base path. - */ - protected function getFixturePath() { - return realpath(dirname(__FILE__) . '/../fixtures'); - } } diff --git a/tests/src/Unit/Definition/PatternDefinitionTest.php b/tests/src/Unit/Definition/PatternDefinitionTest.php deleted file mode 100644 index 6d56c560..00000000 --- a/tests/src/Unit/Definition/PatternDefinitionTest.php +++ /dev/null @@ -1,160 +0,0 @@ - $value]); - $this->assertEquals(call_user_func([$pattern_definition, $getter]), $value); - } - - /** - * Provider. - * - * @return array - * Data. - */ - public function definitionGettersProvider() { - return [ - ['getProvider', 'provider', 'my_module'], - ['id', 'id', 'pattern_id'], - ['getLabel', 'label', 'Pattern label'], - ['getDescription', 'description', 'Pattern description.'], - ['getCategory', 'category', 'Pattern category'], - ['getUse', 'use', 'template.twig'], - ['hasCustomThemeHook', 'custom theme hook', FALSE], - ['getThemeHook', 'theme hook', 'eme hook: custom_my_theme_hook'], - ['getTemplate', 'template', 'my-template.html.twig'], - ['getFileName', 'file name', '/path/to/filename.ui_patterns.yml'], - ['getClass', 'class', '\Drupal\ui_patterns\MyClass'], - ['getBasePath', 'base path', '/path/to'], - ['getTags', 'tags', ['a', 'b']], - ['getWeight', 'weight', 10], - ]; - } - - /** - * Test field singleton. - * - * @covers ::getField - * @covers ::setFields - */ - public function testFields() { - $fields = [ - 'name' => [ - 'name' => 'name', - 'label' => 'Label', - ], - ]; - $pattern_definition = new PatternDefinition(); - $pattern_definition->setFields($fields); - $this->assertEquals( - [ - $fields['name']['label'], - $fields['name']['name'], - NULL, - NULL, - NULL, - ], - [ - $pattern_definition->getField('name')->getLabel(), - $pattern_definition->getField('name')->getName(), - $pattern_definition->getField('name')->getType(), - $pattern_definition->getField('name')->getDescription(), - $pattern_definition->getField('name')->getPreview(), - ]); - - $pattern_definition->getField('name')->setType('type'); - $pattern_definition->getField('name')->setPreview('preview'); - $pattern_definition->getField('name')->setDescription('description'); - - $this->assertEquals( - [ - 'type', - 'description', - 'preview', - ], - [ - $pattern_definition->getField('name')->getType(), - $pattern_definition->getField('name')->getDescription(), - $pattern_definition->getField('name')->getPreview(), - ]); - } - - /** - * Test fields processing. - * - * @dataProvider fieldsProcessingProvider - * - * @covers ::setFields - */ - public function testFieldsProcessing($actual, $expected) { - $pattern_definition = new PatternDefinition(); - $data = $pattern_definition->setFields($actual)->toArray(); - $this->assertEquals($expected, $data['fields']); - } - - /** - * Provider. - * - * @return array - * Data. - */ - public function fieldsProcessingProvider() { - return Yaml::decode(file_get_contents($this->getFixturePath() . '/definition/fields_processing.yml')); - } - - /** - * Test fields processing. - * - * @dataProvider variantsProcessingProvider - * - * @covers ::setVariants - */ - public function testVariantsProcessing($actual, $expected) { - $pattern_definition = new PatternDefinition(); - $data = $pattern_definition->setVariants($actual)->toArray(); - $this->assertEquals($expected, $data['variants']); - } - - /** - * Provider. - * - * @return array - * Data. - */ - public function variantsProcessingProvider() { - return Yaml::decode(file_get_contents($this->getFixturePath() . '/definition/variants_processing.yml')); - } - -} diff --git a/tests/src/Unit/Element/PatternPreviewTest.php b/tests/src/Unit/Element/PatternPreviewTest.php deleted file mode 100644 index 3bd41623..00000000 --- a/tests/src/Unit/Element/PatternPreviewTest.php +++ /dev/null @@ -1,29 +0,0 @@ -getFixturePath() . '/preview_markup.yml')); - foreach ($assertions as $assertion) { - $result = PatternPreview::getPreviewMarkup($assertion['actual']); - $this->assertEquals($assertion['expected'], $result); - } - } - -} diff --git a/tests/src/Unit/Template/TwigExtensionTest.php b/tests/src/Unit/Template/TwigExtensionTest.php deleted file mode 100644 index 1ceac10f..00000000 --- a/tests/src/Unit/Template/TwigExtensionTest.php +++ /dev/null @@ -1,206 +0,0 @@ -createMock('\Drupal\Core\Render\RendererInterface'); - $urlGenerator = $this->createMock('\Drupal\Core\Routing\UrlGeneratorInterface'); - $themeManager = $this->createMock('\Drupal\Core\Theme\ThemeManagerInterface'); - $dateFormatter = $this->createMock('\Drupal\Core\Datetime\DateFormatterInterface'); - $fileUrlGenerator = $this->createMock(FileUrlGeneratorInterface::class); - - $this->systemUnderTest = new TwigExtension($renderer, $urlGenerator, $themeManager, $dateFormatter, $fileUrlGenerator); - } - - /** - * Tests Twig 'add_class' filter. - * - * @covers ::addClass - * @dataProvider providerTestTwigAddClass - */ - public function testTwigAddClass($element, $classes, $expected_result) { - $processed = $this->systemUnderTest->addClass($element, $classes); - $this->assertEquals($expected_result, $processed); - } - - /** - * A data provider for ::testTwigAddClass(). - * - * @return \Iterator - * An iterator. - */ - public function providerTestTwigAddClass(): \Iterator { - yield 'should add a class on element' => [ - ['#type' => 'container'], - 'my-class', - ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], - ]; - - yield 'should add a class from a array of string keys on element' => [ - ['#type' => 'container'], - ['my-class'], - ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], - ]; - - yield 'should add a class from a Markup value' => [ - ['#type' => 'container'], - [Markup::create('my-class')], - ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], - ]; - - yield 'should add a class when an attributes array is already present' => [ - [ - '#type' => - 'container', - '#attributes' => [ - 'foo' => 'bar', - ], - ], - [Markup::create('my-class')], - [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['my-class'], - 'foo' => 'bar', - ], - ], - ]; - - yield 'should add a class when an attributes object is already present' => [ - [ - '#type' => - 'container', - '#attributes' => new Attribute([ - 'foo' => 'bar', - ]), - ], - [Markup::create('my-class')], - [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['my-class'], - 'foo' => 'bar', - ], - ], - ]; - - yield '#printed should be removed after class(es) added' => [ - [ - '#markup' => 'This content is already is rendered', - '#printed' => TRUE, - ], - '', - [ - '#markup' => 'This content is already is rendered', - '#attributes' => [ - 'class' => [''], - ], - ], - ]; - } - - /** - * Tests Twig 'set_attribute' filter. - * - * @covers ::setAttribute - * @dataProvider providerTestTwigSetAttribute - */ - public function testTwigSetAttribute($element, $key, $value, $expected_result) { - $processed = $this->systemUnderTest->setAttribute($element, $key, $value); - $this->assertEquals($expected_result, $processed); - } - - /** - * A data provider for ::testTwigSetAttribute(). - * - * @return \Iterator - * An iterator. - */ - public function providerTestTwigSetAttribute(): \Iterator { - yield 'should add attributes on element' => [ - ['#theme' => 'image'], - 'title', - 'Aloha', - [ - '#theme' => 'image', - '#attributes' => [ - 'title' => 'Aloha', - ], - ], - ]; - - yield 'should merge existing attributes on element' => [ - [ - '#theme' => 'image', - '#attributes' => [ - 'title' => 'Aloha', - ], - ], - 'title', - 'Bonjour', - [ - '#theme' => 'image', - '#attributes' => [ - 'title' => 'Bonjour', - ], - ], - ]; - - yield 'should add JSON attribute value correctly on element' => [ - ['#type' => 'container'], - 'data-slider', - Json::encode(['autoplay' => TRUE]), - [ - '#type' => 'container', - '#attributes' => [ - 'data-slider' => '{"autoplay":true}', - ], - ], - ]; - - yield '#printed should be removed after setting attribute' => [ - [ - '#markup' => 'This content is already is rendered', - '#printed' => TRUE, - ], - 'title', - NULL, - [ - '#markup' => 'This content is already is rendered', - '#attributes' => [ - 'title' => NULL, - ], - ], - ]; - } - -} diff --git a/tests/src/Unit/UiPatternsManagerTest.php b/tests/src/Unit/UiPatternsManagerTest.php deleted file mode 100644 index 90b6cca5..00000000 --- a/tests/src/Unit/UiPatternsManagerTest.php +++ /dev/null @@ -1,373 +0,0 @@ -container = new ContainerBuilder(); - $this->container->set('string_translation', $this->getStringTranslationStub()); - - // Set up for this class. - $namespaces = $this->createMock(\Traversable::class); - - /** @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject $moduleHandler */ - $moduleHandler = $this->createMock(ModuleHandlerInterface::class); - $moduleHandler->expects($this->any()) - ->method('getModuleDirectories') - ->willReturn([]); - - /** @var \Drupal\Core\Extension\ThemeHandlerInterface|\PHPUnit\Framework\MockObject\MockObject $themeHandler */ - $themeHandler = $this->createMock(ThemeHandlerInterface::class); - $themeHandler->expects($this->any()) - ->method('getThemeDirectories') - ->willReturn([]); - - $cache = $this->createMock(CacheBackendInterface::class); - $this->stringTranslation = $this->getStringTranslationStub(); - - $this->uiPatternsManager = new DummyUiPatternsLegacyManager($namespaces, $cache, $moduleHandler, $themeHandler, $this->stringTranslation); - } - - /** - * Tests the constructor. - * - * @covers ::__construct - */ - public function testConstructor(): void { - $this->assertInstanceOf( - UiPatternsLegacyManager::class, - $this->uiPatternsManager - ); - } - - /** - * Tests the processDefinition(). - * - * @covers ::processDefinition - */ - public function testProcessDefinition(): void { - $plugin_id = 'test'; - $definition = ['id' => $plugin_id]; - - $expected = new PatternDefinition($definition); - $expected->setCategory($this->stringTranslation->translate('Other')); - - /** @var \Drupal\ui_patterns\Definition\PatternDefinition $definition */ - $this->uiPatternsManager->processDefinition($definition, $plugin_id); - $this->assertInstanceOf(PatternDefinition::class, $definition); - $this->assertEquals($definition->toArray(), $expected->toArray()); - } - - /** - * @covers ::getCategories - */ - public function testGetCategories(): void { - $this->uiPatternsManager->setPatterns([ - 'id_1' => [ - 'id' => 'id_1', - 'category' => 'Cat 1', - ], - 'id_2' => [ - 'id' => 'id_2', - 'category' => 'Cat 2', - ], - 'id_3' => [ - 'id' => 'id_3', - ], - ]); - $expected = [ - 'Cat 1', - 'Cat 2', - 'Other', - ]; - $categories = $this->uiPatternsManager->getCategories(); - $this->assertEquals($expected, $categories); - } - - /** - * @covers ::getSortedDefinitions - */ - public function testGetSortedDefinitions(): void { - $this->uiPatternsManager->setPatterns([ - 'id_z1z2' => [ - 'category' => 'Z', - 'weight' => 1, - 'label' => '(Z)', - 'id' => 'id_z1z2', - ], - 'id_z1z1' => [ - 'category' => 'Z', - 'weight' => 1, - 'label' => 'Z', - 'id' => 'id_z1z1', - ], - 'id_z1a2' => [ - 'category' => 'Z', - 'weight' => 1, - 'label' => '(A)', - 'id' => 'id_z1a2', - ], - 'id_z1a1' => [ - 'category' => 'Z', - 'weight' => 1, - 'label' => 'A', - 'id' => 'id_z1a1', - ], - 'id_z0z2' => [ - 'category' => 'Z', - 'weight' => 0, - 'label' => '(Z)', - 'id' => 'id_z0z2', - ], - 'id_z0z1' => [ - 'category' => 'Z', - 'weight' => 0, - 'label' => 'Z', - 'id' => 'id_z0z1', - ], - 'id_z0a2' => [ - 'category' => 'Z', - 'weight' => 0, - 'label' => '(A)', - 'id' => 'id_z0a2', - ], - 'id_z0a1' => [ - 'category' => 'Z', - 'weight' => 0, - 'label' => 'A', - 'id' => 'id_z0a1', - ], - 'id_a1z2' => [ - 'category' => 'A', - 'weight' => 1, - 'label' => '(Z)', - 'id' => 'id_a1z2', - ], - 'id_a1z1' => [ - 'category' => 'A', - 'weight' => 1, - 'label' => 'Z', - 'id' => 'id_a1z1', - ], - 'id_a1a2' => [ - 'category' => 'A', - 'weight' => 1, - 'label' => '(A)', - 'id' => 'id_a1a2', - ], - 'id_a1a1' => [ - 'category' => 'A', - 'weight' => 1, - 'label' => 'A', - 'id' => 'id_a1a1', - ], - 'id_a0z2' => [ - 'category' => 'A', - 'weight' => 0, - 'label' => '(Z)', - 'id' => 'id_a0z2', - ], - 'id_a0z1' => [ - 'category' => 'A', - 'weight' => 0, - 'label' => 'Z', - 'id' => 'id_a0z1', - ], - 'id_a0a2' => [ - 'category' => 'A', - 'weight' => 0, - 'label' => '(A)', - 'id' => 'id_a0a2', - ], - 'id_a0a1' => [ - 'category' => 'A', - 'weight' => 0, - 'label' => 'A', - 'id' => 'id_a0a1', - ], - ]); - - $expected = [ - 'id_a0a1', - 'id_a0a2', - 'id_a0z1', - 'id_a0z2', - 'id_a1a1', - 'id_a1a2', - 'id_a1z1', - 'id_a1z2', - 'id_z0a1', - 'id_z0a2', - 'id_z0z1', - 'id_z0z2', - 'id_z1a1', - 'id_z1a2', - 'id_z1z1', - 'id_z1z2', - ]; - - $sorted_definitions = $this->uiPatternsManager->getSortedDefinitions(); - $this->assertEquals($expected, \array_keys($sorted_definitions)); - $this->assertContainsOnlyInstancesOf(PatternDefinition::class, $sorted_definitions); - } - - /** - * @covers ::getGroupedDefinitions - */ - public function testGetGroupedDefinitions(): void { - $this->uiPatternsManager->setPatterns([ - 'cat_1_1_b' => [ - 'id' => 'cat_1_1_b', - 'category' => 'Cat 1', - 'label' => 'B', - 'weight' => 1, - ], - 'cat_1_1_a' => [ - 'id' => 'cat_1_1_a', - 'category' => 'Cat 1', - 'label' => 'A', - 'weight' => 1, - ], - 'cat_1_0_a' => [ - 'id' => 'cat_1_0_a', - 'category' => 'Cat 1', - 'label' => 'A', - 'weight' => 0, - ], - 'cat_2_0_a' => [ - 'id' => 'cat_1_0_a', - 'category' => 'Cat 2', - 'label' => 'A', - 'weight' => 0, - ], - 'no_category' => [ - 'id' => 'no_category', - 'label' => 'B', - 'weight' => 0, - ], - ]); - - $category_expected = [ - 'Cat 1' => [ - 'cat_1_0_a', - 'cat_1_1_a', - 'cat_1_1_b', - ], - 'Cat 2' => [ - 'cat_2_0_a', - ], - 'Other' => [ - 'no_category', - ], - ]; - - $definitions = $this->uiPatternsManager->getGroupedDefinitions(); - $this->assertEquals(\array_keys($category_expected), \array_keys($definitions)); - foreach ($category_expected as $category => $expected) { - $this->assertArrayHasKey($category, $definitions); - $this->assertEquals($expected, \array_keys($definitions[$category])); - $this->assertContainsOnlyInstancesOf(PatternDefinition::class, $definitions[$category]); - } - } - - /** - * @covers ::getPatternsOptions - */ - public function testGetPatternsOptions(): void { - $this->uiPatternsManager->setPatterns([ - 'id_1' => [ - 'id' => 'id_1', - 'label' => 'Label 1', - 'category' => 'Cat 1', - ], - 'id_2' => [ - 'id' => 'id_2', - 'label' => 'Label 2', - 'category' => 'Cat 2', - ], - 'id_3' => [ - 'label' => 'Label 3', - 'id' => 'id_3', - ], - ]); - $expected = [ - 'Cat 1' => [ - 'id_1' => 'Label 1', - ], - 'Cat 2' => [ - 'id_2' => 'Label 2', - ], - 'Other' => [ - 'id_3' => 'Label 3', - ], - ]; - $options = $this->uiPatternsManager->getPatternsOptions(); - $this->assertEquals($expected, $options); - - // Only one category. - $this->uiPatternsManager->setPatterns([ - 'id_1' => [ - 'id' => 'id_1', - 'label' => 'Label 1', - 'category' => 'Cat 1', - ], - 'id_2' => [ - 'id' => 'id_2', - 'label' => 'Label 2', - 'category' => 'Cat 1', - ], - ]); - $expected = [ - 'id_1' => 'Label 1', - 'id_2' => 'Label 2', - ]; - $options = $this->uiPatternsManager->getPatternsOptions(); - $this->assertEquals($expected, $options); - } - -} diff --git a/tests/src/fixtures/definition/fields_processing.yml b/tests/src/fixtures/definition/fields_processing.yml deleted file mode 100644 index b9e04d70..00000000 --- a/tests/src/fixtures/definition/fields_processing.yml +++ /dev/null @@ -1,64 +0,0 @@ -- actual: - - field1 - - field2 - expected: - field1: - name: field1 - label: field1 - description: ~ - type: ~ - preview: ~ - escape: TRUE - additional: [] - field2: - name: field2 - label: field2 - description: ~ - type: ~ - preview: ~ - escape: TRUE - additional: [] -- actual: - field1: Field 1 - field2: Field 2 - expected: - field1: - name: field1 - label: Field 1 - description: ~ - type: ~ - preview: ~ - escape: TRUE - additional: [] - field2: - name: field2 - label: Field 2 - description: ~ - type: ~ - preview: ~ - escape: TRUE - additional: [] -- actual: - field1: - label: field1 - expected: - field1: - name: field1 - label: field1 - description: ~ - type: ~ - preview: ~ - escape: TRUE - additional: [] -- actual: - - name: field1 - label: Field 1 - expected: - field1: - name: field1 - label: Field 1 - description: ~ - type: ~ - preview: ~ - escape: TRUE - additional: [] diff --git a/tests/src/fixtures/definition/variants_processing.yml b/tests/src/fixtures/definition/variants_processing.yml deleted file mode 100644 index d70600f2..00000000 --- a/tests/src/fixtures/definition/variants_processing.yml +++ /dev/null @@ -1,40 +0,0 @@ -- actual: - - variant1 - - variant2 - expected: - variant1: - name: variant1 - label: variant1 - description: ~ - variant2: - name: variant2 - label: variant2 - description: ~ -- actual: - variant1: Variant 1 - variant2: Variant 2 - expected: - variant1: - name: variant1 - label: Variant 1 - description: ~ - variant2: - name: variant2 - label: Variant 2 - description: ~ -- actual: - variant1: - label: variant1 - expected: - variant1: - name: variant1 - label: variant1 - description: ~ -- actual: - - name: variant1 - label: Variant 1 - expected: - variant1: - name: variant1 - label: Variant 1 - description: ~ diff --git a/tests/src/fixtures/libraries.yml b/tests/src/fixtures/libraries.yml deleted file mode 100644 index 19acdd1e..00000000 --- a/tests/src/fixtures/libraries.yml +++ /dev/null @@ -1,105 +0,0 @@ -- actual: - id: pattern_name - base path: /pattern/base/path - libraries: - - drupal/library_one - - drupal/library_two - - library_one: - css: - component: - library_one.css: {} - library_two.css: {} - library_three.css: {} - theme: - library_one.css: {} - library_two.css: {} - js: - library_one.js: {} - - library_two: - css: - component: - library_one.css: {} - js: - library_two.js: {} - expected: - pattern_name.library_one: - css: - component: - /pattern/base/path/library_one.css: {} - /pattern/base/path/library_two.css: {} - /pattern/base/path/library_three.css: {} - theme: - /pattern/base/path/library_one.css: {} - /pattern/base/path/library_two.css: {} - js: - /pattern/base/path/library_one.js: {} - pattern_name.library_two: - css: - component: - /pattern/base/path/library_one.css: {} - js: - /pattern/base/path/library_two.js: {} -- actual: - id: pattern_name - base path: /pattern/base/path - libraries: - - library_one: - css: - component: - library_one.css: {} - http://example.com/external.min.css: { type: external, minified: true } - library_two.css: {} - theme: - http://example.com/external.min.css: { type: external, minified: true } - js: - library_one.js: {} - http://example.com/external.min.js: { type: external, minified: true } - library_two.js: {} - expected: - pattern_name.library_one: - css: - component: - /pattern/base/path/library_one.css: {} - http://example.com/external.min.css: { type: external, minified: true } - /pattern/base/path/library_two.css: {} - theme: - http://example.com/external.min.css: { type: external, minified: true } - js: - /pattern/base/path/library_one.js: {} - http://example.com/external.min.js: { type: external, minified: true } - /pattern/base/path/library_two.js: {} -- actual: - id: pattern_name - libraries: - - drupal/library_one - - drupal/library_two - expected: [] -- actual: - id: pattern_name - base path: '' - libraries: - - library_one: - css: - component: - library_one.css: {} - js: - library_one.js: {} - - library_two: - css: - component: - library_one.css: {} - js: - library_two.js: {} - expected: - pattern_name.library_one: - css: - component: - library_one.css: {} - js: - library_one.js: {} - pattern_name.library_two: - css: - component: - library_one.css: {} - js: - library_two.js: {} diff --git a/tests/src/fixtures/pattern_element_libraries.yml b/tests/src/fixtures/pattern_element_libraries.yml deleted file mode 100644 index a679757a..00000000 --- a/tests/src/fixtures/pattern_element_libraries.yml +++ /dev/null @@ -1,24 +0,0 @@ -- actual: - id: pattern_name - libraries: - - drupal/library_one - - drupal/library_two - - library_one: - css: - component: - library_one.css: {} - theme: - library_one.css: {} - js: - library_one.js: {} - - library_two: - css: - component: - library_one.css: {} - js: - library_two.js: {} - expected: - - drupal/library_one - - drupal/library_two - - ui_patterns/pattern_name.library_one - - ui_patterns/pattern_name.library_two diff --git a/tests/src/fixtures/preview_markup.yml b/tests/src/fixtures/preview_markup.yml deleted file mode 100644 index 68892d70..00000000 --- a/tests/src/fixtures/preview_markup.yml +++ /dev/null @@ -1,14 +0,0 @@ -- actual: - type: pattern - id: image - fields: - image: - theme: image - uri: http://lorempixel.com/400/200/nature/2 - expected: - '#type': pattern - '#id': image - '#fields': - image: - '#theme': image - '#uri': http://lorempixel.com/400/200/nature/2 diff --git a/tests/src/fixtures/preview_process.yml b/tests/src/fixtures/preview_process.yml deleted file mode 100644 index 675fb040..00000000 --- a/tests/src/fixtures/preview_process.yml +++ /dev/null @@ -1,8 +0,0 @@ -- render: - '#type': pattern_preview - '#id': foo - expected: preview -- render: - '#type': pattern - '#id': foo - expected: empty diff --git a/tests/src/fixtures/validation.yml b/tests/src/fixtures/validation.yml deleted file mode 100644 index 79700e9e..00000000 --- a/tests/src/fixtures/validation.yml +++ /dev/null @@ -1,67 +0,0 @@ -- pattern: - id: 'not valid Name' - messages: - - "id: This value is not valid." - - "label: This value should not be null." - - "base path: This value should not be null." - - "file name: This value should not be null." - - "provider: This value should not be null." - - "theme hook: This value should not be null." -- pattern: - id: 'id' - messages: - - "id: This value is not valid." - - "label: This value should not be null." - - "base path: This value should not be null." - - "file name: This value should not be null." - - "provider: This value should not be null." - - "theme hook: This value should not be null." -- pattern: - id: pattern_id - label: label - description: description - fields: - fass: - name: 'not valid name' - messages: - - "base path: This value should not be null." - - "file name: This value should not be null." - - "provider: This value should not be null." - - "fields.0.name: This value is not valid." - - "fields.0.label: This value should not be null." - - "theme hook: This value should not be null." -- pattern: - id: pattern_id - label: label - description: description - fields: - type: - type: text - label: type - description: description - preview: preview - messages: - - "base path: This value should not be null." - - "file name: This value should not be null." - - "provider: This value should not be null." - - "fields.0.name: This value should not be null." - - "theme hook: This value should not be null." -- pattern: - id: pattern_id - label: label - description: description - base path: /path/to/pattern - file name: pattern_id.ui_patterns.yml - theme hook: pattern_pattern_id - provider: module - fields: - - - name: camelName - label: Label - type: text - description: description - preview: preview - - - name: name - label: Label - messages: [] diff --git a/ui_patterns.info.yml b/ui_patterns.info.yml index abb7f6fe..be62e50f 100644 --- a/ui_patterns.info.yml +++ b/ui_patterns.info.yml @@ -3,3 +3,5 @@ type: module description: 'UI patterns.' core_version_requirement: ^10 package: 'User interface' +dependencies: + - drupal:sdc From c0e1d5964709b84498d864b54a929ebbaa5e997e Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 4 Oct 2023 15:04:33 +0200 Subject: [PATCH 32/81] Init test module --- .../Plugin/UiPatterns/Source/TestSource.php | 34 -------- .../ui_patterns_field_source_test.info.yml | 4 - .../ui_patterns_props_widget_test.info.yml | 4 - .../components/foo-bar.twig | 1 - .../components/foo-bar.ui_patterns.yml | 5 -- .../components/foo.twig | 2 - .../components/foo.ui_patterns.yml | 9 --- .../templates/foo-bar.ui_patterns.yml | 5 -- .../templates/foo.ui_patterns.yml | 9 --- .../templates/pattern-foo-bar.html.twig | 1 - .../templates/pattern-foo.html.twig | 0 .../ui_patterns_render_test.info.yml | 4 - .../my-widget/my-widget.component.yml | 0 .../components/my-widget/my-widget.css | 0 .../components/my-widget/my-widget.twig | 0 .../src/DummyUiPatternsLegacyManager.php | 79 ------------------- tests/src/Kernel/AbstractUiPatternsTest.php | 40 ---------- .../Kernel/UiPatternsExtraFieldSourceTest.php | 51 ------------ .../Kernel/UiPatternsSourceManagerTest.php | 49 ------------ tests/src/Traits/RenderTrait.php | 28 ------- tests/src/Traits/TwigDebugTrait.php | 22 ------ tests/src/Unit/AbstractUiPatternsTest.php | 17 ---- 22 files changed, 364 deletions(-) delete mode 100644 tests/modules/ui_patterns_field_source_test/src/Plugin/UiPatterns/Source/TestSource.php delete mode 100644 tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml delete mode 100644 tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml delete mode 100644 tests/modules/ui_patterns_render_test/components/foo-bar.twig delete mode 100644 tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_render_test/components/foo.twig delete mode 100644 tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_render_test/templates/foo-bar.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml delete mode 100644 tests/modules/ui_patterns_render_test/templates/pattern-foo-bar.html.twig delete mode 100644 tests/modules/ui_patterns_render_test/templates/pattern-foo.html.twig delete mode 100644 tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml rename tests/modules/{ui_patterns_props_widget_test => ui_patterns_test}/components/my-widget/my-widget.component.yml (100%) rename tests/modules/{ui_patterns_props_widget_test => ui_patterns_test}/components/my-widget/my-widget.css (100%) rename tests/modules/{ui_patterns_props_widget_test => ui_patterns_test}/components/my-widget/my-widget.twig (100%) delete mode 100644 tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php delete mode 100644 tests/src/Kernel/AbstractUiPatternsTest.php delete mode 100644 tests/src/Kernel/UiPatternsExtraFieldSourceTest.php delete mode 100644 tests/src/Kernel/UiPatternsSourceManagerTest.php delete mode 100644 tests/src/Traits/RenderTrait.php delete mode 100644 tests/src/Traits/TwigDebugTrait.php delete mode 100644 tests/src/Unit/AbstractUiPatternsTest.php diff --git a/tests/modules/ui_patterns_field_source_test/src/Plugin/UiPatterns/Source/TestSource.php b/tests/modules/ui_patterns_field_source_test/src/Plugin/UiPatterns/Source/TestSource.php deleted file mode 100644 index 004bb9bc..00000000 --- a/tests/modules/ui_patterns_field_source_test/src/Plugin/UiPatterns/Source/TestSource.php +++ /dev/null @@ -1,34 +0,0 @@ -getSourceField('field_1', 'Field 1'), - $this->getSourceField('field_2', 'Field 2'), - $this->getSourceField('field_3', 'Field 3'), - $this->getSourceField('field_4', 'Field 4'), - $this->getSourceField('field_5', 'Field 5'), - ]; - } - -} diff --git a/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml b/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml deleted file mode 100644 index 7e18e664..00000000 --- a/tests/modules/ui_patterns_field_source_test/ui_patterns_field_source_test.info.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: 'UI Patterns Field Source Test' -description: 'Provides test plugin.' -type: module -package: 'Testing' diff --git a/tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml b/tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml deleted file mode 100644 index b4711bec..00000000 --- a/tests/modules/ui_patterns_props_widget_test/ui_patterns_props_widget_test.info.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: 'UI Patterns Props Widget Test' -type: module -description: 'Provides test patterns.' -package: 'Testing' diff --git a/tests/modules/ui_patterns_render_test/components/foo-bar.twig b/tests/modules/ui_patterns_render_test/components/foo-bar.twig deleted file mode 100644 index 76c7ac2d..00000000 --- a/tests/modules/ui_patterns_render_test/components/foo-bar.twig +++ /dev/null @@ -1 +0,0 @@ -Foo Bar diff --git a/tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml b/tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml deleted file mode 100644 index 94addb38..00000000 --- a/tests/modules/ui_patterns_render_test/components/foo-bar.ui_patterns.yml +++ /dev/null @@ -1,5 +0,0 @@ -foo-bar: - label: Foo Bar - variants: - default: - label: Default diff --git a/tests/modules/ui_patterns_render_test/components/foo.twig b/tests/modules/ui_patterns_render_test/components/foo.twig deleted file mode 100644 index ba7ad791..00000000 --- a/tests/modules/ui_patterns_render_test/components/foo.twig +++ /dev/null @@ -1,2 +0,0 @@ -JUHUUUUUUUUUUUUUU -{{ test }} diff --git a/tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml b/tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml deleted file mode 100644 index 0ec96efa..00000000 --- a/tests/modules/ui_patterns_render_test/components/foo.ui_patterns.yml +++ /dev/null @@ -1,9 +0,0 @@ -foo: - label: Foo - fields: - test: - type: test - label: Test - variants: - default: - label: Default diff --git a/tests/modules/ui_patterns_render_test/templates/foo-bar.ui_patterns.yml b/tests/modules/ui_patterns_render_test/templates/foo-bar.ui_patterns.yml deleted file mode 100644 index 94addb38..00000000 --- a/tests/modules/ui_patterns_render_test/templates/foo-bar.ui_patterns.yml +++ /dev/null @@ -1,5 +0,0 @@ -foo-bar: - label: Foo Bar - variants: - default: - label: Default diff --git a/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml b/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml deleted file mode 100644 index 0ec96efa..00000000 --- a/tests/modules/ui_patterns_render_test/templates/foo.ui_patterns.yml +++ /dev/null @@ -1,9 +0,0 @@ -foo: - label: Foo - fields: - test: - type: test - label: Test - variants: - default: - label: Default diff --git a/tests/modules/ui_patterns_render_test/templates/pattern-foo-bar.html.twig b/tests/modules/ui_patterns_render_test/templates/pattern-foo-bar.html.twig deleted file mode 100644 index 76c7ac2d..00000000 --- a/tests/modules/ui_patterns_render_test/templates/pattern-foo-bar.html.twig +++ /dev/null @@ -1 +0,0 @@ -Foo Bar diff --git a/tests/modules/ui_patterns_render_test/templates/pattern-foo.html.twig b/tests/modules/ui_patterns_render_test/templates/pattern-foo.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml b/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml deleted file mode 100644 index 868c9314..00000000 --- a/tests/modules/ui_patterns_render_test/ui_patterns_render_test.info.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: 'UI Patterns Render Test' -type: module -description: 'Provides test patterns.' -package: 'Testing' diff --git a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml b/tests/modules/ui_patterns_test/components/my-widget/my-widget.component.yml similarity index 100% rename from tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.component.yml rename to tests/modules/ui_patterns_test/components/my-widget/my-widget.component.yml diff --git a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.css b/tests/modules/ui_patterns_test/components/my-widget/my-widget.css similarity index 100% rename from tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.css rename to tests/modules/ui_patterns_test/components/my-widget/my-widget.css diff --git a/tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig b/tests/modules/ui_patterns_test/components/my-widget/my-widget.twig similarity index 100% rename from tests/modules/ui_patterns_props_widget_test/components/my-widget/my-widget.twig rename to tests/modules/ui_patterns_test/components/my-widget/my-widget.twig diff --git a/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php b/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php deleted file mode 100644 index 3b794efc..00000000 --- a/tests/modules/ui_patterns_test/src/DummyUiPatternsLegacyManager.php +++ /dev/null @@ -1,79 +0,0 @@ -stringTranslation = $translation; - parent::__construct($namespaces, $cache_backend, $module_handler, $theme_handler); - } - - /** - * {@inheritdoc} - */ - public function getDefinitions(): array { - $definitions = $this->patterns; - foreach ($definitions as $plugin_id => &$definition) { - $this->processDefinition($definition, $plugin_id); - } - return $definitions; - } - - /** - * Getter. - * - * GetPatterns is already a method in the real plugin manager. - * - * @return array - * Property value. - */ - public function getDummyPatterns(): array { - return $this->patterns; - } - - /** - * Setter. - * - * @param array $patterns - * Property value. - * - * @return $this - */ - public function setPatterns(array $patterns) { - $this->patterns = $patterns; - return $this; - } - -} diff --git a/tests/src/Kernel/AbstractUiPatternsTest.php b/tests/src/Kernel/AbstractUiPatternsTest.php deleted file mode 100644 index 24ecfe72..00000000 --- a/tests/src/Kernel/AbstractUiPatternsTest.php +++ /dev/null @@ -1,40 +0,0 @@ -getFixturePath() . '/' . $filepath)); - } - -} diff --git a/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php b/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php deleted file mode 100644 index 9d6141a7..00000000 --- a/tests/src/Kernel/UiPatternsExtraFieldSourceTest.php +++ /dev/null @@ -1,51 +0,0 @@ -installEntitySchema('entity_test'); - } - - /** - * Test getSourceFields. - * - * @covers ::getSourceFields - */ - public function testGetSourceFields() { - /** @var \Drupal\ui_patterns\UiPatternsSourceManager $manager */ - $manager = \Drupal::service('plugin.manager.ui_patterns_source'); - - /** @var \Drupal\ui_patterns\Plugin\UiPatterns\Source\ExtraFieldSource $source */ - $fields = $manager->getFieldsByTag('entity_display', [ - 'entity_type' => 'entity_test', - 'entity_bundle' => 'bundle_with_extra_fields', - ]); - - $this->assertArrayHasKey('extra_fields:display_extra_field', $fields); - $this->assertArrayHasKey('extra_fields:display_extra_field_hidden', $fields); - } - -} diff --git a/tests/src/Kernel/UiPatternsSourceManagerTest.php b/tests/src/Kernel/UiPatternsSourceManagerTest.php deleted file mode 100644 index eb74c319..00000000 --- a/tests/src/Kernel/UiPatternsSourceManagerTest.php +++ /dev/null @@ -1,49 +0,0 @@ -getDefinitions(); - $this->assertNotEmpty($definitions); - $this->assertArrayHasKey('test_source', $definitions); - - $expected = [ - ['name' => 'field_1', 'label' => 'Field 1'], - ['name' => 'field_2', 'label' => 'Field 2'], - ['name' => 'field_3', 'label' => 'Field 3'], - ['name' => 'field_4', 'label' => 'Field 4'], - ['name' => 'field_5', 'label' => 'Field 5'], - ]; - - /** @var \Drupal\ui_patterns\Plugin\PatternSourceBase $plugin */ - $plugin = $plugin_manager->createInstance('test_source'); - foreach ($plugin->getSourceFields() as $key => $field) { - $this->assertEquals($expected[$key]['name'], $field->getFieldName()); - $this->assertEquals($expected[$key]['label'], $field->getFieldLabel()); - } - } - -} diff --git a/tests/src/Traits/RenderTrait.php b/tests/src/Traits/RenderTrait.php deleted file mode 100644 index 1ad8a160..00000000 --- a/tests/src/Traits/RenderTrait.php +++ /dev/null @@ -1,28 +0,0 @@ -container->get('renderer')->renderRoot($elements); - } - -} diff --git a/tests/src/Traits/TwigDebugTrait.php b/tests/src/Traits/TwigDebugTrait.php deleted file mode 100644 index d37416e2..00000000 --- a/tests/src/Traits/TwigDebugTrait.php +++ /dev/null @@ -1,22 +0,0 @@ -container->getParameter('twig.config'); - $parameters['debug'] = TRUE; - $this->setContainerParameter('twig.config', $parameters); - $this->rebuildContainer(); - $this->resetAll(); - } - -} diff --git a/tests/src/Unit/AbstractUiPatternsTest.php b/tests/src/Unit/AbstractUiPatternsTest.php deleted file mode 100644 index 46db7ca2..00000000 --- a/tests/src/Unit/AbstractUiPatternsTest.php +++ /dev/null @@ -1,17 +0,0 @@ - Date: Wed, 4 Oct 2023 20:31:50 +0200 Subject: [PATCH 33/81] Add components in ui_patterns_test --- .../ui_patterns_test/components/README.md | 69 +++++++++ .../components/alert/alert.component.yml | 44 ++++++ .../components/alert/alert.twig | 22 +++ .../blockquote/blockquote.component.yml | 32 +++++ .../components/blockquote/blockquote.twig | 14 ++ .../components/button/button.component.yml | 136 ++++++++++++++++++ .../button/pattern-button.html.twig | 31 ++++ .../components/card/card--horizontal.twig | 20 +++ .../components/card/card.component.yml | 74 ++++++++++ .../components/card/card.twig | 19 +++ .../close_button/close_button.component.yml | 28 ++++ .../components/close_button/close_button.twig | 11 ++ .../components/figure/figure.component.yml | 31 ++++ .../components/figure/figure.twig | 6 + .../my-widget/my-widget.component.yml | 16 +-- .../progress/path/to/template/progress.twig | 31 ++++ .../progress/progress.component.yml | 49 +++++++ 17 files changed, 622 insertions(+), 11 deletions(-) create mode 100644 tests/modules/ui_patterns_test/components/README.md create mode 100644 tests/modules/ui_patterns_test/components/alert/alert.component.yml create mode 100644 tests/modules/ui_patterns_test/components/alert/alert.twig create mode 100644 tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml create mode 100644 tests/modules/ui_patterns_test/components/blockquote/blockquote.twig create mode 100644 tests/modules/ui_patterns_test/components/button/button.component.yml create mode 100644 tests/modules/ui_patterns_test/components/button/pattern-button.html.twig create mode 100644 tests/modules/ui_patterns_test/components/card/card--horizontal.twig create mode 100644 tests/modules/ui_patterns_test/components/card/card.component.yml create mode 100644 tests/modules/ui_patterns_test/components/card/card.twig create mode 100644 tests/modules/ui_patterns_test/components/close_button/close_button.component.yml create mode 100644 tests/modules/ui_patterns_test/components/close_button/close_button.twig create mode 100644 tests/modules/ui_patterns_test/components/figure/figure.component.yml create mode 100644 tests/modules/ui_patterns_test/components/figure/figure.twig create mode 100644 tests/modules/ui_patterns_test/components/progress/path/to/template/progress.twig create mode 100644 tests/modules/ui_patterns_test/components/progress/progress.component.yml diff --git a/tests/modules/ui_patterns_test/components/README.md b/tests/modules/ui_patterns_test/components/README.md new file mode 100644 index 00000000..88affd47 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/README.md @@ -0,0 +1,69 @@ +# alert + +A medium complexity component for usual testing, using component() Twig function. + +❌ Not a valid SDC component because of the component() Twig function. + +# blockquote + +A simple component for basic testing. +No props. No variants. + +✅ Valid SDC component. The additions can be ignored. + +I was forced to write this anyway, because the component come from a module: + +``` +props: + type: object + properties: {} +``` + +# button + +A lot of variants. Props (with implicit typing) & slots. +With explicit template path (local, without namespace, the filename from UI Patterns 1.x) + +❌ Not a valid SDC component, because of the explicit template path. + +SDC do a fatal error: + +> Drupal\sdc\Exception\InvalidComponentException: Unable to find the Twig template for the component "ui_patterns_test:button". + +# card + +A complex component with a template by variant. A story calling another component ("button"). + +Props with implicit typing and default values. + +✅ Valid SDC component. The additions can be ignored. + +# close_button + +Here in order to test the component() Twig function in `alert`. + +Only props, no slots. + +There was initially an issue about the use of "\_" and "-" in component ID and template filename. See issue: [SDC should use dashes in file names](https://www.drupal.org/project/drupal/issues/3379527), but we renamed the template. + +✅ Valid SDC component. The additions can be ignored. + +# figure + +1 prop (with explicit typing) and slots. No variants. + +❓ Not sure if the explicit typing will make this component a valid SDC component. + +# my-widget + +A more "traditional" SDC example with JSON schema examples instead of stories, and Twig blocks for slots. + +2 variants. + +✅ Valid SDC component. The additions can be ignored. + +# progress + +With explicit template path (in a subfolder, without namespace, expected filename) + +❌ Not a valid SDC component, because of explicit template path. diff --git a/tests/modules/ui_patterns_test/components/alert/alert.component.yml b/tests/modules/ui_patterns_test/components/alert/alert.component.yml new file mode 100644 index 00000000..99539cde --- /dev/null +++ b/tests/modules/ui_patterns_test/components/alert/alert.component.yml @@ -0,0 +1,44 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Alert" +description: "Provide contextual feedback messages for typical user actions with the handful of available and flexible alert messages." +links: + - "https://getbootstrap.com/docs/5.3/components/alerts/" +variants: + primary: + label: "Primary" + secondary: + label: "Secondary" + success: + label: "Success" + danger: + label: "Danger" + warning: + label: "Warning" + info: + label: "Info" + light: + label: "Light" + dark: + label: "Dark" +props: + type: object + properties: + dismissible: + title: "Dismissible?" + description: "It is possible to dismiss any alert inline." + type: "boolean" +slots: + heading: + title: "Heading" + description: "The alert heading. Optional." + message: + title: "Message" + description: "The alert message." +stories: + preview: + title: "The default preview from UI Patterns 1.x" + props: + dismissible: True + slots: + heading: "Well done!" + message: "A simple alert. Check it out!" diff --git a/tests/modules/ui_patterns_test/components/alert/alert.twig b/tests/modules/ui_patterns_test/components/alert/alert.twig new file mode 100644 index 00000000..1116bf99 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/alert/alert.twig @@ -0,0 +1,22 @@ +{% if variant and variant|lower != 'default' %} + {% set attributes = attributes.addClass('alert-' ~ variant|lower|replace({'_': '-'})) %} +{% endif %} + +{% if dismissible %} + {% set attributes = attributes.addClass(['alert-dismissible', 'fade', 'show']) %} +{% endif %} + + + {% if heading %} +

    {{ heading }}

    + {% endif %} + {{ message|add_class('alert-link') }} + {% if dismissible %} + {{ component('ui_patterns:close_button', {}, { + attributes: create_attribute({ + 'data-bs-dismiss': 'alert' + }), + aria_label: 'Close'|t, + }) }} + {% endif %} +
    diff --git a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml new file mode 100644 index 00000000..0333cf87 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml @@ -0,0 +1,32 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: Blockquote +description: "For quoting blocks of content from another source within your document." +links: + - "https://getbootstrap.com/docs/5.3/content/typography/#blockquotes" +category: "Typography" +props: + type: object + properties: {} +slots: + content: + title: "Content" + description: "The quote." + footer: + title: "Footer" + description: "For identifying the source. Wrap the name of the source work in ." +stories: + preview: + label: "The default preview from UI Patterns 1.x" + slots: + content: + type: "html_tag" + tag: "p" + value: "A well-known quote, contained in a blockquote element." + footer: + - type: "markup" + markup: "Someone famous in " + - type: "html_tag" + tag: "cite" + value: "Source Title" + attributes: + title: "Source Title" diff --git a/tests/modules/ui_patterns_test/components/blockquote/blockquote.twig b/tests/modules/ui_patterns_test/components/blockquote/blockquote.twig new file mode 100644 index 00000000..5721b60a --- /dev/null +++ b/tests/modules/ui_patterns_test/components/blockquote/blockquote.twig @@ -0,0 +1,14 @@ +{% if footer %} + +
    + {{ content }} +
    + + +{% else %} + + {{ content }} + +{% endif %} diff --git a/tests/modules/ui_patterns_test/components/button/button.component.yml b/tests/modules/ui_patterns_test/components/button/button.component.yml new file mode 100644 index 00000000..eae42ab0 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/button/button.component.yml @@ -0,0 +1,136 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Button" +description: "For actions in forms, dialogs, and more with support for multiple sizes, states, and more." +links: + - "https://getbootstrap.com/docs/5.3/components/buttons/" +category: "Button" +template: pattern-button.html.twig +variants: + default: + label: "Default" + description: "No 'btn' class added." + primary__sm: + label: "Primary small" + secondary__sm: + label: "Secondary small" + success__sm: + label: "Success small" + danger__sm: + label: "Danger small" + warning__sm: + label: "Warning small" + info__sm: + label: "Info small" + light__sm: + label: "Light small" + dark__sm: + label: "Dark small" + link__sm: + label: "Link small" + primary: + label: "Primary" + secondary: + label: "Secondary" + success: + label: "Success" + danger: + label: "Danger" + warning: + label: "Warning" + info: + label: "Info" + light: + label: "Light" + dark: + label: "Dark" + link: + label: "Link" + primary__lg: + label: "Primary large" + secondary__lg: + label: "Secondary large" + success__lg: + label: "Success large" + danger__lg: + label: "Danger large" + warning__lg: + label: "Warning large" + info__lg: + label: "Info large" + light__lg: + label: "Light large" + dark__lg: + label: "Dark large" + link__lg: + label: "Link large" + outline_primary__sm: + label: "Outline Primary small" + outline_secondary__sm: + label: "Outline Secondary small" + outline_success__sm: + label: "Outline Success small" + outline_danger__sm: + label: "Outline Danger small" + outline_warning__sm: + label: "Outline Warning small" + outline_info__sm: + label: "Outline Info small" + outline_light__sm: + label: "Outline Light small" + outline_dark__sm: + label: "Outline Dark small" + outline_primary: + label: "Outline Primary" + outline_secondary: + label: "Outline Secondary" + outline_success: + label: "Outline Success" + outline_danger: + label: "Outline Danger" + outline_warning: + label: "Outline Warning" + outline_info: + label: "Outline Info" + outline_light: + label: "Outline Light" + outline_dark: + label: "Outline Dark" + outline_primary__lg: + label: "Outline Primary large" + outline_secondary__lg: + label: "Outline Secondary large" + outline_success__lg: + label: "Outline Success large" + outline_danger__lg: + label: "Outline Danger large" + outline_warning__lg: + label: "Outline Warning large" + outline_info__lg: + label: "Outline Info large" + outline_light__lg: + label: "Outline Light large" + outline_dark__lg: + label: "Outline Dark large" +props: + type: object + properties: + disabled: + title: "Disabled?" + description: "Is the button disabled?" + type: boolean + url: + title: "URL" + type: string + format: uri-reference +slots: + label: + title: "Label" + description: "The button label." +stories: + preview: + title: "The default preview from UI Patterns 1.x" + props: + disabled: false + url: "https://example.com" + slots: + label: "Submit" diff --git a/tests/modules/ui_patterns_test/components/button/pattern-button.html.twig b/tests/modules/ui_patterns_test/components/button/pattern-button.html.twig new file mode 100644 index 00000000..5fc3ddb9 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/button/pattern-button.html.twig @@ -0,0 +1,31 @@ +{% if variant and variant|lower != 'default' and variant|lower != 'dropdown_item' %} + {% set variants = variant|split('__')|map(v => v|lower|replace({(v): 'btn-' ~ v})|replace({'_': '-'})) %} + {% set attributes = attributes.addClass(variants) %} + {% set attributes = attributes.addClass('btn') %} +{% endif %} + +{% set attributes = (variant|lower == 'dropdown_item') ? attributes.addClass('dropdown-item') : attributes %} + +{% if label_visually_hidden %} + {% set label %} + + {{ label }} + + {% endset %} +{% endif %} + +{% if url or attributes.href %} + {% set url = url|default(attributes.href) %} + {% set attributes = attributes.setAttribute('href', url) %} + {% if disabled %} + {% set attributes = attributes.setAttribute('href', false).setAttribute('tabindex', '-1').setAttribute('aria-disabled', 'true').addClass('disabled') %} + {% endif %} + + {{ label }} +{% else %} + {% if disabled %} + {% set attributes = attributes.setAttribute('disabled', '') %} + {% endif %} + + {{ label }} +{% endif %} diff --git a/tests/modules/ui_patterns_test/components/card/card--horizontal.twig b/tests/modules/ui_patterns_test/components/card/card--horizontal.twig new file mode 100644 index 00000000..9e579b35 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/card/card--horizontal.twig @@ -0,0 +1,20 @@ + +
    +
    + {{ image|add_class('img-fluid rounded-start') }} +
    +
    + {% if header %} +
    + {{ header }} +
    + {% endif %} + {{ content }} + {% if footer %} + + {% endif %} +
    +
    +
    diff --git a/tests/modules/ui_patterns_test/components/card/card.component.yml b/tests/modules/ui_patterns_test/components/card/card.component.yml new file mode 100644 index 00000000..912360f4 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/card/card.component.yml @@ -0,0 +1,74 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Card" +description: "A card is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options." +links: + - "https://getbootstrap.com/docs/5.3/components/card/" +category: "Card" +variants: + default: + label: "Default" + horizontal: + label: "Horizontal" +props: + type: object + properties: + image_position: + title: "Image position" + description: "Only for default variant." + type: "string" + enum: + - top + - bottom + image_col_classes: + title: "Image column classes" + description: "Only for horizontal variant." + type: "string" + default: "col-md-4" + content_col_classes: + title: "Content column classes" + description: "Only for horizontal variant." + type: "string" + default: "col-md-8" +slots: + image: + title: "Image" + header: + title: "Header" + content: + title: "Content" + footer: + title: "Footer" +stories: + preview: + title: "The default preview from UI Patterns 1.x" + props: + image_position: "top" + image_col_classes: "col-md-4" + content_col_classes: "col-md-8" + slots: + image: + theme: "image" + uri: "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZm9udC1zaXplOiAxLjEyNXJlbTsgdGV4dC1hbmNob3I6IG1pZGRsZTsgdXNlci1zZWxlY3Q6IG5vbmU7IiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIyNTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgc2xpY2UiIGZvY3VzYWJsZT0iZmFsc2UiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iUGxhY2Vob2xkZXI6IEltYWdlIGNhcCI+PHRpdGxlPlBsYWNlaG9sZGVyPC90aXRsZT48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjODY4ZTk2Ij48L3JlY3Q+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiNkZWUyZTYiIGR5PSIuM2VtIj5JbWFnZSBjYXA8L3RleHQ+PC9zdmc+Cg==" + alt: "© 2017 John Smith photography" + header: "Featured" + content: + - type: "component" + component: "ui_patterns_test:button" + props: + variant: "primary" + slots: + label: "Go somewhere" + - type: "html_tag" + tag: "a" + value: "Card link" + attributes: + href: "#" + - type: "html_tag" + tag: "a" + value: "Another link" + attributes: + href: "#" + footer: + type: "html_tag" + tag: "span" + value: "2 days ago" diff --git a/tests/modules/ui_patterns_test/components/card/card.twig b/tests/modules/ui_patterns_test/components/card/card.twig new file mode 100644 index 00000000..e7e33a55 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/card/card.twig @@ -0,0 +1,19 @@ + + {% if image and image_position != 'bottom' %} + {{ image|add_class('card-img-top') }} + {% endif %} + {% if header %} +
    + {{ header }} +
    + {% endif %} + {{ content }} + {% if footer %} + + {% endif %} + {% if image and image_position == 'bottom' %} + {{ image|add_class('card-img-bottom') }} + {% endif %} +
    diff --git a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml new file mode 100644 index 00000000..c69a1803 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml @@ -0,0 +1,28 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Close button" +description: "A generic close button for dismissing content like modals and alerts." +links: + - "https://getbootstrap.com/docs/5.3/components/close-button" +category: "Button" +variants: + default: + label: "Default" + white: + label: "White (deprecated)" +props: + type: object + properties: + disabled: + title: "Disabled?" + description: "Is the button disabled?" + type: "boolean" + aria_label: + title: "Aria label" + description: "Name of the close button for assistive technology." + type: "string" +stories: + preview: + title: "The default preview from UI Patterns 1.x" + props: + disabled: false + aria_label: "Close" diff --git a/tests/modules/ui_patterns_test/components/close_button/close_button.twig b/tests/modules/ui_patterns_test/components/close_button/close_button.twig new file mode 100644 index 00000000..d1350276 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/close_button/close_button.twig @@ -0,0 +1,11 @@ +{% if variant and variant|lower != 'default' %} + {% set attributes = attributes.addClass('btn-close-' ~ variant) %} +{% endif %} +{% set attributes = attributes.addClass('btn-close') %} + +{% set attributes = attributes.setAttribute('aria-label', aria_label|default('Close'|t)) %} +{% if disabled %} + {% set attributes = attributes.setAttribute('disabled', disabled) %} +{% endif %} + + diff --git a/tests/modules/ui_patterns_test/components/figure/figure.component.yml b/tests/modules/ui_patterns_test/components/figure/figure.component.yml new file mode 100644 index 00000000..2121c71f --- /dev/null +++ b/tests/modules/ui_patterns_test/components/figure/figure.component.yml @@ -0,0 +1,31 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Figure" +description: "Used to display a piece of self-contained content (illustrations, diagrams, photos, code, etc) along with an optional caption. This content can be removed from the document without affecting the meaning of the document." +links: + - "https://getbootstrap.com/docs/5.3/content/figures/" +props: + type: object + properties: + figcaption_attributes: + title: "Figcaption attributes" + description: "The attributes to customize the figcaption tag." + type: "module:/ui_patterns/types.json#$defs/attributes" +slots: + image: + title: "Image" + description: "The content of the figure." + caption: + title: "Caption" + description: "The caption that appears under the content." +stories: + preview: + title: "The default preview from UI Patterns 1.x" + props: + figcaption_attributes: + class: ["text-end"] + slots: + image: + theme: "image" + uri: "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZm9udC1zaXplOiAxLjEyNXJlbTsgdGV4dC1hbmNob3I6IG1pZGRsZTsgdXNlci1zZWxlY3Q6IG5vbmU7IiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIyNTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgc2xpY2UiIGZvY3VzYWJsZT0iZmFsc2UiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iUGxhY2Vob2xkZXI6IEltYWdlIGNhcCI+PHRpdGxlPlBsYWNlaG9sZGVyPC90aXRsZT48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjODY4ZTk2Ij48L3JlY3Q+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiNkZWUyZTYiIGR5PSIuM2VtIj5JbWFnZSBjYXA8L3RleHQ+PC9zdmc+Cg==" + alt: "© 2017 John Smith photography" + caption: "A caption for the above image." diff --git a/tests/modules/ui_patterns_test/components/figure/figure.twig b/tests/modules/ui_patterns_test/components/figure/figure.twig new file mode 100644 index 00000000..21dd87df --- /dev/null +++ b/tests/modules/ui_patterns_test/components/figure/figure.twig @@ -0,0 +1,6 @@ + + {{ image|add_class('figure-img') }} + + {{ caption }} + + diff --git a/tests/modules/ui_patterns_test/components/my-widget/my-widget.component.yml b/tests/modules/ui_patterns_test/components/my-widget/my-widget.component.yml index 4a3b23c9..4fe76844 100644 --- a/tests/modules/ui_patterns_test/components/my-widget/my-widget.component.yml +++ b/tests/modules/ui_patterns_test/components/my-widget/my-widget.component.yml @@ -9,36 +9,31 @@ variants: label: Default other: label: other - props: type: object properties: heading: title: Heading - default: 'Asdf' description: The title for the banner text. - widget: - type: textfield + type: string + default: "Asdf" examples: - Join us at The Conference - type: string ctaText: title: CTA Text type: string - widget: - type: textfield examples: - Click me! ctaHref: title: CTA Href type: string examples: - - 'https://www.example.org' + - "https://www.example.org" ctaTarget: title: CTA Target type: string enum: - - '' + - "" - _blank image: title: Media Image @@ -47,7 +42,6 @@ props: slots: widget_body: title: Body - description: The contents of the banner. + description: The content of the banner. examples: -

    Foo is NOT bar.

    - diff --git a/tests/modules/ui_patterns_test/components/progress/path/to/template/progress.twig b/tests/modules/ui_patterns_test/components/progress/path/to/template/progress.twig new file mode 100644 index 00000000..b7bc09f6 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/progress/path/to/template/progress.twig @@ -0,0 +1,31 @@ +{% if variant and variant|lower != 'default' %} + {% set variants = variant|split('__')|map(v => v|lower|replace({(v): 'progress-bar-' ~ v})|replace({'_': '-'})) %} + {% set attributes = attributes.addClass(variants) %} +{% endif %} + +{% set wrapper_attributes = create_attribute() %} +{# Handle wrapper ID. #} +{% if attributes.hasAttribute('id') %} + {% set wrapper_attributes = wrapper_attributes.setAttribute('id', attributes.offsetGet('id')) %} + {% set attributes = attributes.removeAttribute('id') %} +{% endif %} + +{% set wrapper_attributes = bar_height ? wrapper_attributes.setAttribute('style', 'height:' ~ bar_height ~ 'px') : wrapper_attributes %} +{% set percent = percent|default(0) %} +{% set min = min|default(0) %} +{% set max = max|default(100) %} +{% set width = (percent * 100) // max %} + + + + {{ label }} +

    @`@&pedDPiL#JIL^5Cv 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 z zB{T_Tj-!>$@iJLHEp~3;$s*-g?huhxOk)Vwnl+Cuxv(lvV!M27vEZ`WK$f(0){K8& zUb-{dkSr>M1M0DiVtrJXJV8L3;H#bxQ$Z3flC4BA9oZt3f=tzp;VZ>yRyfJI)*eUg zJaGnv@rNIF$7g3ZH=$WEyj095o>y@OVt%+>CZAqTWGX3o9*q_cqiK?4V#(+3t?}Zr-QX);3D*sReLTJ|$LaJNl%pk|oe3YP4tYEN!R#u0F0@g=) z$rF{V`CyR9nhpjdkqBX>KvmL}N_r=bnSHdS6tGl*>mg5WAd+CT;zSmqH5t24?^kMT ztF>ZrZKjNhm+OBit4&6ul|oFVz*$$0qLj-8#o`Gy7P2Q@`eHNSRdmhPk5pq!uN>;5tRsHpWcdW z1t;?jpOvnTt74^!(!Jq5z*4gAp})gqZIX?T(cAge;ibegNKBD7xs zS%p6mRpj4^p2Np~c<|ux5SXo?FDRRziK?G>`Mc`J$D~(hCnv8GW)1_2pQF;mmC!Mv zD4uwQEywvgKEy$m#U!elF*D-tS)eSDFT%_A*~Wv30ke!hW3cf0qeq-KFjmV=!TTK` zHH9(Zl(Y&(v9cElNa0xi=X@;pBXZ0iZZ-3W@2$=AvGgq3cjjZYn!#(U>;3VW z7LR-3c>RC0yzjBD7?UOD9r@@G-$<~gNw&5kP%M(HpkhUGl`4u*F;g&GDa>q0a;OEa zc9F#I#y5RT7NHfJ{M$^al%qOGkL~T;i$bMZ$@K7gH5DvoTgY}XN93~(4`&Fh9QOrf z>*5Y!m%k36&d&*~<5wpq$FE-1KGFFI+%t9>(qs_p03*bY*J~TJV7eRx4)hxD9WyR$ zimaF|O1iFS5&Rw~u!m|+@y+Ak;(S;WOQhYmp_0#nZlX60Zj5akHbCoKuSE`vQh>o|->o9P9U_88So8ci3 z7k!$?i(&UFbV?m>Auo*yZ5a_VS${UOu~@OD#>PzAFu+_jkz@X_E+=xu!>{75i-Pb|e&8z})6(&nPw_YPyh4wjkmhM_>0EJJI`1wD z@+;oCEuF6tLlZ+IBSS+IgcPHNJv>$rcT%pD1f^u!!=hIyFtux{A-8wt<~WVt&%|kH zxDlV6oFuRwACg?ST!ibQ&g;_Uw_ee{JDQN;rek1yUJ=)Uo z@Og#!sV{R&=hy#Yu!dZ&RJ4NgI7|~)5iN&dC>HHvX(20hpB_cFSpe%kk%ewf5@A++ zlF*86+$MXT%bk|XGa&2f@b8yaYnW-=-iZa*s1~9Wc9CMyc366I8VEGE2K{F%XZ17C zRIe*lCnqrPoJ%oSqD-VfUX~)q6)SOHGGvsIAw4vZ=TcU%=vdH0A9ESg`H=T}P)h7G$0bSM~bq{MX1aH<=Z7?}n*1riG6U zYNj^YY}kF4lGb72eY`73F;vYO=Ch?O=muEa4AP+?9fm^PNJqPgt3`mN4QL$}q8g$S za;I?M!eCumkM(H+dNq^~uqK8Qs$9fXJ~$5(A4{j>lk_JN;}|Qf4n7{Qj;HQ}ELbe& zYL_L8ygPw~?JqyyR-aWW&2VZ(h}Ce$L{Bu@Ijs3-0IeM&D<@c?0@AHNKwv3Q3Rb;d zKV90qhV%P)z5=$85h|jI(b7X^ivtNq$+JwL+TpX?*k&;{>gk)yqtCY85LL*cH`|QO zBCNVm453##<9*asEaFN8OBCxa?%9iYtjnudpC(3V8=9Dy2-0JE+9eAXizF+;ij^P# zH+g69+SH!KahLBexZH4I3V|+$ffxwaMP?B$FH{B*-oo1w5);%`+LG$jl*C(ui?oBj zLK#PRE~-RYgVKi-94Llnk&U}q&C|dv9#g`|Oe^i%jPBq0o^yWp-l_uwzPBj-rLAq6 z_C4{#_k7Ryoa6#9kkw#oMS4|zh51curHRvOwpuNo^E_U;wRvlEbG^OZ{;u8LpSiZN z%~I7zn((2s-TTYm`AQ{UKO(&nc_Ol;6=bZKD>|jM6gw`Q?^6y$XoY>8wwjotU^cbN<@19#RL6sRrBqsW-Tu#G4*=7eJ7xmQtk$aQPV@ zWMM=ofW7_q?q15@*g8ELHi6=(G@Q~J2$^wg&$i3V7NjT_wvI_@DU(80Qg^b_f>)Ul zQ@uLs>Iqf-ngpvL%MG;}?>De!g%PP#LU!*2)%^Syg7s@Yukcj#yuwo-J+JW8|MR@U zPlnK6#fQ-QbF5f&2rwcn2!yLi?hSG3bsp&8CTgV?t16rBtyWXGYLK6YY^}W7+}cXQ z<(9n(oOb(KU-EaXGtAFF!V6f>uttF)i9(Npm0_a=vf!-LYrWmwJ*1yml{}PlPSzp` z5OShmM#~^yHt_O9xlk60aR_L3*BcUY)cu6Ei47>qlCg&L?}k!Ce2 zjY-1jVHGbjqo;~&i#!&JVJKK2>oLe0S%+%b-V%vcx7)tb8FCid8Bl+S7k|ChLjh!l z_715UW->>pGDXop#8ShdjG(C!VvBTYYcy$mlNcygCem7)n+QnNW1$p&Syy;$7ySqo{KWsCli=ml}4htEqk_>VbuFOZaiyl5?u%`ZYdPMs{M;(@{PRbzW zggaSbVN5&xy)w6rIVqg{(6lVm#29kS$!R4tMvFDe#$T&eQ^Cqhh-I*vH9g@h$5?U2 zI$@VQ`GLXWuJA6s1;N}!a{?wS2qLT+e25?@0E$3$zf==g>8Tp9NVM?v*YpLGmB@Nt zB0si7t>KgD9%^B4u?Mi$c+iXtE8`Cq7iG19)6tZ{%IBNu5CK*RWce>JEKG*UCn#}k zA%VTry=)5qz!x%1;#5;(sY|TuiB~(AgKS0M)tZLIWNkg=~IoY-y?eV7gE4U6c5M5L8;r>}@YD11wgsm{9rpo{^;od7?cg)ZZp05>>@w z{c$bJ6|M|fo*VkAq<~?>jVmIt$Y6OcII*L2G#=jDYQl&!ZY#@a#_A5zhFX@4I~7B! zhSLjT+l^w4<2J5oL(~1zE9zK!Pl>?^PnJ!dOAKj@b6_yliT7 z*iy2*L;{*cWK9&tOa14FN<01KpSbLiXZH`@4F;-MVT6@2iiNk7d73>zH7xo4CnLJ2 zpe~6qaHGRq5owl3u*s~39Ttc1)Z4jw4{H|4vVay-q!o~}2>vx?^sR}s@6hsSPKB_npVP4v0^#a3A^OU5BqaPaqrw4vcoEpR0VaZg{CuPp~4^N{qPaq zDnzR(E}nNBq8_n&YZC$dgjdVv39Yf(^?nDhHPiV|e|dQg9-Z9PhxhM0gWiD6do089 zBiYhYsC!!E-A5cdH=<-o1LqhJm@EDqT(OL47{L<7pSgBquvD}xRjsmy7HRW9m*Wa9 zm}o`t(JCb1-LnMNSZ!<-s|}YH2&|h6lDdmt(cApE#8_Y26&}x5^y86EQA`%bg++`D zk&~i3QzQjN2Jhbc)D)J&F?1?EHuPg~ack&rJiv1Z0ZT-d9cu=o zxJsN?>_}GX*=o2HFej8Gwq*Kijx+(RYo5d>_zd}Imi6k=G?T?#Er6>tvzO0|UuJ_P zg7t*~>9M`!34?XjL=6ZXx@KM&k_G3^ZDGS4=IVxk_1QzxrDWYkIrPvQ=Cfm$&Ruxk z8S+cbbk_V7eI&4c^22TS)Dg`RsQhTo1L691RG4us1;~-6&}3O=zQaKmdwP7&f_^C+ zLKerQc2wsq4nNB*PX<7jVUv#1i=P6}T3Qmpx@6E|!8$WLdwEv4lDHvMtdn)g69Mb0 z8AKPmSGz?8>nXq2r4kZZRd!lx+;9Bv!>{YWWs*uGkwqS!PI+nJ+`{h{re~jb2(2}< zFSE9G@a|wQlS)PF?z-#uQBEpg`TaW~SnQx2$vznzh06^`hmLI|i~jKBX2DSD4`S_s zeZJo7X_`Qf6GBDau+_z6$)oPL;Hp4AYiW!GOV>x)ymM3djfSJ2DAtL(H4ahexEQhKji%Z#ku9j!#5XfDQ;WSm>s2Yl#ZMjl>tN51 z@6E`1R!K3-$UsrM#o_9R?C}|}T-P;SAdzLckwNUu7h`|VWfRzMQCW6$?+ARa#-Pj&hX>QU%1MaF}?FPS2)Nl67Ul?NixgvXDFYRT>AJ z4}SS)3><3QJIZr5o$c+8+Y|Pu2RUR9D_CA@ZKT0#Muy!{)+?@k5oF0k?N|#@oeXPS z{w!aeBu1wzt&4P?GSu$V5w&3C<>?iw8oZB~ID;L%1zb(pKo!juB3IKCHpm2YanuKk z!-o1~{baT#KNx#WvP$Z*FeD}aPGF_e%86e93ycpyHH#XR&F1MrL&yT2fln}t$XzNu zIPiyG{kr~qXQ$nMrcssKX*PE~H|W%ZL&NY=Yb|r%I;)813E(7bQ&LM?o)nSp1dd%Rk0Ei*Ktm2e@ccjJ&g0WgkjycP+VG4K(U zEGEmgV}zZ=R58&j`pJGD^$Mz_YMhW|#bTC*6!ecI!DL0_Q~V&Ppb`pMGM_W*$L>MJ zI$2wupn2C6ON48kJKqO|ETBpRD;3bIYaAv}uX{mfrfE&TnHgL%64A=?6eEA9dYK+P z=rHA6Yph zvQINv>A~&J;Vy!8c(_BW6op-Px7loVTsQ0n8XM@f7j~bHR^G1OUZOQ4!){|rr)DBj zumTwH2$08yY`i$YVhuv5h%Ei3e(<904=44H;&m~NRN)nY#Z!lH66XJ<;c3{ zOq1W}(G&|5E7fspr_B3l2qM^RsAOGE0Byb9g1Ql^*z{)CF z`5E$OSt1L+#+qO5?jG(v+uK91s42^BA2fdlWc7Hj=j^UKId}g2yKmo@n9EI^`G9mN z(93{O5H7?ID3T4YY%6dTix1RHG)IIU2~-SAontd>iWYw$Mt_mVjY22UJVz#<$%11l z&i$h~o`PnJbs;f&DL~f3FraEl)n?2^}egyzUJ3Czq&gM%Bt_l|c(cs}OWmLXl4s>K7pPBU~|Sb#)yV3Mma7 zboTe#*xWz|-yI$HUT)sI_xX0)l}4;^Ph;f%^{vN`fB50K^Q(7US$<}Orh_B4;ej}^ zRe9j2<2c4MvZVl-Y8Z>iFN|ybME0g=Y9{BknZFO z*D`d&&1qxB9pMO7%fU17md@G6SIIkJ(XtGMT-+>GIu5NO_@wKTCi{v=re!OIQS$CE zB#dXKhjY`SSBMtr)hG&9;)*(~Hysrod$0x!SU6KCl@Me15<2&^Pu8E<#NS}96dO2x zpm>SZBBAo73y(*ut6K-1y|w`hOJYsPY98$NXq<(Tb$vcFpM3bv+0SZp7A_p;5S;#( z9iJ?kfCeAVRE6-H!zyDrj+_OW73OioM~#d=G@dh0*jr0HLJEWV1aq!)N`mwK$OuA?5SE9kMXje_ zNyog#Mk90DPuLw&8OPC4o$%vC9Y@KsNW&Z^&StnAU&+sddm|A z%Ws^)%%Q}0Q{bR*P@_$=J_z3A1+E<*`K0+;1wd6RxJpQ+`e=Rqv!5YZ zPgJvLRpo|Pf8M71D|)13rnPn{Gk&3Wyp9uzZf#;CG1twJg2q-BbAm1bP&phJGnFun6NM`!yqQ9%Dg?T+ve`oF#w>~!rw#u2Pb*>BS2ETsAS(u7;cjfp zx5p=Hh}J6_AL4qF^+q_1!In-GdS2`4LUDYwzv0o$&PM-4rkf0w6gn(*M_`hlPzrw{ z=F4ank!BmN)N(OcQI)Eg1)SREpb;e1OdV7v11<{>wC>a6mvbCq6@N^NkHWK$tUGfB z52INBYg?XleUprR^ZS2e%ahANmQ%I%{pRv43}vw2z#S+r`gpvBOKYR1P4 zH$P#ys;=Ix(qm|7Xbu#sf-B)I-yXsB-=Dk!ws<|iMgyDRJ^!|eR&8xEab*I*@@h}& z{R?sI3O#KJG!ZU^A;BqxV1+`)WRXfmEY`ClhpiS~9eGrPEISnCv@6PIC7iHi_DG&M z1x@yS7+n^Y!Cdh@aPHXeegVE1s|0}6&x$qK`Zimhbba?*o_xnsIv`doS62r#cqi2= z(b^5jRi35`;eqS7HQ?6)alT z2v_&(2|uG!*jNK&)$qdYo%Y^NXJjGndvW2!R#J#8<|?Gp<}R_^`c_@(CGzW-MyD?uJBA*B!xHSZ^E%M&Vv z1ePI-ZdQHq>0ds20eV&99#Y&BAV0&_{U79=PiRzG7RKFWgn=PLCvRlrP3T(Hrs;=T z)-4EKfj7yfnVLm9P*fW!h%Ri!goKz7E6QwYGSJk-fk3sR5TgZw;Mg!ip$k{N5F!lr zYP2B)#TK1lGp@1_=6v5d_q~eBI3Tl-+*g0zd-W<=)Q|6c_q==WIcXA=4r91*!DFR- zkt#NlHGmzV3S2D-P)jS)Xtfq@3R+)Yy_4qQxfIqXTU(o(ckZl>k6%6T6eA%v9z7a~ zdd;0{gzJ9Hs9(@hwZKUFZ}UW;qeC#=?> zSTj?nfA`>{_4VI^tP$MO(OiRY1+;D^=6!dSJn_9k18epaSBq4xbO8qutOTvGGv?Xn zl7TTwmcE?79THMT@tR4 z&uX zkSi*dXcl{gi#IoSXUP*?9xK%=35(Yx4&FFgVITxqv)U<~p1#O+Zf)M`Q4NdJ^z}Nf zucetQA&T?rVX^dHPQA{B50=mbEaHSv_^K}Z4IJWiVO!e9K(FOi*H%dqXZ>>9;dTvwB&3o zuVGM#T^tXRD-|s9?y}{+ZH}B@|L*5s9cwa?HPJ*Uq<6pheqepD9_yUtM=Lf7jCZ47 zz+wHZGV3SlXtH722GNkJqkq!*UYzNr!Ek=gZZ?ywWkn0`&?^*ksEcGF7=o(0J`E|p zt*kWuD!ZU-`?2ALCA6+Se-5rT9yX*)Mh~7f6Ha+@F7%=$6oovMRn@|=nJzZ$%4faG zAUlYJD^L@~2Cib@m1VEc^9Hd}#Tq17eJWV}%#KRGaOSS~_wzqArBhfeHj8xcJMvh& zt>nqM9bLjxtTA`%);)MEBMaFExU9KW>!K~ah6y`o{Rpi;9KFy%)yKt6nEo^>RWMzyei`j#g{bY75k^ZPX4x{dx?N-*_ruL0h~{LJWf*0VKi4PRl-{ zp(aj+t1t`|snEc}g+p0q=L3^7`m6%OVPGAA0+mot@VlV&(HQ&D`Df z_4U6zTN_cq8g5PuFD{O&WWA4K(OSQ2MZeU%?=|zjH!<(8FA8ESy4i|;Z!8M3)4cCB z^S<}(=Ka4e3Ucn&pHr|jMT-0bHS5|eN6)b2(&cu0jthez8YV;jY>=`a&#=5|p${G_ zaqvqV*sx@+u0c>AWNQaif|cKYweU*$dh(q0PQHbChDUeKY&*emgeml7DJx+Rcpi9} z?IA2W_4POxla~(2!w*w%)y!5!rk?cz|{$G zB@U}OJh3Qfb%N#1``)*jcO@WQ6y#?g{GuS|I4r8n%P%;i z!{5&$8YUx2TXV_HW@fG!1x2zx(__H3M2j_widhgTl`9VxX{wQ{lq~sNjxoXOhgbVvOYlm_lDG2Y z*Krh;1g_^BS89zzg-Dv%br)vH6+>Z|fn?EX`3!_pmGY8>E5p=f$#wj8!6KZ~({ZA0 zI0=`A*Mb{#36b(~j3>dGB3pSVR`J*s~5kzB5bHryOu6wM-Ai=FohcTdR^9o>LM z{VFfVd9FKnnXYN1T~TZ5Jw8dfD1S|k*~I(#rcv-a%oKS&(R<(O(v z!5SVHt`^5{@77+S4x2&v^JKAf;cVsBEqZs(g3xfdDzbHwi#XVfd5+ZLgu<4;Qcp$=N@}kh3fK( z)%6y|N=sk(8Ls$0uY=X~Mjp%N7TW0d3c#vV+7(QuXjc-f+N-NAO}-~nD=?;p$@aE) zR|E@l@Ah$-VKZBR3d0)8>>Q#6QmSVh(;BxY}L z)gfEz@R^B7KLA}ZGFGueOpFl?2VFQQ8+w1r)S$g)60F$zs(o-+`FwvKT+QSlSGgh` z*04M#0G7{?n#bw{DwP-#8wpn+${ymf zKF^XQ`H5ArXyzUL)=sc|s)1KzLLWlqdr_b!E4hrq7rRh9nHF4~bq>OgFG8`{E9{1I zhj|ytk~TtzrGd~}_q12I8%v%5Ea6F~+WYzxHb7ZIV%xaldPdXJ$s|^tUI9JROv7dK z>%Yc=)fgjQ7{Js6A&a$|%BU6MJB3P?`gjX{3fAcaC9(y)RIvIJ%@WBH`!1~~>g$Ub z1aU+dGH>Wrm^iPD|0q&M0%Q^5^s*59V#Z=cN@8K`^m}n8r6u>rQ8skHCa}$PSds5)VuF&H3UI|Wj2fACBOWBVJRw+EOJE? zFTepDvDcX$9zOI&|Mfsgm5bDnIdWyDO6&d@;=O%&S(J-tjU|}|fHgmtrbb1Y)LL;U zFNX0JCZSuC94$bp5(_rTBq{Uk7XCEN;JuZ{sOI;nS9%Vy5RwJ5BKUdy>L5uX79J7t z$tY`P@KfU*X-p&{+|`sAFA2!a{tT$yuYpn34V(T!5z4UI)6-M^MR&eCKlMw3Ma{BU zs3waZYh2y?_EvqEKJm!1~W$B_7KL<+W}#JiDp*$4E-tQoO|!9Ql`6y zkfs;LucT7_lM?WcbI-f))+;GDQPt{1`Fz!tZ7BeYqU*Hzrm=(5kkoXirz3 z3S6RDo5afcgi6-ckNfktf8DG{o?Ko|I9~WO)^Ewy+t*+J=WEGj$#Yf#*4Y$bO~0SA zA{6mZ!Fu_UbF%&AJ#U^MS5q0t!Ez5Ub?lY|-YX?BXmooonmJjM9O zxEa?K$U3chTZk;wD$(RsoBs&T+;R*-#~m(x;&l&~^EegCV)PPqGTd5|_0J zuGC~fuwHy)l68o8F;@6_k31=>8_ERfCsa3x9Nqra>W151xzLR(=5Fu_(%-0VxcyOe zgM`EXH+RFI?tbypbMQpDx||G$!&4HbkMe!{3d>ua9b_h09rd^S425FaH8%>Z&Srwu zls&=D5?y=QvP5E`eRO>BB|b-{PX|5AEm;1_TlBF=mM6>mE29dOMF5Lo36d2hv_!^` zh*1)I2x1SGny}dXnA0nrD3)D>qZG4xz00bm1pojb07*naRQQ^2CV!4UDCa0y3kyMY z6ZbLQQWSdWT8xDT|x`C+cQ;r6doH{719ZqNkjwXbeaup|ybv4+FH4=GufvsdyM zl()0lhuQ1b{}RR8ndV1FYiAdfE4%CdAKz+1{z5!E!rf!uR{3YMvm2hbUF z*pe^sB0;DfhjsrW@~sf$3RGA@t{4grfYvI}TCslNb4xaCd9c9M7OeZ>;Sc}3fu+d` zmPNwT;p7zB^lElFolHMS{QEEyykFl;tAG$Qs&d!=9nbV3crKlt=^C>bn)z`0Vn zoS7*eRcM^C=MXxjk(jU~NZh~360Z*Jq+Z#!9|Bf`1-4a?Wufq?^a>yOK4CGS1BZ3B zeKh5*Je||K|M|uAJ z*&BIwAv2{#G0pqg*_!nEI$1}PS5h^uRVje2#zZyCsOpB-m|P_QYCzxK8wgO6WKXR- zXt@R@OW?xM4GWBE6a`p!AK>rJzczzm;`2;yPO-wWY3L(e{LhF8H7Ix*8_@BespbHRd8JtI}bD+gPk>B%u0 zxsrB%bUqz*M>#Ck8zbrsv+c7B&S5*dKzC4QsP53a9I@V&XrXtQTdD~PG)SsS#Cb%so7Z@aGgzMsLXVlU86SI5wmNNDM z83z+ltVFnq5?>XNWSWcsSqTUKAX}Cc9a~>eT$LuVdc9`RC|Mc|qli}p0Lzgg=@~{6 zz2vRdNb7M zEFIAK?rQ(WdaTVtuO>qguVZors80K;SI6Da=vbafv^_oBIm){u8Sd-m*ySkC*_4SZ zDpis_=#Zq$I)ak7u9s9LNiQ;oPx{7*=+wZH36^b8SJkxcVXA#YS@7B72>~mG$pQg1 z?F?L0A`4ZJ91$k&2|RNl(K+v?oWw!fj$;dhgv$U__Rt}0_)`E%*OmK~sB*%9HF0;6u?VxDwDBe20rX!Ji;m%Tz41<*IUJ z`&O z1-A13wBJ9^bGhetFN0vMjW{CH$vjz|?H$4DF$mEa4U&4TLwJByV&f-tSUt&NDqz^= zh0#E`>Lg%>{Xj{D_QYMKVyD!xH_cz!7<4ZRKvt?Ai`vs_#Vqm+1+B=b$*LGwq{oxP zc*e%ih^rF`orI_2FK8$}LZ4h??#$Q<<-BezlC0(G^788P>TaP|8$J-eD7iY^Kg8?b zUwzJues|_(^!tMp>sF~)xBf@5ZheYk{bCN%JAMvQpCAQUe||P((w#Xi5Jh*@&m|Tf z4|v~iCJz)HF{uD1>NQC&u*hn}sF!7R%T+Da#9P@e{Cho0l`BOnB2t4Oh$N+zNtKoK z`OH{J$g{5ChLW|h;n~8sTaYRjAR@LPOb{*v!^i6lNLLU=*0odNGW80*X3-}pJ*rjG zzY`4-YbEdqO$>MyRX340BahcELbe34#YK<>w6IuRLy^SVDh8lsNN0Zzta1?g&L=2V z`DBV~So2&C%IyU@RuDQclWwoBE-AP2ZKZk;$(0}%u-@laSKREt%+3N zC9HCa(pJHhya!UPH-K-?5*Vg&sy=GI6Og+c4=_?y# z**e4`dR2n;S+gcz-*m&SWZu=k51DSDWc8_6Pb3x+uY-d?RV%AyQK0%$x^W>(hiqV> zB1KM?5-j-)q?Ae`A z%9ICYK^Ay1N7bp6y*^Q-u;7ovM1UIW#B&iZ&z&k3&;FOZ^XF|NyP~)|a+6{>91ON0 z0s#UBUISAQ8E}T6NGOdYf`3F1M%bj_SlGaFL3sfd0)d4KyV*`IQV3Y=Cvcm_{vrXQ zCMCDd2gp6=-uF0?oyBIA4I+5-j~vd(_VTyqoOkb=Hv(1)vJkT@VR6z7Iyy2aJTtVc zqx15X75?Y*vF>xrliDY5c~bi?-14OM)3-c<6)rI<)L!A$a56j}o-fW9L6a;ApM@YBlz@;p8ODQ3$dMTrfqxPGONksmtqDWXStFFTAB+JJ^u* zi|%nsunGgKL2$gi#V#5#zyqw10w+JP6N${SRfRl!7k~tzSVOvwBY~S@8Yxyt?uiZx zN!F~RT+L=@N|tP9gw>4o8Qt>4wREi5z(%6$}vr!=vZKO5Q(-KOGQ7h=3g5Q(+Ro}i zja+T^SK59AxaWXksR?yUjaw#4{l1}PL2G))`c$6^9S@2H#sR>>aF8?o;$RoxNtS|x zL0kZ-ak>?_z=cnzOsa4E6(ligG4%&_xE~3ms$nQLU>DLmtWbu8&1$h>b~rPv;Dq@8 z=IP6gSFF$JO6Xm=f!}bU&Y$1E-(C#~)@vOOuI0ZwQZg=5Kq%5w#fPU#80!x5{7y}Q zhbUCx9+hM-HqBDBtcVbDjCeOrRdUu;of8Hcc3TA`453{IaN=X~B-lxl3Q}PBFm~P2 zBEJbgp>1SIn>YStz$t$OJTYqQKA5tiS4px&`+1((fKaI7CO^Q{M!~N zH0z92l2WaJ(*K<1kt1xPRQx@%CxK!un@tVwGY$x64lR%+V7-x#vnp8sPO)k~saOws zKGxNhq6H(|z850|ShC>`L}7V!RLYRBAW?uqgvz23tDLBFDocJZ&96m%Jx*#X*x7A}5SIiHHkHQ$QS40V8*dO5{)iCmNF? zg|HdekWUVm zd^Z2WzE#`EZpX*{WxW!b=YHZ!=$gM_hP%;;&4;jkwYyhB+YL2vL}gTX{?9EmOXxb0 zxV1O|SUrLzWa0A6LcJ{JsQuKFrL92sj3}uQqA`rOnrGQF(Hbh)p7#_;L`=t8sTAs*!fC)Y(aWVF3X_Ky)x~;{XJkvP(6W$$I zzpd@S+7c-37B0}$LE&8ls}QAH2joi?OPGNv3PRv&eM%8hQw&Y8s7CRg@npk)RE|7j zTBw9E#)WnQbNm5*oXGZ|0A*mAUrrIS`gy;T=WEO%4rEW!=pK)|-7%WWVpGgXT`x8b zt3VV?_YGI$LwFj+ z(zUGqsb zRk?puS{U<^#ZFyyPpI8JQUqj_t7{LI!UVo#@7rrTNbFEPr(rjhlr_z>CTpNV1p#0} zkF>T4iNRArC{(lxpp|GQ=L}Fu?%>ccRQb~DBy=)EG>8ZmWlO*s477t7u}O$cq7@X& z7aK&ftd7N*SV-?157uY2xUE{@7rve6B?&qi#jY6+j8)i&$A=8mEy%2ytG?8dHw+bj&?gw8Q`s9GCT-9N~Opmo2+ZEXRzM>ShJ!B*gVq>X2N&N+L!)*g?=Gd-M=XJ@b{ zS<_$OR${2t-6{SkcV@lrj;-k*VPLs)b*%_@>)!fqslPgI-HRslWVKpNCM&yEXZyny zy3xIL4_tK$zLf}C>vbuLB{C&ww1pG6oeGx8*p8VHv+Jt*q-{Li2VaG=GAp;@LaFKG zhtMnXlQC6IUC25_!ao!x>%$90wmNy%9k<|wh1ypVx%vUphbbMONrf%;WfUZJwbU7%{{hvKFRj2Qi3{-qfR&DIbcpik5{<&-)xkk; zwAA2!s69e7bKVfGr(o;D=3b-1|Ix)1wY!zE`&~?784HTV_zpunIUm9ZC1b5g)QIn- zY(0>*HjNUpj*eo7Q?XMmD|VrTsad5-C$dfRx+Gqtb%Jd2!hK#l3tkJyVYgiy_qh2UnZSHr=lZP7KZ{B`ATdhW`)!Z`tXad3N^)5Rd0c$Oc zWg`JASeIoWjufsH1`1YT1$p8Y$9}Z8XG>=Q4!#D$bS({F#zkRs%UY0;zuo+J^W*h& z9R};m7uVAlmzNhkEa&nZE68G(*x~xi@{iwqgGVoLAGhPbq~G6sCuDth^Sz8LdObnw z$?{;iJFyiWJib{s#@538javz-3RUFX@wWr7Q-*+dNP-0#f0EOn1$mq&_>WuCJWPz1@WRK zV4e`cXeeFG6tbu{1@DG|Zo2TJ3*%K#yf1h$f5V(}?zQ8-H$TcNvk1L*j4eCq4}bDK z-}Alq%ChVMS0%O`<@B5$P47Q48Mo^gFnAp3a5yWe2Ww!p;8=S6XD5#9b&5<|_xVPC zu$rZcP>e}7@e>HRo8 zx}P4sbnJ07c8=14()HNM1IAo#yt23*Dq~w^2qnwEdX?WO6CRd`*J~mW|Tb6@$>K)y~meJrp;cPUl&t^OrAJk9BPWQY?i4 z7AO{c?FQQqFdPLnpn}Zb z!*yKfhl=IZpcKNjW(R29X>-6KZ}Cj~y^HM`=H0&-VG zmFV#B2{EU_46)9MQM*Wr1z_n@JI<8hP)R~1ndnNCD1*_ef12~B2|ftZdVjQ;3v(-@ zp0b?yN)jmvXjS|c6e}AjQK$@`>zkpjgz#Kv0Z^e@J$!~_!OiQh?juhGSf00M zqhQqlENE`T=MC9`q>3hVTvwjG?n-w6Ek~?cpmvX%MOAEN5Q_y+lGf18fPAev)QYXk zITc~+h3kP*$b9|6v9oU@{kyku^lkUfQao7>hd07^zB0TS^)Gu}FYSlQn~#|Sxa`;U zrFwkY9o*=qe~Jgo1Alpce^rlebo--l(z`L5eiM1)Z-&DgQFkz6npvpgRzMi{?rju4 z>F}_b)E2>|N|eG5nX3+Y_Ze8f z@gq;JZ7Bq>a1($>n@yU{CSnzUV3Em!nh2iw*u@zgCd7+qV6t;Zy`mnG-1_=_xw&i! zRVEvJR4o}JiIoJ#tUCj+902QO3epqmkr#J22Y&K#s)yliKk<`rv->f9ae3&aQEu9Q zo{PepQ@$Fd@mmiR%Rcf3s{mG#_)&6mw8wt3hstQW8tsiYhd+h-O}z`|O9Q{8ZIpf} z!9crJF32trycQ*^bGedq&8lTVR7Vo60?zGdq;GJo6fLNjTkUq^imiv|Kvqpo>;PGv zSy%Y_Yn&@2#iCOj;wM*;SRL9;fQmkG0nl>YW9$iqW(lYeFG1Ft@ZWja1I`;i)i4$u zm7LdI7i(7xa?fIy3-)Ox4q#2(?B?otDR;qs-puYK{Yeg_se5@E?XM>3L?3!~3Xks& zlwO#dxH8hU7^0(wS%o=Gp9~pb;S0<6s*PZn>^3Vh$6)VAID`uw4u5 znFM9CCBid_%8O`=7I@-YX2_j+YJQvV_(~4`j|ZSZ`gS*F}cl;QM_g3osqy_?|ofMfH)of%pM0kKf7 zek@sN(gi1$RW1u?kfgKfr`1pdp?b89vn?1bxu(k`Qe>87!CM$`aATHj&$ln+ z!SS1zaFM!+cS|g{)4+v6Y>`9n} zqyC%IsrO_*N%s4b-Y8$rk9!ji4+m3t-VaCoq8E)P@MC)8$ESFhS=bI*)&_tDBvl1B zC|RIa8&*L|*37jFwB#Hj@I!b3#w$70;j?FFh!<|qX&Vhg;M4%D9#b_GWYLr%j`N34 zJIEE0^<^I`_gSTIW}*IFDZKsPO5yF#E`?X0PBt%vEB~Psu86qdvRQ8);?fTr+QMca zjC&K4EFQ3+1zepxLfg)$9v-M5P?o7mnaZm~3%l}ADx0mV!v*9fz05RA>n7$9S9`Az zzPz@9g_SYe+uMsh?lP>%oX2rJ?#Ff$c;hsTaf1zSqKC4E?y@=jCcy zwG9oLvpw6jJveS@oEwZcxUJ@Gc%#{tA!ZTH4UnjN<$05bzDrwh?l{Rj^Hw5Y5aB`t z_d6TyB1jgIg+Hq}6RRD#v~fm9jM_YF0JJ*i9jREqS~l~`ozVX)dB2|K{j%Py^(wMGe(*;VM0#qK43D?fIPGtY2(kyannc4}dn2s-gDb zza6RCBE5RN0M_pLt_T~RbpS0OOPKdRd|8flJ-66HDFm>f7C-n6L_nF;~iY) zKv4s}$y3vhr06+b+zKgIbSr&K;UDT9=hEftFOwql5iUkR+-NriG=(*Ue<1(>AOJ~3K~y;NT*TA>{ZfOjZP^9NI22Ll(b%a7#kwL3*rLdtl2O#Gg}K7tw8j4a3D&|l-1-Grw>}4~PtE(n zUxmQ>J1ADO2|laYq5UiVEgIEyQCEC|VlRNP#S? zU{%pDm7v(6i7HTF1%hSa=T@L+FG2H%Egk$c!+=*?NLNu4H~}ppXct3fqyjeti^%#H zc~|h-Hl9YIm+oPwcBSdfw}KD?--ISJ)*2t`$j0^%LN*1TJPutt5x5gJ2h&RgO(3PL zs1YHXrS#BU$RW8D)Jr?d=8}WvByD**{|6`+jm}*xlQlN@N<_vMks3^ZVZS zzVG+@i7*pviOlaGb=*C-<97DCfK>^sQE88su~2X&(eT}0exBU+_Ww?9d;9+b>-J~f z^IrV$aV#6V{l?SYWWqow)f}K|7py@)%ds^CK^KhRF5@76x0G6yl9gDTCgjR+sA8kN zva}nPvka?%V}Ql#dX>lmd`euY5um)!SO|u4u36970EAYg=F2lX-(Dlj(bPB_>uc+Q zr?ziAK;AlCE3; z%XI-PS722mcS#@<8D0@JzcfT;(FS%!P-YQ*^eM!)Qu({3Bbqb~M6W551?)FWmqAag zEw-lEYH?+!XUi@CYxcYQEA&F_>9(`E`PEm>b_-Je#AY{wZd7X)a=veXCdNMt6y{k1 zmS*2ueFkWG{l2n2jfQ~R0`hR28k#Bz;Zk3Tm;$ou%My=j&61q3so>S2r3Gk>)HYDK zTA!?+j@Oix#Wqr@=P(l9%#|s`Zde;HH{yzJDYc9e(@M2d18GWFxRU7+M`d4=CdNZ` z2`mP}Ykg%_ODo=lH0lvMhGAND6B?U>+t#WLyjahqc$a9%D62IHW?7|ea~roBy%v(j z-S3=})+Xa&>s$8r-L+erwoXuVSg`TvCaemuJmR1)C1bizHSdpn%<>?>Tv`Zwu3q<6 zYqe(Oj1UHCp0oFc6wwr=nnk@+N?_zatZ5 z{W2@OX^iy<%+17*+I`GOD1bs8Lph|T+wB5c6%p`c6UH!|R4YAt8k@H9g@II*>< z14vDGw|8dH>?hlgAK%@*cW-y9Sy1Xnb-mN~oOD_Z`j%vad#fy}PlD^~0mBwIyd!UGZ;k@_5i=rALtE2x%>g#uk7@g#~zN zrEa3y(n?!&$yL?Tl|X!PwMLk=Z0F^H%Sb07Q6`0bet4B%*;;FM{%Mh~oWIHkj?C-% zDs@`h8(H2e@}i(-2Y)gerrG&?ev!_LeC6aYU%k6>PX^W>9LSstjr5gMsyIZrW?4)& z^oQ}GR?g#L7|vndC&Pj+^MiwWUjm|aU#vDUxxh^dS!y`63V%n64+3PsT4#WH^kG1qN ziNiQ47RSRZEQWrNFRrqj2WGQJ=V7ps`ax8j7hzbm7GaWRaT=ycI8SjM`1U;NLg@|ERUniI*u;TV)9QDu4k62Cckz(H@aya1D03|I_;ih-aA$u95^IBQ z!@GCy{`PC<$NIeHZupG?);}JN$EV}XQoV~m3S> z0QY6^GKpi&pn=5_72}{qWzIWX_F2stA~K#V#XOds5WML!x0FSD`O%QLSj`!)l?n@l z^=4~-2YmH(Us2Ylo4Di&Lg6MP`=;`-m=-$Dgx&9tnd^)O(g4q-P>fQJgt4fwJl`4hh2EW{s>76^*$LcH_2R~`g` zrE%hi!{SpK#led+FqR*+{ZAME;uyz(ea_|=q@=(p*sqnOB)lk!Yt_gn+W}Z9&V{|C zGK5Jz%5uJ95Fg@YqbSM#P7ZqwwhZGx7#(PI3Fk^h-8XB1)$0A4#Jzq~r{79TRp<=Y zd}AhZWRAV;*&IjFTW2c0M#^GZxOKWuWNoPNs{fT0-kc>*IIi%~+qaXqlM-1|M8N^a z+z)npy@4x3D+Zi>#Iy>zW__kL0Y4uLb&O?;ypDLDZpk)^}!VfhK$9rU$(=x z_tUbX18AL|LS)I>Umu>{#3fIzf%P00!4^4dI(dRyPrzD@fsD2%vS?)Cmx@f=#ShqG zZfEG7j%!LcjYSkuYf*XeK=7HR3u9HZN<))4DO3Nnz%Xfxb1e3DHSzREOgoXYIUDCl z`p!>`rq2H8sWU?&IGc0le7-X~pYaCW_BR_Ft*y1uQFAo1kznAc7EaLbYj)}E8>2k^ zK%0^+W`Y0;^V3&#|c)qdi0Vu&z6+md&H4j`YtYqN+xvETnS~8QL$kpx0jC!BBqm=p<5Po>T8!!M_byg^HzlqDT7)^m{7L`LcWS=$pRtO-Qcpd1X= z7yx7q`V71<>DIK7p<5mm2jiiM@Mnq*(G^Cs^y^Zyrotl7=Hfxf`^9tHtyUlRq)EZR z8LVgC>(@ur!(&3pY0!*iYZ!v^b_;vXXD74S&W_yL+@5WW2VKKJUfQX9_-?1u-QGy= zuwA$#-a!AT-zDgEy{n2h^y6zDD?R@q?|MSpNbhLc!v=Q?-bnE+hr)t74CE%%wDTdR zke&GkS;YvUp|A%@#-O+$Ths@Uiwfg~&A|mx=HMk5si@wzG zju-H_s9Mq7EwCjLW9=P(pFqWroKsjrR#VkNm2>9Ho|zj(=)1M$iGa0r{^I3}VSf`c zVd##gmah_8B7I!r1DfDmnqb+Uz(~hWw}qN5Jweh)@09i_@V0KtrlQ)6v$;rPa_$3D zuGT(?vn=5g?p3P@nZQ+=+qyB`YG=AwfPlh|N`a$M$mOetBZ7)-hKCl5!?0GjhNYk& z%c4P+9UR5TQlYD5xC1Hidb#ZG#A)oigGnFj(OKlm(l*YmtWsC@jD;foA62pay1yP5 z47|$yoK{Pe^~9A&7FIdJB{sM$>1w!Kr$bylcZ&+2@1J~qi!D!X)$_jctv&B6|AU_Q zmGAC(XDA#Vzj)b{0PRy>fvp-`f9aYV5D>bI%DKLM@9r%sad?ee{^O ziUy#kQPgu1UJX}~@9Lq2&E=`*M`fePH)Je++GNHw3dSm`V)NxutSQ{zzudq4`gT3< z@345^LKx)4uoiR`CmOkTt0iP9>mgYIS}A~43e-J?G&`V!g;Qj;QrHXjT=TNWI<=`N zGc1F?d{P*x!$^u%R2w2t0gAyG+!W$@)BGr|w%gO98)oVN24JynSqlNE*RL;5-z*ld zR^EUAN2g)?;`HM5leH}Veav0T0I;+`77?aZ5|3lzi2d&Cgw;CjPg~cxn9779)A};7 zB2pQ3Q?K`6b)Q8h zPQL1~qeedmup&=eIcX$Lk2JqKGc4Sz z%$jgRu%_1Qm?YyAc$?WYg){;!sM&;XcjB`Tv|BOqy~tQ5G68hG$ztgCz-Vd0q)f%{ zU~00Y5w;J<$soxR6Acayc0wWRP+|*=g{-lJ^H)7RYfmrErf(kJ`}uqK{s3V8;_%?r zr;h-q+)h*>W^ki|B3ODHFR|KN>qVYx^rP&nTQQn1t)zG@3ZgvqROY zkgVLAwmn8duk5lAe2f?hOSG^wZibXDh99!QK>=}pKAVk3MAaO?`YR{;y>&v|M8E%X z#_nHVzniOc04yR)v)7J~`~59^_4}G=1T1x_umFK)rb%GQD+PcOAE6QPE00N0jC;Xy z5f8gxN;p}{65~V`D*uxj#ks6yq7)#o7H5cC!B=ZxINllW1%qfX7>vVZEG)v|Zab{C zx~25&^y2Kz;_wSdke{9|rmu1LZ|gb!1k0lFgsPjs0Z_DOXD{L<>T$wlB&K3M%g&mf z2JHJ2mYRZw)vDu%zleuCKsL>Nc*g2=*U zrmp1u6B!T7*$w|`mCpSi=t}6KgA_D%JlxWk>?}uHi8NMIk|?ntFXKeK;-UqzEJu%3 zL{MshC{S66Foz2%ZNP04ZF61c8e_DlF=+lf<|D(G}&k#>;}Qf77f9Wd2wb2eY9 zEQt5nXLoVSldINb@|40Laze_ihT>t;q;zNagThX_R91O_ zfq3I-A|{YP9%7&e@a))Rtg#$+)Imgs&c{4zg(h= z31d5iB0JxLDyx-NOBI*eAu|92;xCaK)vd5V9{UBDJURvpk3yF)M%`sCuRTdXgv_g< zgYF2f7IDiU)i1{D{^Pn^JYDoVg0z}O*^O+5zM79Zoen!22&+p6t>-7#J@0pE%M$@> zhRYn#60C*=qOK14?@S4SA1Nc!l{@NoqE3g{a1CdL+(n1pKwF8$2GrIp{Sc8hOB}I9 zS?VLK2FYNsGZ-MN!{gb(cs$-N<`7qda=B7PHn_XEI!1CG+O*(DqjuPk>2#FJ_lyQn z7F+f9(M)$L-L0T6m;Ji$?s2oJh=}v)O3+8>K}A6W3R!Bdrnd65!n@*b(_nqi5X)Hi%BW#neKELKHh^=-2GXRt0ip_PoQ_c2 z+`YyWj$LXmDX=H5GcN#hnHUm2|B8!NvpAxgrp{>%YKVX3C`gjt^P*mnFgn$aP*@CJ z>UohLFMmyQ;TRTGboTsmnW&@gH4Kqhyl9X$2Mgkqp~*vz~I_RDkQEr!Z@Y~_`cBeyh9r3VTF3f9hZnV1Hi8b98yJHX;H#gi+7vicNB3|w>dO2+c5uX+H zAN8U~MH3M1H(K`U^3U_X z=Q&+_RyOf!iv|boDTlWU7pT?NTWrNpc$+Oxgq`owSl0&WiZe)8R+bJJjmvLpta~># z*1y@ip4PUKB>pCWef|7q6^gPDs5 z6c}=_kq>4kV0_37JBQ6>AO^EO%%{i_@F}O@Jq-I1wtiLJulqeow)`(EdDXV0r}w)1 zRrf1ZRaaG4d4W-oS{X|er0`bY+QOy>QIPUm%)5crR@$*)MGk~kl}cqntVSs(QO!vR zuhj&liq+j}gjc(STHu4=l#FzoubeQc%A67?h1YjnmH7xOBdCLRyZ++&i?yBSt=5Z; zQkAJ3k*TA%tE;Q?*;)?4f(W5FMsbBOZU?NAgs}OYLXPo@;}el^)n;u16rv<+f!#@@ ziAfTy79lKg-M{`BYSTY(wE4*>2w_V5Kcw}-kfe7wnSo^Gv5yd^Np39ix|1kyiKPJ6 zHqfF_T>NCmDg-PNt|YMS0mAydjDmuQf;1sx=`cp@-N4udO{&Mw-_ve0BdU4lRbjJ9 zLTA%>kuEXiYBWlXhJ@&DRIxAwPGNzSuQIhd*%kaE0tQr}7c)kZn1q-SDgo_sz0Bl}R>?_i2 zkT)Bppz39n7fjbF*3aF)mo922zZ%=OW@k4WCz~v{H!DrVnt{Rr{~R7`jRX2c$;K}d zA8V9To?tHpl{tDL^-0sXWI=Kl0Ui_!*d?5gDOGWo*&k7!&AjLyK&qHTLFfWq+TTZ( z*4!-6jo4gOoUo=u-3sBqoGq7taMf0`1yPF~Rl#yV4D&T+g^0pfL69>Eo`=|G^CHjB zXUjxFo^A5hhD1x* zs^JvZ6Jadl`wht(m5SnL#$E;`TT2}`0H5ihW`BC|swOvD<2 zS3t0KZni88uOwEXIyOz^+Kgv(j`&-vzO}bkZ*4)3m4}UpgL+n&?~1oDaElXd@iAST zzj6 z?*+sFOFc9GfoZcIBykIiLzWof*8p1%$yl>6bb$>n{S@KVOC1jc+f@%%k0J$W?12p8 znjDOD4APzB<_a@E5v>}aRKO>e8bsw#Z&a%t;!aS$1AUmRh_tLBn zgPt8e*w5~zp=V{XjYtd8ueaOZew zWywY|f}xN^2KFJX%yv3^Ou~p>k;GN3GCCEj{8>e^dphDJBXm)$b`V*gyYsjg74~V`#Bm&lUM64ROoFhm(o+y)k=+$?K&W(SMlTjqlT%U9RH)MLVrS9sCbdRXB=~gtD@g7s z-Z;rF)vVD%9MWA;H=I0yvn$X$U!27*9gb1Qe#ww0G$+(fenyY=|CR{TYTjAfKr6!I zW>cgrk*x?bIEi7S3glwK)gi(qtnWnl9dSHX5?`he;V)(n+pS|z?cT8=m*M-C!N zkz3!$ZV>v+fF&=|AKqAeUtC|$rLay+m*ndr$9#t14g28KI*!-Z*Ga$<1EzL}V1kur z+80@ybNS8U?&04L4+XV~Af~h;qz~kG1ck3ikOHF{U4CiH!KLE74|}nWeqPei@A&w* zd3@Y({~C>-U(v^_#$Kn>VSOn+I*pFdA;PW>7#!X@)xFLSINs|3hv;itsMY@dhr`3e z{SS;;oqrzU+W!9D-d?r6x7T>p`kBa7laP@aCYW!e?;tFHW6HtXg9CbEZw|P;5qBQR ztk{5Avq_z_;FnBGODc6KTPV=!(?>~6-Be2Qzg41>Tjor?J_~y#H(`R>s+U*>ETLry z?3>mu^gB5u#M&43ej+MlR`{LJ#58Szmv?h%g&<#9`E})2DZgCA(JxxukEm<%zduQ` z+4<#pNtCU#u|o;T>LQMUSj$S(tgMbNgbfuE@Enr!{d@N_l4>oJl?X)eOvq*sY%R+U zE{N(WiR2I~`R?7R0w;SeWRd9MevRaDWG9`gv8j;lH>*c1q0g;;`bg#B*MEBW@Z+bC z*r`K=Yff_4NKD2#4y7f&%sa1N|K$;q03*ro93kRE40Q-()sTze!US9oX9*I?th+TtY{}Pp>_wRrF@%{U+ zFO&UiBuzRK!R@~-Y;#%I)(7w2ZO6(xI*~jlr=^&q?Y_m-bDPP9?RaAx5F9Am3k(oW zEG&F4f8XP10ibUGE8f410L{!Tq7Uc5ZDNO{uUeqo5`F(qVz_))eGv7!(5L+4TX{ei zPJ}sXG8WEJF6ICL2{cJWK~#>yP>i2p7)BQISjuTSAEGe&1KUF>a#n@ z%&u&1Ej{oFJ{2Mgi|$Ru+w<{+98Uz{#01v}#ZC`^rHaTU(q5TnbjU5eAna9?fmxcE zkLa?B!H|SW&z3^x1ch&=(78^x5A@-PuOUU#0&83(xsR?cpkD44+@!ibhK2K1e{N7d4^qulRl9 zT8rB~-|!+W1>W=A71izEMD!W!vrkhHuo$FD{RaPu(e%By+=+PA3liN1jF+^E^`WT6rP6jMMaK+GtIh1q0 zT=aup$$}D)$NN^X8PURQcWxr3%*31Dq>fmqq2hQg$?=*lO%wv>&4oxMn4uDYLZ_DU z902{~V7%lE4%-&{Ys*aG+dCMZ9)muxz`L+TV`LkI0bFUBE|Ts1BbfvzddEj$vQ&<0G^9%oWNh{0+` z>#Lqa_iQ~2m?aj8dI!6DAsucuZ^5*vNG#W^T=_{u2T7XAH*^)tx5pr zhJp5aMIBE(ZGE}O6YqHm#1aU+F2wT0H&=IRU3I$<%Y;mk-hEF4<%{b_wEPYtk68z? zJaGllD{4InT-W?sy#!)K3xAoVsBg@P_@4+iSj3VUw1{(J^3Wz`MJsvC4K z@gGD_#7e3glyGVdqmJDofXPTjNuhaSOx+-{I|8K@L0s1pP*j(?K^b*}GQ&S5<;jgp zD>sOYrOvHxP-n?l?nGUgx}M#2VcZPMQW7r zEiR0ZYx132DDpmf<<4hrclplmoYnNE2=BMh8YYT$b26;n`R%w2 zS@|DlSiK5$gZg4zhOYen%K<$|ht*bS)pLVKJ{r2>edczT?|gteJ4)VuJE4bJcvp!$ z7h}X47R15^#SvZ=hCvWF$d6+2K6AUvcRs+K?XIf6JE5)zg6&sfn<}-9@&aMRBaBh9mFyA*!b{!*Bi$Rn!b0SpY@X^@a;sPADs^L zd&&uYIna-u3G}0{1^THf8r}019E%*sLW5)B2#%%PlJ(_3gMSG@72J{b70MZHD|0ju;##;1M5u#>(<$3 zJeA4vU$f2Nr?Sm7J?vzgxpgfaDbJFG9BH+5L|AXM+#{>!Qj)@t>0eG*<;?Mk%=yHs z0!mng6ILVrOJ48jUraTHTSRNiwDhk7gw>#L?ej{ScVF~RCXuDVZ|GmiO=)@AmHx$q zRgz!?rGF*9VZ5)ZmJa`~+B~YIbJL_CQK*>|d4KfE1<{w#)p9Bw z=&7Wk^h7JGbg$5N)1boxFRp#1lfOL@DmK3zxN^UCvDzDE0ARQ&QU6=aB zqu+@PkmRRUfYi*P0BJUWv|JQ`v<~KxT5-HD^z@D+pl&kmg0q(g38q zDTC~sG}-F_AQd-mwh(_vk8Ft5Ly!gc6*DJ`Gue(n5}r?tEnv`U!Yj73!Hze)wM(oK9oTeBPd0gBFYP2{qdiF=3l zhcuD_&_Y#1RYV|t5N%7PW44O4+A1iLVc0f>7l7IxRRcWWR6jXamD8w+-|MTLtBGLd zW__vs0Qx^_iv|fv+CuZWqXnr^w$e{pqtF~*yf2v|I-zIohVSQYIO2rWI1q+ZJG|Z%j zG$6Qipe`MVQw8GEfxC3zE*%7CiMe!qduS=TT9=L+dzwoO9pT^1j~JeP7q%%qi%;s^W3VGn8axWXJE` zx%GgI>@Yw^Mm}?t93C0llCgz94w>Chxj{yjA3?Qico;rE;PgQ8CRuJ5%RDSec3)ZJ zHU$3!er>8YH;B92E*ZC=-22dI z8C30}`Hp4XMO#El=RP5#@2E#lTHw7L!L?m=C# zQ1yt#dr=7NCoT;`1^vW^XOK*sMiB%B)e;GK=m#Dez!dewC&VX=%=bV&iBJL;bToxSQo!Khd{FfS{@7>2(Nd{1(#-5jTInt8w|V z2tnO5kX{z_b+xs%b$b{3u{N{5_45oGdiLxYl-u_(>KHVRgXY$u@Iez}6N7T7X$D$a zpFfoVL0>`m$0SieDicn99fX;(v$cZ~#|8%n=jP@j!XqG$a_CYt6yFBbxbE)l*45VG zCZNs*XdL{qumM4%Yfw}p^syVlIU492Ko34bEsHbDql2>@yF>`8vWF^$prQejD%ep@ zR5#?&2n`PpLuF<}+C-1Ddbs>ca^F-#@fhUn?3~yK5$nOaui3#t!S@TG z*RNlvr=pcAn5Vk4Th>b6xN+&uwUVd6`%?N33A!5-? zP?7%T)&}&xgV_Ai$k1qFVxqINvw5T-G&Iz)4r(7S_sPBAI$8qNxIx(IsmUo~OYri_ zQkiAxF)}g^vb(o#Xt)ji7_G5-?Ml|XA{^CrLtS)a>V!^c2;IdC$L_p3aY#bGz9T=*^zfu?lSc_XcUjonc8I+v6f~o_Tt2&m zXM5M)*q+!u3kuvIKYk;M`|kh!D|J3TCj4g~&8bIEs$Tl$R-OV1(fBqj8^9_kg6Kve znk!OFWI#P))}eqxJ&Rm9SQylVgEVRMaF$*d=pclDofD zNx(>|v>@ztAJ{KnW-`Bh{iVtVIi#UGBlyZauvI0;Ib9A?Q`m&PgEv9_ye9#9mJP_r8B zOx=Brl=?;W7!bJiOl>fuwfbx$OHSj=reMTGPNPvcpx~Glkt#0N7|*Um2Im##y7Y~5 z+2^W+{`(!h1QvD&UL2BljG)ueOW8leUiiihbUsGPJBf*yXvG9JN_<4CIQ1r#q}Fm& zpt9f#|IDeT%ID$0-jsGg1YYE|^meV|Um6=x*^GJSgz*LI8;aT0z$!nmaF{h`(6T+d z?93nc;x(c@<|`_EbLRd8gSAK0fcyfkrq4}LF3DQCmQyC^JB2t91KEY`yH z&8s)Ka!t;Eoa8z?7!sT}F&PBIO?$%du9|NPfG51I(zeZo|iyQ;tvtPr%WL_XK z_x@Ieg?_p})G+Mh@8rr@ zt08+;L|zAIsEeTRBb615-vMx`nc%BQ%b$^AF#70X*8ab{o(U=RBq4M+cg(uJ!S z2|N{bz2xKH2C%j=n07pcA1UrinqOFbZy-pRkm|5JH|FVK5+2OG`U)K%m~2oO27<7g zq4?33fQRXYnW&e9!T*h_01b=7-eKrQ- zOaMD|0covDKHEog_=q8^03!CwZCE}E8-ALE2MaUixW@xiS_1=lii=~%3OE7u09>+2 ztiZwkLDi0e#lw*#=oIpHwbnBFk4cmn?4$Jmm7SbGc%GV6xMBA8^=mmu(f=kJUiCUY zA}ypI1(NNfVPt~c8t6V?gyrw;Q+f;*=7#xWH6CffrI@U z6$p#(JA)S-?m!{lQ?n7K?Abm_(qe(RDrJK|Mc}PL%JIJbK|fOTzn6eEElfZi2WbQ5 zlK(>UD}{G;PyOF5s1Vb=4&$L2)|C03ltHQDMRprcPsf6iMKNXc;O<;*cZ^RpkiK=Z zcv#G?IZfV8lD-nRlNYZlE4zo>G}48d@ug_tY~6Z7pVK_K={r|x<#G1*vGTWG72jhh z655uscE6NOKJspEoJuTg&9QU!Dxr82gND4lXYOUxpznB33!l^J9$y03Tbq5pdM~F6 zOH;zk;Zfmuo$2wC`W+FUWvz{Qy~SEWYK}eyO)ctkv6)A8+1g;<5IZQW*H{fF-xeop z;9yX4j@e3$)PfTRf9Xo3Oe$@u_RAB({?{qhwiQWA;%{Y1;{owe7~N|Sa9wwHg% zREFWFq;@?9cc7`|B|khE@l;sVqa}nN|9V)FPuCA|Dk1g}OHTMcv zouyE&`^oO%x)$|y^9b4Q1m|<2pHB8!o0BwxoB{9s?{uK}p$%#KJXC;;gvbP^vIiuK^$`-sVx-ui0XJuz!`eJBr zjG`!`_>K8?qpf+|SiLr#9vP}ydhKi!>IJa@Rgq`Pv| zUVLM4aqQ{ga1kvzj!BoA?+E-UiGr@5L}3J-~f-3AK4h2%SX=yF)+T|ikD+2>+xD3L+8s{RZsaF z(MN@DuD40Dh8JGMC7CTY`v|W7prK-K&+%c5-TFRen(2P^?QA(RLfW!7O1lP4zwJpB zIt-PrACMYoqVyQLR9WYG{0HYRCoTE5WbLi)mg%l~SKNe-N}S2NXXX{f*xj+JD3k9k z2--THr88cR*hJ{R@#uDGu4qpg%h07&0(ds1*_}TuRXoRVQ8Rt@;A)mkj4mPyzCRuA zHYYMV-EYdSUN2nkdZ?)pt(n9q(ub*LQWdUls90qaDIGWs%Hqh(bY(NqRAZkV*AL{4 z?5=!YjPMmP-~jzh4+o^{y?649X!monu9*KALzey`x~h}g-OYr}aJ_>d?&jI#tCE}7 zg|=@Q6&HV{4S6{g_SF-%wo1$z#drh31bpJCu5fnAN;Zc5eu>X`2kIMsD%ohS-_={l zZS&o;3kdIqBr0?IyjRE<2gp6_j1lQhQL4oW^5MMtkG~hYhvDB_JblJ;lxJC{(AFA@ z@z#oI7Py;qH;yU4p!$}oqX_RzZS|8vMKuW$5Wj|c;Ja$|_S@(h2@k{js^VN)G2Fn-<1lr?n`9|0sLXlbG z^oy&Fm32~><*2YL#H8at*9fnVl`!sBIk&#(`?99`vv0^}&d3tOL&uKHF>%}wL5-TQ zSm0KpG9_h~hXU<8O&L8*Tf%)_;cMC?orUJs-*@H5$qbrDBQX?qs@)HNJ!vB_?Xk!q1Fko|&* z5aa+`P~sTRK-efh;hOLV!}Jc_Ytf@s)99d z_)~ih;-WWSbw1}Ew2n!8X4iU2c;=EKDT`7{N}%Su$2KUZo!el5LYZE6sEvQYv0|sQ z3cWu@FosjhoqT6*o7wotJ@oO6z{jYXV8#Kfonq9+F-`U(!2E;oc(aSD#uvnS6GjAO zOk5v%Je8p+l&}~)XI%Mk-9O}*nD<6&1zerRvN0jw=nE}uG?O&wuF4wghMjO1B`SGx zV49Z&IC0vu)9=w{Uq$$VS9SC>l^%<7q&X1P*q^0Sy;JUSGN3ltRVObd+hpw(5DZw zrxk(kcBd7dod?50QNv4NL&7$#H$%$5d`7obgb2+cB^^?`(uz$Ia}O@dY`f3C^ZWl#hHXBh1Qq~q)Mo1694sUzy6sE6h8dJl!b9uPXb|8~^FFo@IFTDFZ zCGFp8*cyJGK4qp+KY!@F9ab1n>;AgOq^RVDdYT_Gf@*U)+{|nfbjPmCyj_wtce6}% zxuy}7#*xgAn0*<0;5Evcz?bx$OMy#`x8((U*L=oauUl}Vf!la!<3s@0!RE`IPboRw zMWitipJK=6F~V!wY2i6BX^-O3cFjil1>c{^SmrgRT$EA5%BJPpqIsb7caicaZNNtA zO}?H}!1%X{r<+jQ@Ip%g+enX-$Sy-xjAByk_^lx^klwLZyT;M{b&ddV7L?e@>$TT! zrNRiFVMVx_hisuj^V%!u4E>R_R+oB3-WeBaF!(GU7Iit)A;x`jm}7I5-~R2zX0j;G zSLt3aWFqlwnsPpuC$T7AS&G4{Ht|Yt`3I9HXU{W7)^Kx!RNE;5cfMU;;8CZ$I9y=s zVPb$gSkL%9@T<>FKrRV?)-feYa5w7wFumA9ySkf1i>-P6y?e$~j(n@-g~zOPGR!3I z>)lD*{Zb=Mc*bxFt=DQHZ!E_T6_+vTJog%Jq#iutn$*f5)g!}v=K;`fc04vPb&VXX zVlaES?NfjHD`ypjdN^Uq*B7IW=>DwB#@O*bE|%g18XA0J16x}w*WHvCDGYFrV=CE8O-4%|`r;4b#&62iPe^v zl}8-;W5M{&LSo|R=#tPk$Jxh^9c`i666g-r+ibAd;q3XOdI26;>We+Ih9_GnvIQ@v z*){MOd)>#xo9!`07n7WcO%%Rkj#QgBG-jRjn;18e zSwGJyjCewnp4{HKf9qSlzl)YyT0WZ(t|sJq|JedrCZO?wzG>f0R}I)CI;`jGgcN)G zbH*-7kJnzYctEu?qwwR+k{SlMnulYc!+?!|p>gJr`>h-#pQZ@b441{`huQ2zaj&=O z_r#ZwgF!JXL|HSL?0oMr9=Iq_V%RfzE9HSmyl^Yw=Pe4L@fO}m9qrxCTJ8~nx4bPi zlVr3P{k#C}k3_vwA~b$3Vnqm-nBj(~NP8EvmSn5hItOOWjEPjr`opHh5Nm^?rVfRkb<&>M6GykmlLjtg+LI zPKa{uG#2{rPp$(Lbrm*!{3>~N!U)EdML*=QK;K9O=6wQz^S6G5Ls$Eo#Odc41CKWk zA2J{wlxotUHMX_#y2{?7USepUBU7b};T~kSt6swD$Uk`RtKoWMg-!ZF@b(gFekFUI z240@Dc9#>^1$v6?Yd4cyC11|uODN@4#dXcXHA?&7Pu9q+vxKd!On28zZVypS1E zcgZy`4YEJxXQTXmSv5BDnM>jJg9Rwx6{-r0ryvWw&3b=I%J}h-iGtpT>c;aYj^3C} zzJ22I;{Eq0dOk!-noWGkd?)!fQTnK5B)Q~C)}o?&hVK*B4_yA@_ewB2aCKS^G)nYZ zZ8Qt=Mo)8VCDSZ}J1a85G4UU4(kC01X}vruWf49*?IJ2Zd+ln&`q#8nY-7hQ89SSs zb_GXr$99Q3fu+Ya&+!H#fDJ0X*KY-qJ6#H;25%55bnWPI6u zUAOx?h5ff4JsoOx=uM5+V=)e}fTpDavn~npI{31h(rU&c4dDg3cd8KmsdXjPcK4PK zGdyOD6W5GLF_&V}5sGcE8pHD2b+s=}vp*51ZI2CGx4i0ERny7YW&~e#MdAa`+9qe{ zaM-2ry&KRLYL74E-|U^3aafyca#V9)Ycx_9EE))DoR(rW{`{Su+7Ht%Suhrh$xEI? z*&6^Uty_S%>GVw2hDF?-t5$9xBkY`2)90d-dk~bD;Fl zR=iALKiUMRgRwgT3$(}Aic|QnS$MiA0*dZC(?cTY67gv1r3D$~)$Q$!axwR)>56@y z(HOONZ#EBTvYSpnui)S2zBm$+&0Y0D>xGk8`}TCfu4Rc%z@6@ffkKr>tJm5KJS(Uh zg@cVw2VU_h$!&oD2q7jFvbHfT4lk;B8BL#8j2}k($O~op8+56&3cq#6ZG4nkleH2L zKhglsm@;gf-MA`{I_R1RJW-F>WdePc>P41=gFUPczUuGZDv=gQ(ycM0Qf0X=aCG;;D z8a!UK88yM7vWo^Uj}%1=0fzcTW>0^G>-}Cm7N0gPu$9tOP>*|K1 zU;6~?jlRL#TgrH@PrC2CO!jbk1iO+ZkC|jv(}eJlxNXaqQXnkGfBncd30elqojNaJVlf zQIE4%Q043>X)8JD_dE8!i-WqF>}jJ^dBcpTWybmz$rhBp?1BI;2rn(@lBXZa8{N8O zr^vrty&<+YX}_YJ&)WG;xT7J^v$Ov0YVt8BuftQuV@o-jezB#%V%pD7D_I$NesQ$% z4UH3f5q=oB*>i)_Ln!yP;g?aKsM?$Em&5d-+X|BoUOv}kt=Lx{@0>l~2k%AqrH_@* z^hhy(b^Dy??|w*_#;0w!crp}2Uf{ju=;ff?5HH(mG^*$&^VBp#mKeOSHpx7?8x$^D zAO!!ys9(S)`}~p&x_9)}ZBA1AC(`JJMopO?aed33!s~AMyKE@}CZj)E;VoYw0?P=2 z1znc{27gFk+A1(qq{c+DMY?d|0!`K6g=i?^K^m#}HFK1_KuBPJ5|**ufG?PYso%=L zmM1x=VVM@m`e&H-U<|q9Ex?5J6;d-2c^$TK9k@h%hIzVs12EAAq=c|iCVvX6E-Xi? zlBA-h2wd{mhXh-K(XDfJn06aJf_0eS$ynrHV(`8WhoKHelSW`|(hSqSuYDW)o0iGP zu$C0$Sqj7JfXQv(ul*)c1nxJSVC_lRI>kOh(#6j!WT+em$m9dZ6pIh1`k@*J`~OcCNSt-B!Djcp z!JBOFXyKp};tf)h@d$|*xQPDZv=a@Zt2fvnmDS%d{AS}9q3~jmY|>}iPjl0aUqr%X zEB3S|MfgkD`aZ&EunkfM55OV79jXH3s$<~V2K)a{7D$0U-GHMY74ha)Ni{H#sv*J! z{sKv9JZ@I>@6_%0)nOQ>$*A0Y%~BG$VUtl87?Ta0P`NG)IR{AAB1u+2^VjFI{h$?E| zOA7xBLhuSA|JQpzu04Ez`?qXy>rC5fPb8=*?1~c&hNYY?6CZgw?OL0)5A?_QJSmmS zvV}H)LfPTHF3FuAYDqpRi?CE*Ear(UVHM3tXtjGjzKElMgJ;nODE#!t_iol?u&oEq zjK%ijlFs_TS&|sS(8|V;@-_buP)uqgt=$Ai0NS4Y7BTKAn_JW9eUY@xo!T^^kGLI+ z2cwV{X(6REYC_*82^W<;Hv6H$%BM+upO1u3p3OQ;TVoz~8dLnll;j?2(t8L844 z(@?vVoL*(!e9H$?P+YwJ?m=_Y=dIn(c2?w;r;BK`c)SjEWH=y}esdR|d#fISZVb@ha#=T=|fQ=_{n_i~Buzs}d2xV!> z7XfqDB5%GNwOP8!7-mF<#8OPM`)q+zaB-?z;_%>bydGdFQ}wu?pn&Yn{{j|Xk(*#J z)woDfV#;A`xODZbO7{S(+n$FNu1Ke{?bc(^uUspqrd!Be;NoE6gGyi70q{MthhSL5 zR?()I!h2NNR%$ia!xC5H{YF1~S@v;8A4)J}C93qMp^5joc_{8-AxXThc#J*<3{PwJ zGvK`^)3{cbZ#CV%(%9JF!&6vR_VuCAXwK`jwe3dx7u9hWRc#IhMdUmgmnl^N+e;-) zuSOB^{bjU#OIhq8H5U^?O6_}{j$H@e4%XNe>&5s3N^zyM9x_L?oLxSKRXxizYvvL(4dMI5rz6^6*i^?Akqt5Ayxbfg50$CC*MFS@Z|b<9X|i zrjJh@483V{C@#42(o)FkOUQrJVpmhVi0Hx$=mPdQW4rL5)Z2h5F3Mb!zf8M0aBSnmcBvh2snu;YpIFz3A3Tx^ z*bRvGzXjH|bAsO49rMd#C<>q94n5A2BP(Xb!4?#?o1M~B%ob2g-F>Kk=^psqg1ouJ zF8f@LqElB*PG||s<6;;KFY90>UQFyfZ3bSu-^@F8b?hEkDmoh}Zy_`B_0}q~&L)i{ zm1^%$9o1HNN=H)C{(4TccmhLI*GABhrW{^z`e_fKD^}C$7AWI&GV>cn40xo6tSCH< z)BW3o&RhWV+iY2iZ`ZXVJZ>m7u%@Jbx6$&^Pr={k_wRi*TM)Z5d0aOqB2_J{)squa zCNBy&+5OG%>Wu2HyZsb&)R*~j;epZ)wsUi`vfC=nri;%rnCK2 zJxX3njr~ZKqq~LdcCYip&gVcc=2T!Qt3dnwa#)y^7r%$ky!}kQ`+HIG3W_3)wJLmT zZ^i71Q)|a9WT0vyy#_lsd5`3~@TW*fi%3^rXtHpk%&XL=wWHima62Q0Rw%|X|Mbm-uWxE;!=3!b(12!3x^M= zGgkxnNIv6r zh(#iBg-haQILy?$NS5iNT7jnA@HAl{3pp7|!~B|Qny;CQkqee4>Hm^$Q~xb~PEjiz z*?A}2@lcMuSk&+7mcM4F`Tm-I&6Shw^S>weOa3GBpKxFN!+$OjhTWzfT9iv{M)Y6(l0sA^{6su_&&oJ77^R zbE|szd!#vuHzo_+y%6~SmdoCX*fYyqbZ|=R+yG^D$O}rI{s~Q(p`{u zVPmpOZKn_Ap`!a4<+UiAihq|I_YgnK4iokP&rwKmB}VSbEzmbQ={9IHFd=RS?M%5Y ze!sVxsj8KZSHs|^M67V`Gb=vcOO$=W=6iR{Wxlw1Xc2gdx9@Am$-Pqp<&ZJ%ZhMu> zeySc$6de;HS`oLs_}4R%yiYH;q>jdhZ|24V5g9=DSc`q}#ifjjVaLD$)IE=DpBjFA z7|5AF&6>l0Ag5UnROQV1kgMyh-8vmEK3DwdvVJ_!*FGUve*?;m>;_gZ<&?6FZ&DEK zik9$2&gZv_pVp+$1ybY`3#)+5n-WV)>-{Bu=6i0lJ&az`=e5Sy&N|&&o{uo!b8ffQ zqLuO5%mX~t$AJwo=Ht2+=2eRuLzO))?*QmSM1Rainqt+s3C}BoSxLc6BQ|XF}r%0y@d$+wACpPOjdrQR0>9V$6%F~+m zM#9{fPT=B)q!1*N$>}Tdf@(zF(-fZD79(0t0<#}FWjBUs>s99jj=rWYbsJCDTD|Vn_B26^pot^CxnSV&<9uwjOh!dj%x%`&MwDEYR8J>zM|K^lJP!KJZeWDOQgt$ZJRg?C_y^mbsU-C0e={7_4+Yp?1#W+qCu>6JgMao$q?|#nt$ZIH3>zi0v`#U`W?mW~lC&Q)~ z`M*U!Bcm3hUN(Pa#&YU3SVH}d4pZ~FA}-();Z|`kzdwF8!Fx8J-@L(0OPVK@F_UcdJPbln1eeKf>;09M_z`$KY znfsHr`5i{MI8i{XC9gsH2!{5;f#fAcgj{&P+~j(a!W4zp%5hNNXVYA&sX$6zz8Uo> z_J+zQYt7b6N2q)S6Jn)4#abD?-%HIvD9R>W#_*y>*|<$w%3KMO0KIO9z3|%~9RA*> zy`tag@lSHrlJdtarthFApW_3@jLx1ksP)IP1TThtH;Lb%!^k^}oats%osKZ-fX;w6 zIUA;w;Al~(LbIgx%Y@Y(M00Tjd_$(yr9}cGWf6v@Wj*~)$mOkT1L3so_!|S?$oslu1^?V7_80V>vv7?#}aOgObe7l~I^7GCGOy$AICfcvTCyu(~X9U8B z%Hb~$>W-j$nTs2LtPSv^FVV-dIP!i-INi?P-A|RjG<pSp?b4T+HZyf< z%u~gLte8YMql-5@yASjU$D~l!^|K)fp`a5Lz8tel=9j5ngn z?l<;e!=sn0XPYU?r7vK1uDzsbXq9biAZ&$R&QR-`3c8mP_%)(8M@%TGnJ`>B(?Fr4 zH1Oyoo9N(dC9-MSt${G`I!Fi9o!3!EB=LusN=@!u#dgTc=CgciSNDw7J>?r`QA?f8 za2k9-xR9@Bdsjf}*-H5vgzMn_<%?5YkBvqRN4jQtsq4Vhy{r+LsHna5mc>{K?&l6i#OZ;A!bHc0z+ zrgo+lHzcwo_X-@t5})8ilqZg^HpQhr^}1cwcPeEzlK$s$wTSa{Xu)}~)WPh%yfc-( zp_X<{2Ew(mHWtx*;n8Ms#R0%9@wJ?u5pHcV$GKH*>$#4h=B|tB+ku-llHF0=My%d0 zH-z>ySuhcZkgx{~&3d-nD!nQ{r(@JupA6r_(k!aG8%>TFB{DJ6gVzB2q*=ugk1T98 zx6;c>7EGzRDBna^rV>W(ajVTy{~T(n#0G*`en`{AtPHkS3e&g<@7HPnyVtD4d00UY z&i1)u0(&hrDXNah*Llt)xeSAsp(lo33CMa58?Lp@mI0YM#i7@msVt+Y z6DhHNyJ_3FoVM+w(K(3QtDW3brIea`GAt;GE z1YU|5DGtnE32UBIM6jzndDO}4P=Z!;CyzT~*&Z}|2?74*jtZ_GQs+u#{O}AlS5KC? zo%C+=-DBO}^a;ivn|+eFp7crbTWVv-x6d?Y8iDyKM_xqmD858|VsW7iMGBu49gVKI zB6SGl*??;{p4}+Sv&rqn$aNs%Nrvuj5iHvnl>PLyS_qh`;x6i{&lM+ zFy@X!Mt0+KMou(z4j?~mVROZ@NxYeW$qBxlPh&ItQ-~o-GYC(EAZK80lLXi^H-#C& z5&W!GLo>tEEoz{7D0E0k_=M*qPqpEmWdD$)`LWJBqKZLyk?6PeGndcE^EN%gss%;` z$-armiJwk3V(qAZUF&eKm@y})zM^MBmTH!wj&O(3dh7F+&QH9<>XOWMC6_*JMN$Ld zVxQICLBl=6#7MXIHq{eUMvn@V(36%SP1`5H)RV_`FHSCp;a%T|xW)co7w)Ww;_7N*|Zo<&fsutxQ@bG0_ zn}k$H&RuTL_@r7C!WdK2Vz8oa=+(AZ8|N(3A=^|DAA)}{j=L-~Bi=;FD@5d3DAg-4 zfQpup^yGR*qf&nOc}>i?1tO67QA%3a>^Rmt>|iJYnfdaG5$n~`&I1>GIdn$?BnC|F zH#2U@+@VprD#OnQ(2C+CW9oW+EVCsvJN=ktD26r&N-yP!zTHo>L6s87vb)dqPNV@Y5+!{NU9!|dWrg0JY(yL4FR$qiN zo}vRq_|F!FcpL&{jyFl9D@S*@2k7xIg6^tsbHee};rNoPDLw6v&|}#R5ApCOQvY^h z{Od=s@)O}4W2eZW0Ji%9VR`CWx1KB^buT~kIZY4dS8O{Q=?N@VL`swOz1NBfc(rm5 z(-0+bB;!nEz2vUv+4FY^%qA{vj707iL$1b~eb*xJdp<1%1YPV{O!}Qj85Zz;Y`k;- z#>^epSbk$APR|p~-HnokG77@&!k46TU*L5Y%1y5=e#)b8zN~G?0J18k@-;r_ZmwVx zWj!{oqMUH;3M+UIB`D#c;WaiXRDgt2PYK_eyLH?)2v6Z?)G$_i!)Ts&GP#*gluwineq+b#T4lA-4utEFs(?j{&* z7sk$Dhr11g;QOYWXEHv}C2RkbEVuxu@SMPBBIOGCSAy|OZ*80N{{`S$@02L$M%OW%I#{OqVvq23YkEPP}k#rm-M3khvgKL<+i zPS&MCz2dus&(94jL?8-zT_C#*ctXLQT~3=y0U;(*ffnsyz%YY(_Hg)CfV@(t&iK$Bpw;%#Q~j_!!%| zcn~`(-Ak{kJhtJnzcsn$K*x)3+d=5(r!|{#yQx%8B?+leu61Ko+oPnK2Q%hb_)meH ztwAGW--cYi9&!V~YD ztXDnud}HN$+~_{ER3$n4pqPDAKB3%wTeUqD><)`PRc9FPR4N(@Wh?ukWQZ&9e5uG= zc6Gh6>wu$jOw!AK_}b@6m561ZLcY*rJSF3kvN_vY&jNExK)~3{gk!3~!Grm`2LCBQ z%%YPIa9^mgm!5ThMMzaP;z#pcH)1uMV)Y4r8Tr%Haqfjg{rtlj)t>A(N;YrmB$pdA z&T|$Q3@B$`Lo^$6&V(GTj3|Cfen9tCltXoW<@_g&mq_{PYe5dzJaPpRd0rQkjumWptO_!sro<92WS^!>7_--48JpCGt zg}7kWnKu=57ad*J<&va+dKlpz38Z567UmtheG^y$*PnXx0A7s~(@`@w7pwCq`6{=& z+s62i(-&w#0a4#8MnXRxIAB$K?B7pVMh2?ielC7yK)0Iq0q6Xn8uGPQrsy>qyln|p zFg{Af?2P2%Q9x>8x8;Wq{FtSh#^6DUpYg}S&L91udY}Ff`&aTPrYj5zelM7mE?qu* zR-F0fL>v3JDC?_v`L9$dGx)YBE}H9|b8loge}3qG@Th=@w)Ltp{aV-FB{9bFvg8Sz z&gPoUOtrM;l4rL>l7v`C$KrAep|(0NPWGzYrSUlYoF35wxopa()=^qR=WYl!M|R;h zaU;dmUNz>%WMCur-b|K%20hr@uyM$JbV$1-Pc#yDt!HRnE}>hc#6$hIrX1ePs(g0r zm{{TtrvHoo270z~M&JwE4r&Tet>~gbOURT(~m-Naj zlJMW#A1WT@;x2TbbX& z?zzVf`Bgi$y3%&mc$|6^fDh_h+?AH{!sCPrYO?!t@u~W3GCp>kn;ILHbKhIOZ9v6}6&M4nX7TIOPQ&O2kz#Bl4zQj$)^&mSWrEudmA zVs|{rW-o2fbPcKy6YGoB9VJX9I<;n~Fn09TIqj_P+-2imjyUA1wzuPL&ntx49*A4_ zQrH<2(T-p27DB~Va@?|6+>Ffh|30Wy!~pRYZerC&K=!z}yK=3US8UHmn&mFv5R@#y zB!zYpOE<+0?y7VaR*%-4SR&?i+Zvz!rnL%{d|^XVIjxSZ6a}j01?b@-tB-GMI38~< zzV9NP9o<@NC%-W_nmCp0>N+;9%vQOPw4E78{29$S!9Hh^&&FEgxb$^&dpDIu7|+|c z&0Zs0>CC;iqo4_uq0D404p1kQdltVJ6G%we+sxTJIxhc#(_cV!~qU;uOXY!7W z3~SmNVi9ZkiBO7;l30Lh=|>{cmTRA!+R|QkBU@PZ-fE+rnT%{%|Bg##1`&B2U}2)r zd*76rua6nlyfsNrOCcZ@KtDaNhkZvSg5okRZ&_oNjngjIr zI>$$P1`gwM034rR=|d!)5o0Hvv+Dvrx_y)ISI|A1JU_wDsS*3F5?+j?`mPQ zNPNpiiym4ZayGP!Yb2P-5t>2_lb@Y|Rj(|lM~rKPAxcBPWGKoQv4Sy?Z1@aC_~As+ z&na^!L3m<4L$R&`p@8}Vf82?KQhWR^=Klb<#c4^rdmb${ND3g!P^7HCpsEdY@My!gYNBl@EgT2lq zZsDV+x)BIS8lb8PVQsG2E!=tl;NAFGGSwpvt0`h8;Ya*_X~?__|Dy|oA`Rv$V$S;P zr@UD*d>v+zl>LE}`QJzIC04%e?!GfPc=q3hxJV(B)bZvpp8qg}`c$#<8z2np8tEy+ zaHI^(-CrP#Mz=^##94n^-uK8API^QVRNa942j>8FTA{(BZYQL5&iOHib=GDLUgGRjFBJnn`CA!=8Wd6J9HR~6{3R-555I!eWH?2`+ zj5JAs!(l~ToF3A{XwMaCo#13VMit{@0yNMXPeSt7ye3Q<$jInhNdGSY%kk1|U0r;# zCaoMcQN`b$sgTWf?D0#y!j^9+{>gU{Q;`N3dC@nJ+#ImJYwSC#U z&M=bNtHldf8@>nG561(V%p?~c!Ez))T((ulxB0RWwCCi$8G2A3xof5P9DfGLM=Kv9 z<<14(A=)JRZA-(El`x~DwxhQD!_5_=6{C{CA}Jm4#BdIYcUYD*_>Uo%KPDDo*^q^N z_}guG{$;>QH&dP`AFWc|YX4Q+0`R#QfiFY|2jLePWLulOs#pN0N%p)6_UvYY?b#&6 zR;weT=4%r#1JQFDk(X2QGsu6OFA(2(RW|=;UREjWaN>f(^7&F(EMVvA%@WZAN(M4b>3UWlu4%~t zr3Jjqi(Zb*m3YQQo)dEZqYWr^j9j>WU4nA>LfkPPEE_#SUk$yUn{4AN zDDG;R?%kEZG)Ht)7S9+h<#2#@+QO8w-B%NMuzK0z`lqDF<3At-0V{#*PR7Fn;t%e( z8_0OuXVRYU_6ex}{t(01h$@iEt@&D2?3G`B`+_f?cSUk&u)26V8SS8V1(@6(>fd^5 zZ)I_1vWgm*jHRwX3)57G>h~*M%R2+E9LclVp8jTpaj$njhT)}+$iAoT=dqHaT<_lK z0=6!d?GUE=78AF=r8^?>8fh;u#OYIvF)CC(|{_9)dog|^YvK3 zUQ?@s>3d)Idt(hnS$!Wc~v5l34T)j3Q4yTYpzc*F^tz#&e9~tgH5>frPt6CM<}j z&ndh!oP7rD?F*Y>Wf}@;Gm=3K4qXO#xiE|^sCBpUlWpt^tce|ZW7e1TPRkN|0du=o zxXSzlA#`(?!lZR7B7#BDbwcq|{G`AgAGNH|<^L5uK$) z)fjx=luz!9Gm13?=TE4ZI_=_axwwXkIm1Q3!1<+_`epE(oK3Fc^yZeS)AY;rElpRB zj!$no#is|Da_orCU#~ED3idUZqFH=R_MEYge>i_47=Gf(f~y=G{P;^+-%0RyIU^OS zG*UaP$f!v0|z;nU>5TQz=UG>TjS zZn~4>X$o+g|8JlFby1MrT=DXyf$ptaSRqbM1mnrNf1jSjg#2@M|CANnmn0n@1#aJh z|GLHr$N%T}Ir1dQ3OqFh-+;l{F0`Gw9+{gv(lf*Q(v3o|j>(ONjm-Q3I_v~p7*8w* zC7SG(ito)E{(O8HoRo7Gc5j)xBQ6>6O0>eyUR^mW#njET@veJ(mx*+rx)v%1WXPWB zABXQ_UkBk^*6=q4;5*nw;qcvT_`WuomLhx$+ng1ae2;|hZa)KhNrV5B?Ec{YAp6U} zzwQ2Q;NN!t;p`8Fzh(D<{lV}LSs2)VLHz%A+aMQL&{>-F6*Be}+)b(qCT{A>k-~rA z?pR&g=!s<~jJr*sD$0}IfkQ5j=06@=2kdgE+$$UWn`m{KqWErxjMKkIZUtKC{w&!w z%n_e;P`B*Y(aL+t=v71mV@W~AWEV?LV*MD&{h#ZbryFwV<=B8sN=4C=@^^UUcbfe9q@C+?ea@>u5|1@6><^- zR@u5!Z1;08mVKzFW|A~biB!{@2ZaW9i2V0msXG<+vM3Y3qY4LOpGTO>T-yZWZ8DkJ zFA6_>FRUP5RK@=4sQX(pCk+zDMWe5o>fh35a z7?IEx0g-NtG-;uUkYE~0Vqj$#5HN|93|$1I=uo7L2#CB%-aqfpH|NZmbH4N4`@3`R zZ|O`DB-a1*-^V!qEl!a{$emsc)T{+jDPSua}kfZM65SK2XG_c=8?A^TP1`1=Wz zoXe%aJg^T{z^g`~w)3k^tTtttU6?R6%E=7opV%yPD-3&;wO&vP`vz5Jrrb?iSGFPg zua?E`&-y8AeV+J^!!yWV99~|K6i{m>ze~5p|0L_R?x=s(C_$EY83pi%-{ov#T17A` z#D_{nUmbJ`Kx}-z-mb+b!WxxfSOb{4+vl4>+hBKfmJ;q`eyiZeqitSo2?or0rlm3( zyrcAWIVv>pU@Ev2zoK3Xaq>_y+JYySf4zUXm91Y~8-die#q4AIA}4Q?&biPg%68sW z6#w1CACc^_zBYlb;EHP6dGWXX8uV8{u*OFByN$cZSO-Y5q_wd0m|LMyZU9WhG)5cR zYZCCL)S*jM6|AsPuZHM)1jZ|O{=&7%RV(rRP635QXV_%*-7eJXts|rMqsPu&tYX3~(6i!LGrwa?hG5nsid~7s zdiaCztPiuJt7J)AkqRbsa2YkZ2 zosN<)o7vRqI^_t%Zt_QF3O2xS$8+w(4~hI-0(9`cX)3VKAPv3qV8&c$)|sb^Oq;iw zzyaOO!*Mn0GSK=I;{}gB7tA}swf(xfzUk=6sivm*wfi-pzPJ>+a@JBMSW?m4x2Xn@ z6tEP6iOp*LRa#eZ&gj+RaxA1?;ezK$w8K4;ZGPa>9h z+jUGiO{Rrs<&S$~J`W}rQFbrUL`0duF{{j0Oi0%}?}E<$Obc*CF%B^R+kAihO43ZU z4Z@ETdDJdHy&E%Eewud#Iu@cz*_Ytk$__GIi4dqrc|tgMOTvV!l)l8-@`FkqcpQ}a zh8D%p!wF|w$%PD$cR85zqt|$cD3+D|?Oaycn+?WJ)poety3U1n{FcRx{sTz!{<4wj zkm_m2-Ogi)f;e`U5;s&GH_#Hs#sTw}%NjhB6MH?2T}whJpZ*M;E4(GMU-ErOiH8L* z&?#n7v+CO8tAC{OS(Gj3B|TAnmo1K9FT{*_wixTrxCBg>UNB2a+XBs`uSfDWU$vMI#}Oa@025T=Ly2JJ2I*3A^>Q8D~> zbo3rklZAhnmjGO@B0AAmYmc*Y^=2>-+x$?9I@gtmlQ`w_oZg%jZ~rcy#j_DD(ff+)ov6M{J|<#jA)&dfhvue&O@?{|OS zLTi)+cyvum$(~A~zX$CnzM~y<4p%Fld`(XRQiVz*4XrE#hhDCSG|*tBFTwMA*yU!7 zn>NzXdFylAX#I2PcrqrChd;E3&vKRc41Ro%Ux1L->@ga1l)fnffY8iz8>mN^k+%*= zoA7!$=Va6wB*7$Y_0jhaJP<4I3_x6}pJ*H2iG>C#y1NFBZ@EGi4OFV6JzWy#Jno7t zgzp$}+dth}jseX-MT@!QaQYbGO%crXtWODopVf}S`TPKbn^eRR!l78eaL2eF#-7>y zB_w~32-#bZsr|`@Hr(}Qd(d%OAzXyHiK9}TEtyo)&ajQaW0hHIx03*De90{f$vI66 z$x}EJA9o6?3kn>&rSmrnjs#tJt(V}s1@RvF4`D@IM<6X@Ntj<(mlUYq?L1II@hs&9 z*f1sy+U`HkrEPAgD{#i=i*6Odd0r~0`GB|W(h90+CUGZGGe)4jGZ7rd6@PQh^)wa& zQ+%z2Yvv~RWf@fsCgN}3CSO_*ru}y?%057^Ta-z3ElNX($S?9B0h9Hd})k zJQmnAQPW(BQ_6PT!^`p7pgU66Gt_W->`Ci!Uc=$0UCm}HhuUa8*S(kWlfah6jyq7~ z{g?$hU>JhtMO4E?G;2O?*VyLki!Lui@Q~Xo@CBcx#s}sG^(@fZPMKB^?aRUd(+`cy z;NerWue2oUy73w10Jg*LIeubjF+HZ^NJ!OC;Ihawu~~14f+TaLHgH)LScwG)Br%SR z;pFxiY47DvS_CjIhFcrh8bwTlnTH#@ETdDvWVhR zr!*>gVCa)B2sxQoV;@%k-U=cu`~3~4u0^i>YCyG@{WJh@h9?|~Z*&{yRGJbQ$tJEX1TtjZ=us!M{< zl_wB-5j0sVr=S`~C~<*(iTV0ElUlLOGB89V1Pk;hU)GzSOAr80`{?SE`>$)fP#e+8 zG{3zdqLYHieY5-O#nIVm4$xGj5z(yJ_!ydhm14wv))q@UG7P;qSwD>v7I@#G?bE#rmyw zH|FA9FHc|NvbSrl&9XHTVCNd$2I?g{2v?lS&6C~2O(Hc!m2HrkXF&R3avU_&6L#2h zO)0T-tLwc0t(siEarahn9oS)eB93w&H#|?~Ym3sj0qULRYN&U#YI(Kdl1NOen+)O5 z%qr%J%D`enwEXoqaKzVdL@f^v_@!edk9rb{)V^Nbf>Lcb3H7V7Rw}a1&EmHy)gGp7 z0*RJDlVtx_D~-amu2p67H6F zAstp3rM)pLA6@u5@O?&&Q$2~(){u)-Y3!;Uob~VYq1mADK!r|+KY~m=$t{Yw%Qmbs9r!p88Z+y?MnzQNK>R^ZetaB|@O zAdH^^XRiWhseI)v%u~dMO_#^E?#<#nPWs>biIU?4-Yt(5!~ZCzOj*j^u73bat8z@` zdNR0Ao{GONaw2Ba%@hM7#XuA%=dJDsci@Uo!i|fVR-C=QecXwE1aW3w#xZB0u-Qn^5|9e$0NhLFr z&P>ngGt)hlBtl6+5(yp`9t;c&Nm@!w84L^@_?5ngh4?y$R4=}LT_DYc<%Pk(>f;dJ z457aA-<*{tMZo?{;va*7L4YaAsft@!S>4}X%gM=oe0+RfP$($v&cQc5x)z+X*GeSCaeT3VXsC-wF9xssCN z+uLhcSQr)-=H30hy}kYQ_4V1={{8)}rKKeu9qs4mhm@rFME}KRn#-?(TxS4j3C7 z=j7x-ICOe@d)L?3OG!yq*HruY`NhY_pP!%K-d>-co*o<=6c-mqMn+CgPd_|77#bRi ziVCNvr+0UEdw6*0=;#y_6buXufZ6ol+?@0B@+2iC$;n6(dXM(?^$H0IfxGnG-Ca&j zPP)3eiHeB_1qCfHFHO&kpPcN)#Khd*-iCyP;5hdX6O)XMjosW_Iy*aebacGDyj)(L zG&MD)q@*}HI<~g9y2LbqsZ=a3E_M%1ZES297#MVRcFM^~UtL{YU!7fCTx4cu4h;>l zv9Zt1&Aq>WeD!l+pqHDQ>*(m{%aSq@f}5M0Pfzz06r}X@4DTPG^73*50s^Y4sztyJ zFw;&Lx53AkkC&H+%gakzRzY@dVRDcDn};_t27Xkx0Xcd3{rxR4t>%D$fUTq3m$wgc z3aZ-7FN6vwW!n-@ZS9Bx4g;jGVz)w89YD{S{c|B|_u@xAEk1*o{s>VciqzbnIA zpEJwrU^=Bomrp1tsO3|yy|YJd`Lk3hpR(a2pC6z8Z4W;vxiQg^S()eyy z`GSYRB0r9f_F-Y+Hc#KQ7kl7cloeF|8vtM1*GM3m?au}SBL~Bs-&-;k=jWgunc2}37cUwNO#n?Pw zcc(n;2c9ky+sE-V-|c{(J1tmVM(btiMpgn+d0W*22}oUE)IyJ$P6Lo(IW#Bv|6f zO+=~7dC0$a_gLF4%S~_sOAp!kFZqJ4uLPL3*|*>I5glIOK=7BYYri#9Lt2*3Enl$| zM2`MSu0$ao9qb*^sw0#8+?-vCN*>IWTJQRuUuXHlnM}C8+7TX#I5B2uy`3Q8)mp+k zpF$(kqK(!#v>?;t3SfTa7v6-;!XKT0K-K-xb@t_ z8xkQpq{7pvEqC)ZQ4y3;Qf7)HJ|N#udi9juUzoGh!x2$-*b?6eAUc`4LnkZe4;8iw zD=??(9bXDl4k2%DtkR|YJ(r94%qEv|if`grmN~mM+yKou6d0azvY$?=YeFhL)3{j{ zWm6V9oGU_7X4No&%|SvEeETk?F(UhS z6A7oadt9$sRGkLDf{$&cJ!p(JWw<>QQA28fOT%{A0uBY7OdnV%EZ)BtN~Tc2>{;{! zc}CMn>U$dcOmP7_gdxnh6Rw%1bfFZwe8R0oWI!?1#A6|M#_-;vD4^i`pj3Kxpbg2m zuY5j%r2&6P5`|x8YT>A+2{heeog2~F3RCFZ4W;3LVK@d=`W~7Q)GnL$`aDYE2RuJC6gfxsCvFDSC4=>ivfo@d<=d|q1aYEEapOL^;v4K zRS4$HKuhV`+qo{Opi}MyOplmcL<(*VdTUZ^FAx!7d||T?@B$X1j;4sw8$<(q)7q*t z!HE*(r$@3}H8^FVgv6~hh(}Y{G9`ejv#0*yaQ3?)A2|$`dC!g&krICn+o`Zpq-!bi zb0z>j5uaa;T+MB#oy2-ANQx2N=PoNPMqOFD|B1N01Vk(A(>brFVt}U?En6Rt^QjcbXWHGdlj?|w{ zS*#OB3QuzZqk($bq%zn&7!C-LHvmieoS{T;L;geD+NV7><@T2jhLk=$>lrmm@SIyJ z*8P6npmm=&);G%tSEfH&Jpw59n{|3#y5Y|@SC^YUy@p(AyemqLd(^W6(mZqCE4nv^ zy)86yI}lq)%!_DjwMb5`t&GOHVVvt1mq_Voo;N!Hzo_1*1HYeeMvqs zugylH=+%RB=hMYk5hWga4U3hh38kT$^=M!)P}_s~_}J@$@&{SXV&vFz2^Gjofoe11 z?8#2$WW#|gO4V~9^zp%0^aVSr)dk<_tC769r z1|b&4^+la*Qya={BVDi$lFFl*9qO5hD=pmq|(6;uN_T^#t&Y_q2f4~BbQ7aas zh*Q84mJIXNg;b?;NsQd~QI#l&xa=U7BEjlW8!k_*tY=&iA2yqWmpoDRPR+tsdSZ>T2Sf2dn>9 zlTgz+9moNrsO{`@fvgOJp#-|`d1`sJefzD?>D9t#yX@r&1%QL;9l1O~N)g$_a!D25 z5!9Q;I%`WuAh$I!G=&7W=UWFrHHB>PyfeTV0n2nppcCI)<>mtMA$rp(-QHmA2!}9_3nN!$bnurAuWP2TNSi7`+At~=5-Jji+zon;8TR#fILPE&fIK7 zXB}}gjpcu&S8X|iF0K$5jHT)ylVFF$D--bX$`gRquv&CPt?R7D(`)0&|55j?IV@+p zt@A1Lsdk|Z{QP3i$@0DTRGxn;Oys$C<)i+?Bvok)ZQQQH8(w+L&^2WfeTNBtAUbga z+^!xgl(CkWrZuOC@k%eTO;2w+H5dFU5Ain{6I5t8!z3j65wu`5jGyNRm=*rXDkz-} znoiVKGlU#V56QBMRfG?nI3HY=dbWE8+xw652MB)lpseew>#1%}`ys+hOrk}jNgWJh z0vr0C;%>OruJ|x_znoi;z9y)vE_*bfrHYqOzQ$V(MB@EOC0t(P-dB7NAnG`qm6KD{ z)Qs)n&8R2OclSi*!Gi;dYN^dF(?wJ50426z2l5^Q1y@l80JUcXiPgO3%3qGphpl+Mjb2YKaehS1kA&=`GY}R&D1j@aEGrkgiIQ{^AZMkfLp&qV1M$7 zf8xA3pBD6Oa^KRTq@S|d9s|7aaK;Pv02TT&Y;{2CG2`flD5eOhVx3GaR$G&)K);Ms!y#<)&2;4I#hLb$1{E3N zW$uRlg5~7Z15CHL_F|VJ4WgzUqnP2do(2&Zbs;!2%~mM2UbuM zT}RQ__vOIJnWHnPXQ$s&@k;1g+RJ3aLBl}5(z|@>ew(!|q0NyGHk)&#OhgLhvL{79 z!Y=a7!0gNpa*p|@5Zk8N(94Xq(r|}qB;)NVIbff0O?eaMizks7wMLk@&azZpVNJ=1O$Hh8o#<-L|0Ey zwKNXVvv>8pt(U!Ad_!P7sa8*tmK+w*n&TlE(n~VrN}42F|g z!psE9&J8(Y(HQUXSiW;v{^XY|O2^ORob6ECMXX}hp4bZ;Kj}N(-WdcO6HFS6AY6u^ z3%=F)3MM(L;h4w&`MK)>AvqZmSmUfkGqLBHYwE!hTWjdaWA$(DnKSor56NQ0xB>-< zhBaP)v@l=1xF8^*y{KmNBmMw7c2TDG`)W-eaaugQCm!8-)=C>}==_aI+DPT=59HJ` zUYTani8>nW*?2p7VXuN$Mc(xZR-_w9wv|5t7_7+0($j9m>iv!;!;8j5;+2Nm{3 zwR9CR9f}vA0x(YeLmrc;?TL4|cxIZ+8;gm(QS|m$6#z05h%laVvC+pMKEpYj3M;E< zc5Vq-_=d-6ae=CYG-cLZVZtObV+10N%l?kK?DT3L?*}@)hw948NCv1C1x^?P^D3Fy z$-*CYYO!SquPxuS82uhzhLfmOX^w~l)j70HJ&vj5HlGCCZfeVv%F(9pBTox#pa0Id zZ*_X!c!`UPdwPm{#?B-Lp7FaROpWcA%vbLa$BoX^srYld`e6ZHOobL};eb|6?Lb|< zT;u<%`Clhb8g><;J!WoPiLIJ9DfhSMTa;Yc&3>~^dU1ejh<2y--KV|GyF#ftQM_7m zX~hgY8+!~864Y61zp#-?#t83vq;`!7h}b%tixmba1u5Jyea*_D69u z9so-cZpW_tuFUoZXQ1(Ix!#J9PFl9#*YXyK3YALojfvE|5;%Nv)IP88j#EK=wP6cRaYP~9;8pKp9t4mz|`Q5@K@kJ+5h}sNl7|;*Y z;3jrqRCUOPO6A%m+5#IvWLdV$cW$@Q|AY$rUd1Hq*=g-I+VATw5)o??iD`kW%j@eN z;>R0pLV7gD^zSmiK-~=Kd?Npe_wzc{Utp#fr629ex|1ux)#=;XyuUpk3fkNHP*fiY ze^Rd#hi$U(yUr@Xv0>BhOBOFoN)z@)U11`D#{6@OmXT>0(9b0ShP!3<9dQJw9}i_n z!f@NuJyMl=k@dXw&B7n9hlX<)0XSq|oqHRRt?#UE^}0VH1X}l1)uh$Zq}J-&bq02M z3>;Xth-%n#9_8J0uL<$aodi4pP@*~6;!X^ioitEnqQ798+3u7{L0}wp`n(^D67&?Ftv0U!#0Sf zga>{}9)dLo9o*7-mD|k*luO}NZ3ef_$^x&TIJD|JpnN;d)~K-myW~xh-bi6h;!-pE z1p8!p6tn_oA>C^b?57TwTSo#M3~}G7e_sDl$Vo}F7#vf>XlQC_3Dvd!!su;|HLIRE zQ1DS|+D66jftK$Nk`ukM3;reIL?jZ`uGYiCw=YFF#7e1(GZ>w4As%K~u22 zbNQDyiR~{;GoEDi47>s?9n`h9coydkz|;ij{MAtbEyvLOHG_^X?1hrs-U)tUs%**= z^!e$T$4l&8&%?I%9S0f7oMaJ_6$uqry|H~deRdk|n%SbkeP$KsiyM9b5C6KQ=^*zA zlPki2EswXIrAq>^*h_!k>*acQyDA0w^a0^BP zXAddk90%WSLfT#jFC&2P^J({b9E*1kit#WrL@$7`Il^p-0M|trtooJ|m`4DbczOAW zY`@uTPXAxhO9$6GH|;e2w~XG=QYe?M8$w`u+Wn01fYl z!Vy_YxRz?L&?{2lK91*F=4!UOIzfphTth$1C0v^*nYo;<8+v zqQBN4H)~(inJ!&8&o!Iq~p%?wp ztHMV2x9*wp6C=kq=_zCFYMGARLLK&qh^LeH4 zAw;E?GW^KSC~AcbW_zNCm)ucl)o_%7rmedh0WGk`Rf z7Hsgjr<`>&SZ^44$P}L3abG=} zbeI#%JLw^46P1|FyQ=d$ZNoa8?23Plf8nY}>Q_ZYwk|z0F>I2~K`< zB>@Y>A_eP&m}FckxQwE{dl9xpjH?N*Dl5Y!Ueu}iJA^cA!!(OLmYj?qcElz&MQ_po ztlO$64bbSm2>lbFt4Hdqw@^i*yr0)W7HZx4wxDxmGZ}ol%-j@vGhz^*kf6bW;lCo#QsKB!Khu+0&tjr8ZD7g)@TDob z!?2*i5XcT|coZF2hF;qK1(*ONXfg0UN2sb2m@Z4SA8@T{gP~lY z3bd_82;|SPRlhRr-zX@FpYzST&_JB)uh&#K%Raf6zU|ywTRWo;4%tN`RW z@c|ODoK?TuHi-M*1|eUgzzMjTaYHo}`Fo7qaU5Z05@bDLXlJ%?Olf|Qi0t4A^vSEw=0u*6Fd zn{~sYFt~u6z`^s}C(_zvDJh@PG$<8JC5N7cdsZx-B^nDQf3S4jf8)NK`n1v@BCCyD$#$Vm-<-s zzWo;zq+ZRY1i`e4^-A}ub%mw{rlCM~CBc8Wk<>YLbrZ_-hQVD9EL*#u61qk0I*Lq0 zgAm<_Xkw+u7Z~6YUp20iGO8U6=)p&=kq$@UYK%bpUsyo{9JGXRSLV7=h#aafslF}5 zwUK);>BCB?hjM5VzH6Vnf&MRashA7xL&D>~oNwKU>h}(4XPTRmu) z()`==_)GXJB2(U|Q&U&ZZ8t1(009nt>+4%;X<(71G?*{00;pnt=G1?J z{tJZ7SdPr;KIGaH?SSs&8*3#{aX z#}n<(>F_1*c2(%i0}S!9(u^9>{PJz7E@Rl#F;tMU1nA(UFZAbN6h{0UyZjWhP#bW) z4~PnW`%oA9;oqpo@t=QRWk?WQf#_Zti)2JWIA-g%-1UN*LVBzg(>;0%MzARW(h|=`vU02%+uoo|fdg^w)CiN6Y^L zZRnht&b#vE)vC8L9M3qqI{4VK?zfxyZ+Hi}7}cxe9Z$C)B%qzy`aH>(`QY4o*tTlc z>1?unr|Q4)Dn6hfKjfXEMwiS;uR*XF(#ge3w#ym1brw+n&(Z;t=8tVNi$_#i7y-CV zw~udYxBt`2eltXJYDqm|GZ9p@RlH1JMahofqB!XnR%PM9iEp*;wzlWRl{OHu`jmI#Y(egjv)!Bqfdv z+ao_b#>G88qJV7BJr1d=?mQl><2>3_Y+FrbV@#n>iI;(O%luAq?uh2%al=$(tJt%e-S9~U#h zN9G%odvg|#Yyr|Uu_d+rF{zpg%z#^{K?EEFLkc}6GIgwQHX;mTv1zEwx=mU##PrXZ z0V2H4-1Kr8QGkE?^@m6OZnGw3LtF2f1((h0T3Emfs#_$u5cd_vnMe)%sNq& z!7iwMAr!hO+a?W$(2PMX=2yA%whpyhhyDE=+6E@SN>T0$?~^@#|(K<|XsdI=a*A6!(qfyP%X9)fm9 z-c-I-S*RVib4gT!=SpteI!T++`3@P1f#Zl%je$S?yqnW|zp6sL^7FhNsu1x;5$+rd zA9c+VIbz^-j7y2^^Ha)m#d;*H7eWL;+fM~Qj7S2TZyaL0pW;tF#G3_QKMC>w(?1NY zjG^^-xp(5t695cj`i1Xw&V)muYRMvhIc=!}sxBBAvU&R?_BK`j! zOuOQdW}MJL33T9t1dU;uQ2gQBKU;RqSwsD=L6l1luD@uhLsGorB}sL&Vv9#4yH1U|}8v-4nKomTzsH=A^T)?iHO?OqLRy8CtMy)|1;})UBN31UKlxw z9_~FQ7#QaAf8PSgE@ANDbfnr3(gt*27x;~gE@SKz1->9z;=Iq|J&T0EUZ(xJHgkQi zmGGb*HS~h3VEJP?Ln8geVFH82>VX67eD0Olis9j=DTD-+yBkH|)f-8TK*G2l;mp@Y znEYF#dU{cZ17>i^Jl+}1Q1iE|{1yQFgwUNKMJJW$isf8={(}uf1Oa+Z5vtQGXEouT zjygj#w5ArQd+$-QOpQFp!$FMkP1cDnUmpq}?l|E55EeTkgfBMTYH0BU2V=6oXxLDjzX(k^F51)htH7|Om|M18zRs#Rk9}bOS{zv{uee^gM ze#c;ntVlZ}c3-@YAHdXWkFP3j(9Yw&}tE2ho#TrU@8` zfM$a)JL3Sqn8)}9$@mv(Sv*z(|8L@d%XR)!@_z&#KcN9YBA~5jsEWc&MHkX1H{qPG z*P!$!+t&Lw)a2G-&JbA8;~ZSi+Y-&U^wOj75{?_dl~XELe}sxj$%v1J%Ye-#4@G;R zm9jF{Sd)9~yo*}D;|tUUKX(w$Uw>e<+j7a_#eC+Vxx49$r^CS?Zs^OlCSdk%nRVja z^NnN{LF+0mkM2^DZC9&8LQS?Yyh?}7l71S!>*B@apO);EbuN;}rHgUEerxP-maoaA z6wEn{`QwSr`uErRd5VAIeG86?ZmPd^a}?<0IbQiQ&Z{8~KO_+*z~1UUfT?5Q25^sA z%stKH42KOlUhy_ljRt`SZLHEeQJGA{Hvo=8dn!-d*;)!(1d3dv;-^ItKFq%@jx>0+$g zxCMFWVWDGkTDpp|^!+h_a{~^pUaab0ib#z0W%2uD2d7JdwVHkpGX*j9rDtGTcBKcd zp_50a~%@f=%cB8TZv2^R$)i8IjF7zAd%Rl=RHQ?x7BCu~86y634L4 zaH1UA->{J66Iw4^lQ<_Krh+60rxLL^sYKGzVw-sIgtMh^p(;xw`fc>>qA*%$RU?2W zl&r8U1Z*XxSs%+!zGHc2Gv)d0Bn2ti6~9B}5F6^-M8@=|zjCeI18$ zxwt~EJnZXCfw11+aT6MPuLot{2fSXO8OAME)rkLlStUd0I7^gjyQ{UYFe zOHqS|<4zpBNCdKGlq;B6l3vVLdpLWgyh;y*xg@LW1b_Uo;mBe}EN0%E_k0>wzCT!i zNfgwir5T*pjMRmfabsxAsFC-qZF!7${DM>^1896H=($qb?FPPcjycd%J@U>F91 zkF`iBVJq(mOmqjkcKOwI+lEx>PKCs-%h7$pGnungALc$nN9e9;x z*VnouOt0C!IwB8y?C2>M+NE^=N;$GD@v_@0^=Bkx{1mR+tA!Bpk3Ye8rrc0>3tuNo z=`c=N(B~x;3#@4ZCFrc4Ib zr`hF+@oM{@R)B9GQ359pr#G1ua4*iq*eMzx9qE}2k`)$YqJlM!9W6-SG3fRkGZ;L8 zymbf_Et45vw=k9l)G)!J8luI;)L6};51t?=EcaRzsZ2ppK}s<;D`v%GssH%AGBp)% z=B#R)zmfM3s@ogni}m^#JH>zawBDq}h9hm=hBh;{esEMkryBDuUkLi(yN+ny1z(Q= z!V4GCMTqnL{Jsg)hQY=8oED6i5Yds%50V>P#Ur_SJqNqEW*xPy1(btR#*3pl+Ky={ zgC$Y@D2>_~6yhMr3Kh#R#FKNSxt~NV$8yaKl_ph>E?8_MB-7RnqA-f44<{#Rlkjq& z8YzH$6NZPRiRo+1J%46r@`?fXHwdXF?z0;9+NOYG^po@hNtT*in+kMU*Mm$l++9DD ztTAMZyZfR3!-)b_V!CHv7POWeM(=ETMn>V_&un_pHPT}Rr0zK{$951F2}fZ zjD3AIvMQXVqEU6&nLlG0H`7~L>UrWQS)UZlH~h3#6v+WU*h3EtkhBKTJ5`GQCZL%> zL{+-xByLyYib7bx2!)p0%q9j+7m46w5LH0jGTnU$#SSGPN z@}^e)i5cw}9HC`(h2TPzN_o&&vYz_IQA-%>*=x<5DX-0ccUh-w!*rk7XzDduXD9^+ zFB-)AYicRq3focD(sn;6Z8U0g!HS(Ml!SMriiUu)!-nXWa1OjjC`r!IA(QwDuv`m2 zM^C5qLCeC*@r6gZ7s0{T@p*Az_Jps*lUE+iLB$zPDU)EVNjm~CR6&$%Wvh*I&pcyz zbUN>r4xBa*z)iv?FBeU;4-4>_#*6lKQUVGu{KZ&WyHif6T%t^RB%-x z-~1YVNp}`vp~CllXgqOsdfj#wBqpA3bLgW=(9wWAJ-~j?0CiK75b3PYM2L#m68HBc z7p~}spnS`Gh~ngx(BGo&)4x!d)#&8c($fD>|ALj0TT-Txq$=PpDpDVd3y?sabCE-w zrZMvUByuehtTyp<#fu*`x?WzZb9IN1@e+_;#TlnD0_H|2K^&)27$YCE@Qm&p|AU}E z_N?fEBskvLW7^>+-~G4mz+mGrN4hdI?Q{e`oq&MMij^rX=SjF)pO!?2BGmzy>(Vuo za_L?X%(v?ymC|_Jj+aYvc}lD<#85q=f1lbbp5(Q^4BZn0WAbeI>6s+lWa1JHkk2w*UWZHc0J9nmTMNCxEp#vIoBZ5mc$%g{#%*xrS_^3!}*?KHSIsG7fIAVTkm?ZZqvy_s^ zZKeuC?rI*c__0+(_`SN>Y2}xwBs!RsnTg83Nrdz{Tk+Ge;{Jv9DF`m9qQA_L#hf0h zA)BIqt@LqMyTD4FDPg2k8?$iRM$MM}3t}=d6u^yq|h19%=b>5u~Kt+9K92UE@3)d=D-XOuO=s zQ7)%9sNd&HYALfPP(XKRB+pLf5kNh&OMCu#(0>UeBL7-=uLVAt8T)^indTf2F>Ue#ro~v5Io(=SHb9N~mY1 zRWWb}Vc1i;r==Cj&y3)UG_pjlIv%dhJ9+RaB0~4j@7d1t zjAhO>yco`f4CZVqE*t4AY76jyzf)5^OLUvw9z6o{Q|{+vx&kh}i8&&h41L|>pDZL| zlCc=5U5!9RY6sZ5ZbUNLo5k1~-59Y7j#8th%eLn9wg4nHzr<5kPo+s;fEPxdZM zfF8A-46FvFSdsiAyRl38PCS)^%8_xwibG{JDK@; z9S)T(=-nIim<+UKyg%R8x0wgdY+{v7tMU=%US{iumQWRR^SvLAr8ulVJ*%DGQ(7KL zZ=9{px8IT{N`JEigaQeN9rK8hHYYaIzkb{Z5$Jt=X|XlFuk~7$S=%VFeanA~3k}$G zZSBg~a>Mn5a=SO;Q@r)NTT1hLdl<$W@owSL&K~kO^O_hNE2z*cw>X!6)20*nVV%eT zj2-co7AFHKPc~a z>fOvDi>K*2Ousp4n!E^eLOO5 zu@?ItZf{UsA3sBRu=&wo=wNA>gkyI5vG_=&>zeLE#Wg)6%yC_`_yGF}lw8m0Ed&>K z8s2Bq^l{UDO2iHGY&JG>^Yow0nxZHGt@g7ds=H#c(Y2J9wf-HCueE0JoN=%M(J_BV zc|W)820V4zTIB>hs#^)7^89;VZ_beRMGseoY0VEKSFi=W&FL!W+uowSZC)cjAmep8 zuF`4Xv%Ob!-Xhqph3|54c*BaTW-SR3Bg31pB%LM%#4wM;R`F3UGt5^WCa}P7K(74@ zU(&Q>cSsK-DLD_<36H$$$2U$9ijT><>m4Mo3uCcR#}49{5QUk*y=kx+7nNCJSY+<;y3%^!9;l@ggHjxs)TqToI3=e%Ly zvJ<;?8bpoEbTlPk3E+7*{9-D1f8uEjS?Okce-S4Uc-{LRUe~*I|8!O(dD_pu8e{*) zT-H4or4=jhnFBxq)GL%_?t3m2QPfqNFKoP))fSGk4)xGlKf3m11zGOIoew>5SwG+&4%jk zo>K}j8PmJ$0~b5CZ!9kpat9B8!^W{P4WGC3THODE>Vc0B=5xk}nS1XuQ~Fc+5klb- zJFe&4VMwe5>;!ln^N3{v|Nfn)b48f4_zLRs&610vfMcS!%Z4uR2G)>*2w43kdpzscT?YZlf;Ee}KC>FTy$w<9e}sjTDUSmM)uE{UWbk&`?S-uBftkV4Ny za))zZFiIzSTo9r$79ue&JMaY`D(#6k8t)A{W(V#N2yX;hbdSis#Cv={C*O4R{TKE6 zcVJ6WQn1HMm1&|!Uv5G*2GGHHtIT|EYXBjHT=m4%sb0H$DcRIsaq6ftj62tokpx2# zMhO_ZEG4z4e}iHtSk`&?VXs!Z5pG5{SWkE zRniF18Q_4bcKs4g=X_n~B&x2Zd*|5#SbL$3_mVrZ&gTh8Ius>Q>&W!DPIky_TD7r> zh8xCCMH=qo;IJ_j9>p{(hzP;ivi?C7O7jj)+Xb%OijKJE(+)6_3_$|!tB6))X7YT9#_sT6auo$KH!|^4bBa4B_h2^Aa zNon|=v55rPa-*`(q~gDvKnY~p-zD7%kO!RwU+-3BjxD`@C301;ZRY4;bZKKKbilM( z)1v>1xI_F%YNv8(p^v4`o$VP_jl3y&D-0_v6i}3*k~!L#AyI9rir^I6;fhFds!t~h z2MJi^q0Rc|$gmG7&F zlqIopw+zL?x5(643>zZT3%RXbLn2Gmz@K^zg=*pxiP;)=@;kN^APq1Yg8SFpAP*k2 zUyW;{JM3dZa5dWV3lNM7NQmV>C3b9^mT4v2NUYR9Yh4;V9#tvuHFj#oY3Ts@lkp?$ jV&oNL@#QMsyMjKTAG}v!p$%Vu!GTGOD~SCOF$nxWE?TZEM%(`_1{Ei!*bsPI8f~OeW8o z$x0^kWJM|~N+H7G!GVB)Aj(LKtAc=l0YN}OEn&d_8|k3!Ed8&6uo6)a0Rd@@hyOHz z{11b=sY;20)Xfr{|2O+pSzcY@=jZ41>*sB7@W0O2=ck;U+{44|&wH#!|UtQzkkmi9UV_k_cGFwPEJnh z>gsQAum7#EvagoCZ@!|gJ>Uei= z@4uicD=W`WPa`8ES65eWZ%@j~%AcQ~|Iz;V{CIwT&dbXa5)yiQds9$QFf%uupP#?K zyHrt886O}2`T1^aYw;y}i9TIoT#A zCUYHMr3eh+PJZSCyrq@*PK z_;?>5?Vp{Uo}Qkxx3|yCj8#`xy}!L|Z7rXlUtC;XW@TlGh=}Ov>4}SrX=`im@9(1k z#)yeY&CSgxCMF~$B|r>IH@0>VT!4ocPtVVHEG#US_y1^V={UJKF>Go<0F%pq=fA$c z&(DuHH`l(ue|QB&JEu=p_HVzx-US5s$r<^3d%N|MrfY`wKrFf+UO!p6e|`V_h)K$j zdJGnJZ+(1y+h@-c;9{jTZJ@dJ@7?^2t(^b+_@-kMf`oyT207Q>(k#SnO2thU##r-4sp?k-}z&^KV8Lq4BG$l2Tz<^h4wF$ zC!W3bHg})uE|U(JBUjy7C09eUJiiXL9<`3U>e9B*A754r@thEMt3z$C=L>iG>Rrie61!Nx=?bDsu0tk?K`Oj)s&_j*qr?P zrh-DNLBnm?n453D@Ua;5VR`hwI3Jt9m)*q%{)31qAyGb4ndviBvL&z)EfV2sR$vCI zpOp7S(X2)AoImu30AAOH)P!glG=19{V3i!TDP+Yvt!pZCg=Ux{7*8Xpv^}^w^)EyV z{T{fg#g#P`aAMmCDNDt_DBI3a)!a6gPDiy`^sk_~7a*c#cqer$8xjhBlC46{6r)=VTNK!tl)sbg(#ANY+*6{nn5T7I70f)DI^8!UJBy@cB}Z#B zwMgZoVF_%bk;-QwJZ0|Fho=l;61$Zj``F^r$(T<;Oh4vgJ+^_iZtB|E4{>KOTQ z_|VV(w>8)XF{lO=Z$`LdQ;E@a5&A>G>2}SNR<_&JDIi&;<<0|_GyJ1&!`%iFHp!jS&auyVQ@udVfqL99CCH6+$fLZ9iV>K5@{|JbJ$$rI^t^}Lhalsyll-GSUq>~>?`by?W_gj35lMUS@z-iADzozs9YIIV^1 zR8`Z>Fm2rpQ19%&Ovb1dvvV4X4=kThd#v%)#EkhHD=Qr zA!9~wXXsMkoQHonDAXUM#9m%B>lApXHr*z9a?NUGNH)cGp5tXfeuVH56${6=YfrYn z*{GA*p3TneMoUHJ!=0CzaRR)a)+>S=DM|K{sG`Ae_h3#`as@Iin{{fwV!?)#aM`DM zb*}-*3dM&*Qq1*DzpXjyyIZ|R!rF^B$?m@=u(=3FV%94RX*iWlSB-r=&6@p@_a z$)OBjXk1MEX6&d!Q8zhMj_vrY$?{8)^`*^>PrbNSztkvVc=TxWicG=HDJE0!gC&#S zFy9H|Wkv;uf627kDH5$9!#zo&8O*?qQcWXYN9`swO|+W&_KZYJY$Th2kYnh^W7eml zqAU$*ARt%e%?gQ1aK}bL#JRBoGndz)#8Qy00i{hIC}gpRhg`4@L{0UZ+nRgmv)HNQ zs;_wiIZ4?Pa~`#pdwj`YV-}e#UwC67Fz&)X#uUd85lKH(TAME;k`p zpI44q70 za5eWSKI&6bRW;K4wM8 z?|%SSjjJs66HRO`eysQXSl@2&Z96Zb9K2osROiIJDID{6!$JdDrk_Sk6mapTfV#;M z&k04@#HtaHl$fA^dgJxagd&9#%yVo2+apamL|b#>@h_YQsZ2c)ztPOykaHI^tf}b_ zdwT@mu5V03A}NzgMPD1nUI9-W8OXK2JX|jTcCD&ROjJw`(091jNCuEXNtIZl89M6;-*t z_8|hnRmb2&uUwfD$bSQc3b!$f(ELAfA_+|V;eALS6~7`)3_prPzA8D;LL!x?zmGpw z8;zaG+1d+m;RP7GKxUtPhVJMJps$B^tmp8@qvz7Xsl$z+A_(xpmgnn!4vF>JRkXj6|VU&tRV7?nc>~=Amx?h_@5FVl8xlE3E5a^{oSU$|Yu&#hjn?v@KQ&>iyE_h`> z&7cwn_v`z0b-=*hKnb6Na8={iqy|LKct4i6A#mbe#(wdB;;};$0J-3cZ4o=#M)=(; zVn%tSPxmT0Xv}{{_|b8Q^q*hQH{K{3sOyUuoaN3xC)#OfG}Mm|i8YL%JAfkxfc?Bx z_-ck~Kahm|F~~~)Wf0iJ`98~=QQS!y5vdrVl1b^kJ3TlszyA-o`>l_TO{%*Xl|cCK z&irYA+AS?hg4WufuPp=jzJHHQUKKAyDU{_3ImC%^G}q}Sgw$66;nCFB#<@kQkV;VTlv>Ee114rO5EX*H;yi(G;W72ya+Yce7=%RUU!za|^9bD|&L zUYf{^U4H}V67-~`t0`(4#S$}FwjTOgp}ASLg{()9Rxi{=3>|Ydl~S+v!Wnd~lJn7@ z6;i$;xgbS>+p;e)nMt{zO~U@5#@$hq+@@5Wh#7zP_JBxf4no}ehnXPAc9$?^?&aoD zDDb2ZG*Z=ZK{OIFYBY28gHX?eZnLXTQ_>Jh5z`QI@|YE#5->{`shi&RjBC-+(|0oY zmHe(4A33m3m^*xK-4thK8xL)ViT7MOeUQ-~aWvv+sn0UCZ7H-<>;#Irw~gjSa! zYP?NUk2)9_{O}GKEajS3UiM9fOuPX!oaK%(tss4pf+Ti+JdZG=qk!@mpP=No?pR(r zN40n3Q~#@rP*n2T8~tR=(LgH+LrFu)op{ny&NznWRSP-Co{l>9(mwfji-%34tg-K; zu)cA=G-yYEi~j5SrAltvs;aCDy~|9NN(>v!YPQ7o>+kEC4f}~&>{a(N`!tz8`9Iup zj~zOTci1{(5B=LU2p!qzc|9Cu%xKl(jnCKE3G^^jt8%KvX>mH(g2#%uzd z|9>(wWYP}>Zqj+dmM))79rM#iFgmwHV_|+xo21%pWm4mhi71lpI*aEg3s_d9f=3{Y zR{jB1#3ww0Oess}{c&|MpVd7`3$9tmbfzbSO~Dm=$#y6Sm1AI&gVP6H(_fi?z&moU z9@Jpu_|rM)tkKR}*Z|YP8?B0=uX7FN%zm_7hPW|4g&7Cjf+S*rPf# z6(M)jbL196|Dwl?I^s3~6R<^+zF|Otc)P!_N}zYz29gqA7$nS)G1h?yQ4vg&x)>YN z=;hL^iP)VeUXl1s_R>t`6oIKA6IhGZ!>E2L?yxeX531l*W$zkWRby%IrPRX95>Y{m zlb8Y3HEBR7G@|FTlYR{bjz7H5EqtI_Iy=cng6MT@aeMDmhhr z9tjlzAq|rO2|cMPvN|)ev2&`_kRr<2fEoTxJ5(D~|Ey4wlmq}BzFWru*NO9p({!Sj z%GM~s$Pe^R#t>%Tl0<0rdWM?yrkY3x_GX5gVTZZ!?Lh6^hVt^1kztQI(^}lt?$3AL zq>~eij~!S%#6tw6k{L@}I@noH|7Jf1nTd{RHR4U^nHvrZ;G-ATUNr}^-_FI<6t!$@ zD0{`F2#hNlo12$fD)5Ttq>|2^5r$*dPPi1A!cW5K0Hp4Ba^m@23rzxqVFRqa@U6wh zoE^^-eylV{@B0fh@9FXW*zWGn`_+1LtI^Nf=Ih>-90i)D0?i0SBm)YyLGwmHMouDK z-G%ZmLdRN-+ZNUAVcU9g(allMaqe9wzWc*40-aJzZ(V5>e3|PsN`-dS1iKZiB=N(V zpi;#2)1d2#Kz~ncjmRQxe|c9!ZkQXVov$ecn0vnOZ$zFCgHJ;(mOpM0w^t8?Jy`Qn z;G_Y{uiLaJj`I9AZTRn|#*kS~^2meElW>VgWC&SV0~A-Qtqfx;S**Kyj2~F^96L85 z1Erh|irwA^Al~S=9Efl|JJ!QjzbOyr*X%Cd`2jxUX(avWt)La>oY}DD*mkwf&Cva+ zHK3PaUW+zuEdld__2Iwz1=g=(LWu(^xkS-j+9)5A>?3_$&y>oSH-|Xv0FSfqQuXo9 zlAlP)+?b7#xoTrb)x~D(cl}!M6Z^`@zx{4(3<5F3heU#m7WYuoO(q*#Yn)#7z~Ebb z>=?sH^kW_MpJ%TW_WqhYumI_p;I6B$my~;MfqVptQlLP$Vk#ewd6S*G8$&&vZQQ8M z&9RGczH9bK4C0IX;U~M7HMM^b41U$Hg>Hnle&BvA4#?$sAyPzxLyTisfGa)z)`7$n zug0HhFIrNo>;>jA%csuM&0W9r*7m@JS381W7os;K(VgeD#Re8Gkg_toJ1>1$X$B@L zsQRwGS=R=W-b-f;YA~hgJzN}z?A|Z^j3-cztg7-c_u_;op^RV|S*LPGaMhFXLNEH0 zzYcx%!KD5`t*4;6O%25N_ty2s@xm#Jm4JYdKVt`*5uH+f zZmSl^@|wXG&|OwR=SNS6cuELe8w0G!xek_DfQFX+rV>OnFP9aP$s_lvBlm72=_3Vz z^WugTsFr+y!Wh858Cu^@!~lZ<9Mt6*sgvvx4~)e>eWjop`G63;viwl)Fku5=jB8!lHg1?bhpm5+>?-hhkhL5ik-pauyLh2u8u-9>xZA2%6AmX7)|5cKc zE+Es`-JPvT)K78r1@*`G{vb4kOFg^MPzjCbP3%1p7NavlYbSaFzhUme z^L1}Wk-Uc4C%;|-|6t|!Rx7qX2Nd*1XaNeFu^mLS2~H?J?U@&hVa0Xe286b6+XY1( zW5|9rQij36s=eLMpMda12u3U~;fI;CMevNtWQ;v3Z2O-CwCMVK$jv=xh)c$9kS-rS z&C!7rxPW!g6T9nP*nh4)PSvQM+0`GTNhmtE@SFNmC`PZLej@P$`^`cRh`MT1T)s9Q z{@`IV#*wW)IqWJhmzKVu_STPrp|r@uR!@8H#6DV&8`7>L@P@aQYJ}6jg9p|(6D(jv zHJb@r&q&y?S{4gL94X$7>7ibOFv-tJI>(X(EfY-5?2)0eadEv^#w~S-xx!J_hD1q@ zFkTXb$&Wh1KOrmYzY`eht+=j8r!z!0pYIKOyq_=J-MDGmqrM>kbsv?&%{8> zKJ|^QCxnZR;G)AABhLc;hi*I@(_>8adXnXbaNA>$yp_VPRAI+A=zqnhUjYKQe?jHD zG{U~$^8J5m&%0)X{eQlM(|RqWae`;OnOKyu%^(X6;{oC&9f%}04c;UsEJ0AxXmXiw zx+R+iAzdb_#u+!`Xhiw8=C~w@7OcvyNZY7sEz&clls(5)Ae)SGrPW%opR&J4QNfbp zDIjRhRJ?md5L&z>$c|vtG1U<>u7v;z2(Te){W&m&GC67#5COOcsjMMh#CjCp6Un~R zk!XPGJ%4DYTw2F*%3&9)#@sz$<{dt{t&HnjUF0bK(iBfLZp&Blkx#Y3H;#u-pJJzumB zQfZX(;LZ^rW_Bo{MuWxT>2EAs^7<=7NPi#s|H})PVM8y7Vx6HFF_hfB6|zu>?_aUR ziH16_{db3`vq@A^A`pv2Av?}w>vOT};zOT^E{!`i5_^|_Qyhb7^F{;U&l)MlmfRk+Tbz8AUXUdFrPEI(~4BZ5T#=(SXgp@D^|onagjswL$9os)$V*A zda@@p-}lu%S^N@BI=s_~Z9P=GpNQkl-p}Kx=38;{San@h=NsJE#EVDFs)j-CDUDR& zS;TA&d3pEP|DxK;MfS$B{29xe@MPLBOPAYq^TxUJbbVUK3IcGv@PBHf8Cf+EjPOsg z;zbN;8{Myu39k*)G;=a5j)%IS;l`|mWSDE4!2QGyvZx$X$odB3aWnF|!cIA4mG`@S<=O0CZk*9^Chc19bMCzweiZA?I4^9THtk0fWS|rSY6jGY1 zQcf13TO^7_flPktYzHssp=yLiWr*vSXqIFZ%~c6#P~Hp=+RhUW)QqXblhgpvD^iFh z&%Qebo~%*o-5j_sUTBO1KxG7BDEw7g&QFpegOzLSiyK%GXCq`qOPivJwv58TIhnjf z9z*G2NO3eUY+?$&i*I(-uzmQj=-323iA!F{w}3f~NBuj{_6+J4td`m(61NlMIp-X+ z15ZY%VN#kVoL>CEAW0$-qp8KdgwtkFV8x3X7uA788C44-N^VY8=Eq#iIfNN=9j4#}p5GrdQL zv>BisuwajTn(nC zB}qhqb316W!@wy5v?)##Q}ME=$%7kH3W+z(v5sy=)Nyv=bM0kR?+>sq&xqeysByVl zGtGh+y+T?qKEl+uX2EiWPVte+NA9uNaKQF5_CE@$~>pEfRsa#I2LeZUNo7(Q6S~ zUnn}Mx54Ts<0QINUDoC6$XE{C+bC!44temM8z%;5;x^G=8VB+VfEU}F0U@p(c!)B% zpsKG#Z#7bpjDrQ}dh!rsr=UvP@UrMPwrd+aYF$B$lWj~bvbid1fnD`thg~)%%ODyP zx(euEcp+C%ezw53W~xHu4)1J>Wo6Q8uo!pg+=~o1+YW^WY!M?B`AyE|5_+kfZ6N#Np zNuy}k9Z{l8K?Yo+2HTTW!bh;zLdnVLMi0Yz8|1kbo{S}9sxr#);`VJAoVepqQw>RV z+_+kOIla1L6+w~ikgCCC=8Xq)nnuU=QsqRp_)RNb?*`Rq_J;PMcMC&0!iI&T{nJvJ z%wjRr)=8li=r-kkl3n1| zmYe3gQGsL8BZ~i0`HTIv&RBraZwrAg^Do za799!THm≥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 diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 90ff1669..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,49 +0,0 @@ -Welcome to UI Patterns' documentation -===================================== - -The UI Patterns module allows developers to define self-contained UI patterns as Drupal plugins and use them seamlessly -in their `panels `_, `field groups `_ -or `Display Suite `_ view modes. - -.. image:: images/patterns-overview.png - :align: center - -Project overview ----------------- - -The UI Patterns project provides 6 modules: - -* **UI Patterns**: the main module, it exposes the UI Patterns system APIs and it does not do much more than that. -* **UI Patterns Library**: allows to define patterns via YAML and generates a pattern library page available at ``/patterns`` - to be used as documentation for content editors or as a showcase for business. Use this module if you don't plan to - use more advanced component library systems such as PatternLab or Fractal. - `Learn more `_ -* **UI Patterns Field Group**: allows to use patterns to format field groups - provided by the `Field group `_ module. -* **UI Patterns Layouts**: allows to use patterns as layouts. This allows patterns to be used on - `Display Suite `_ view modes or on `panels `_ - out of the box. -* **UI Patterns Display Suite**: allows to use patterns to format `Display Suite `_ - field templates. -* **UI Patterns Views**: allows to use patterns as Views row templates. - -By the way plugin definitions are handled the UI Patterns module also integrates with with tools like -`PatternLab `_ or modules like `Component Libraries `_. - -Try it out -^^^^^^^^^^ - -Download and install the `Bootstrap Patterns `_ theme on a vanilla -Drupal 8 installation to quickly try out the UI Patterns module. - -.. toctree:: - :caption: Table of Contents - - content/patterns-definition - content/field-group - content/layout-plugin - content/field-templates - content/views - content/developer-documentation - content/tests - diff --git a/modules/ui_patterns_layouts/README.md b/modules/ui_patterns_layouts/README.md index f133c8dc..a6cc0ed4 100644 --- a/modules/ui_patterns_layouts/README.md +++ b/modules/ui_patterns_layouts/README.md @@ -3,10 +3,5 @@ Integrates UI Patterns with the **Layout Discovery** core module. - To use pattern layouts on view modes install the [Display Suite](https://www.drupal.org/project/ds) module. -- To use pattern layouts with [Page Manager](https://www.drupal.org/project/page_manager) +- To use pattern layouts with [Page Manager](https://www.drupal.org/project/page_manager) install the [Panels](https://www.drupal.org/project/panels) module. - -## Note - -Integration with the [Layout Plugin](https://www.drupal.org/project/layout_plugin) module is supported only until -version 1.0-beta6 included. diff --git a/modules/ui_patterns_library/README.md b/modules/ui_patterns_library/README.md index 3fa42511..df884983 100644 --- a/modules/ui_patterns_library/README.md +++ b/modules/ui_patterns_library/README.md @@ -1,6 +1,8 @@ # UI Patterns Library -The UI Patterns Library module allows developers to expose patterns via YAML definitions and to display them via a -pattern library page to be used as documentation for content editors or as a showcase for business, available at `/patterns`. +The UI Patterns Library module allows developers to expose patterns via YAML +definitions and to display them via a pattern library page to be used as +documentation for content editors or as a showcase for business, available at +`/patterns`. -For more information please refer to the [official documentation](http://ui-patterns.readthedocs.io). +For more information please refer to the [official documentation](https://www.drupal.org/docs/contributed-modules/ui-patterns). diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 26a16505..00000000 --- a/tests/README.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to the tests documentation [here](http://ui-patterns.readthedocs.io/en/8.x-1.x/content/tests.html). From 75def67a7d6c2c164d29035992e6e8b6be45e016 Mon Sep 17 00:00:00 2001 From: pvbergen Date: Mon, 21 Nov 2022 13:02:33 +0000 Subject: [PATCH 14/81] Issue #3314463 by pvbergen: Add support for additional fields on PatternDefinitionField --- src/Definition/PatternDefinitionField.php | 24 +++++++++++++++++++ .../fixtures/definition/fields_processing.yml | 6 +++++ 2 files changed, 30 insertions(+) diff --git a/src/Definition/PatternDefinitionField.php b/src/Definition/PatternDefinitionField.php index 75183b4a..ec3b17e7 100644 --- a/src/Definition/PatternDefinitionField.php +++ b/src/Definition/PatternDefinitionField.php @@ -23,6 +23,7 @@ class PatternDefinitionField implements \ArrayAccess { 'type' => NULL, 'preview' => NULL, 'escape' => TRUE, + 'additional' => [], ]; /** @@ -162,4 +163,27 @@ public function setEscape($escape) { return $this; } + /** + * Get Additional property. + * + * @return array + * Property value. + */ + public function getAdditional() { + return $this->definition['additional']; + } + + /** + * Set Additional property. + * + * @param array $additional + * Property value. + * + * @return $this + */ + public function setAdditional(array $additional) { + $this->definition['additional'] = $additional; + return $this; + } + } diff --git a/tests/src/fixtures/definition/fields_processing.yml b/tests/src/fixtures/definition/fields_processing.yml index 97219e18..b9e04d70 100644 --- a/tests/src/fixtures/definition/fields_processing.yml +++ b/tests/src/fixtures/definition/fields_processing.yml @@ -9,6 +9,7 @@ type: ~ preview: ~ escape: TRUE + additional: [] field2: name: field2 label: field2 @@ -16,6 +17,7 @@ type: ~ preview: ~ escape: TRUE + additional: [] - actual: field1: Field 1 field2: Field 2 @@ -27,6 +29,7 @@ type: ~ preview: ~ escape: TRUE + additional: [] field2: name: field2 label: Field 2 @@ -34,6 +37,7 @@ type: ~ preview: ~ escape: TRUE + additional: [] - actual: field1: label: field1 @@ -45,6 +49,7 @@ type: ~ preview: ~ escape: TRUE + additional: [] - actual: - name: field1 label: Field 1 @@ -56,3 +61,4 @@ type: ~ preview: ~ escape: TRUE + additional: [] From c50169048766b4be5e46a2b72e226237c91beed1 Mon Sep 17 00:00:00 2001 From: Jigar Mehta <19926-jigarius@users.noreply.drupalcode.org> Date: Mon, 21 Nov 2022 14:11:01 +0100 Subject: [PATCH 15/81] Issue #3315660 by G4MBINI, Grimreaper, ademarco, jigarius: Link to the pattern library page in the admin menu --- .../ui_patterns_library/ui_patterns_library.links.menu.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 modules/ui_patterns_library/ui_patterns_library.links.menu.yml diff --git a/modules/ui_patterns_library/ui_patterns_library.links.menu.yml b/modules/ui_patterns_library/ui_patterns_library.links.menu.yml new file mode 100644 index 00000000..133cfa6f --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.links.menu.yml @@ -0,0 +1,5 @@ +ui_patterns_library.overview: + title: 'UI Patterns library' + description: 'See a list of UI Patterns which exist on the site.' + parent: system.admin_reports + route_name: ui_patterns.patterns.overview From b91e6187114a8f6af5f24714f1171d0eb75f72bf Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <14238-florenttorregrosa@users.noreply.drupalcode.org> Date: Tue, 13 Dec 2022 10:31:22 +0000 Subject: [PATCH 16/81] Issue #3311464 by Grimreaper, G4MBINI, DuaelFr, barig, ademarco, drclaw, pdureau: Sort UI Pattern definitions. --- .../tests/fixtures/overview-page-patterns.yml | 110 +++++++++--------- src/UiPatternsManager.php | 35 ++++++ .../patterns/aaa/aaa.ui_patterns.yml | 2 + .../patterns/aaa/pattern-aaa.html.twig | 0 .../patterns/bbb/bbb.ui_patterns.yml | 2 + .../patterns/bbb/pattern-bbb.html.twig | 0 .../patterns/ccc/ccc.ui_patterns.yml | 2 + .../patterns/ccc/pattern-ccc.html.twig | 0 .../patterns/lll/lll.ui_patterns.yml | 2 + .../patterns/lll/pattern-lll.html.twig | 0 .../patterns/zzz/pattern-zzz.html.twig | 0 .../patterns/zzz/zzz.ui_patterns.yml | 2 + ...ui_patterns_definitions_sort_test.info.yml | 4 + .../ui_patterns_field_source_test.info.yml | 2 +- .../Kernel/UiPatternsManagerSortingTest.php | 59 ++++++++++ 15 files changed, 164 insertions(+), 56 deletions(-) create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/aaa.ui_patterns.yml create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/aaa/pattern-aaa.html.twig create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/bbb.ui_patterns.yml create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/bbb/pattern-bbb.html.twig create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/ccc.ui_patterns.yml create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/ccc/pattern-ccc.html.twig create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/lll.ui_patterns.yml create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/lll/pattern-lll.html.twig create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/pattern-zzz.html.twig create mode 100644 tests/modules/ui_patterns_definitions_sort_test/templates/patterns/zzz/zzz.ui_patterns.yml create mode 100644 tests/modules/ui_patterns_definitions_sort_test/ui_patterns_definitions_sort_test.info.yml create mode 100644 tests/src/Kernel/UiPatternsManagerSortingTest.php diff --git a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml index a08b7cf4..8443c4df 100644 --- a/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml +++ b/modules/ui_patterns_library/tests/fixtures/overview-page-patterns.yml @@ -1,3 +1,36 @@ +- name: 'button' + label: 'Button' + description: 'A simple button.' + has_variants: true + preview: ~ + fields: + - name: 'title' + type: 'text' + label: 'Label' + description: 'The button label' + preview: 'Submit' + - name: 'url' + type: 'text' + label: 'URL' + description: 'The button URL' + preview: 'http://example.com' + variants: + - meta: + name: 'default' + label: 'Default' + description: 'A default button, nothing to see here.' + preview: 'Submit' + - meta: + name: 'primary' + label: 'Primary' + description: 'A primary button.' + preview: 'Submit' + - meta: + name: 'danger' + label: 'Danger' + description: 'A button for dangerous operations.' + preview: 'Delete' + - name: 'simple' label: 'Simple' description: 'A simple pattern' @@ -9,28 +42,6 @@ label: 'Field' description: 'Field description' -- name: 'with_variants' - label: 'With variants' - description: 'Pattern with variants' - has_variants: true - preview: ~ - fields: - - name: 'field' - type: 'string' - label: 'Field' - description: 'Field description' - variants: - - meta: - name: 'one' - label: 'One' - description: 'First variant' - preview: '

  • + diff --git a/tests/modules/ui_patterns_test/components/progress/progress.component.yml b/tests/modules/ui_patterns_test/components/progress/progress.component.yml new file mode 100644 index 00000000..1ecd81a0 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/progress/progress.component.yml @@ -0,0 +1,49 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Progress" +description: "The progress element displays an indicator showing the completion progress of a task, typically in the form of a bar. Progress components are built with two HTML elements, some CSS to set the width, and a few attributes. Bootstrap does not use the HTML5 element, ensuring you can stack progress bars, animate them, and place text labels over them." +links: + - "https://getbootstrap.com/docs/5.3/components/progress/" +template: path/to/template/progress.twig +variants: + default: + label: "Default" + striped: + label: "Striped" + striped__animated: + label: "Animated stripes" +props: + type: object + properties: + aria_label: + title: "Aria label" + description: "Name of the progress bar for assistive technology." + type: "string" + percent: + title: "Total progress (%)" + description: "Width of the progress element representing total progress (25%, 50%, etc.)." + type: "number" + min: + title: "Minimum value" + description: "Minimum value of the progress element (default is 0). Used for an aria attribute." + type: "number" + max: + label: "Maximum value" + description: "Maximum value of the progress element (default is 100). Used for an aria attribute." + type: "number" + bar_height: + label: "Height" + description: "Height of progress element in pixels (px). Leave empty for default height." + type: "number" +slots: + label: + title: "Label" + description: "Text shown inside the progress bar." +stories: + preview: + title: "The default preview from UI Patterns 1.x" + props: + percent: 50 + min: 0 + max: 100 + slots: + label: "Label" From 0fd72cd5e00927f816319cebd3e49501a66ec9f8 Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 4 Oct 2023 20:34:23 +0200 Subject: [PATCH 34/81] Add component Twig function --- src/Template/TwigExtension.php | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Template/TwigExtension.php b/src/Template/TwigExtension.php index fc920fce..d946895b 100644 --- a/src/Template/TwigExtension.php +++ b/src/Template/TwigExtension.php @@ -4,6 +4,7 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFilter; +use Twig\TwigFunction; /** * Twig extension providing UI Patterns-specific functionalities. @@ -21,6 +22,18 @@ public function getName() { return 'ui_patterns'; } + /** + * {@inheritdoc} + */ + public function getFunctions() { + return [ + new TwigFunction('component', [ + $this, + 'renderComponent', + ]), + ]; + } + /** * {@inheritdoc} */ @@ -31,4 +44,28 @@ public function getFilters() { ]; } + /** + * Render given component. + * + * @param string $component_id + * Component ID. + * @param array $slots + * Pattern slots. + * @param array $props + * Pattern props. + * + * @return array + * Pattern render array. + * + * @see \Drupal\sdc\Element\ComponentElement + */ + public function renderComponent(string $component_id, array $slots = [], array $props = []) { + return [ + '#type' => 'component', + '#component' => $component_id, + '#slots' => $slots, + '#props' => $props, + ]; + } + } From 7140050d28fa13bae4f61c6bbfc04b7a6d449aa4 Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 4 Oct 2023 21:00:45 +0200 Subject: [PATCH 35/81] Add UI Patterns Legacy Test module --- .../patterns/alert/alert.ui_patterns.yml | 41 +++++ .../patterns/alert/pattern-alert.html.twig | 22 +++ .../blockquote/blockquote.ui_patterns.yml | 27 ++++ .../blockquote/pattern-blockquote.html.twig | 14 ++ .../patterns/button/button.ui_patterns.yml | 140 ++++++++++++++++++ .../patterns/button/pattern-button.html.twig | 31 ++++ .../patterns/card/card.ui_patterns.yml | 89 +++++++++++ ...ard--variant-horizontal--preview.html.twig | 4 + ...pattern-card--variant-horizontal.html.twig | 20 +++ .../patterns/card/pattern-card.html.twig | 19 +++ .../card_body/card_body.ui_patterns.yml | 60 ++++++++ .../pattern-card-body--preview.html.twig | 9 ++ .../card_body/pattern-card-body.html.twig | 15 ++ .../close_button/close_button.ui_patterns.yml | 25 ++++ ...e-button--variant-white--preview.html.twig | 3 + .../pattern-close-button.html.twig | 11 ++ .../patterns/figure/figure.ui_patterns.yml | 26 ++++ .../patterns/figure/pattern-figure.html.twig | 6 + .../progress/pattern-progress.html.twig | 31 ++++ .../progress/progress.ui_patterns.yml | 47 ++++++ .../ui_patterns_legacy_test.info.yml | 4 + 21 files changed, 644 insertions(+) create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/alert.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/pattern-alert.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/pattern-blockquote.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/button.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/pattern-button.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/card.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal--preview.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/figure.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/pattern-figure.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/pattern-progress.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/progress.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/alert.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/alert.ui_patterns.yml new file mode 100644 index 00000000..31cde6a4 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/alert.ui_patterns.yml @@ -0,0 +1,41 @@ +alert: + label: "Alert (Legacy)" + description: "Provide contextual feedback messages for typical user actions with the handful of available and flexible alert messages." + links: + - "https://getbootstrap.com/docs/5.3/components/alerts/" + variants: + primary: + label: "Primary" + secondary: + label: "Secondary" + success: + label: "Success" + danger: + label: "Danger" + warning: + label: "Warning" + info: + label: "Info" + light: + label: "Light" + dark: + label: "Dark" + settings: + dismissible: + type: "boolean" + label: "Dismissible?" + description: "It is possible to dismiss any alert inline." + preview: True + allow_expose: true + allow_token: true + fields: + heading: + type: "text" + label: "Heading" + description: "The alert heading. Optional." + preview: "Well done!" + message: + type: "render" + label: "Message" + description: "The alert message." + preview: "A simple alert. Check it out!" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/pattern-alert.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/pattern-alert.html.twig new file mode 100644 index 00000000..e482d41d --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/alert/pattern-alert.html.twig @@ -0,0 +1,22 @@ +{% if variant and variant|lower != 'default' %} + {% set attributes = attributes.addClass('alert-' ~ variant|lower|replace({'_': '-'})) %} +{% endif %} + +{% if dismissible %} + {% set attributes = attributes.addClass(['alert-dismissible', 'fade', 'show']) %} +{% endif %} + + + {% if heading %} +

    {{ heading }}

    + {% endif %} + {{ message|add_class('alert-link') }} + {% if dismissible %} + {{ pattern('close_button', { + attributes: create_attribute({ + 'data-bs-dismiss': 'alert' + }), + aria_label: 'Close'|t, + }) }} + {% endif %} + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml new file mode 100644 index 00000000..eebed253 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml @@ -0,0 +1,27 @@ +blockquote: + label: "Blockquote (Legacy)" + description: "For quoting blocks of content from another source within your document." + links: + - "https://getbootstrap.com/docs/5.3/content/typography/#blockquotes" + category: "Typography" + fields: + content: + type: "render" + label: "Content" + description: "The quote." + preview: + - type: "html_tag" + tag: "p" + value: "A well-known quote, contained in a blockquote element." + footer: + type: "render" + label: "Footer" + description: "For identifying the source. Wrap the name of the source work in ." + preview: + - type: "markup" + markup: "Someone famous in " + - type: "html_tag" + tag: "cite" + value: "Source Title" + attributes: + title: "Source Title" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/pattern-blockquote.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/pattern-blockquote.html.twig new file mode 100644 index 00000000..5721b60a --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/pattern-blockquote.html.twig @@ -0,0 +1,14 @@ +{% if footer %} + +
    + {{ content }} +
    + + +{% else %} + + {{ content }} + +{% endif %} diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/button.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/button.ui_patterns.yml new file mode 100644 index 00000000..0e27a7d0 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/button.ui_patterns.yml @@ -0,0 +1,140 @@ +button: + label: "Button (Legacy)" + description: "For actions in forms, dialogs, and more with support for multiple sizes, states, and more." + links: + - "https://getbootstrap.com/docs/5.3/components/buttons/" + category: "Button" + variants: + default: + label: "Default" + description: "No 'btn' class added." + primary__sm: + label: "Primary small" + secondary__sm: + label: "Secondary small" + success__sm: + label: "Success small" + danger__sm: + label: "Danger small" + warning__sm: + label: "Warning small" + info__sm: + label: "Info small" + light__sm: + label: "Light small" + dark__sm: + label: "Dark small" + link__sm: + label: "Link small" + primary: + label: "Primary" + secondary: + label: "Secondary" + success: + label: "Success" + danger: + label: "Danger" + warning: + label: "Warning" + info: + label: "Info" + light: + label: "Light" + dark: + label: "Dark" + link: + label: "Link" + primary__lg: + label: "Primary large" + secondary__lg: + label: "Secondary large" + success__lg: + label: "Success large" + danger__lg: + label: "Danger large" + warning__lg: + label: "Warning large" + info__lg: + label: "Info large" + light__lg: + label: "Light large" + dark__lg: + label: "Dark large" + link__lg: + label: "Link large" + outline_primary__sm: + label: "Outline Primary small" + outline_secondary__sm: + label: "Outline Secondary small" + outline_success__sm: + label: "Outline Success small" + outline_danger__sm: + label: "Outline Danger small" + outline_warning__sm: + label: "Outline Warning small" + outline_info__sm: + label: "Outline Info small" + outline_light__sm: + label: "Outline Light small" + outline_dark__sm: + label: "Outline Dark small" + outline_primary: + label: "Outline Primary" + outline_secondary: + label: "Outline Secondary" + outline_success: + label: "Outline Success" + outline_danger: + label: "Outline Danger" + outline_warning: + label: "Outline Warning" + outline_info: + label: "Outline Info" + outline_light: + label: "Outline Light" + outline_dark: + label: "Outline Dark" + outline_primary__lg: + label: "Outline Primary large" + outline_secondary__lg: + label: "Outline Secondary large" + outline_success__lg: + label: "Outline Success large" + outline_danger__lg: + label: "Outline Danger large" + outline_warning__lg: + label: "Outline Warning large" + outline_info__lg: + label: "Outline Info large" + outline_light__lg: + label: "Outline Light large" + outline_dark__lg: + label: "Outline Dark large" + dropdown_item: + label: "(Dropdown item)" + settings: + disabled: + type: "boolean" + label: "Disabled?" + description: "Is the button disabled?" + preview: false + allow_expose: true + allow_token: true + label_visually_hidden: + type: "boolean" + label: "Hide button label?" + description: "Is the button's label hidden?" + preview: false + allow_expose: true + allow_token: true + url: + type: "url" + label: "URL" + description: "The button URL. Optional." + preview: "https://example.com" + fields: + label: + type: "text" + label: "Label" + description: "The button label." + preview: "Submit" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/pattern-button.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/pattern-button.html.twig new file mode 100644 index 00000000..5fc3ddb9 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/button/pattern-button.html.twig @@ -0,0 +1,31 @@ +{% if variant and variant|lower != 'default' and variant|lower != 'dropdown_item' %} + {% set variants = variant|split('__')|map(v => v|lower|replace({(v): 'btn-' ~ v})|replace({'_': '-'})) %} + {% set attributes = attributes.addClass(variants) %} + {% set attributes = attributes.addClass('btn') %} +{% endif %} + +{% set attributes = (variant|lower == 'dropdown_item') ? attributes.addClass('dropdown-item') : attributes %} + +{% if label_visually_hidden %} + {% set label %} + + {{ label }} + + {% endset %} +{% endif %} + +{% if url or attributes.href %} + {% set url = url|default(attributes.href) %} + {% set attributes = attributes.setAttribute('href', url) %} + {% if disabled %} + {% set attributes = attributes.setAttribute('href', false).setAttribute('tabindex', '-1').setAttribute('aria-disabled', 'true').addClass('disabled') %} + {% endif %} + + {{ label }} +{% else %} + {% if disabled %} + {% set attributes = attributes.setAttribute('disabled', '') %} + {% endif %} + + {{ label }} +{% endif %} diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/card.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/card.ui_patterns.yml new file mode 100644 index 00000000..d5138f6a --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/card.ui_patterns.yml @@ -0,0 +1,89 @@ +card: + label: "Card (Legacy)" + description: "A card is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options." + links: + - "https://getbootstrap.com/docs/5.3/components/card/" + category: "Card" + variants: + default: + label: "Default" + horizontal: + label: "Horizontal" + settings: + image_position: + type: "select" + label: "Image position" + description: "Only for default variant." + options: + top: "Top (Default)" + bottom: "Bottom" + preview: "top" + allow_expose: true + allow_token: true + image_col_classes: + type: "textfield" + label: "Image column classes" + description: "Only for horizontal variant. Default value: col-md-4" + default_value: "col-md-4" + preview: "col-md-4" + allow_expose: true + allow_token: true + content_col_classes: + type: "textfield" + label: "Content column classes" + description: "Only for horizontal variant. Default value: col-md-8" + default_value: "col-md-8" + preview: "col-md-8" + allow_expose: true + allow_token: true + fields: + image: + type: "render" + label: "Image" + description: "Card image." + preview: + theme: "image" + uri: "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZm9udC1zaXplOiAxLjEyNXJlbTsgdGV4dC1hbmNob3I6IG1pZGRsZTsgdXNlci1zZWxlY3Q6IG5vbmU7IiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIyNTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgc2xpY2UiIGZvY3VzYWJsZT0iZmFsc2UiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iUGxhY2Vob2xkZXI6IEltYWdlIGNhcCI+PHRpdGxlPlBsYWNlaG9sZGVyPC90aXRsZT48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjODY4ZTk2Ij48L3JlY3Q+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiNkZWUyZTYiIGR5PSIuM2VtIj5JbWFnZSBjYXA8L3RleHQ+PC9zdmc+Cg==" + alt: "© 2017 John Smith photography" + header: + type: "render" + label: "Header" + description: "Card header." + preview: "Featured" + content: + type: "render" + label: "Content" + description: "Card body." + preview: + - type: "pattern" + id: "card_body" + variant: "body" + fields: + title: "Card title" + subtitle: "Card subtitle" + text: "Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit." + content: + type: "pattern" + id: "button" + variant: "primary" + fields: + label: "Go somewhere" + links: + - type: "html_tag" + tag: "a" + value: "Card link" + attributes: + href: "#" + - type: "html_tag" + tag: "a" + value: "Another link" + attributes: + href: "#" + footer: + type: "render" + label: "Footer" + description: "Card footer." + preview: + type: "html_tag" + tag: "span" + value: "2 days ago" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal--preview.html.twig new file mode 100644 index 00000000..05e46072 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal--preview.html.twig @@ -0,0 +1,4 @@ +{% set header = '' %} +{% set footer = '' %} +{% set attributes = attributes.setAttribute('style', 'max-width: 540px;') %} +{% extends 'pattern-card--variant-horizontal.html.twig' %} diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal.html.twig new file mode 100644 index 00000000..9e579b35 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card--variant-horizontal.html.twig @@ -0,0 +1,20 @@ + +
    +
    + {{ image|add_class('img-fluid rounded-start') }} +
    +
    + {% if header %} +
    + {{ header }} +
    + {% endif %} + {{ content }} + {% if footer %} + + {% endif %} +
    +
    + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card.html.twig new file mode 100644 index 00000000..e7e33a55 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card/pattern-card.html.twig @@ -0,0 +1,19 @@ + + {% if image and image_position != 'bottom' %} + {{ image|add_class('card-img-top') }} + {% endif %} + {% if header %} +
    + {{ header }} +
    + {% endif %} + {{ content }} + {% if footer %} + + {% endif %} + {% if image and image_position == 'bottom' %} + {{ image|add_class('card-img-bottom') }} + {% endif %} + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml new file mode 100644 index 00000000..ef737a0b --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml @@ -0,0 +1,60 @@ +card_body: + label: "(Card body) (Legacy)" + description: "Internal: to be used in the 'Card' component." + links: + - "https://getbootstrap.com/docs/5.3/components/card/" + category: "Card" + settings: + heading_level: + type: "select" + label: "Heading level" + options: + 2: "h2" + 3: "h3" + 4: "h4" + 5: "h5 (Default)" + 6: "h6" + preview: 5 + allow_expose: true + allow_token: true + fields: + title: + type: "text" + label: "Title" + description: "Card title. Plain text." + preview: "Card title" + subtitle: + type: "text" + label: "Subtitle" + description: "Card subtitle. Plain text." + preview: "Card subtitle" + text: + type: "text" + label: "Text" + description: "Card text. Plain text." + preview: "Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit." + content: + type: "render" + label: "Content" + description: "Free content outside of any wrapper." + preview: + type: "pattern" + id: "button" + variant: "primary" + fields: + label: "Go somewhere" + links: + type: "render" + label: "Links" + description: "Array of link elements" + preview: + - type: "html_tag" + tag: "a" + value: "Card link" + attributes: + href: "#" + - type: "html_tag" + tag: "a" + value: "Another link" + attributes: + href: "#" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig new file mode 100644 index 00000000..601bf46a --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig @@ -0,0 +1,9 @@ +{# +/** + * For preview, to avoid warnings due to preview value transformed as markup + * object. + */ +#} +
    + {{ include('pattern-card-body.html.twig') }} +
    diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig new file mode 100644 index 00000000..78908777 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig @@ -0,0 +1,15 @@ +{% set heading_level = heading_level|default(5) %} + + + {% if title %} + {{ title }} + {% endif %} + {% if subtitle %} + {{ subtitle }} + {% endif %} + {% if text %} +

    {{ text }}

    + {% endif %} + {{ content }} + {{ links|add_class('card-link') }} + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml new file mode 100644 index 00000000..cf10343f --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml @@ -0,0 +1,25 @@ +close_button: + label: "Close button (Legacy)" + description: "A generic close button for dismissing content like modals and alerts." + links: + - "https://getbootstrap.com/docs/5.3/components/close-button" + category: "Button" + variants: + default: + label: "Default" + white: + label: "White (deprecated)" + settings: + disabled: + type: "boolean" + label: "Disabled?" + description: "Is the button disabled?" + preview: false + allow_expose: true + allow_token: true + aria_label: + type: "textfield" + label: "Aria label" + description: "Name of the close button for assistive technology." + preview: "Close" + allow_token: true diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig new file mode 100644 index 00000000..63f08c29 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig @@ -0,0 +1,3 @@ +
    + {{ include('pattern-close-button.html.twig') }} +
    diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig new file mode 100644 index 00000000..d1350276 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig @@ -0,0 +1,11 @@ +{% if variant and variant|lower != 'default' %} + {% set attributes = attributes.addClass('btn-close-' ~ variant) %} +{% endif %} +{% set attributes = attributes.addClass('btn-close') %} + +{% set attributes = attributes.setAttribute('aria-label', aria_label|default('Close'|t)) %} +{% if disabled %} + {% set attributes = attributes.setAttribute('disabled', disabled) %} +{% endif %} + + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/figure.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/figure.ui_patterns.yml new file mode 100644 index 00000000..27881a25 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/figure.ui_patterns.yml @@ -0,0 +1,26 @@ +figure: + label: "Figure (Legacy)" + description: "Used to display a piece of self-contained content (illustrations, diagrams, photos, code, etc) along with an optional caption. This content can be removed from the document without affecting the meaning of the document." + links: + - "https://getbootstrap.com/docs/5.3/content/figures/" + settings: + figcaption_attributes: + type: "attributes" + label: "Figcaption attributes" + description: "The attributes to customize the figcaption tag." + preview: 'class="text-end"' + allow_expose: true + fields: + image: + type: "render" + label: "Image" + description: "The content of the figure." + preview: + theme: "image" + uri: "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZm9udC1zaXplOiAxLjEyNXJlbTsgdGV4dC1hbmNob3I6IG1pZGRsZTsgdXNlci1zZWxlY3Q6IG5vbmU7IiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIyNTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgc2xpY2UiIGZvY3VzYWJsZT0iZmFsc2UiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iUGxhY2Vob2xkZXI6IEltYWdlIGNhcCI+PHRpdGxlPlBsYWNlaG9sZGVyPC90aXRsZT48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjODY4ZTk2Ij48L3JlY3Q+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiNkZWUyZTYiIGR5PSIuM2VtIj5JbWFnZSBjYXA8L3RleHQ+PC9zdmc+Cg==" + alt: "© 2017 John Smith photography" + caption: + type: "text" + label: "Caption" + description: "The caption that appears under the content." + preview: "A caption for the above image." diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/pattern-figure.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/pattern-figure.html.twig new file mode 100644 index 00000000..21dd87df --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/figure/pattern-figure.html.twig @@ -0,0 +1,6 @@ + + {{ image|add_class('figure-img') }} + + {{ caption }} + + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/pattern-progress.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/pattern-progress.html.twig new file mode 100644 index 00000000..b7bc09f6 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/pattern-progress.html.twig @@ -0,0 +1,31 @@ +{% if variant and variant|lower != 'default' %} + {% set variants = variant|split('__')|map(v => v|lower|replace({(v): 'progress-bar-' ~ v})|replace({'_': '-'})) %} + {% set attributes = attributes.addClass(variants) %} +{% endif %} + +{% set wrapper_attributes = create_attribute() %} +{# Handle wrapper ID. #} +{% if attributes.hasAttribute('id') %} + {% set wrapper_attributes = wrapper_attributes.setAttribute('id', attributes.offsetGet('id')) %} + {% set attributes = attributes.removeAttribute('id') %} +{% endif %} + +{% set wrapper_attributes = bar_height ? wrapper_attributes.setAttribute('style', 'height:' ~ bar_height ~ 'px') : wrapper_attributes %} +{% set percent = percent|default(0) %} +{% set min = min|default(0) %} +{% set max = max|default(100) %} +{% set width = (percent * 100) // max %} + + + + {{ label }} + + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/progress.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/progress.ui_patterns.yml new file mode 100644 index 00000000..fe06ac6f --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/progress/progress.ui_patterns.yml @@ -0,0 +1,47 @@ +progress: + label: "Progress (Legacy)" + description: "The progress element displays an indicator showing the completion progress of a task, typically in the form of a bar. Progress components are built with two HTML elements, some CSS to set the width, and a few attributes. Bootstrap does not use the HTML5 element, ensuring you can stack progress bars, animate them, and place text labels over them." + links: + - "https://getbootstrap.com/docs/5.3/components/progress/" + variants: + default: + label: "Default" + striped: + label: "Striped" + striped__animated: + label: "Animated stripes" + settings: + aria_label: + type: "textfield" + label: "Aria label" + description: "Name of the progress bar for assistive technology." + allow_token: true + percent: + type: "number" + label: "Total progress (%)" + description: "Width of the progress element representing total progress (25%, 50%, etc.)." + allow_token: true + preview: 50 + min: + type: "number" + label: "Minimum value" + description: "Minimum value of the progress element (default is 0). Used for an aria attribute." + allow_token: true + preview: 0 + max: + type: "number" + label: "Maximum value" + description: "Maximum value of the progress element (default is 100). Used for an aria attribute." + allow_token: true + preview: 100 + bar_height: + type: "number" + label: "Height" + description: "Height of progress element in pixels (px). Leave empty for default height." + allow_token: true + fields: + label: + type: "text" + label: "Label" + description: "Text shown inside the progress bar." + preview: "Label" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml new file mode 100644 index 00000000..05e5da34 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml @@ -0,0 +1,4 @@ +name: "UI Patterns Legacy Test" +type: module +description: "Provides test plugin." +package: "Testing" From 85aa0c725a40d6ff0d2b5456ddff9b3f64ebcf75 Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 4 Oct 2023 23:51:08 +0200 Subject: [PATCH 36/81] Add Pattern & PatternPreview in ui_patterns_legacy --- .../src/Element/Pattern.php | 115 ++++++++++++++++++ .../src/Element/PatternPreview.php | 44 +++++++ .../src/Template/TwigExtension.php | 43 +++---- .../ui_patterns_legacy.info.yml | 3 +- 4 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 modules/ui_patterns_legacy/src/Element/Pattern.php create mode 100644 modules/ui_patterns_legacy/src/Element/PatternPreview.php diff --git a/modules/ui_patterns_legacy/src/Element/Pattern.php b/modules/ui_patterns_legacy/src/Element/Pattern.php new file mode 100644 index 00000000..885364b4 --- /dev/null +++ b/modules/ui_patterns_legacy/src/Element/Pattern.php @@ -0,0 +1,115 @@ + [ + [$this, 'convert'], + [$this, 'preRenderComponent'], + ], + '#component' => '', + '#props' => [], + '#slots' => [], + '#propsAlter' => [], + '#slotsAlter' => [], + ]; + } + + /** + * + */ + private static function resolveCompactFormat(array $element): array { + foreach (Element::properties($element) as $property) { + if (in_array($property, self::COMMON_RENDER_PROPERTIES)) { + continue; + } + // @todo test if slot or prop, looking the component definition. + } + return $element; + } + + /** + * + */ + private static function getComponentNamespace(array $element): array { + if (!array_key_exists("#id", $element)) { + // Nothing to do. + return $element; + } + $parts = explode(":", $element["id"]); + if (count(array_filter($parts)) === 2) { + // Already namespaced. + return $element; + } + if (count(array_filter($parts)) > 2) { + // Unexpected situation. + return $element; + } + $components = \Drupal::service('plugin.manager.sdc')->getAllComponents(); + // @todo Search first in current active theme, then parents themes, then modules. + foreach ($components as $component) { + if ($component->getPluginDefinition()["machineName"] === $element["#id"]) { + $element["#id"] = $component->getPluginId(); + return $element; + } + } + return $element; + } + + /** + * + */ + public function convert(array $element): array { + $element = self::resolveCompactFormat($element); + $element = self::getComponentNamespace($element); + $element["#type"] = "component"; + $element["#component"] = $element["#id"]; + unset($element["#id"]); + if (array_key_exists("#fields", $element) && is_array($element["#fields"])) { + $element["#slots"] = $element["#fields"]; + unset($element["#fields"]); + } + if (array_key_exists("#settings", $element) && is_array($element["#settings"])) { + $element["#props"] = $element["#settings"]; + unset($element["#settings"]); + } + if (array_key_exists("#variant", $element) && is_string($element["#variant"])) { + $element["#props"]["variant"] = $element["#variant"]; + unset($element["#variant"]); + } + // @todo Translate message + \Drupal::logger('ui_patterns_legacy')->warning("Deprecated pattern render element or pattern Twig function: " . $element["#component"]); + // @todo Remove before shipping + $messenger = \Drupal::service('messenger'); + $messenger->addWarning("Deprecated pattern render element or pattern Twig function: " . $element["#component"]); + return $element; + } + +} diff --git a/modules/ui_patterns_legacy/src/Element/PatternPreview.php b/modules/ui_patterns_legacy/src/Element/PatternPreview.php new file mode 100644 index 00000000..83328881 --- /dev/null +++ b/modules/ui_patterns_legacy/src/Element/PatternPreview.php @@ -0,0 +1,44 @@ + [ + [$this, 'loadPreviewStory'], + [$this, 'convert'], + [$this, 'preRenderComponent'], + ], + '#component' => '', + '#props' => [], + '#slots' => [], + '#propsAlter' => [], + '#slotsAlter' => [], + ]; + } + + /** + * Load preview. + * + * @param array $element + * Render array. + * + * @return array + * Render array. + */ + public function loadPreviewStory(array $element): array { + // @todo Load slots as fields & props as settings from 'preview' story. + return $element; + } + +} diff --git a/modules/ui_patterns_legacy/src/Template/TwigExtension.php b/modules/ui_patterns_legacy/src/Template/TwigExtension.php index ef334562..8bd3d102 100644 --- a/modules/ui_patterns_legacy/src/Template/TwigExtension.php +++ b/modules/ui_patterns_legacy/src/Template/TwigExtension.php @@ -2,28 +2,27 @@ namespace Drupal\ui_patterns_legacy\Template; -use Drupal\ui_patterns_legacy\UiPatternsLegacyManager; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; /** * Twig extension providing UI Patterns-specific functionalities. * - * @package Drupal\ui_patterns\Template + * @package Drupal\ui_patterns_legacy\Template */ class TwigExtension extends AbstractExtension { /** * {@inheritdoc} */ - public function getName() { + public function getName(): string { return 'ui_patterns_legacy'; } /** * {@inheritdoc} */ - public function getFunctions() { + public function getFunctions(): array { return [ new TwigFunction('pattern', [ $this, @@ -49,21 +48,14 @@ public function getFunctions() { * @return array * Pattern render array. * - * @see \Drupal\ui_patterns\Element\Pattern + * @see \Drupal\ui_patterns_legacy\Element\Pattern */ - public function renderPattern($id, array $fields = [], $variant = "") { - $component = UiPatternsLegacyManager::getComponentByUiPatternId($id); - if ($component) { - return [ - '#type' => 'component', - '#id' => $id, - '#slots' => $fields, - '#variant' => $variant, - ]; - } + public function renderPattern(string $id, array $fields = [], $variant = ""): array { return [ - '#type' => 'markup', - '#markup' => 'No component found for ' . $id, + '#type' => 'pattern', + '#id' => $id, + '#variant' => $variant, + '#fields' => $fields, ]; } @@ -78,20 +70,13 @@ public function renderPattern($id, array $fields = [], $variant = "") { * @return array * Pattern render array. * - * @see \Drupal\ui_patterns\Element\Pattern + * @see \Drupal\ui_patterns_legacy\Element\PatternPreview */ - public function renderPatternPreview($id, $variant = "") { - $component = UiPatternsLegacyManager::getComponentByUiPatternId($id); - if ($component) { - return [ - '#type' => 'component', - '#id' => $component['id'], - '#variant' => $variant, - ]; - } + public function renderPatternPreview(string $id, string $variant = ""): array { return [ - '#type' => 'markup', - '#markup' => 'No component found for ' . $id, + '#type' => 'pattern_preview', + '#id' => $id, + '#variant' => $variant, ]; } diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml index 685fafc9..ee986ee0 100644 --- a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml @@ -1,8 +1,7 @@ name: 'UI Patterns Legacy' type: module -description: 'Legacy discovery of UI Patterns configurations.' +description: 'Discovery of UI Patterns 1.x components.' core_version_requirement: ^10 package: 'User interface' dependencies: - ui_patterns:ui_patterns - - drupal:sdc From 005999b29063cfb91933a1597ffea8cb21e26811 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 00:09:47 +0200 Subject: [PATCH 37/81] Remove unused stuff --- config/schema/ui_patterns.schema.yml | 8 - .../ui_patterns_props_widget/css/toggler.png | Bin 14513 -> 0 bytes .../css/ui_patterns_props_widget.css | 36 --- .../ui_patterns_props_widget.toggle_token.js | 49 ---- .../src/Annotation/PropWidget.php | 46 ---- .../src/Definition/PropWidgetDefinition.php | 251 ----------------- .../src/Element/ComponentPropsWidget.php | 58 ---- .../src/Form/PropsWidgetFormBuilder.php | 193 ------------- .../src/Plugin/PropWidgetBase.php | 259 ------------------ .../src/Plugin/PropWidgetInterface.php | 74 ----- .../UiPatterns/EnumerationPropWidgetBase.php | 66 ----- .../PropWidget/SelectPropWidget.php | 29 -- .../PropWidget/TextfieldPropWidget.php | 36 --- .../src/UiPatternsPropsWidget.php | 130 --------- .../src/UiPatternsPropsWidgetManager.php | 101 ------- .../ui_patterns_props_widget.info.yml | 9 - .../ui_patterns_props_widget.libraries.yml | 12 - .../ui_patterns_props_widget.module | 29 -- .../ui_patterns_props_widget.services.yml | 4 - templates/patterns-destination.html.twig | 11 - ui_patterns.api.php | 85 ------ ui_patterns.module | 6 - 22 files changed, 1492 deletions(-) delete mode 100644 config/schema/ui_patterns.schema.yml delete mode 100644 modules/ui_patterns_props_widget/css/toggler.png delete mode 100644 modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css delete mode 100644 modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js delete mode 100644 modules/ui_patterns_props_widget/src/Annotation/PropWidget.php delete mode 100644 modules/ui_patterns_props_widget/src/Definition/PropWidgetDefinition.php delete mode 100644 modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php delete mode 100644 modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php delete mode 100644 modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php delete mode 100644 modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php delete mode 100644 modules/ui_patterns_props_widget/src/Plugin/UiPatterns/EnumerationPropWidgetBase.php delete mode 100644 modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php delete mode 100644 modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/TextfieldPropWidget.php delete mode 100644 modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php delete mode 100644 modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php delete mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml delete mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml delete mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.module delete mode 100644 modules/ui_patterns_props_widget/ui_patterns_props_widget.services.yml delete mode 100644 templates/patterns-destination.html.twig delete mode 100644 ui_patterns.api.php delete mode 100644 ui_patterns.module diff --git a/config/schema/ui_patterns.schema.yml b/config/schema/ui_patterns.schema.yml deleted file mode 100644 index 4e3b522c..00000000 --- a/config/schema/ui_patterns.schema.yml +++ /dev/null @@ -1,8 +0,0 @@ -core.entity_view_display.*.*.*.third_party_settings.field_layout.settings.pattern: - type: mapping - label: 'Per-view-mode Layout Pattern settings' - mapping: - variant: - type: string - label: 'Variant' - diff --git a/modules/ui_patterns_props_widget/css/toggler.png b/modules/ui_patterns_props_widget/css/toggler.png deleted file mode 100644 index e8db53e8361249c25f17fdeb1911e0984679e4a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14513 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 diff --git a/modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css b/modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css deleted file mode 100644 index 3f7c4f6f..00000000 --- a/modules/ui_patterns_props_widget/css/ui_patterns_props_widget.css +++ /dev/null @@ -1,36 +0,0 @@ -.js-ui-patterns-props-widget__wrapper { - position: relative; -} - -#ui-patterns-props-widget-token-link { - display: none; -} - -.ui-patterns-props-widget-show-token-a { - display: block; -} - -.js-ui-patterns-props-widget__toggler { - position: absolute; - width: 14px; - height: 14px; - right: 0; - top: 0; - display: block; - cursor: pointer; - background-image: url("toggler.png"); - background-repeat: no-repeat; - background-size: cover; -} - -.js-ui-patterns-props-widget__wrapper .js-ui-patterns-props-widget__token-wrapper > * { - display: none; -} - -.js-ui-patterns-props-widget__wrapper.js-ui-patterns-props-widget--token-has-value > .js-ui-patterns-props-widget__input-wrapper > * { - display: none; -} - -.js-ui-patterns-props-widget__wrapper.js-ui-patterns-props-widget--token-has-value > .js-ui-patterns-props-widget__token-wrapper > * { - display: block; -} diff --git a/modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js b/modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js deleted file mode 100644 index e3c9b072..00000000 --- a/modules/ui_patterns_props_widget/js/ui_patterns_props_widget.toggle_token.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file - * JavaScript file for the UI Pattern settings module. - */ - -(function ($, Drupal, drupalSettings, DrupalCoffee) { - - 'use strict'; - - /** - * Attaches ui patterns settings module behaviors. - * - * Handles enable/disable token element. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attach ui patterns settings toggle functionality to the page. - * - */ - Drupal.behaviors.ups_toggle_token = { - attach: function () { - once('ui-patterns-props-widget-show-token-link', '.js-ui-patterns-props-widget-show-token-link').forEach(function (elm) { - $(elm).after($('' + Drupal.t('Browse available token') + '').click(function (event) { - event.preventDefault(); - $('#ui-patterns-props-widget-token-link:first a').click(); - })); - }); - - once('ui-patterns-props-widget-wrapper', '.js-ui-patterns-props-widget__wrapper').forEach(function (el) { - var wrapper = $(el); - var toggler = $('
    '); - $(toggler).click(function () { - var tokenInput = $('.js-ui-patterns-props-widget__token', wrapper); - if ($(wrapper).hasClass('js-ui-patterns-props-widget--token-has-value')) { - tokenInput.attr('data-init-val', tokenInput.val()); - tokenInput.val(''); - wrapper.removeClass('js-ui-patterns-props-widget--token-has-value'); - } else { - tokenInput.val(tokenInput.attr('data-init-val')); - wrapper.addClass('js-ui-patterns-props-widget--token-has-value'); - } - }); - $('.js-ui-patterns-props-widget__input-wrapper', wrapper).append(toggler) - $('.js-ui-patterns-props-widget__token-wrapper', wrapper).append(toggler.clone(true)) - }); - } - }; -})(jQuery, Drupal, drupalSettings); diff --git a/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php b/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php deleted file mode 100644 index f9317b9b..00000000 --- a/modules/ui_patterns_props_widget/src/Annotation/PropWidget.php +++ /dev/null @@ -1,46 +0,0 @@ - NULL, - 'label' => NULL, - 'description' => NULL, - 'type' => NULL, - 'required' => FALSE, - 'default_value' => NULL, - 'forced_value' => NULL, - 'options' => NULL, - 'form_visible' => TRUE, - 'allow_token' => FALSE, - ]; - - /** - * PatternDefinitionSetting constructor. - */ - public function __construct($name, $value) { - if (is_scalar($value)) { - $this->definition['name'] = is_numeric($name) ? $value : $name; - $this->definition['label'] = $value; - $this->definition['type'] = 'textfield'; - $this->definition['preview'] = NULL; - $this->definition['allow_token'] = FALSE; - } - else { - $this->definition['name'] = !isset($value['name']) ? $name : $value['name']; - $this->definition['label'] = $value['label']; - $this->definition['required'] = $value['required'] ?? FALSE; - $this->definition['default_value'] = $value['default_value'] ?? NULL; - $this->definition['preview'] = $value['preview'] ?? NULL; - $this->definition['options'] = $value['options'] ?? NULL; - $this->definition['allow_token'] = $value['allow_token'] ?? FALSE; - $this->definition = $value + $this->definition; - } - } - - /** - * Return array definition. - * - * @return array - * Array definition. - */ - public function toArray() { - return $this->definition; - } - - /** - * Get Name property. - * - * @return mixed - * Property value. - */ - public function getName() { - return $this->definition['name']; - } - - /** - * Get Label property. - * - * @return mixed - * Property value. - */ - public function getLabel() { - return $this->definition['label']; - } - - /** - * Get required property. - * - * @return mixed - * Property value. - */ - public function getRequired() { - return $this->definition['required']; - } - - /** - * Get allow token property. - * - * @return bool - * Property value. - */ - public function getAllowToken() { - return $this->definition['allow_token']; - } - - /** - * Get options array. - * - * @return mixed - * Property option. - */ - public function getOptions() { - return $this->definition['options']; - } - - /** - * Get default value property. - * - * @return mixed - * Property value. - */ - public function getDefaultValue() { - return $this->definition['default_value']; - } - - /** - * Set default value property. - * - * @return mixed - * Property value. - */ - public function setDefaultValue($defaultValue) { - $this->definition['default_value'] = $defaultValue; - return $this; - } - - /** - * Set allow token value property. - * - * @param bool $allow_token - * Property value. - * - * @return $this - */ - public function setAllowToken($allow_token) { - $this->definition['allow_token'] = $allow_token; - return $this; - } - - /** - * Get default value property. - * - * @return mixed - * Property value. - */ - public function getForcedValue() { - return $this->definition['forced_value']; - } - - /** - * Get preview property. - * - * @return mixed - * Property value. - */ - public function getPreview() { - return $this->definition['preview']; - } - - /** - * Set default value property. - * - * @return mixed - * Property value. - */ - public function setForcedValue($forcedValue) { - $this->definition['forced_value'] = $forcedValue; - return $this; - } - - /** - * Get Description property. - * - * @return string - * Property value. - */ - public function getDescription() { - return $this->definition['description']; - } - - /** - * Set Description property. - * - * @param string $description - * Property value. - * - * @return $this - */ - public function setDescription($description) { - $this->definition['description'] = $description; - return $this; - } - - /** - * Is form visible property. - * - * @return bool - * Property value. - */ - public function isFormVisible() { - return $this->definition['form_visible']; - } - - /** - * Set form visible property. - * - * @param bool $visible - * Property value. - * - * @return $this - */ - public function setFormVisible($visible) { - $this->definition['form_visible'] = $visible; - return $this; - } - - /** - * Get Type property. - * - * @return string - * Property value. - */ - public function getType() { - return $this->definition['type']; - } - - /** - * Set Type property. - * - * @param string $type - * Property value. - * - * @return $this - */ - public function setType($type) { - $this->definition['type'] = $type; - return $this; - } - -} diff --git a/modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php b/modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php deleted file mode 100644 index 9a992c53..00000000 --- a/modules/ui_patterns_props_widget/src/Element/ComponentPropsWidget.php +++ /dev/null @@ -1,58 +0,0 @@ -find($element['#component'])->metadata; - $variant = $element['#variant'] ?? NULL; - $processed_props = UiPatternsPropsWidget::preprocess($component_metadata, $props_configuration, $variant, $preview, $entity); - unset($element['#props_configuration']); - foreach ($processed_props as $name => $prop_value) { - if (!isset($element['#props'][$name])) { - $element['#props'][$name] = $prop_value; - } - else { - if ($prop_value instanceof Attribute && $element['#props'][$name] instanceof Attribute) { - $element['#props'][$name] = new Attribute(array_merge($prop_value->toArray(), $element['#props'][$name]->toArray())); - } - elseif (is_array($element['#props'][$name]) && is_array($prop_value)) { - $element['#props'][$name] = array_merge($element['#props'][$name], $prop_value); - } - } - } - return $element; - } - - /** - * {@inheritdoc} - */ - public static function trustedCallbacks() { - return ['processPropsWidget']; - } - -} diff --git a/modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php b/modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php deleted file mode 100644 index 1c9d33f5..00000000 --- a/modules/ui_patterns_props_widget/src/Form/PropsWidgetFormBuilder.php +++ /dev/null @@ -1,193 +0,0 @@ -getDefinitions(); - /** @var EntityTypeInterface $definition */ - foreach ($entity_type_definations as $definition) { - if ($definition instanceof ContentEntityType) { - $content_entity_types[] = $definition->id(); - } - } - $form['token_link'] = [ - '#prefix' => '', - '#theme' => 'token_tree_link', - '#token_types' => $content_entity_types, - '#show_restricted' => TRUE, - '#weight' => 90, - ]; - } - - /** - * Build pattern props widget fieldset. - * - * @param array $form - * Form array. - * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata - * The pattern definition. - * @param array $configuration - * The pattern configuration. - */ - public static function layoutForm(array &$form, ComponentMetadata $component_metadata, array $configuration) { - $widgets = UiPatternsPropsWidget::getPatternDefinitionWidgets($component_metadata); - self::buildTokenLink($form); - - $form['#attached']['library'][] = 'ui_patterns_props_widget/widget'; - if (UiPatternsPropsWidgetManager::allowVariantToken($component_metadata)) { - $variant_token_value = $configuration['pattern']['variant_token'] ?? NULL; - $form['variant_token'] = [ - '#type' => 'textfield', - '#title' => 'Variant token', - '#attributes' => ['class' => ['js-ui-patterns-props-widget-show-token-link']], - '#default_value' => $variant_token_value, - ]; - } - - $form['variant']['#attributes']['class'][] = 'ui-patterns-variant-selector-' . $component_metadata->id; - if (!empty($widgets)) { - foreach ($widgets as $key => $widget) { - if (empty($widget->getType()) || !$widget->isFormVisible()) { - continue; - } - - if (!isset($form['settings'])) { - $form['settings'] = [ - '#type' => 'fieldset', - '#title' => t('Settings'), - ]; - } - $setting_value = $configuration['pattern']['settings'][$key] ?? NULL; - $token_value = $configuration['pattern']['settings'][$key . "_token"] ?? ""; - $widget = UiPatternsPropsWidget::createWidget($component_metadata, $widget); - $form['settings'] += $widget->buildConfigurationForm([], $setting_value, $token_value, 'layouts_display'); - } - PropsWidgetFormBuilder::buildVariantsForm(".ui-patterns-variant-selector-" . $component_metadata->id, $form['settings'], $component_metadata); - } - } - - /** - * Build widget display form. - * - * @param array $form - * Form array. - * @param array $configuration - * Configurations array. - */ - public static function displayForm(array &$form, array $configuration) { - $form['#attached']['library'][] = 'ui_patterns_props_widget/widget'; - self::buildTokenLink($form); - - /** @var \Drupal\sdc\ComponentPluginManager $plugin_manager */ - $plugin_manager = \Drupal::service('plugin.manager.sdc'); - /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ - $components = $plugin_manager->getDefinitions(); - - foreach ($components as $component_id => $component) { - $widgets = UiPatternsPropsWidget::getPatternDefinitionWidgets($component); - $form['variants'][$component_id]['#attributes']['class'][] = 'ui-patterns-variant-selector-' . $component_id; - if (UiPatternsPropsWidgetManager::allowVariantToken($component)) { - $variant_token_value = $configuration['variants_token'][$component_id] ?? NULL; - $form['variants']['#weight'] = 20; - $form['pattern_mapping']['#weight'] = 30; - $form['pattern_settings']['#weight'] = 40; - $form['variants_token'] = [ - '#type' => 'container', - '#title' => t('Pattern Variant'), - '#weight' => 25, - '#states' => [ - 'visible' => [ - 'select[id="patterns-select"]' => ['value' => $component_id], - ], - ], - ]; - $form['variants_token'][$component_id] = [ - '#type' => 'textfield', - '#title' => t('Variant token'), - '#default_value' => $variant_token_value, - '#attributes' => ['class' => ['js-ui-patterns-props-widget-show-token-link']], - '#states' => [ - 'visible' => [ - 'select[id="patterns-select"]' => ['value' => $component_id], - ], - ], - ]; - } - if (!empty($widgets)) { - foreach ($widgets as $key => $widget) { - if (empty($widget->getType()) || !$widget->isFormVisible()) { - continue; - } - if (!isset($form['pattern_widgets'][$component_id])) { - $form['pattern_widgets'][$component_id] = [ - '#type' => 'fieldset', - '#title' => t('Settings'), - '#states' => [ - 'visible' => [ - 'select[id="patterns-select"]' => ['value' => $component_id], - ], - ], - ]; - } - $fieldset = &$form['pattern_settings'][$component_id]; - $settingType = UiPatternsPropsWidget::createWidget($component, $widget); - $setting_value = $configuration['pattern_settings'][$component_id][$key] ?? NULL; - $token_value = $configuration['pattern_settings'][$component_id][$key . "_token"] ?? NULL; - $fieldset += $settingType->buildConfigurationForm([], $setting_value, $token_value, 'display'); - } - PropsWidgetFormBuilder::buildVariantsForm('.ui-patterns-variant-selector-' . $component_id, $fieldset, $component); - } - } - } - - /** - * Hide all settings which are configured by the variant. - * - * @param string $select_selector - * The id of the variant select field. - * @param array $fieldset - * The fieldset. - * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata - * The pattern definition. - */ - private static function buildVariantsForm($select_selector, array &$fieldset, ComponentMetadata $component_metadata) { - $variants = $component_metadata->variants ?? []; - foreach ($variants as $variant_ary) { - $settings = $variant_ary['settings'] ?? []; - foreach ($settings as $name => $setting) { - if (isset($fieldset[$name])) { - // Add an or before a new state begins. - if (isset($fieldset[$name]['#states']['invisible']) && count($fieldset[$name]['#states']['invisible']) != 0) { - $fieldset[$name]['#states']['invisible'][] = 'or'; - } - // Hide configured setting. - $fieldset[$name]['#states']['invisible'][][$select_selector]['value'] = $variant->getName(); - $fieldset[$name . '_token']['#states']['invisible'][][$select_selector]['value'] = $variant->getName(); - } - } - } - } - -} diff --git a/modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php b/modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php deleted file mode 100644 index d2d534d3..00000000 --- a/modules/ui_patterns_props_widget/src/Plugin/PropWidgetBase.php +++ /dev/null @@ -1,259 +0,0 @@ -defaultConfiguration(); - $this->propWidgetDefinition = $configuration['prop_widget_definition']; - $this->componentMetadata = $configuration['component_metadata']; - unset($configuration['prop_widget_definition']); - unset($configuration['component_metadata']); - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - - /** - * Return value if set otherwise take the default value. - * - * @param mixed $value - * The provided value. - * - * @return string - * The value for this setting - */ - protected function getValue($value) { - if ($value === NULL) { - return $this->getPropWidgetDefinition()->getDefaultValue(); - } - else { - return $value ?? ""; - } - } - - /** - * Returns the widget definition. - * - * @return \Drupal\ui_patterns_settings\Definition\PropWidgetDefinition - * The widget definition. - */ - protected function getPropWidgetDefinition() { - return $this->propWidgetDefinition; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - $plugin = new static($configuration, $plugin_id, $plugin_definition); - - /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ - $translation = $container->get('string_translation'); - - $plugin->setStringTranslation($translation); - - return $plugin; - } - - /** - * {@inheritdoc} - */ - public function label() { - $plugin_definition = $this->getPluginDefinition(); - return $plugin_definition['label']; - } - - /** - * {@inheritdoc} - */ - public function getDescription() { - $plugin_definition = $this->getPluginDefinition(); - return $plugin_definition['description'] ?? ''; - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - return []; - } - - /** - * {@inheritdoc} - */ - public function getConfiguration() { - return $this->configuration; - } - - /** - * {@inheritdoc} - */ - public function setConfiguration(array $configuration) { - $this->configuration = $configuration + $this->defaultConfiguration(); - } - - /** - * {@inheritdoc} - */ - public function calculateDependencies() { - return []; - } - - /** - * {@inheritdoc} - */ - public function preprocess($value, array $context) { - $def = $this->getPropWidgetDefinition(); - $value = $this->propPreprocess($value, $context, $def); - return $value; - } - - /** - * {@inheritdoc} - */ - public function propPreprocess($value, array $context, PropWidgetDefinition $def) { - return $value; - } - - /** - * Returns the bind form field. - * - * @param array $form - * The fieldset definition array for the widget form. - * @param string $value - * The stored default value. - * @param \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition $def - * The widget definition. - * - * @return array - * The form. - */ - protected function tokenForm(array $form, $value, PropWidgetDefinition $def) { - $form[$def->getName() . "_token"] = [ - '#type' => 'textfield', - '#title' => $this->t("Token for %label", ['%label' => $def->getLabel()]), - '#default_value' => $this->getValue($value), - '#attributes' => ['class' => ['js-ui-patterns-props-widget-show-token-link', 'js-ui-patterns-props-widget__token']], - '#wrapper_attributes' => ['class' => ['js-ui-patterns-props-widget__token-wrapper']], - ]; - return $form; - } - - /** - * Check required input fields in layout forms. - * - * @param array $element - * The element to validate. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. - * @param array $form - * The form. - */ - public static function validateLayout(array $element, FormStateInterface &$form_state, array &$form) { - $parents = $element['#parents']; - $value = $form_state->getValue($parents); - $parents[count($parents) - 1] = $parents[count($parents) - 1] . '_token'; - $token_value = $form_state->getValue($parents); - if (empty($value) && empty($token_value)) { - // Check if a variant is selected and the value - // is provided by the variant. - $variant = $form_state->getValue([ - 'layout_configuration', - 'pattern', - 'variant', - ]); - if (!empty($variant)) { - $variant_def = $element['#pattern_definition']->getVariant($variant); - $variant_ary = $variant_def->toArray(); - if (!empty($variant_ary['settings'][$element['#pattern_setting_definition']->getName()])) { - return; - } - } - - $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); - } - } - - /** - * Add validation and basics classes to the raw input field. - * - * @param array $input - * The input field. - * @param \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition $def - * The widget definition. - * @param string $form_type - * The form type. Either layouts_display or display. - */ - protected function handleInput(array &$input, PropWidgetDefinition $def, $form_type) { - $input['#attributes']['class'][] = 'js-ui-patterns-props-widget__input'; - $input['#wrapper_attributes']['class'][] = 'js-ui-patterns-props-widget__input-wrapper'; - if ($def->getRequired()) { - $input['#title'] .= ' *'; - if ($form_type === 'layouts_display') { - $input['#prop_widget_definition'] = $this->propWidgetDefinition; - $input['#component_metadata'] = $this->componentMetadata; - $input['#element_validate'][] = [ - PropWidgetBase::class, - 'validateLayout', - ]; - } - } - } - - /** - * {@inheritdoc} - * - * Creates a generic configuration form for all widgets. - * Individual widgets can add elements to this form by - * overriding PatternSettingTypeBaseInterface::widgetForm(). - * Most plugins should not override this method unless they - * need to alter the generic form elements. - * - * @see \Drupal\Core\Block\BlockBase::blockForm() - */ - public function buildConfigurationForm(array $form, $value, $token_value, $form_type) { - $def = $this->getPropWidgetDefinition(); - $form = $this->widgetForm($form, $value, $def, $form_type); - $classes = 'js-ui-patterns-props-widget__wrapper'; - if ($def->getAllowToken()) { - if (!empty($token_value)) { - $classes .= ' js-ui-patterns-props-widget--token-has-value'; - } - $form[$def->getName()]['#prefix'] = '
    '; - } - if ($def->getAllowToken()) { - $form = $this->tokenForm($form, $token_value, $def); - $form[$def->getName() . '_token']['#suffix'] = '
    '; - } - - return $form; - } - -} diff --git a/modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php b/modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php deleted file mode 100644 index 0100a68a..00000000 --- a/modules/ui_patterns_props_widget/src/Plugin/PropWidgetInterface.php +++ /dev/null @@ -1,74 +0,0 @@ - $this->t("Please select")]; - } - - /** - * Returns the enumeration type. - * - * @return string - * The enumeration type. - */ - abstract protected function getEnumerationType(); - - /** - * Returns the enumeration options. - * - * @param \Drupal\ui_patterns_settings\Definition\PatternDefinitionSetting $def - * The pattern definition. - * - * @return mixed - * The options. - */ - protected function getOptions(PropWidgetDefinition $def) { - return $def->getOptions(); - } - - /** - * {@inheritdoc} - */ - public function widgetForm(array $form, $value, PropWidgetDefinition $def, $form_type) { - if ($def->getRequired() == FALSE) { - $options = $this->emptyOption(); - } - else { - $options = []; - } - - $options += $this->getOptions($def); - $form[$def->getName()] = [ - '#type' => $this->getEnumerationType($def), - '#title' => $def->getLabel(), - '#description' => $def->getDescription(), - '#default_value' => $this->getValue($value), - '#options' => $options, - ]; - $this->handleInput($form[$def->getName()], $def, $form_type); - return $form; - } - -} diff --git a/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php b/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php deleted file mode 100644 index 95bb10af..00000000 --- a/modules/ui_patterns_props_widget/src/Plugin/UiPatterns/PropWidget/SelectPropWidget.php +++ /dev/null @@ -1,29 +0,0 @@ -getName()] = [ - '#type' => 'textfield', - '#title' => $def->getLabel(), - '#description' => $def->getDescription(), - '#default_value' => $this->getValue($value), - ]; - $this->handleInput($form[$def->getName()], $def, $form_type); - return $form; - } - -} diff --git a/modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php b/modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php deleted file mode 100644 index fa9ce0e0..00000000 --- a/modules/ui_patterns_props_widget/src/UiPatternsPropsWidget.php +++ /dev/null @@ -1,130 +0,0 @@ - $widget_definition) { - if ($widget_definition->getForcedValue()) { - $value = $widget_definition->getForcedValue(); - } - elseif (!empty($props_configuration[$key . '_token'])) { - $token_value = $props_configuration[$key . '_token']; - $token_data = []; - if ($entity !== NULL) { - $token_data[$entity->getEntityTypeId()] = $entity; - } - $value = \Drupal::token()->replace($token_value, $token_data, ['clear' => TRUE]); - } - elseif (isset($props_configuration[$key])) { - $value = $props_configuration[$key]; - } - elseif ($preview && !empty($widget_definition->getPreview())) { - $value = $widget_definition->getPreview(); - } - else { - $value = $widget_definition->getDefaultValue(); - } - if ($variant != 'default' && $variant != NULL) { - $variant_ob = NULL; - if ($variant_ob != NULL) { - $variant_ary = $variant_ob->toArray(); - if (isset($variant_ary['settings']) && isset($variant_ary['settings'][$key])) { - $value = $variant_ary['settings'][$key]; - } - } - } - $widget = UiPatternsPropsWidget::createWidget($component_metadata, $widget_definition); - $processed_widgets_data[$key] = $widget->preprocess($value, $context); - } - return $processed_widgets_data; - - } - - /** - * Get setting definitions for a pattern definition. - * - * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata - * The definition. - * - * @return \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition[] - * Setting pattern definitons. - */ - public static function getPatternDefinitionWidgets(ComponentMetadata $component_metadata) { - $props = $component_metadata->schema['properties']; - $widgets = []; - if (!empty($props)) { - foreach ($props as $key => $prop) { - $def = ['label' => $prop['title'], 'options' => $prop['enum'] ?? NULL]; - $widget = UiPatternsPropsWidget::getManager()->getWidgetByProp($component_metadata, $key, $prop); - if ($widget !== NULL) { - $def['type'] = $widget['id']; - $widgets[$key] = new PropWidgetDefinition($key, $def); - } else { - $def['type'] = 'string'; - $widgets[$key] = new PropWidgetDefinition($key, $def); - } - } - } - return $widgets; - } - - /** - * Create prop widget type plugin. - * - * @param \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition $widget_defintion - * The widget defintion. - * - * @return \Drupal\ui_patterns_props_widget\Definition\PropWidgetDefinition - * Widget Plugin instance. - */ - public static function createWidget(ComponentMetadata $component_metadata, PropWidgetDefinition $widget_defintion) { - $configuration = []; - $configuration['prop_widget_definition'] = $widget_defintion; - $configuration['component_metadata'] = $component_metadata; - return \Drupal::service('plugin.manager.ui_patterns_props_widget_manager') - ->createInstance($widget_defintion->getType(), $configuration); - } - -} diff --git a/modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php b/modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php deleted file mode 100644 index 35f78d96..00000000 --- a/modules/ui_patterns_props_widget/src/UiPatternsPropsWidgetManager.php +++ /dev/null @@ -1,101 +0,0 @@ -moduleHandler = $module_handler; - $this->alterInfo('ui_patterns_props_widget_info'); - $this->setCacheBackend($cache_backend, 'ui_patterns_props_widget', ['ui_patterns_props_widget']); - } - - /** - * Returns the right widget for given . - */ - public function getWidgetByProp(ComponentMetadata $component_metadata, $prop_name, array $prop) { - $definitions = $this->getDefinitions(); - $widget = NULL; - $metadata_schema = $component_metadata->schema; - // Check for an existing widget. - if (isset($prop['widget']['type'])) { - $widget_type = $prop['widget']['type']; - $widget = $this->getDefinition($widget_type); - } - - // Check for default widgets. - $schema_stub = ['name' => $prop_name, 'properties' => []]; - usort($definitions, function($a, $b) { - return $a['priority'] ?? 1 > $b['priority'] ?? 1; - } ); - foreach ($definitions as $definition) { - $origin_schema = $schema_stub; - $annotation_schema = $origin_schema; - $origin_schema['properties'][$prop_name] = $metadata_schema['properties'][$prop_name]; - $annotation_schema['properties'][$prop_name] = $definition['schema']; - try { - $this->compatibilityChecker->isCompatible($annotation_schema, $origin_schema); - $widget = $definition; - } catch (IncompatibleComponentSchema $exception) { - // Do nothing. - } - } - return $widget; - } - - /** - * Returns TRUE if a variant token can configured. - * - * @param \Drupal\sdc\Component\ComponentMetadata $component_metadata - * The pattern definition. - * - * @return bool - * Returns TRUE if a variant token can configured. - */ - public static function allowVariantToken(ComponentMetadata $component_metadata) { - if (isset($component_metadata->allow_variant_token) && $component_metadata->allow_variant_token) { - return TRUE; - } - else { - return FALSE; - } - } - - /** - * {@inheritdoc} - */ - public function createInstance($plugin_id, array $configuration = []) { - $plugin_definition = $this->getDefinition($plugin_id); - $plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition); - // If the plugin provides a factory method, pass the container to it. - if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) { - $plugin = $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition); - } - else { - $plugin = new $plugin_class($configuration, $plugin_id, $plugin_definition); - } - return $plugin; - } - -} diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml b/modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml deleted file mode 100644 index 37f9b96d..00000000 --- a/modules/ui_patterns_props_widget/ui_patterns_props_widget.info.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: UI Patterns Props Widget -type: module -description: Configure components properties with widgets -package: User interface -core_version_requirement: ^10 -dependencies: - - ui_patterns:ui_patterns - - drupal:sdc - - token:token diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml b/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml deleted file mode 100644 index e9c0ac3a..00000000 --- a/modules/ui_patterns_props_widget/ui_patterns_props_widget.libraries.yml +++ /dev/null @@ -1,12 +0,0 @@ -widget: - version: VERSION - js: - js/ui_patterns_props_widget.toggle_token.js: {} - css: - component: - css/ui_patterns_props_widget.css: {} - dependencies: - - core/jquery - - core/once - - core/drupal - - core/drupalSettings diff --git a/modules/ui_patterns_props_widget/ui_patterns_props_widget.module b/modules/ui_patterns_props_widget/ui_patterns_props_widget.module deleted file mode 100644 index 509108d6..00000000 --- a/modules/ui_patterns_props_widget/ui_patterns_props_widget.module +++ /dev/null @@ -1,29 +0,0 @@ -setLabel('My new label'); -} - -/** - * Alter UI Patterns Source definitions. - * - * @see \Drupal\ui_patterns\UiPatternsSourceManager - */ -function hook_ui_patterns_ui_patterns_source_info_alter(&$definitions) { - $definitions['my_field_source']['tags'][] = 'new_tag'; -} - -/** - * Provide hook theme suggestions for patterns. - * - * @see ui_patterns_theme_suggestions_alter() - */ -function hook_ui_patterns_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('views_row')) { - $hook = $variables['theme_hook_original']; - $view_name = $context->getProperty('view_name'); - $display = $context->getProperty('display'); - - $suggestions[] = $hook . '__views_row__' . $view_name; - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $display; - } -} - -/** - * Provide hook theme suggestions for patterns destination wrapper. - * - * A pattern render element having '#multiple_sources' set to TRUE can render - * multiple sources on the same destination field. Sources will be rendered - * using the 'patterns_destination' theme function which will use the - * 'patterns-destination.html.twig' template file. - * - * Developers can take over rendering of the template above by providing proper - * suggestions, this is useful in case you wish to provide separators or other - * wrapping elements. - * - * @see ui_patterns_theme_suggestions_alter() - * @see \Drupal\ui_patterns\Element\Pattern::processMultipleSources() - */ -function hook_ui_patterns_destination_suggestions_alter(array &$suggestions, array $variables, PatternContext $context) { - if ($context->isOfType('views_row')) { - $hook = $variables['theme_hook_original']; - $view_name = $context->getProperty('view_name'); - $display = $context->getProperty('display'); - $pattern = $context->getProperty('pattern'); - $field = $context->getProperty('field'); - - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $pattern . '__' . $field; - $suggestions[] = $hook . '__views_row__' . $view_name . '__' . $display . '__' . $pattern . '__' . $field; - } -} - -/** - * Alter pattern settings form under "Manage display". - * - * @param array $form - * Pattern settings fieldset. - * @param array $configuration - * Pattern configuration. - */ -function hook_ui_patterns_display_settings_form_alter(array &$form, array $configuration) { - $form['element'] = ['#type' => 'input']; -} diff --git a/ui_patterns.module b/ui_patterns.module deleted file mode 100644 index 5c049f6e..00000000 --- a/ui_patterns.module +++ /dev/null @@ -1,6 +0,0 @@ - Date: Thu, 5 Oct 2023 00:44:21 +0200 Subject: [PATCH 38/81] Init ui_patterns_library --- .../src/Controller/LibraryController.php | 77 +++++++++++++++++++ .../src/Element/ComponentStory.php | 39 ++++++++++ .../src/Template/TwigExtension.php | 61 +++++++++++++++ .../patterns-meta-information.html.twig | 67 ++++++++++++++++ .../patterns-overview-page.html.twig | 49 ++++++++++++ .../templates/patterns-single-page.html.twig | 18 +++++ .../ui_patterns_library.info.yml | 7 ++ .../ui_patterns_library.links.menu.yml | 5 ++ .../ui_patterns_library.module | 23 ++++++ .../ui_patterns_library.permissions.yml | 2 + .../ui_patterns_library.routing.yml | 14 ++++ 11 files changed, 362 insertions(+) create mode 100644 modules/ui_patterns_library/src/Controller/LibraryController.php create mode 100644 modules/ui_patterns_library/src/Element/ComponentStory.php create mode 100644 modules/ui_patterns_library/src/Template/TwigExtension.php create mode 100644 modules/ui_patterns_library/templates/patterns-meta-information.html.twig create mode 100644 modules/ui_patterns_library/templates/patterns-overview-page.html.twig create mode 100644 modules/ui_patterns_library/templates/patterns-single-page.html.twig create mode 100644 modules/ui_patterns_library/ui_patterns_library.info.yml create mode 100644 modules/ui_patterns_library/ui_patterns_library.links.menu.yml create mode 100644 modules/ui_patterns_library/ui_patterns_library.module create mode 100644 modules/ui_patterns_library/ui_patterns_library.permissions.yml create mode 100644 modules/ui_patterns_library/ui_patterns_library.routing.yml diff --git a/modules/ui_patterns_library/src/Controller/LibraryController.php b/modules/ui_patterns_library/src/Controller/LibraryController.php new file mode 100644 index 00000000..fe0adabf --- /dev/null +++ b/modules/ui_patterns_library/src/Controller/LibraryController.php @@ -0,0 +1,77 @@ +get('plugin.manager.sdc') + ); + } + + /** + * Title callback. + * + * @return string + * Pattern label. + */ + public function title($name) { + $definition = $this->componentPluginManager->getDefinition($name); + return $definition["name"]; + } + + /** + * Render a single component page. + * + * @param string $name + * Plugin ID. + * + * @return array + * Return render array. + */ + public function single($name) { + $definition = $this->componentPluginManager->getDefinition($name); + return [ + '#theme' => 'patterns_single_page', + '#component' => $definition, + ]; + } + + /** + * Render the components overview page. + * + * @return array + * Patterns overview page render array. + */ + public function overview() { + // @todo use UI Patterns plugin maanger instead of SDC, because we look for categorized definitions. + $definitions = $this->componentPluginManager->getAllComponents(); + $categorized_definitions = [ + "all" => $definitions, + ]; + return [ + '#theme' => 'patterns_overview_page', + '#components' => $categorized_definitions, + ]; + } + +} diff --git a/modules/ui_patterns_library/src/Element/ComponentStory.php b/modules/ui_patterns_library/src/Element/ComponentStory.php new file mode 100644 index 00000000..32a9f26e --- /dev/null +++ b/modules/ui_patterns_library/src/Element/ComponentStory.php @@ -0,0 +1,39 @@ + [ + [$this, 'loadStory'], + [$this, 'preRenderComponent'], + ], + '#component' => '', + '#props' => [], + '#slots' => [], + '#propsAlter' => [], + '#slotsAlter' => [], + ]; + } + + /** + * + */ + public function loadStory(array $element): array { + // @todo Load slots & props from a component story. + return $element; + } + +} diff --git a/modules/ui_patterns_library/src/Template/TwigExtension.php b/modules/ui_patterns_library/src/Template/TwigExtension.php new file mode 100644 index 00000000..1b105ec3 --- /dev/null +++ b/modules/ui_patterns_library/src/Template/TwigExtension.php @@ -0,0 +1,61 @@ + 'component_story', + '#component' => $component_id, + '#component' => $story_id, + '#slots' => $slots, + '#props' => $props, + ]; + } + +} diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig new file mode 100644 index 00000000..27a3b7a4 --- /dev/null +++ b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig @@ -0,0 +1,67 @@ +{# +/** + * @file + * UI Pattern meta information. + */ +#} + +{% if pattern is not empty %} + + {# Pattern name and description. #} +

    {{ pattern.label }}

    +

    {{ pattern.description }}

    + {% if pattern.tags %} +
    + {{ "Tags:"|t }} +
      + {% for tag in pattern.tags %} +
    • {{ tag }}
    • + {% endfor %} +
    +
    + {% endif %} + + {# Pattern fields descriptions. #} + {% if pattern.fields or pattern.additional.settings %} + + + + + + + + + + + + {% for field in pattern.fields %} + + + + + + + + {% endfor %} + {% for name, setting in pattern.additional.settings %} + + + + + + + + {% endfor %} + +
    {{ "Type"|t }}{{ "Name"|t }}{{ "Label"|t }}{{ "Type"|t }}{{ "Description"|t }} / {{ "Options"|t }}
    {{ "Field"|t }}{{ field.name }}{{ field.label }}{{ field.type }}{{ field.description }}
    {{ "Setting"|t }}{{ name }}{{ setting.label }}{{ setting.type }}{{ setting.description }} + {% if setting.options %} +
      + {% for key, label in setting.options %} +
    • {{ key }}: {{ label }}
    • + {% endfor %} +
    + {% endif %} +
    + {% endif %} + +{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig new file mode 100644 index 00000000..96cfd4b1 --- /dev/null +++ b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig @@ -0,0 +1,49 @@ +{# +/** + * @file + * UI Pattern library page template, override this in your theme. + */ +#} + +{% if patterns is not empty %} +

    {{ "Available patterns"|t }}

    + + {# List of available patterns with anchor links. #} + {% for group_name, group_patterns in patterns %} + {% if patterns|length > 1 %} +

    {{ group_name }}

    + {% endif %} +
      + {% for pattern_name, pattern in group_patterns %} +
    • + {{ pattern.label }} +
    • + {% endfor %} +
    + {% endfor %} + +
    + + {% for group_patterns in patterns %} + {% for pattern_name, pattern in group_patterns %} +
    + {{ pattern.meta }} + + {# Rendered pattern preview. #} +
    + {{ "Preview"|t }} + {{ pattern.rendered }} +
    + + {# Link to standalone pattern preview page.#} +

    + + {% trans %}View {{ pattern.label }} as stand-alone{% endtrans %} + +

    +
    + +
    + {% endfor %} + {% endfor %} +{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-single-page.html.twig b/modules/ui_patterns_library/templates/patterns-single-page.html.twig new file mode 100644 index 00000000..6d6294ce --- /dev/null +++ b/modules/ui_patterns_library/templates/patterns-single-page.html.twig @@ -0,0 +1,18 @@ +{# +/** + * @file + * UI Pattern library standalone page, override this in your theme. + */ +#} + +{% if pattern is not empty %} +
    + {{ pattern.meta }} + + {# Rendered pattern preview. #} +
    + {{ "Preview"|t }} + {{ pattern.rendered }} +
    +
    +{% endif %} diff --git a/modules/ui_patterns_library/ui_patterns_library.info.yml b/modules/ui_patterns_library/ui_patterns_library.info.yml new file mode 100644 index 00000000..e5e4bae5 --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.info.yml @@ -0,0 +1,7 @@ +name: 'UI Patterns Library' +type: module +description: 'Browse components in library pages.' +core_version_requirement: ^10 +package: 'User interface' +dependencies: + - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_library/ui_patterns_library.links.menu.yml b/modules/ui_patterns_library/ui_patterns_library.links.menu.yml new file mode 100644 index 00000000..49b255f8 --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.links.menu.yml @@ -0,0 +1,5 @@ +ui_patterns_library.overview: + title: 'UI Patterns library' + description: 'Browse components in library pages.' + parent: system.admin_reports + route_name: ui_patterns_library.overview diff --git a/modules/ui_patterns_library/ui_patterns_library.module b/modules/ui_patterns_library/ui_patterns_library.module new file mode 100644 index 00000000..cc3adb2c --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.module @@ -0,0 +1,23 @@ + [ + 'variables' => ['components' => NULL], + ], + 'patterns_single_page' => [ + 'variables' => ['component' => NULL], + ], + 'patterns_meta_information' => [ + 'variables' => ['component' => NULL], + ] + ]; +} diff --git a/modules/ui_patterns_library/ui_patterns_library.permissions.yml b/modules/ui_patterns_library/ui_patterns_library.permissions.yml new file mode 100644 index 00000000..92951358 --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.permissions.yml @@ -0,0 +1,2 @@ +access patterns page: + title: 'Access library page' diff --git a/modules/ui_patterns_library/ui_patterns_library.routing.yml b/modules/ui_patterns_library/ui_patterns_library.routing.yml new file mode 100644 index 00000000..b233b9e9 --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.routing.yml @@ -0,0 +1,14 @@ +ui_patterns_library.overview: + path: '/patterns' + defaults: + _controller: '\Drupal\ui_patterns_library\Controller\LibraryController::overview' + _title: 'Components library' + requirements: + _permission: 'access patterns page' +ui_patterns_library.single: + path: '/patterns/{name}' + defaults: + _controller: '\Drupal\ui_patterns_library\Controller\LibraryController::single' + _title_callback: '\Drupal\ui_patterns_library\Controller\LibraryController::title' + requirements: + _permission: 'access patterns page' From 3ccf1113d7d741c398d26e125d46c3c1aa923489 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 00:51:52 +0200 Subject: [PATCH 39/81] Update README.md --- README.md | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 183444d1..18def1bf 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# UI Patterns +# UI Patterns 2.x -Define and expose self-contained UI patterns as Drupal plugins and use them -seamlessly as drop-in templates for [panels](https://www.drupal.org/project/panels), -[field groups](https://www.drupal.org/project/field_group), views, -[Display Suite](https://www.drupal.org/project/ds) view modes and field templates. +Define and expose self-contained UI Components as Drupal plugins and use them seamlessly in Drupal development and site-building. + +Also called "components", UI patterns are reusable, nestable, guided by clear standards, and can be assembled together to build any number of applications. Examples: card, button, slider, pager, menu, toast... The UI Patterns module also integrates with with tools like [PatternLab](http://patternlab.io/) or modules like [Component Libraries](https://www.drupal.org/project/components) @@ -11,28 +10,19 @@ thanks to [definition overrides](https://www.drupal.org/docs/contributed-modules ## Project overview -The UI Patterns project provides 6 modules: +The UI Patterns project provides 5 modules: -- **UI Patterns**: the main module, it exposes the UI Patterns system APIs and it does not do much more than that. -- **UI Patterns Library**: allows to define patterns via YAML and generates a pattern library page available at `/patterns` +- **UI Patterns**: the main module, based on Drupal Core SDC API, with additional powerful API and quality-of-life improvments +- **UI Patterns Library**: generates a pattern library page available at `/patterns` to be used as documentation for content editors or as a showcase for business. Use this module if you don't plan to - use more advanced component library systems such as PatternLab or Fractal. + use more advanced component library systems such as Storybook, PatternLab or Fractal. [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/define-your-patterns) -- **UI Patterns Field Group**: allows to use patterns to format field groups provided by the - [Field group](https://www.drupal.org/project/field_group) module. - [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-with-field-groups) -- **UI Patterns Layouts**: allows to use patterns as layouts. This allows patterns to be used on - [Display Suite](https://www.drupal.org/project/ds) view modes or on [panels](https://www.drupal.org/project/panels) +- **UI Patterns Layouts**: allows to use patterns as layouts. This allows patterns to be used with Layout Builder, + [Display Suite](https://www.drupal.org/project/ds) or [Panels](https://www.drupal.org/project/panels) out of the box. [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-as-layouts) -- **UI Patterns Display Suite**: allows to use patterns to format [Display Suite](https://www.drupal.org/project/ds) - field templates. [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-with-field-templates) -- **UI Patterns Views**: allows to use patterns as Views row templates. +- **UI Patterns Views**: allows to use patterns as Views styles or Views rows. [Learn more](https://www.drupal.org/docs/contributed-modules/ui-patterns/use-patterns-with-views) - -## Try it out! - -Download and install the [Bootstrap Patterns](https://github.com/nuvoleweb/bootstrap_patterns) theme on a vanilla Drupal -8 installation to quickly try out the UI Patterns module. +- **UI Patterns Legacy**: Load your UI Patterns 1.x components inside UI Patterns 2.x ## Documentation From ad05a9b6523a457e559c666304aab3620d714593 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 00:54:13 +0200 Subject: [PATCH 40/81] composer.json update --- composer.json | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 057c80dd..6a355514 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,9 @@ { - "name": "drupal/ui_patterns", - "description": "UI Patterns.", - "license": "GPL-2.0-or-later", - "type": "drupal-module", - "authors": [ - { - "name": "Nuvole Web", - "email": "info@nuvole.org" - } - ], - "require": { - "drupal/token": "^1.0" - } - + "name": "drupal/ui_patterns", + "description": "Define and expose self-contained UI components and use them seamlessly in development and site-building.", + "license": "GPL-2.0-or-later", + "type": "drupal-module", + "require": { + "drupal/token": "^1.0" + } } From 7ee5ac3f45baf69e34919c73b3d0fe30f508a51a Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 11:02:52 +0200 Subject: [PATCH 41/81] First ui_patterns_library results; new TemporaryHelper & ComponentElement --- .../src/Element/Pattern.php | 39 ++------- .../src/Controller/LibraryController.php | 13 ++- .../src/Element/ComponentStory.php | 20 ++++- .../src/Template/TwigExtension.php | 4 +- .../patterns-meta-information.html.twig | 67 --------------- .../patterns-overview-page.html.twig | 49 ----------- .../templates/patterns-single-page.html.twig | 18 ---- .../ui-patterns-component-metadata.html.twig | 57 +++++++++++++ .../ui-patterns-overview-page.html.twig | 48 +++++++++++ .../ui-patterns-single-page.html.twig | 28 ++++++ .../ui_patterns_library.module | 8 +- .../ui_patterns_library.services.yml | 5 ++ src/Element/ComponentElement.php | 85 +++++++++++++++++++ src/TemporaryHelper.php | 49 +++++++++++ .../components/alert/alert.component.yml | 3 +- .../components/alert/alert.twig | 2 +- .../blockquote/blockquote.component.yml | 3 +- .../components/button/button.component.yml | 3 +- .../components/button/button.twig | 0 .../components/card/card.component.yml | 3 +- .../close_button/close_button.component.yml | 3 +- .../components/figure/figure.component.yml | 3 +- .../progress/progress.component.yml | 3 +- .../components/progress/progress.twig | 0 24 files changed, 323 insertions(+), 190 deletions(-) delete mode 100644 modules/ui_patterns_library/templates/patterns-meta-information.html.twig delete mode 100644 modules/ui_patterns_library/templates/patterns-overview-page.html.twig delete mode 100644 modules/ui_patterns_library/templates/patterns-single-page.html.twig create mode 100644 modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig create mode 100644 modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig create mode 100644 modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig create mode 100644 modules/ui_patterns_library/ui_patterns_library.services.yml create mode 100644 src/Element/ComponentElement.php create mode 100644 src/TemporaryHelper.php create mode 100644 tests/modules/ui_patterns_test/components/button/button.twig create mode 100644 tests/modules/ui_patterns_test/components/progress/progress.twig diff --git a/modules/ui_patterns_legacy/src/Element/Pattern.php b/modules/ui_patterns_legacy/src/Element/Pattern.php index 885364b4..b8246783 100644 --- a/modules/ui_patterns_legacy/src/Element/Pattern.php +++ b/modules/ui_patterns_legacy/src/Element/Pattern.php @@ -3,7 +3,8 @@ namespace Drupal\ui_patterns_legacy\Element; use Drupal\Core\Render\Element; -use Drupal\sdc\Element\ComponentElement; +use Drupal\ui_patterns\Element\ComponentElement; +use Drupal\ui_patterns\TemporaryHelper; /** * Renders a pattern element as a SDC element. @@ -55,43 +56,17 @@ private static function resolveCompactFormat(array $element): array { return $element; } - /** - * - */ - private static function getComponentNamespace(array $element): array { - if (!array_key_exists("#id", $element)) { - // Nothing to do. - return $element; - } - $parts = explode(":", $element["id"]); - if (count(array_filter($parts)) === 2) { - // Already namespaced. - return $element; - } - if (count(array_filter($parts)) > 2) { - // Unexpected situation. - return $element; - } - $components = \Drupal::service('plugin.manager.sdc')->getAllComponents(); - // @todo Search first in current active theme, then parents themes, then modules. - foreach ($components as $component) { - if ($component->getPluginDefinition()["machineName"] === $element["#id"]) { - $element["#id"] = $component->getPluginId(); - return $element; - } - } - return $element; - } - /** * */ public function convert(array $element): array { $element = self::resolveCompactFormat($element); - $element = self::getComponentNamespace($element); $element["#type"] = "component"; - $element["#component"] = $element["#id"]; - unset($element["#id"]); + if (array_key_exists("#id", $element) && is_string($element["#id"])) { + $element["#id"] = TemporaryHelper::getNamespacedId($element["#id"]); + $element["#component"] = $element["#id"]; + unset($element["#id"]); + } if (array_key_exists("#fields", $element) && is_array($element["#fields"])) { $element["#slots"] = $element["#fields"]; unset($element["#fields"]); diff --git a/modules/ui_patterns_library/src/Controller/LibraryController.php b/modules/ui_patterns_library/src/Controller/LibraryController.php index fe0adabf..39bc9beb 100644 --- a/modules/ui_patterns_library/src/Controller/LibraryController.php +++ b/modules/ui_patterns_library/src/Controller/LibraryController.php @@ -4,6 +4,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\sdc\ComponentPluginManager; +use Drupal\ui_patterns\TemporaryHelper; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -51,7 +52,7 @@ public function title($name) { public function single($name) { $definition = $this->componentPluginManager->getDefinition($name); return [ - '#theme' => 'patterns_single_page', + '#theme' => 'ui_patterns_single_page', '#component' => $definition, ]; } @@ -63,14 +64,10 @@ public function single($name) { * Patterns overview page render array. */ public function overview() { - // @todo use UI Patterns plugin maanger instead of SDC, because we look for categorized definitions. - $definitions = $this->componentPluginManager->getAllComponents(); - $categorized_definitions = [ - "all" => $definitions, - ]; + $groups = TemporaryHelper::getGroupedDefinitions(); return [ - '#theme' => 'patterns_overview_page', - '#components' => $categorized_definitions, + '#theme' => 'ui_patterns_overview_page', + '#groups' => $groups, ]; } diff --git a/modules/ui_patterns_library/src/Element/ComponentStory.php b/modules/ui_patterns_library/src/Element/ComponentStory.php index 32a9f26e..c8829810 100644 --- a/modules/ui_patterns_library/src/Element/ComponentStory.php +++ b/modules/ui_patterns_library/src/Element/ComponentStory.php @@ -2,7 +2,7 @@ namespace Drupal\ui_patterns_library\Element; -use Drupal\sdc\Element\ComponentElement; +use Drupal\ui_patterns\Element\ComponentElement; /** * Renders a component story. @@ -21,6 +21,7 @@ public function getInfo(): array { [$this, 'preRenderComponent'], ], '#component' => '', + '#story' => '', '#props' => [], '#slots' => [], '#propsAlter' => [], @@ -32,7 +33,22 @@ public function getInfo(): array { * */ public function loadStory(array $element): array { - // @todo Load slots & props from a component story. + if (!isset($element["#story"])) { + return $element; + } + $story_id = $element["#story"]; + $component = \Drupal::service('plugin.manager.sdc')->getDefinition($element["#component"]); + if (!isset($component["stories"])) { + return $element; + } + if (!isset($component["stories"][$story_id])) { + return $element; + } + $story = $component["stories"][$story_id]; + $slots = $story["slots"] ?? []; + $props = $story["props"] ?? []; + $element["#slots"] = array_merge($element["#slots"], $slots); + $element["#props"] = array_merge($element["#props"], $props); return $element; } diff --git a/modules/ui_patterns_library/src/Template/TwigExtension.php b/modules/ui_patterns_library/src/Template/TwigExtension.php index 1b105ec3..becdb5e5 100644 --- a/modules/ui_patterns_library/src/Template/TwigExtension.php +++ b/modules/ui_patterns_library/src/Template/TwigExtension.php @@ -1,6 +1,6 @@ 'component_story', '#component' => $component_id, - '#component' => $story_id, + '#story' => $story_id, '#slots' => $slots, '#props' => $props, ]; diff --git a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig b/modules/ui_patterns_library/templates/patterns-meta-information.html.twig deleted file mode 100644 index 27a3b7a4..00000000 --- a/modules/ui_patterns_library/templates/patterns-meta-information.html.twig +++ /dev/null @@ -1,67 +0,0 @@ -{# -/** - * @file - * UI Pattern meta information. - */ -#} - -{% if pattern is not empty %} - - {# Pattern name and description. #} -

    {{ pattern.label }}

    -

    {{ pattern.description }}

    - {% if pattern.tags %} -
    - {{ "Tags:"|t }} -
      - {% for tag in pattern.tags %} -
    • {{ tag }}
    • - {% endfor %} -
    -
    - {% endif %} - - {# Pattern fields descriptions. #} - {% if pattern.fields or pattern.additional.settings %} - - - - - - - - - - - - {% for field in pattern.fields %} - - - - - - - - {% endfor %} - {% for name, setting in pattern.additional.settings %} - - - - - - - - {% endfor %} - -
    {{ "Type"|t }}{{ "Name"|t }}{{ "Label"|t }}{{ "Type"|t }}{{ "Description"|t }} / {{ "Options"|t }}
    {{ "Field"|t }}{{ field.name }}{{ field.label }}{{ field.type }}{{ field.description }}
    {{ "Setting"|t }}{{ name }}{{ setting.label }}{{ setting.type }}{{ setting.description }} - {% if setting.options %} -
      - {% for key, label in setting.options %} -
    • {{ key }}: {{ label }}
    • - {% endfor %} -
    - {% endif %} -
    - {% endif %} - -{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/patterns-overview-page.html.twig deleted file mode 100644 index 96cfd4b1..00000000 --- a/modules/ui_patterns_library/templates/patterns-overview-page.html.twig +++ /dev/null @@ -1,49 +0,0 @@ -{# -/** - * @file - * UI Pattern library page template, override this in your theme. - */ -#} - -{% if patterns is not empty %} -

    {{ "Available patterns"|t }}

    - - {# List of available patterns with anchor links. #} - {% for group_name, group_patterns in patterns %} - {% if patterns|length > 1 %} -

    {{ group_name }}

    - {% endif %} -
      - {% for pattern_name, pattern in group_patterns %} -
    • - {{ pattern.label }} -
    • - {% endfor %} -
    - {% endfor %} - -
    - - {% for group_patterns in patterns %} - {% for pattern_name, pattern in group_patterns %} -
    - {{ pattern.meta }} - - {# Rendered pattern preview. #} -
    - {{ "Preview"|t }} - {{ pattern.rendered }} -
    - - {# Link to standalone pattern preview page.#} -

    - - {% trans %}View {{ pattern.label }} as stand-alone{% endtrans %} - -

    -
    - -
    - {% endfor %} - {% endfor %} -{% endif %} diff --git a/modules/ui_patterns_library/templates/patterns-single-page.html.twig b/modules/ui_patterns_library/templates/patterns-single-page.html.twig deleted file mode 100644 index 6d6294ce..00000000 --- a/modules/ui_patterns_library/templates/patterns-single-page.html.twig +++ /dev/null @@ -1,18 +0,0 @@ -{# -/** - * @file - * UI Pattern library standalone page, override this in your theme. - */ -#} - -{% if pattern is not empty %} -
    - {{ pattern.meta }} - - {# Rendered pattern preview. #} -
    - {{ "Preview"|t }} - {{ pattern.rendered }} -
    -
    -{% endif %} diff --git a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig new file mode 100644 index 00000000..1a1eb60a --- /dev/null +++ b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig @@ -0,0 +1,57 @@ +{# +/** + * @file + * UI Pattern meta information. + */ +#} + +{% if component is not empty %} + {% if component.description %} +

    {{ component.description }}

    + {% endif %} + {% if component.tags %} +
    + {{ "Tags:"|t }} +
      + {% for tag in component.tags %} +
    • {{ tag }}
    • + {% endfor %} +
    +
    + {% endif %} + + {% if component.slots or component.props %} + + + + + + + + + + + + {% for slot_id, slot in component.slots %} + + + + + + + + {% endfor %} + {% for prop_id, prop in component.props.properties %} + + + + + + + + {% endfor %} + +
    {{ "Type"|t }}{{ "Name"|t }}{{ "Label"|t }}{{ "Type"|t }}{{ "Description"|t }} / {{ "Options"|t }}
    {{ "Slot"|t }}{{ slot_id }}{{ slot.title }}{{ slot.description }}
    {{ "Prop"|t }}{{ prop_id }}{{ prop.title }}{{ prop.type }}{{ prop.description }}
    + {% endif %} + +{% endif %} diff --git a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig new file mode 100644 index 00000000..8ac9f541 --- /dev/null +++ b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig @@ -0,0 +1,48 @@ +{# +/** + * @file + * UI Pattern library page template, override this in your theme. + */ +#} + +{% if groups is not empty %} +

    {{ "Available components"|t }}

    + + {# List of available patterns with anchor links. #} + {% for group_name, components in groups %} + {% if components|length > 1 %} +

    {{ group_name }}

    + {% endif %} + + {% endfor %} + +
    + + {% for components in groups %} + {% for component in components %} +
    +

    {{ component.name }}

    + {{ include('ui-patterns-component-metadata.html.twig', {'component': component}, with_context = false) }} + +
    + {% for variant_id, variant in component.variants %} + {% for story_id, story in component.stories %} + {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }} + {% endfor %} + {% endfor %} +
    +
    +
    + {% endfor %} + {% endfor %} +{% endif %} diff --git a/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig new file mode 100644 index 00000000..b6530f6c --- /dev/null +++ b/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * UI Pattern library standalone page, override this in your theme. + */ +#} + +{% if component is not empty %} +
    + {{ include('ui-patterns-component-metadata.html.twig', {'component': component}, with_context = false) }} +
    + {% for variant_id, variant in component.variants %} +
    +

    {{ variant.label }} ({{ variant_id }})

    + {% if variant.description %} +

    {{ variant.description }}

    + {% endif %} + {% for story_id, story in component.stories %} +

    {{ story.title }}

    + {% if story.description %} +

    {{ variant.description }}

    + {% endif %} + {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }} + {% endfor %} + {% endfor %} +
    +
    +{% endif %} diff --git a/modules/ui_patterns_library/ui_patterns_library.module b/modules/ui_patterns_library/ui_patterns_library.module index cc3adb2c..12c5874e 100644 --- a/modules/ui_patterns_library/ui_patterns_library.module +++ b/modules/ui_patterns_library/ui_patterns_library.module @@ -10,13 +10,13 @@ */ function ui_patterns_library_theme() { return [ - 'patterns_overview_page' => [ - 'variables' => ['components' => NULL], + 'ui_patterns_overview_page' => [ + 'variables' => ['groups' => NULL], ], - 'patterns_single_page' => [ + 'ui_patterns_single_page' => [ 'variables' => ['component' => NULL], ], - 'patterns_meta_information' => [ + 'ui_patterns_component_metadata' => [ 'variables' => ['component' => NULL], ] ]; diff --git a/modules/ui_patterns_library/ui_patterns_library.services.yml b/modules/ui_patterns_library/ui_patterns_library.services.yml new file mode 100644 index 00000000..6c9176d3 --- /dev/null +++ b/modules/ui_patterns_library/ui_patterns_library.services.yml @@ -0,0 +1,5 @@ +services: + ui_patterns_library.twig.extension: + class: Drupal\ui_patterns_library\Template\TwigExtension + tags: + - { name: twig.extension } diff --git a/src/Element/ComponentElement.php b/src/Element/ComponentElement.php new file mode 100644 index 00000000..11971dc4 --- /dev/null +++ b/src/Element/ComponentElement.php @@ -0,0 +1,85 @@ + $this->doTrustedCallback( + $callback, + [$carry], + '%s is not trusted', + ), + $props + ); + $inline_template = $this->generateComponentTemplate( + $element['#component'], + $element['#slots'], + $element['#slotsAlter'], + $props, + ); + $element['inline-template'] = [ + '#type' => 'inline_template', + '#template' => $inline_template, + '#context' => $props, + ]; + return $element; + } + + /** + * {@inheritdoc} + * + * Related SDC issue: https://www.drupal.org/project/drupal/issues/3391702 + */ + private function generateComponentTemplate( + string $id, + array $slots, + array $slots_alter_callbacks, + array &$context, + ): string { + $template = '{# This template was dynamically generated by sdc #}' . PHP_EOL; + $template .= sprintf('{%% embed \'%s\' %%}', $id); + $template .= PHP_EOL; + foreach ($slots as $slot_name => $slot_value) { + if (!Utilities::isRenderArray($slot_value) && \is_scalar($slot_value)) { + $slot_value = [ + "#plain_text" => (string) $slot_value, + ]; + } + $context[$slot_name] = array_reduce( + $slots_alter_callbacks, + fn(array $carry, callable $callback) => $this->doTrustedCallback( + $callback, + [$carry, $context], + '%s is not trusted', + ), + $slot_value + ); + $template .= " {% block $slot_name %}" . PHP_EOL + . " {{ $slot_name }}" . PHP_EOL + . " {% endblock %}" . PHP_EOL; + } + $template .= '{% endembed %}' . PHP_EOL; + return $template; + } + +} diff --git a/src/TemporaryHelper.php b/src/TemporaryHelper.php new file mode 100644 index 00000000..f1821f97 --- /dev/null +++ b/src/TemporaryHelper.php @@ -0,0 +1,49 @@ + 2) { + // Unexpected situation. + return $component_id; + } + $components = \Drupal::service('plugin.manager.sdc')->getAllComponents(); + // @todo Search first in current active theme, then parents themes, then modules. + foreach ($components as $component) { + if ($component->getPluginDefinition()["machineName"] === $component_id) { + return $component->getPluginId(); + } + } + return $component_id; + } + + /** + * + */ + public static function getGroupedDefinitions(): array { + $definitions = []; + // @todo use category metadata from ui_patterns_library + // Do we move this method to ui_patterns_library? + foreach (\Drupal::service('plugin.manager.sdc')->getAllComponents() as $component) { + $definitions[] = $component->getPluginDefinition(); + } + $groups = [ + "All" => $definitions, + ]; + return $groups; + } + +} diff --git a/tests/modules/ui_patterns_test/components/alert/alert.component.yml b/tests/modules/ui_patterns_test/components/alert/alert.component.yml index 99539cde..6e898e32 100644 --- a/tests/modules/ui_patterns_test/components/alert/alert.component.yml +++ b/tests/modules/ui_patterns_test/components/alert/alert.component.yml @@ -36,7 +36,8 @@ slots: description: "The alert message." stories: preview: - title: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" props: dismissible: True slots: diff --git a/tests/modules/ui_patterns_test/components/alert/alert.twig b/tests/modules/ui_patterns_test/components/alert/alert.twig index 1116bf99..45a9e07a 100644 --- a/tests/modules/ui_patterns_test/components/alert/alert.twig +++ b/tests/modules/ui_patterns_test/components/alert/alert.twig @@ -12,7 +12,7 @@ {% endif %} {{ message|add_class('alert-link') }} {% if dismissible %} - {{ component('ui_patterns:close_button', {}, { + {{ component('ui_patterns_test:close_button', {}, { attributes: create_attribute({ 'data-bs-dismiss': 'alert' }), diff --git a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml index 0333cf87..6a2ec724 100644 --- a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml +++ b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml @@ -16,7 +16,8 @@ slots: description: "For identifying the source. Wrap the name of the source work in ." stories: preview: - label: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" slots: content: type: "html_tag" diff --git a/tests/modules/ui_patterns_test/components/button/button.component.yml b/tests/modules/ui_patterns_test/components/button/button.component.yml index eae42ab0..50b021ab 100644 --- a/tests/modules/ui_patterns_test/components/button/button.component.yml +++ b/tests/modules/ui_patterns_test/components/button/button.component.yml @@ -128,7 +128,8 @@ slots: description: "The button label." stories: preview: - title: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" props: disabled: false url: "https://example.com" diff --git a/tests/modules/ui_patterns_test/components/button/button.twig b/tests/modules/ui_patterns_test/components/button/button.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/ui_patterns_test/components/card/card.component.yml b/tests/modules/ui_patterns_test/components/card/card.component.yml index 912360f4..8bb4b038 100644 --- a/tests/modules/ui_patterns_test/components/card/card.component.yml +++ b/tests/modules/ui_patterns_test/components/card/card.component.yml @@ -40,7 +40,8 @@ slots: title: "Footer" stories: preview: - title: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" props: image_position: "top" image_col_classes: "col-md-4" diff --git a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml index c69a1803..81365b5e 100644 --- a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml +++ b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml @@ -22,7 +22,8 @@ props: type: "string" stories: preview: - title: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" props: disabled: false aria_label: "Close" diff --git a/tests/modules/ui_patterns_test/components/figure/figure.component.yml b/tests/modules/ui_patterns_test/components/figure/figure.component.yml index 2121c71f..cb602eea 100644 --- a/tests/modules/ui_patterns_test/components/figure/figure.component.yml +++ b/tests/modules/ui_patterns_test/components/figure/figure.component.yml @@ -19,7 +19,8 @@ slots: description: "The caption that appears under the content." stories: preview: - title: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" props: figcaption_attributes: class: ["text-end"] diff --git a/tests/modules/ui_patterns_test/components/progress/progress.component.yml b/tests/modules/ui_patterns_test/components/progress/progress.component.yml index 1ecd81a0..570ebfa1 100644 --- a/tests/modules/ui_patterns_test/components/progress/progress.component.yml +++ b/tests/modules/ui_patterns_test/components/progress/progress.component.yml @@ -40,7 +40,8 @@ slots: description: "Text shown inside the progress bar." stories: preview: - title: "The default preview from UI Patterns 1.x" + title: "Preview" + description: "The default preview from UI Patterns 1.x" props: percent: 50 min: 0 diff --git a/tests/modules/ui_patterns_test/components/progress/progress.twig b/tests/modules/ui_patterns_test/components/progress/progress.twig new file mode 100644 index 00000000..e69de29b From 3cd89c502f7919d74a119cc650339ec6b73a6329 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 11:23:27 +0200 Subject: [PATCH 42/81] Rename variant label to variant title --- .../ui-patterns-single-page.html.twig | 2 +- .../components/alert/alert.component.yml | 16 +-- .../components/button/button.component.yml | 104 +++++++++--------- .../components/card/card.component.yml | 4 +- .../close_button/close_button.component.yml | 4 +- .../progress/progress.component.yml | 6 +- 6 files changed, 68 insertions(+), 68 deletions(-) diff --git a/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig index b6530f6c..081b320f 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig @@ -11,7 +11,7 @@
    {% for variant_id, variant in component.variants %}
    -

    {{ variant.label }} ({{ variant_id }})

    +

    {{ variant.title }} ({{ variant_id }})

    {% if variant.description %}

    {{ variant.description }}

    {% endif %} diff --git a/tests/modules/ui_patterns_test/components/alert/alert.component.yml b/tests/modules/ui_patterns_test/components/alert/alert.component.yml index 6e898e32..438a7713 100644 --- a/tests/modules/ui_patterns_test/components/alert/alert.component.yml +++ b/tests/modules/ui_patterns_test/components/alert/alert.component.yml @@ -5,21 +5,21 @@ links: - "https://getbootstrap.com/docs/5.3/components/alerts/" variants: primary: - label: "Primary" + title: "Primary" secondary: - label: "Secondary" + title: "Secondary" success: - label: "Success" + title: "Success" danger: - label: "Danger" + title: "Danger" warning: - label: "Warning" + title: "Warning" info: - label: "Info" + title: "Info" light: - label: "Light" + title: "Light" dark: - label: "Dark" + title: "Dark" props: type: object properties: diff --git a/tests/modules/ui_patterns_test/components/button/button.component.yml b/tests/modules/ui_patterns_test/components/button/button.component.yml index 50b021ab..219f8af1 100644 --- a/tests/modules/ui_patterns_test/components/button/button.component.yml +++ b/tests/modules/ui_patterns_test/components/button/button.component.yml @@ -7,110 +7,110 @@ category: "Button" template: pattern-button.html.twig variants: default: - label: "Default" + title: "Default" description: "No 'btn' class added." primary__sm: - label: "Primary small" + title: "Primary small" secondary__sm: - label: "Secondary small" + title: "Secondary small" success__sm: - label: "Success small" + title: "Success small" danger__sm: - label: "Danger small" + title: "Danger small" warning__sm: - label: "Warning small" + title: "Warning small" info__sm: - label: "Info small" + title: "Info small" light__sm: - label: "Light small" + title: "Light small" dark__sm: - label: "Dark small" + title: "Dark small" link__sm: - label: "Link small" + title: "Link small" primary: - label: "Primary" + title: "Primary" secondary: - label: "Secondary" + title: "Secondary" success: - label: "Success" + title: "Success" danger: - label: "Danger" + title: "Danger" warning: - label: "Warning" + title: "Warning" info: - label: "Info" + title: "Info" light: - label: "Light" + title: "Light" dark: - label: "Dark" + title: "Dark" link: - label: "Link" + title: "Link" primary__lg: - label: "Primary large" + title: "Primary large" secondary__lg: - label: "Secondary large" + title: "Secondary large" success__lg: - label: "Success large" + title: "Success large" danger__lg: - label: "Danger large" + title: "Danger large" warning__lg: - label: "Warning large" + title: "Warning large" info__lg: - label: "Info large" + title: "Info large" light__lg: - label: "Light large" + title: "Light large" dark__lg: - label: "Dark large" + title: "Dark large" link__lg: - label: "Link large" + title: "Link large" outline_primary__sm: - label: "Outline Primary small" + title: "Outline Primary small" outline_secondary__sm: - label: "Outline Secondary small" + title: "Outline Secondary small" outline_success__sm: - label: "Outline Success small" + title: "Outline Success small" outline_danger__sm: - label: "Outline Danger small" + title: "Outline Danger small" outline_warning__sm: - label: "Outline Warning small" + title: "Outline Warning small" outline_info__sm: - label: "Outline Info small" + title: "Outline Info small" outline_light__sm: - label: "Outline Light small" + title: "Outline Light small" outline_dark__sm: - label: "Outline Dark small" + title: "Outline Dark small" outline_primary: - label: "Outline Primary" + title: "Outline Primary" outline_secondary: - label: "Outline Secondary" + title: "Outline Secondary" outline_success: - label: "Outline Success" + title: "Outline Success" outline_danger: - label: "Outline Danger" + title: "Outline Danger" outline_warning: - label: "Outline Warning" + title: "Outline Warning" outline_info: - label: "Outline Info" + title: "Outline Info" outline_light: - label: "Outline Light" + title: "Outline Light" outline_dark: - label: "Outline Dark" + title: "Outline Dark" outline_primary__lg: - label: "Outline Primary large" + title: "Outline Primary large" outline_secondary__lg: - label: "Outline Secondary large" + title: "Outline Secondary large" outline_success__lg: - label: "Outline Success large" + title: "Outline Success large" outline_danger__lg: - label: "Outline Danger large" + title: "Outline Danger large" outline_warning__lg: - label: "Outline Warning large" + title: "Outline Warning large" outline_info__lg: - label: "Outline Info large" + title: "Outline Info large" outline_light__lg: - label: "Outline Light large" + title: "Outline Light large" outline_dark__lg: - label: "Outline Dark large" + title: "Outline Dark large" props: type: object properties: diff --git a/tests/modules/ui_patterns_test/components/card/card.component.yml b/tests/modules/ui_patterns_test/components/card/card.component.yml index 8bb4b038..8a87f528 100644 --- a/tests/modules/ui_patterns_test/components/card/card.component.yml +++ b/tests/modules/ui_patterns_test/components/card/card.component.yml @@ -6,9 +6,9 @@ links: category: "Card" variants: default: - label: "Default" + title: "Default" horizontal: - label: "Horizontal" + title: "Horizontal" props: type: object properties: diff --git a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml index 81365b5e..b2bb1187 100644 --- a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml +++ b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml @@ -6,9 +6,9 @@ links: category: "Button" variants: default: - label: "Default" + title: "Default" white: - label: "White (deprecated)" + title: "White (deprecated)" props: type: object properties: diff --git a/tests/modules/ui_patterns_test/components/progress/progress.component.yml b/tests/modules/ui_patterns_test/components/progress/progress.component.yml index 570ebfa1..e58835f8 100644 --- a/tests/modules/ui_patterns_test/components/progress/progress.component.yml +++ b/tests/modules/ui_patterns_test/components/progress/progress.component.yml @@ -6,11 +6,11 @@ links: template: path/to/template/progress.twig variants: default: - label: "Default" + title: "Default" striped: - label: "Striped" + title: "Striped" striped__animated: - label: "Animated stripes" + title: "Animated stripes" props: type: object properties: From 608f007675a13cf869d5682aece4575e0122c5fe Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 13:45:38 +0200 Subject: [PATCH 43/81] Add a component to test replace mechanism --- tests/modules/ui_patterns_test/components/README.md | 8 ++++++++ .../components/figure/figure.component.yml | 1 + .../replaced_figure/replaced_figure.component.yml | 13 +++++++++++++ .../components/replaced_figure/replaced_figure.twig | 3 +++ 4 files changed, 25 insertions(+) create mode 100644 tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml create mode 100644 tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.twig diff --git a/tests/modules/ui_patterns_test/components/README.md b/tests/modules/ui_patterns_test/components/README.md index 88affd47..c1f8b88a 100644 --- a/tests/modules/ui_patterns_test/components/README.md +++ b/tests/modules/ui_patterns_test/components/README.md @@ -52,6 +52,8 @@ There was initially an issue about the use of "\_" and "-" in component ID and t 1 prop (with explicit typing) and slots. No variants. +Replace `replaced_figure` + ❓ Not sure if the explicit typing will make this component a valid SDC component. # my-widget @@ -67,3 +69,9 @@ A more "traditional" SDC example with JSON schema examples instead of stories, a With explicit template path (in a subfolder, without namespace, expected filename) ❌ Not a valid SDC component, because of explicit template path. + +# replaced_figure + +To test replacement mechanism + +✅ Valid SDC component. diff --git a/tests/modules/ui_patterns_test/components/figure/figure.component.yml b/tests/modules/ui_patterns_test/components/figure/figure.component.yml index cb602eea..b3cf411a 100644 --- a/tests/modules/ui_patterns_test/components/figure/figure.component.yml +++ b/tests/modules/ui_patterns_test/components/figure/figure.component.yml @@ -3,6 +3,7 @@ name: "Figure" description: "Used to display a piece of self-contained content (illustrations, diagrams, photos, code, etc) along with an optional caption. This content can be removed from the document without affecting the meaning of the document." links: - "https://getbootstrap.com/docs/5.3/content/figures/" +replace: "ui_patterns_tests:replaced_figure" props: type: object properties: diff --git a/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml new file mode 100644 index 00000000..548e20ab --- /dev/null +++ b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml @@ -0,0 +1,13 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Figure without caption (replaced by the 'real' Figure)" +props: + type: object + properties: + figcaption_attributes: + title: "Figcaption attributes" + description: "The attributes to customize the figcaption tag." + type: "module:/ui_patterns/types.json#$defs/attributes" +slots: + image: + title: "Image" + description: "The content of the figure." diff --git a/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.twig b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.twig new file mode 100644 index 00000000..5c9670dc --- /dev/null +++ b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.twig @@ -0,0 +1,3 @@ + + {{ image|add_class('figure-img') }} + From fe8e991571efe64debbdaa97b92656809fdf030b Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 14:18:23 +0200 Subject: [PATCH 44/81] Render stories slot in ui_patterns_library --- .../src/Element/ComponentStory.php | 4 +- .../ui-patterns-overview-page.html.twig | 14 ++++++ .../ui-patterns-single-page.html.twig | 48 ++++++++++++------- src/TemporaryHelper.php | 29 +++++++++++ 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/modules/ui_patterns_library/src/Element/ComponentStory.php b/modules/ui_patterns_library/src/Element/ComponentStory.php index c8829810..26c790f9 100644 --- a/modules/ui_patterns_library/src/Element/ComponentStory.php +++ b/modules/ui_patterns_library/src/Element/ComponentStory.php @@ -3,6 +3,7 @@ namespace Drupal\ui_patterns_library\Element; use Drupal\ui_patterns\Element\ComponentElement; +use Drupal\ui_patterns\TemporaryHelper; /** * Renders a component story. @@ -47,7 +48,8 @@ public function loadStory(array $element): array { $story = $component["stories"][$story_id]; $slots = $story["slots"] ?? []; $props = $story["props"] ?? []; - $element["#slots"] = array_merge($element["#slots"], $slots); + $slots = array_merge($element["#slots"], $slots); + $element["#slots"] = TemporaryHelper::processStoriesSlots($slots); $element["#props"] = array_merge($element["#props"], $props); return $element; } diff --git a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig index 8ac9f541..e7a23aca 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig @@ -34,13 +34,27 @@ {% trans %}View {{ component.name }} as stand-alone{% endtrans %}

    + {% if component.variants is defined and component.variants|length > 0 %}
    {% for variant_id, variant in component.variants %} +
    {% for story_id, story in component.stories %} +
    {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }} +
    {% endfor %} +
    {% endfor %}
    + {% else %} +
    + {% for story_id, story in component.stories %} +
    + {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }} +
    + {% endfor %} +
    + {% endif %}

    {% endfor %} diff --git a/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig index 081b320f..1e9abd00 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-single-page.html.twig @@ -6,23 +6,37 @@ #} {% if component is not empty %} -
    - {{ include('ui-patterns-component-metadata.html.twig', {'component': component}, with_context = false) }} -
    - {% for variant_id, variant in component.variants %} -
    -

    {{ variant.title }} ({{ variant_id }})

    - {% if variant.description %} -

    {{ variant.description }}

    +
    + {{ include('ui-patterns-component-metadata.html.twig', {'component': component}, with_context = false) }} + {% if component.variants is defined and component.variants|length > 0 %} +
    + {% for variant_id, variant in component.variants %} +
    +

    {{ variant.title }} ({{ variant_id }})

    + {% if variant.description %} +

    {{ variant.description }}

    + {% endif %} + {% for story_id, story in component.stories %} +
    +

    {{ story.title }}

    + {% if story.description %} +

    {{ variant.description }}

    {% endif %} - {% for story_id, story in component.stories %} -

    {{ story.title }}

    - {% if story.description %} -

    {{ variant.description }}

    - {% endif %} - {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }} - {% endfor %} - {% endfor %} -
    + {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }} +
    + {% endfor %} + {% endfor %}
    + {% else %} + {% for story_id, story in component.stories %} +
    +

    {{ story.title }}

    + {% if story.description %} +

    {{ story.description }}

    + {% endif %} + {{ component_story(component.id, story_id) }} +
    + {% endfor %} + {% endif %} +
    {% endif %} diff --git a/src/TemporaryHelper.php b/src/TemporaryHelper.php index f1821f97..aa7e09c9 100644 --- a/src/TemporaryHelper.php +++ b/src/TemporaryHelper.php @@ -46,4 +46,33 @@ public static function getGroupedDefinitions(): array { return $groups; } + /** + * Stories slots have no "#" prefix in render arrays. Let's add them. + * A bit like UI Patterns 1.x's PatternPreview::getPreviewMarkup() + * Do we move this method to ui_patterns_library? + * Is iy something we want to remove in UI Patterns 2? + */ + public static function processStoriesSlots(array $slots): array { + foreach ($slots as $slot_id => $slot) { + if (!is_array($slot)) { + continue; + } + if (array_is_list($slot)) { + $slots[$slot_id] = self::processStoriesSlots($slot); + } + $slot_keys = array_keys($slot); + $render_keys = ["theme", "type", "markup", "plain_text"]; + if (count(array_intersect($slot_keys, $render_keys)) > 0) { + foreach ($slot as $key => $value) { + if (is_array($value)) { + $value = self::processStoriesSlots($value); + } + $slots[$slot_id]["#" . $key] = $value; + unset($slots[$slot_id][$key]); + } + } + } + return $slots; + } + } From 4fc8f0011a1845d4169546de4c77bec3cd931e6d Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 14:49:17 +0200 Subject: [PATCH 45/81] ui_patterns_library: small tweaks --- .../templates/ui-patterns-component-metadata.html.twig | 8 +++----- .../templates/ui-patterns-overview-page.html.twig | 2 +- .../components/figure/figure.component.yml | 2 +- .../components/progress/progress.component.yml | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig index 1a1eb60a..d5da8cb3 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig @@ -6,6 +6,7 @@ #} {% if component is not empty %} +

    ID: {{ component.id }}

    {% if component.description %}

    {{ component.description }}

    {% endif %} @@ -21,10 +22,9 @@ {% endif %} {% if component.slots or component.props %} - +
    - @@ -34,16 +34,14 @@ {% for slot_id, slot in component.slots %} - - + {% endfor %} {% for prop_id, prop in component.props.properties %} - diff --git a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig index e7a23aca..c348f444 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig @@ -27,7 +27,7 @@ {% for components in groups %} {% for component in components %}
    -

    {{ component.name }}

    +

    {{ component.name }}

    {{ include('ui-patterns-component-metadata.html.twig', {'component': component}, with_context = false) }}

    {{ story.title }}

    {% if story.description %} -

    {{ variant.description }}

    +

    {{ story.description }}

    {% endif %} {{ component_story(component.id, story_id, {}, {'variant': variant_id}) }}
    From 52f7476b63341a1ab1aaa850fe6f11e0e24184c9 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Thu, 5 Oct 2023 10:55:03 +0200 Subject: [PATCH 47/81] Add decorators for SdcComponent Plugin manager --- .../UiPatternsLegacyPluginDiscovery.php | 7 +- .../src/UiPatternsLegacyPluginManager.php | 55 +++++---- .../src/UiPatternsLegacyServiceProvider.php | 26 ---- .../ui_patterns_legacy.services.yml | 17 +++ src/Sdc/ComponentPluginManagerDecorator.php | 111 ++++++++++++++++++ 5 files changed, 165 insertions(+), 51 deletions(-) delete mode 100644 modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php create mode 100644 src/Sdc/ComponentPluginManagerDecorator.php diff --git a/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php b/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php index ca20a010..15c999d5 100644 --- a/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php +++ b/modules/ui_patterns_legacy/src/Plugin/Discovery/UiPatternsLegacyPluginDiscovery.php @@ -4,6 +4,7 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Plugin\Discovery\YamlDiscovery; use Drupal\Core\Plugin\Discovery\YamlDiscoveryDecorator; /** @@ -11,7 +12,7 @@ * * @internal */ -final class UiPatternsLegacyPluginDiscovery extends YamlDiscoveryDecorator { +final class UiPatternsLegacyPluginDiscovery extends YamlDiscovery { /** * Constructs a YamlDirectoryDiscovery object. @@ -28,10 +29,10 @@ final class UiPatternsLegacyPluginDiscovery extends YamlDiscoveryDecorator { * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. */ - public function __construct(DiscoveryInterface $decorated, array $directories, $file_cache_key_suffix, FileSystemInterface $file_system) { + public function __construct(array $directories, $file_cache_key_suffix, FileSystemInterface $file_system) { // Intentionally does not call parent constructor as this class uses a // different YAML discovery. - parent::__construct($decorated, 'sdc', $directories); + parent::__construct('ui_patterns_legacy', $directories); $this->discovery = new UIPatternsLegacyDiscovery($directories, $file_cache_key_suffix, $file_system); } diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php index d0edd884..a41c576e 100644 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php @@ -2,32 +2,33 @@ namespace Drupal\ui_patterns_legacy; -use Drupal\sdc\ComponentPluginManager; -use Drupal\sdc\Plugin\Discovery\DirectoryWithMetadataPluginDiscovery; +use Drupal\ui_patterns\Sdc\ComponentPluginManagerDecorator; use Drupal\ui_patterns_legacy\Plugin\Discovery\UiPatternsLegacyPluginDiscovery; +use Drupal\ui_patterns\Sdc\ComponentNegotiator; /** * Plugin Manager for *.ui_patterns.yml configuration files. * - * Plugin Manager overwrites getDiscovery() to provide a decorated - * Discovery. Decoration of the service seems not possible for me. - * Probably there is more gentle way. - * * @see plugin_api * * @internal */ -class UiPatternsLegacyPluginManager extends ComponentPluginManager { +class UiPatternsLegacyPluginManager extends ComponentPluginManagerDecorator { + + /** + * {@inheritdoc} + */ + protected function getCacheKey() { + return 'ui_patterns_legacy'; + } /** * {@inheritdoc} */ protected function getDiscovery() { if (!isset($this->discovery)) { - $directories = $this->getScanDirectories(); - $decorated = new DirectoryWithMetadataPluginDiscovery($directories, 'sdc', $this->fileSystem); + $directories = parent::getScanDirectories(); $this->discovery = new UiPatternsLegacyPluginDiscovery( - $decorated, $directories, 'ui_patterns_legacy_sdc', $this->fileSystem @@ -37,20 +38,26 @@ protected function getDiscovery() { } /** - * + * {@inheritdoc} */ protected function isUiPatternFile($definition) { - return isset($definition['_discovered_file_path']) && str_ends_with($definition['_discovered_file_path'], 'ui_patterns.yml'); + return isset($definition['_discovered_file_path']) && str_ends_with( + $definition['_discovered_file_path'], + 'ui_patterns.yml' + ); } /** - * + * {@inheritdoc} */ protected function mapPatternToComponent($pattern, $component) { - $component['props'] = ['type' => 'object', 'properties' => [ - 'context' => [ - ] - ]]; + $component['props'] = [ + 'type' => 'object', + 'properties' => [ + 'context' => [ + ], + ], + ]; if (isset($pattern['fields'])) { foreach ($pattern['fields'] as $field_id => $field) { $component['slots'][$field_id] = [ @@ -88,21 +95,25 @@ protected function alterDefinitions(&$definitions) { foreach ($patterns as $pattern_id => $pattern) { if ($definition_patterns_id !== $pattern_id) { $cloned_definition = $definition; - $cloned_definition[] = ''; $cloned_definition_id = $cloned_definition['provider'] . ':' . $pattern_id; $cloned_definition['id'] = $cloned_definition_id; - $definitions[$cloned_definition_id] = $this->mapPatternToComponent($pattern, $cloned_definition); + $definitions[$cloned_definition_id] = $this->mapPatternToComponent( + $pattern, + $cloned_definition + ); } else { - $definitions[$id] = $this->mapPatternToComponent($pattern, $definition); + $definitions[$id] = $this->mapPatternToComponent( + $pattern, + $definition + ); } $definitions[$id]['ui_pattern_id'] = $pattern_id; } } } - } - return parent::alterDefinitions($definitions); + parent::alterDefinitions($definitions); } } diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php deleted file mode 100644 index 3d30ef80..00000000 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyServiceProvider.php +++ /dev/null @@ -1,26 +0,0 @@ -hasDefinition('plugin.manager.sdc')) { - $definition = $container->getDefinition('plugin.manager.sdc'); - $definition->setClass( - 'Drupal\ui_patterns_legacy\UiPatternsLegacyPluginManager' - ); - } - } - -} diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml index 3db4d733..a96a9eb4 100644 --- a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml @@ -6,3 +6,20 @@ services: class: Drupal\ui_patterns_legacy\Template\TwigExtension tags: - { name: twig.extension } + ui_patterns_legacy.plugin.manager.sdc.decorator: + class: Drupal\ui_patterns_legacy\UiPatternsLegacyPluginManager + decorates: plugin.manager.sdc + decoration_priority: 9 + public: false + arguments: + - '@ui_patterns_legacy.plugin.manager.sdc.decorator.inner' + - '@module_handler' + - '@theme_handler' + - '@cache.discovery' + - '@config.factory' + - '@theme.manager' + - '@Drupal\sdc\ComponentNegotiator' + - '@file_system' + - '@Drupal\sdc\Component\SchemaCompatibilityChecker' + - '@Drupal\sdc\Component\ComponentValidator' + - '%app.root%' diff --git a/src/Sdc/ComponentPluginManagerDecorator.php b/src/Sdc/ComponentPluginManagerDecorator.php new file mode 100644 index 00000000..f30aab06 --- /dev/null +++ b/src/Sdc/ComponentPluginManagerDecorator.php @@ -0,0 +1,111 @@ +parentSdcPluginManager = $parent_sdc_plugin_manager; + parent::__construct( + $module_handler, + $themeHandler, + $cacheBackend, + $configFactory, + $themeManager, + $componentNegotiator, + $fileSystem, + $compatibilityChecker, + $componentValidator, + $appRoot + ); + $this->setCacheBackend($cacheBackend, $this->getCacheKey()); + } + + public function createInstance($plugin_id, array $configuration = []): Component { + if (parent::hasDefinition($plugin_id)) { + return parent::createInstance($plugin_id, $configuration); + } + else { + return $this->parentSdcPluginManager->createInstance($plugin_id, $configuration); + } + } + + /** + * Returns the cache key for the decorated service. + * + * @return string + * The cache key. + */ + protected abstract function getCacheKey(); + + /** + * {@inheritdoc} + */ + public function find(string $component_id): Component { + if (parent::hasDefinition($component_id)) { + return parent::find($component_id); + } + return $this->parentSdcPluginManager->find($component_id); + } + + /** + * {@inheritdoc} + */ + public function getAllComponents(): array { + $original_components = $this->parentSdcPluginManager->getAllComponents(); + return array_merge($original_components, parent::getAllComponents()); + } + + /** + * {@inheritdoc} + */ + public function getDefinitions() { + $decorated_definitions = parent::getDefinitions(); + $original_definitions = $this->parentSdcPluginManager->getDefinitions(); + return array_merge($original_definitions, $decorated_definitions); + } + + /** + * {@inheritdoc} + */ + public function getDefinition($plugin_id, $exception_on_invalid = TRUE) { + $original_definition = parent::getDefinition($plugin_id, FALSE); + return $original_definition ?? $this->parentSdcPluginManager->getDefinition($plugin_id, $exception_on_invalid); + } + +} From 706f029f6a24f9e1128f319f897300b7ba0e19a0 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Thu, 5 Oct 2023 18:07:24 +0200 Subject: [PATCH 48/81] Add template discovery --- .../src/UiPatternsLegacyPluginManager.php | 26 +++++++- .../blockquote/blockquote.ui_patterns.yml | 6 +- .../card_body/card_body.ui_patterns.yml | 60 ------------------- .../pattern-card-body--preview.html.twig | 9 --- .../card_body/pattern-card-body.html.twig | 15 ----- .../close_button/close_button.ui_patterns.yml | 25 -------- ...e-button--variant-white--preview.html.twig | 3 - .../pattern-close-button.html.twig | 11 ---- .../blockquote/blockquote.component.yml | 6 +- .../components/figure/figure.component.yml | 2 +- .../replaced_figure.component.yml | 2 +- 11 files changed, 37 insertions(+), 128 deletions(-) delete mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml delete mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig delete mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig delete mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml delete mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig delete mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig diff --git a/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php b/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php index a41c576e..54c83624 100644 --- a/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php +++ b/modules/ui_patterns_legacy/src/UiPatternsLegacyPluginManager.php @@ -22,12 +22,25 @@ protected function getCacheKey() { return 'ui_patterns_legacy'; } + /** + * {@inheritdoc} + */ + protected function getScanDirectories(): array { + $extension_directories = [ + ...$this->moduleHandler->getModuleDirectories(), + ...$this->themeHandler->getThemeDirectories(), + ]; + return array_map( + static fn(string $path) => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'patterns', + $extension_directories + ); + } /** * {@inheritdoc} */ protected function getDiscovery() { if (!isset($this->discovery)) { - $directories = parent::getScanDirectories(); + $directories = $this->getScanDirectories(); $this->discovery = new UiPatternsLegacyPluginDiscovery( $directories, 'ui_patterns_legacy_sdc', @@ -114,6 +127,17 @@ protected function alterDefinitions(&$definitions) { } } parent::alterDefinitions($definitions); + foreach ($definitions as & $definition) { + $pattern_directory = dirname($definition['_discovered_file_path']); + $template = $this->findAsset( + $pattern_directory, + 'pattern-' . $definition['machineName'], + 'html.twig' + ); + $definition['template'] = basename($template); + } + } + } diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml index eebed253..70e3b835 100644 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/blockquote/blockquote.ui_patterns.yml @@ -1,5 +1,5 @@ blockquote: - label: "Blockquote (Legacy)" + label: "Blockquote (Legacy)22" description: "For quoting blocks of content from another source within your document." links: - "https://getbootstrap.com/docs/5.3/content/typography/#blockquotes" @@ -25,3 +25,7 @@ blockquote: value: "Source Title" attributes: title: "Source Title" + settings: + dismissible: + type: "boolean" + label: "Dismissible?" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml deleted file mode 100644 index ef737a0b..00000000 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml +++ /dev/null @@ -1,60 +0,0 @@ -card_body: - label: "(Card body) (Legacy)" - description: "Internal: to be used in the 'Card' component." - links: - - "https://getbootstrap.com/docs/5.3/components/card/" - category: "Card" - settings: - heading_level: - type: "select" - label: "Heading level" - options: - 2: "h2" - 3: "h3" - 4: "h4" - 5: "h5 (Default)" - 6: "h6" - preview: 5 - allow_expose: true - allow_token: true - fields: - title: - type: "text" - label: "Title" - description: "Card title. Plain text." - preview: "Card title" - subtitle: - type: "text" - label: "Subtitle" - description: "Card subtitle. Plain text." - preview: "Card subtitle" - text: - type: "text" - label: "Text" - description: "Card text. Plain text." - preview: "Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit." - content: - type: "render" - label: "Content" - description: "Free content outside of any wrapper." - preview: - type: "pattern" - id: "button" - variant: "primary" - fields: - label: "Go somewhere" - links: - type: "render" - label: "Links" - description: "Array of link elements" - preview: - - type: "html_tag" - tag: "a" - value: "Card link" - attributes: - href: "#" - - type: "html_tag" - tag: "a" - value: "Another link" - attributes: - href: "#" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig deleted file mode 100644 index 601bf46a..00000000 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig +++ /dev/null @@ -1,9 +0,0 @@ -{# -/** - * For preview, to avoid warnings due to preview value transformed as markup - * object. - */ -#} -
    - {{ include('pattern-card-body.html.twig') }} -
    diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig deleted file mode 100644 index 78908777..00000000 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig +++ /dev/null @@ -1,15 +0,0 @@ -{% set heading_level = heading_level|default(5) %} - - - {% if title %} - {{ title }} - {% endif %} - {% if subtitle %} - {{ subtitle }} - {% endif %} - {% if text %} -

    {{ text }}

    - {% endif %} - {{ content }} - {{ links|add_class('card-link') }} -
    diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml deleted file mode 100644 index cf10343f..00000000 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml +++ /dev/null @@ -1,25 +0,0 @@ -close_button: - label: "Close button (Legacy)" - description: "A generic close button for dismissing content like modals and alerts." - links: - - "https://getbootstrap.com/docs/5.3/components/close-button" - category: "Button" - variants: - default: - label: "Default" - white: - label: "White (deprecated)" - settings: - disabled: - type: "boolean" - label: "Disabled?" - description: "Is the button disabled?" - preview: false - allow_expose: true - allow_token: true - aria_label: - type: "textfield" - label: "Aria label" - description: "Name of the close button for assistive technology." - preview: "Close" - allow_token: true diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig deleted file mode 100644 index 63f08c29..00000000 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig +++ /dev/null @@ -1,3 +0,0 @@ -
    - {{ include('pattern-close-button.html.twig') }} -
    diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig deleted file mode 100644 index d1350276..00000000 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% if variant and variant|lower != 'default' %} - {% set attributes = attributes.addClass('btn-close-' ~ variant) %} -{% endif %} -{% set attributes = attributes.addClass('btn-close') %} - -{% set attributes = attributes.setAttribute('aria-label', aria_label|default('Close'|t)) %} -{% if disabled %} - {% set attributes = attributes.setAttribute('disabled', disabled) %} -{% endif %} - - diff --git a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml index 6a2ec724..f696d09f 100644 --- a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml +++ b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml @@ -6,7 +6,11 @@ links: category: "Typography" props: type: object - properties: {} + properties: + dummy_prop: + title: "Dummy Prop" + description: "The attributes to customize the figcaption tag." + type: "object" slots: content: title: "Content" diff --git a/tests/modules/ui_patterns_test/components/figure/figure.component.yml b/tests/modules/ui_patterns_test/components/figure/figure.component.yml index 932f5a9e..b90d784f 100644 --- a/tests/modules/ui_patterns_test/components/figure/figure.component.yml +++ b/tests/modules/ui_patterns_test/components/figure/figure.component.yml @@ -10,7 +10,7 @@ props: figcaption_attributes: title: "Figcaption attributes" description: "The attributes to customize the figcaption tag." - type: "module:/ui_patterns/types.json#$defs/attributes" + type: "object" slots: image: title: "Image" diff --git a/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml index 548e20ab..18e8f97d 100644 --- a/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml +++ b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml @@ -6,7 +6,7 @@ props: figcaption_attributes: title: "Figcaption attributes" description: "The attributes to customize the figcaption tag." - type: "module:/ui_patterns/types.json#$defs/attributes" + type: "object" slots: image: title: "Image" From 157c26507cf599001db27d6958da398bda1b9e90 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Thu, 5 Oct 2023 18:07:31 +0200 Subject: [PATCH 49/81] Add template discovery --- .../card_body/card_body.ui_patterns.yml | 60 +++++++++++++++++++ .../pattern-card-body--preview.html.twig | 0 .../card_body/pattern-card-body.html.twig | 15 +++++ .../close_button/close_button.ui_patterns.yml | 25 ++++++++ ...e-button--variant-white--preview.html.twig | 0 .../pattern-close-button.html.twig | 0 6 files changed, 100 insertions(+) create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig create mode 100644 modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml new file mode 100644 index 00000000..ef737a0b --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/card_body.ui_patterns.yml @@ -0,0 +1,60 @@ +card_body: + label: "(Card body) (Legacy)" + description: "Internal: to be used in the 'Card' component." + links: + - "https://getbootstrap.com/docs/5.3/components/card/" + category: "Card" + settings: + heading_level: + type: "select" + label: "Heading level" + options: + 2: "h2" + 3: "h3" + 4: "h4" + 5: "h5 (Default)" + 6: "h6" + preview: 5 + allow_expose: true + allow_token: true + fields: + title: + type: "text" + label: "Title" + description: "Card title. Plain text." + preview: "Card title" + subtitle: + type: "text" + label: "Subtitle" + description: "Card subtitle. Plain text." + preview: "Card subtitle" + text: + type: "text" + label: "Text" + description: "Card text. Plain text." + preview: "Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit." + content: + type: "render" + label: "Content" + description: "Free content outside of any wrapper." + preview: + type: "pattern" + id: "button" + variant: "primary" + fields: + label: "Go somewhere" + links: + type: "render" + label: "Links" + description: "Array of link elements" + preview: + - type: "html_tag" + tag: "a" + value: "Card link" + attributes: + href: "#" + - type: "html_tag" + tag: "a" + value: "Another link" + attributes: + href: "#" diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body--preview.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig new file mode 100644 index 00000000..78908777 --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/card_body/pattern-card-body.html.twig @@ -0,0 +1,15 @@ +{% set heading_level = heading_level|default(5) %} + + + {% if title %} + {{ title }} + {% endif %} + {% if subtitle %} + {{ subtitle }} + {% endif %} + {% if text %} +

    {{ text }}

    + {% endif %} + {{ content }} + {{ links|add_class('card-link') }} + diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml new file mode 100644 index 00000000..cf10343f --- /dev/null +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/close_button.ui_patterns.yml @@ -0,0 +1,25 @@ +close_button: + label: "Close button (Legacy)" + description: "A generic close button for dismissing content like modals and alerts." + links: + - "https://getbootstrap.com/docs/5.3/components/close-button" + category: "Button" + variants: + default: + label: "Default" + white: + label: "White (deprecated)" + settings: + disabled: + type: "boolean" + label: "Disabled?" + description: "Is the button disabled?" + preview: false + allow_expose: true + allow_token: true + aria_label: + type: "textfield" + label: "Aria label" + description: "Name of the close button for assistive technology." + preview: "Close" + allow_token: true diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button--variant-white--preview.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/templates/patterns/close_button/pattern-close-button.html.twig new file mode 100644 index 00000000..e69de29b From dffbc098ea49a2fd592c07d284ad8bae5096db25 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 17:54:05 +0200 Subject: [PATCH 50/81] Init prop_type plugin type --- src/Annotation/PropType.php | 39 +++++++++++++++++++++++++++++++++++ src/PropTypeInterface.php | 18 ++++++++++++++++ src/PropTypePluginBase.php | 20 ++++++++++++++++++ src/PropTypePluginManager.php | 37 +++++++++++++++++++++++++++++++++ ui_patterns.services.yml | 3 +++ 5 files changed, 117 insertions(+) create mode 100644 src/Annotation/PropType.php create mode 100644 src/PropTypeInterface.php create mode 100644 src/PropTypePluginBase.php create mode 100644 src/PropTypePluginManager.php diff --git a/src/Annotation/PropType.php b/src/Annotation/PropType.php new file mode 100644 index 00000000..c82a2a05 --- /dev/null +++ b/src/Annotation/PropType.php @@ -0,0 +1,39 @@ +pluginDefinition['label']; + } + +} diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php new file mode 100644 index 00000000..1a604521 --- /dev/null +++ b/src/PropTypePluginManager.php @@ -0,0 +1,37 @@ +alterInfo('prop_type_info'); + $this->setCacheBackend($cache_backend, 'prop_type_plugins'); + } + +} diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index 2cb044ce..6ecddf11 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -2,6 +2,9 @@ services: plugin.manager.ui_patterns_source: class: Drupal\ui_patterns\UiPatternsSourceManager parent: default_plugin_manager + plugin.manager.ui_patterns_prop_type: + class: Drupal\ui_patterns\PropTypePluginManager + parent: default_plugin_manager ui_patterns.twig.extension: class: Drupal\ui_patterns\Template\TwigExtension tags: From 8a8c2af231fbd1e2e2e1b3cf3e16f87367ca8af2 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 5 Oct 2023 20:04:34 +0200 Subject: [PATCH 51/81] Remove everything related to Soruce plugins; and PatternDisplayFormTrait --- src/Annotation/UiPatternsSource.php | 52 ---- src/Definition/ArrayAccessDefinitionTrait.php | 46 ---- src/Definition/PatternSourceField.php | 154 ----------- src/Form/PatternDisplayFormTrait.php | 248 ------------------ src/Plugin/PatternSourceBase.php | 62 ----- src/Plugin/PatternSourceInterface.php | 42 --- .../UiPatterns/Source/ExtraFieldSource.php | 81 ------ src/Plugin/UiPatterns/Source/FieldSource.php | 70 ----- src/UiPatternsSourceManager.php | 62 ----- ui_patterns.services.yml | 3 - 10 files changed, 820 deletions(-) delete mode 100644 src/Annotation/UiPatternsSource.php delete mode 100644 src/Definition/ArrayAccessDefinitionTrait.php delete mode 100644 src/Definition/PatternSourceField.php delete mode 100644 src/Form/PatternDisplayFormTrait.php delete mode 100644 src/Plugin/PatternSourceBase.php delete mode 100644 src/Plugin/PatternSourceInterface.php delete mode 100644 src/Plugin/UiPatterns/Source/ExtraFieldSource.php delete mode 100644 src/Plugin/UiPatterns/Source/FieldSource.php delete mode 100644 src/UiPatternsSourceManager.php diff --git a/src/Annotation/UiPatternsSource.php b/src/Annotation/UiPatternsSource.php deleted file mode 100644 index 1f02b88b..00000000 --- a/src/Annotation/UiPatternsSource.php +++ /dev/null @@ -1,52 +0,0 @@ -definition); - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function offsetGet($offset) { - return $this->definition[$offset] ?? NULL; - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function offsetSet($offset, $value) { - $this->definition[$offset] = $value; - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function offsetUnset($offset) { - unset($this->definition[$offset]); - } - -} diff --git a/src/Definition/PatternSourceField.php b/src/Definition/PatternSourceField.php deleted file mode 100644 index 16cfd825..00000000 --- a/src/Definition/PatternSourceField.php +++ /dev/null @@ -1,154 +0,0 @@ -fieldName = $field_name; - $this->fieldLabel = $field_label; - $this->pluginId = $plugin_id; - $this->pluginLabel = $plugin_label; - } - - /** - * Get FieldName property. - * - * @return string - * Property value. - */ - public function getFieldName() { - return $this->fieldName; - } - - /** - * Set FieldName property. - * - * @param string $fieldName - * Property value. - * - * @return $this - */ - public function setFieldName($fieldName) { - $this->fieldName = $fieldName; - return $this; - } - - /** - * Get FieldLabel property. - * - * @return string - * Property value. - */ - public function getFieldLabel() { - return $this->fieldLabel; - } - - /** - * Set FieldLabel property. - * - * @param string $fieldLabel - * Property value. - * - * @return $this - */ - public function setFieldLabel($fieldLabel) { - $this->fieldLabel = $fieldLabel; - return $this; - } - - /** - * Get Plugin property. - * - * @return string - * Property value. - */ - public function getPluginId() { - return $this->pluginId; - } - - /** - * Set Plugin property. - * - * @param string $pluginId - * Property value. - * - * @return $this - */ - public function setPluginId($pluginId) { - $this->pluginId = $pluginId; - return $this; - } - - /** - * Get PluginLabel property. - * - * @return string - * Property value. - */ - public function getPluginLabel() { - return $this->pluginLabel; - } - - /** - * Set PluginLabel property. - * - * @param string $pluginLabel - * Property value. - * - * @return $this - */ - public function setPluginLabel($pluginLabel) { - $this->pluginLabel = $pluginLabel; - return $this; - } - - /** - * Get unique field key. - * - * @return string - * Field key. - */ - public function getFieldKey() { - return $this->getPluginId() . self::FIELD_KEY_SEPARATOR . $this->getFieldName(); - } - -} diff --git a/src/Form/PatternDisplayFormTrait.php b/src/Form/PatternDisplayFormTrait.php deleted file mode 100644 index 6f688879..00000000 --- a/src/Form/PatternDisplayFormTrait.php +++ /dev/null @@ -1,248 +0,0 @@ - 'select', - '#empty_value' => '_none', - '#title' => $this->t('Pattern'), - '#options' => $this->patternsManager->getPatternsOptions(), - '#default_value' => $configuration['pattern'] ?? NULL, - '#required' => TRUE, - '#attributes' => ['id' => 'patterns-select'], - ]; - $form['variants'] = ['#type' => 'container']; - - /** @var \Drupal\ui_patterns\Definition\PatternDefinition $definition */ - foreach ($this->patternsManager->getDefinitions() as $pattern_id => $definition) { - if ($definition->hasVariants()) { - $form['variants'][$pattern_id] = [ - '#type' => 'select', - '#title' => $this->t('Variant'), - '#options' => $definition->getVariantsAsOptions(), - '#default_value' => $configuration['pattern_variant'] ?? NULL, - '#weight' => 0, - '#states' => [ - 'visible' => [ - 'select[id="patterns-select"]' => ['value' => $pattern_id], - ], - ], - ]; - } - $form['pattern_mapping'][$pattern_id] = [ - '#type' => 'container', - '#weight' => 1, - '#states' => [ - 'visible' => [ - 'select[id="patterns-select"]' => ['value' => $pattern_id], - ], - ], - 'settings' => $this->getMappingForm($pattern_id, $tag, $context, $configuration), - ]; - } - - $this->moduleHandler->alter('ui_patterns_display_settings_form', $form, $configuration); - } - - /** - * Get mapping form. - * - * @param string $pattern_id - * Pattern ID for which to print the mapping form for. - * @param string $tag - * Source field plugin tag. - * @param array $context - * Plugin context. - * @param array $configuration - * Default configuration coming form the host form. - * - * @return array - * Mapping form. - */ - public function getMappingForm($pattern_id, $tag, array $context, array $configuration) { - /** @var \Drupal\ui_patterns\Definition\PatternDefinition $pattern */ - $pattern = $this->patternsManager->getDefinition($pattern_id); - - $elements = [ - '#type' => 'table', - '#header' => [ - $this->t('Source'), - $this->t('Plugin'), - $this->t('Destination'), - $this->t('Weight'), - ], - ]; - $elements['#tabledrag'][] = [ - 'action' => 'order', - 'relationship' => 'sibling', - 'group' => 'field-weight', - ]; - - $destinations = ['_hidden' => $this->t('- Hidden -')] + $pattern->getFieldsAsOptions(); - - $fields = []; - foreach ($this->sourceManager->getFieldsByTag($tag, $context) as $field_name => $field) { - $weight = (int) $this->getDefaultValue($configuration, $field_name, 'weight'); - $fields[$field_name] = [ - 'info' => [ - '#plain_text' => $field->getFieldLabel(), - ], - 'plugin' => [ - '#plain_text' => $field->getPluginLabel(), - ], - 'destination' => [ - '#type' => 'select', - '#title' => $this->t('Destination for @field', ['@field' => $field->getFieldLabel()]), - '#title_display' => 'invisible', - '#default_value' => $this->getDefaultValue($configuration, $field_name, 'destination'), - '#options' => $destinations, - ], - 'weight' => [ - '#type' => 'weight', - '#default_value' => $weight, - '#delta' => 20, - '#title' => $this->t('Weight for @field field', ['@field' => $field->getFieldLabel()]), - '#title_display' => 'invisible', - '#attributes' => [ - 'class' => ['field-weight'], - ], - ], - '#attributes' => [ - 'class' => ['draggable'], - ], - '#weight' => $weight, - ]; - } - - uasort($fields, [SortArray::class, 'sortByWeightProperty']); - return array_merge($elements, $fields); - } - - /** - * Normalize settings coming from a form submission. - * - * @param array $settings - * Pattern display form values array. - */ - public static function processFormStateValues(array &$settings) { - if (isset($settings['variants']) && isset($settings['variants'][$settings['pattern']])) { - $settings['pattern_variant'] = $settings['variants'][$settings['pattern']]; - unset($settings['variants']); - } - - // Normalize only when necessary. - if (isset($settings['pattern_mapping'][$settings['pattern']]['settings'])) { - $settings['pattern_mapping'] = $settings['pattern_mapping'][$settings['pattern']]['settings']; - - // Process fields and filter out the hidden ones. - foreach ($settings['pattern_mapping'] as $key => $setting) { - if ($setting['destination'] == '_hidden') { - unset($settings['pattern_mapping'][$key]); - } - else { - [$plugin, $source] = explode(PatternSourceBase::DERIVATIVE_SEPARATOR, $key, 2); - $settings['pattern_mapping'][$key]['plugin'] = $plugin; - $settings['pattern_mapping'][$key]['source'] = $source; - } - } - - // Normalize weights. - $weight = 0; - uasort($settings['pattern_mapping'], [ - SortArray::class, - 'sortByWeightElement', - ]); - foreach ($settings['pattern_mapping'] as $key => $setting) { - $settings['pattern_mapping'][$key]['weight'] = $weight++; - } - } - } - - /** - * Helper function: return mapping destination given plugin id and field name. - * - * @param string $plugin - * Current plugin ID. - * @param string $source - * Source field name. - * @param array $settings - * Setting array. - * - * @return string|null - * Destination field or NULL if none found. - */ - public function getMappingDestination($plugin, $source, array $settings) { - $mapping_id = $plugin . PatternSourceBase::DERIVATIVE_SEPARATOR . $source; - if (isset($settings['pattern_mapping'][$mapping_id])) { - return $settings['pattern_mapping'][$mapping_id]['destination']; - } - return NULL; - } - - /** - * Helper function: check if given source field has mapping destination. - * - * @param string $plugin - * Current plugin ID. - * @param string $source - * Source field name. - * @param array $settings - * Setting array. - * - * @return bool - * TRUE if source has destination field, FALSE otherwise. - */ - public function hasMappingDestination($plugin, $source, array $settings) { - return $this->getMappingDestination($plugin, $source, $settings) !== NULL; - } - - /** - * Helper function: get default value. - * - * @param array $configuration - * Configuration. - * @param string $field_name - * Field name. - * @param string $value - * Value name. - * - * @return string - * Field property value. - */ - protected function getDefaultValue(array $configuration, $field_name, $value) { - if (isset($configuration['pattern_mapping'][$field_name][$value])) { - return $configuration['pattern_mapping'][$field_name][$value]; - } - return NULL; - } - -} diff --git a/src/Plugin/PatternSourceBase.php b/src/Plugin/PatternSourceBase.php deleted file mode 100644 index 560f69a3..00000000 --- a/src/Plugin/PatternSourceBase.php +++ /dev/null @@ -1,62 +0,0 @@ -pluginDefinition['id'], $this->pluginDefinition['label']); - } - - /** - * {@inheritdoc} - */ - public function getConfiguration() { - return $this->configuration; - } - - /** - * {@inheritdoc} - */ - public function setConfiguration(array $configuration) { - $this->configuration = $configuration; - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - return [ - 'context' => [], - ]; - } - - /** - * {@inheritdoc} - */ - public function getContextProperty($name) { - $configuration = $this->getConfiguration(); - if (isset($configuration['context'][$name]) && !empty($configuration['context'][$name])) { - return $configuration['context'][$name]; - } - $reflection = new \ReflectionClass($this); - $message = sprintf("Context property '%s' from %s is missing or empty.", $name, $reflection->name); - throw new PluginException($message); - } - -} diff --git a/src/Plugin/PatternSourceInterface.php b/src/Plugin/PatternSourceInterface.php deleted file mode 100644 index a98016cc..00000000 --- a/src/Plugin/PatternSourceInterface.php +++ /dev/null @@ -1,42 +0,0 @@ -entityFieldManager = $entity_field_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_field.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function getSourceFields() { - $sources = []; - $entity_type_id = $this->getContextProperty('entity_type'); - $bundle = $this->getContextProperty('entity_bundle'); - $extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle); - - if (!isset($extra_fields['display'])) { - return $sources; - } - - try { - $limit = $this->getContextProperty('limit'); - } - catch (PluginException $e) { - $limit = array_keys($extra_fields['display']); - } - - foreach ($extra_fields['display'] as $extra_field_name => $field) { - if (in_array($extra_field_name, $limit)) { - $sources[] = $this->getSourceField($extra_field_name, $field['label']); - } - } - - return $sources; - } - -} diff --git a/src/Plugin/UiPatterns/Source/FieldSource.php b/src/Plugin/UiPatterns/Source/FieldSource.php deleted file mode 100644 index 7ff73013..00000000 --- a/src/Plugin/UiPatterns/Source/FieldSource.php +++ /dev/null @@ -1,70 +0,0 @@ -entityFieldManager = $entity_field_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_field.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function getSourceFields() { - $sources = []; - $fields = $this->entityFieldManager->getFieldDefinitions($this->getContextProperty('entity_type'), $this->getContextProperty('entity_bundle')); - - /** @var \Drupal\Core\Field\FieldDefinitionInterface $field */ - foreach ($fields as $field) { - if (!$this->getContextProperty('limit')) { - $sources[] = $this->getSourceField($field->getName(), $field->getLabel()); - } - elseif (in_array($field->getName(), $this->getContextProperty('limit'))) { - $sources[] = $this->getSourceField($field->getName(), $field->getLabel()); - } - } - return $sources; - } - -} diff --git a/src/UiPatternsSourceManager.php b/src/UiPatternsSourceManager.php deleted file mode 100644 index 990fda29..00000000 --- a/src/UiPatternsSourceManager.php +++ /dev/null @@ -1,62 +0,0 @@ -alterInfo('ui_patterns_ui_patterns_source_info'); - $this->setCacheBackend($cache_backend, 'ui_patterns_ui_patterns_source_plugins'); - } - - /** - * Filter definitions by given tag. - * - * @param string $tag - * Tag used on plugin annotation. - * - * @return array - * List of definitions tagged with given tag. - */ - public function getDefinitionsByTag($tag) { - return array_filter($this->getDefinitions(), function ($definition) use ($tag) { - return in_array($tag, $definition['tags']); - }); - } - - /** - * Get field source definitions by specified tags. - * - * @param string $tag - * Field source tag. - * @param array $context - * Plugin context. - * - * @return \Drupal\ui_patterns\Definition\PatternSourceField[] - * List of source fields. - */ - public function getFieldsByTag($tag, array $context) { - /** @var \Drupal\ui_patterns\Plugin\PatternSourceInterface $plugin */ - $fields = []; - foreach (array_keys($this->getDefinitionsByTag($tag)) as $id) { - $plugin = $this->createInstance($id, ['context' => $context]); - foreach ($plugin->getSourceFields() as $field) { - $fields[$field->getFieldKey()] = $field; - } - } - - return $fields; - } - -} diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index 6ecddf11..8ba0cc5c 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -1,7 +1,4 @@ services: - plugin.manager.ui_patterns_source: - class: Drupal\ui_patterns\UiPatternsSourceManager - parent: default_plugin_manager plugin.manager.ui_patterns_prop_type: class: Drupal\ui_patterns\PropTypePluginManager parent: default_plugin_manager From ce0ac78ec88fcc1d248ab39d47c92fa2a3ae1047 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Fri, 6 Oct 2023 00:08:52 +0200 Subject: [PATCH 52/81] Add initial PropType definitions --- .../ui_patterns_legacy_test.info.yml | 2 + .../ui_patterns_legacy.services.yml | 1 + .../ui-patterns-component-metadata.html.twig | 2 +- src/Annotation/PropType.php | 15 ++++++- .../UiPatterns/PropType/StringPropType.php | 22 ++++++++++ .../UiPatterns/PropType/UrlPropType.php | 23 ++++++++++ src/PropTypeInterface.php | 7 +++ src/PropTypePluginBase.php | 4 ++ src/PropTypePluginManager.php | 32 +++++++++++++- src/Sdc/ComponentPluginManagerDecorator.php | 6 +-- src/Sdc/UiPatternsSdcPluginManager.php | 33 ++++++++++++++ .../UiPatternsPropTypeStreamWrapper.php | 43 +++++++++++++++++++ .../components/card/card.component.yml | 8 ++++ ui_patterns.services.yml | 26 ++++++++++- 14 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 src/Plugin/UiPatterns/PropType/StringPropType.php create mode 100644 src/Plugin/UiPatterns/PropType/UrlPropType.php create mode 100644 src/Sdc/UiPatternsSdcPluginManager.php create mode 100644 src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php diff --git a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml index 05e5da34..e06299d8 100644 --- a/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml +++ b/modules/ui_patterns_legacy/tests/modules/ui_patterns_legacy_test/ui_patterns_legacy_test.info.yml @@ -2,3 +2,5 @@ name: "UI Patterns Legacy Test" type: module description: "Provides test plugin." package: "Testing" +dependencies: + - ui_patterns_legacy diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml index a96a9eb4..13bc693d 100644 --- a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml @@ -13,6 +13,7 @@ services: public: false arguments: - '@ui_patterns_legacy.plugin.manager.sdc.decorator.inner' + - '@plugin.manager.ui_patterns_prop_type' - '@module_handler' - '@theme_handler' - '@cache.discovery' diff --git a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig index d5da8cb3..1b03345d 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig @@ -44,7 +44,7 @@
    - + {% endfor %} diff --git a/src/Annotation/PropType.php b/src/Annotation/PropType.php index c82a2a05..373767ae 100644 --- a/src/Annotation/PropType.php +++ b/src/Annotation/PropType.php @@ -25,7 +25,7 @@ class PropType extends Plugin { * * @ingroup plugin_translatable */ - public $title; + public $label; /** * The description of the plugin. @@ -36,4 +36,17 @@ class PropType extends Plugin { */ public $description; + /** + * The json schema of the plugin matches. + * + * @var array + */ + public $schema; + + /** + * The priority of the PropType. + * + * @var integer + */ + public int $priority; } diff --git a/src/Plugin/UiPatterns/PropType/StringPropType.php b/src/Plugin/UiPatterns/PropType/StringPropType.php new file mode 100644 index 00000000..b6ed6752 --- /dev/null +++ b/src/Plugin/UiPatterns/PropType/StringPropType.php @@ -0,0 +1,22 @@ +pluginDefinition['label']; } + public function getSchema():array { + return (array) $this->pluginDefinition['schema']; + } + } diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index 1a604521..61018190 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -5,6 +5,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\sdc\Component\SchemaCompatibilityChecker; /** * PropType plugin manager. @@ -22,7 +23,7 @@ class PropTypePluginManager extends DefaultPluginManager { * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler to invoke the alter hook with. */ - public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, protected SchemaCompatibilityChecker $compatibilityChecker) { parent::__construct( 'Plugin/UiPatterns/PropType', $namespaces, @@ -31,7 +32,34 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac 'Drupal\ui_patterns\Annotation\PropType' ); $this->alterInfo('prop_type_info'); - $this->setCacheBackend($cache_backend, 'prop_type_plugins'); + $this->setCacheBackend($cache_backend, 'ui_patterns_prop_type_plugins'); } + public function getPropType($prop_schema): ?PropTypeInterface { + $definition = $this->getPropTypeDefinition($prop_schema); + if ($definition !== NULL) { + return $this->createInstance($definition['id'], []); + } + } + + public function getSortedDefinitions() { + $definitions = $this->getDefinitions(); + usort($definitions, function($a, $b) { + return $a['priority'] ?? 1 > $b['priority'] ?? 1; + } ); + return $definitions; + } + public function getPropTypeDefinition($prop_schema): ?array { + $definitions = $this->getSortedDefinitions(); + foreach ($definitions as $definition) { + $annotation_schema['properties'][$definition['id']] = $definition['schema']; + $mapped_prop_schema['properties'][$definition['id']] = $prop_schema; + try { + $this->compatibilityChecker->isCompatible( $mapped_prop_schema, $annotation_schema); + return $definition; + } catch (IncompatibleComponentSchema $exception) { + // Do nothing. + } + } + } } diff --git a/src/Sdc/ComponentPluginManagerDecorator.php b/src/Sdc/ComponentPluginManagerDecorator.php index f30aab06..2649c9fb 100644 --- a/src/Sdc/ComponentPluginManagerDecorator.php +++ b/src/Sdc/ComponentPluginManagerDecorator.php @@ -12,6 +12,7 @@ use Drupal\sdc\Component\SchemaCompatibilityChecker; use Drupal\sdc\ComponentPluginManager; use Drupal\sdc\Plugin\Component; +use Drupal\ui_patterns\PropTypePluginManager; /** * Plugin Manager for *.ui_patterns.yml configuration files. @@ -27,8 +28,8 @@ abstract class ComponentPluginManagerDecorator extends ComponentPluginManager { public function __construct( - - protected ComponentPluginManager $parent_sdc_plugin_manager, + protected ComponentPluginManager $parentSdcPluginManager, + protected PropTypePluginManager $propTypePluginManager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $themeHandler, CacheBackendInterface $cacheBackend, @@ -40,7 +41,6 @@ public function __construct( ComponentValidator $componentValidator, string $appRoot, ) { - $this->parentSdcPluginManager = $parent_sdc_plugin_manager; parent::__construct( $module_handler, $themeHandler, diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php new file mode 100644 index 00000000..d36747fd --- /dev/null +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -0,0 +1,33 @@ + & $prop) { + $prop_type = $this->propTypePluginManager->getPropType($prop); + $prop['type_definition'] = $prop_type?->label() ?? 'undefined'; + } + } + } +} diff --git a/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php b/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php new file mode 100644 index 00000000..9ba60315 --- /dev/null +++ b/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php @@ -0,0 +1,43 @@ +getDefinition($plugin_id); + $stream = fopen('php://memory','r+'); + fwrite($stream, json_encode($plugin['schema'])); + rewind($stream); + $this->handle = $stream; + return $stream; + } + + public function getDirectoryPath() { + return NULL; + } + + public function getName() { + return 'ui_patterns'; + } + + public function getDescription() { + return 'ui_patterns'; + } + + public function getExternalUrl() { + return NULL; + } + +} diff --git a/tests/modules/ui_patterns_test/components/card/card.component.yml b/tests/modules/ui_patterns_test/components/card/card.component.yml index 8a87f528..c7e351c7 100644 --- a/tests/modules/ui_patterns_test/components/card/card.component.yml +++ b/tests/modules/ui_patterns_test/components/card/card.component.yml @@ -23,12 +23,20 @@ props: title: "Image column classes" description: "Only for horizontal variant." type: "string" + format: uri-reference default: "col-md-4" content_col_classes: title: "Content column classes" description: "Only for horizontal variant." type: "string" default: "col-md-8" + url_test_1: + title: "Url (explitci typing)" + $ref: "ui-patterns://url" + url_test_2: + title: "Url (implicit typing)" + type: "string" + format: "iri-reference" slots: image: title: "Image" diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index 8ba0cc5c..c93ae2b4 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -1,8 +1,32 @@ services: plugin.manager.ui_patterns_prop_type: class: Drupal\ui_patterns\PropTypePluginManager - parent: default_plugin_manager + arguments: [ '@container.namespaces', '@cache.discovery', '@module_handler', '@Drupal\sdc\Component\SchemaCompatibilityChecker' ] ui_patterns.twig.extension: class: Drupal\ui_patterns\Template\TwigExtension tags: - { name: twig.extension } + ui_patterns.plugin.manager.sdc: + class: Drupal\ui_patterns\Sdc\UiPatternsSdcPluginManager + decorates: plugin.manager.sdc + decoration_priority: 9 + public: false + arguments: + - '@ui_patterns.plugin.manager.sdc.inner' + - '@plugin.manager.ui_patterns_prop_type' + - '@module_handler' + - '@theme_handler' + - '@cache.discovery' + - '@config.factory' + - '@theme.manager' + - '@Drupal\sdc\ComponentNegotiator' + - '@file_system' + - '@Drupal\sdc\Component\SchemaCompatibilityChecker' + - '@Drupal\sdc\Component\ComponentValidator' + - '%app.root%' + stream_wrapper.ui_patterns_prop_type: + class: Drupal\ui_patterns\StreamWrapper\UiPatternsPropTypeStreamWrapper + arguments: + - '@plugin.manager.ui_patterns_prop_type' + tags: + - { name: stream_wrapper, scheme: ui-patterns } From 887d3f41c070b3d26df8dc9c7facb7aff95f3c08 Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 6 Oct 2023 08:47:11 +0200 Subject: [PATCH 53/81] Apply PHPCS --- src/Annotation/PropType.php | 3 ++- src/PropTypeInterface.php | 1 + src/PropTypePluginBase.php | 3 +++ src/PropTypePluginManager.php | 22 ++++++++++++++----- src/Sdc/ComponentPluginManagerDecorator.php | 11 ++++++++-- src/Sdc/UiPatternsSdcPluginManager.php | 3 ++- .../UiPatternsPropTypeStreamWrapper.php | 21 +++++++++++++----- 7 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/Annotation/PropType.php b/src/Annotation/PropType.php index 373767ae..14146257 100644 --- a/src/Annotation/PropType.php +++ b/src/Annotation/PropType.php @@ -46,7 +46,8 @@ class PropType extends Plugin { /** * The priority of the PropType. * - * @var integer + * @var int */ public int $priority; + } diff --git a/src/PropTypeInterface.php b/src/PropTypeInterface.php index 2654dce8..09b3db06 100644 --- a/src/PropTypeInterface.php +++ b/src/PropTypeInterface.php @@ -22,4 +22,5 @@ public function label(); * The schema. */ public function getSchema():array; + } diff --git a/src/PropTypePluginBase.php b/src/PropTypePluginBase.php index d85c0d86..f143854b 100644 --- a/src/PropTypePluginBase.php +++ b/src/PropTypePluginBase.php @@ -17,6 +17,9 @@ public function label() { return (string) $this->pluginDefinition['label']; } + /** + * + */ public function getSchema():array { return (array) $this->pluginDefinition['schema']; } diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index 61018190..0b54048e 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -35,6 +35,9 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac $this->setCacheBackend($cache_backend, 'ui_patterns_prop_type_plugins'); } + /** + * + */ public function getPropType($prop_schema): ?PropTypeInterface { $definition = $this->getPropTypeDefinition($prop_schema); if ($definition !== NULL) { @@ -42,24 +45,33 @@ public function getPropType($prop_schema): ?PropTypeInterface { } } + /** + * + */ public function getSortedDefinitions() { $definitions = $this->getDefinitions(); - usort($definitions, function($a, $b) { + usort($definitions, function ($a, $b) { return $a['priority'] ?? 1 > $b['priority'] ?? 1; - } ); + }); return $definitions; } - public function getPropTypeDefinition($prop_schema): ?array { + + /** + * + */ + public function getPropTypeDefinition($prop_schema): ?array { $definitions = $this->getSortedDefinitions(); foreach ($definitions as $definition) { $annotation_schema['properties'][$definition['id']] = $definition['schema']; $mapped_prop_schema['properties'][$definition['id']] = $prop_schema; try { - $this->compatibilityChecker->isCompatible( $mapped_prop_schema, $annotation_schema); + $this->compatibilityChecker->isCompatible($mapped_prop_schema, $annotation_schema); return $definition; - } catch (IncompatibleComponentSchema $exception) { + } + catch (IncompatibleComponentSchema $exception) { // Do nothing. } } } + } diff --git a/src/Sdc/ComponentPluginManagerDecorator.php b/src/Sdc/ComponentPluginManagerDecorator.php index 2649c9fb..7dc07dde 100644 --- a/src/Sdc/ComponentPluginManagerDecorator.php +++ b/src/Sdc/ComponentPluginManagerDecorator.php @@ -10,6 +10,7 @@ use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\sdc\Component\ComponentValidator; use Drupal\sdc\Component\SchemaCompatibilityChecker; +use Drupal\sdc\ComponentNegotiator; use Drupal\sdc\ComponentPluginManager; use Drupal\sdc\Plugin\Component; use Drupal\ui_patterns\PropTypePluginManager; @@ -27,6 +28,9 @@ */ abstract class ComponentPluginManagerDecorator extends ComponentPluginManager { + /** + * + */ public function __construct( protected ComponentPluginManager $parentSdcPluginManager, protected PropTypePluginManager $propTypePluginManager, @@ -35,7 +39,7 @@ public function __construct( CacheBackendInterface $cacheBackend, ConfigFactoryInterface $configFactory, ThemeManagerInterface $themeManager, - \Drupal\sdc\ComponentNegotiator $componentNegotiator, + ComponentNegotiator $componentNegotiator, FileSystemInterface $fileSystem, SchemaCompatibilityChecker $compatibilityChecker, ComponentValidator $componentValidator, @@ -56,6 +60,9 @@ public function __construct( $this->setCacheBackend($cacheBackend, $this->getCacheKey()); } + /** + * + */ public function createInstance($plugin_id, array $configuration = []): Component { if (parent::hasDefinition($plugin_id)) { return parent::createInstance($plugin_id, $configuration); @@ -71,7 +78,7 @@ public function createInstance($plugin_id, array $configuration = []): Component * @return string * The cache key. */ - protected abstract function getCacheKey(); + abstract protected function getCacheKey(); /** * {@inheritdoc} diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index d36747fd..fe0163c1 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -24,10 +24,11 @@ protected function getCacheKey() { protected function alterDefinitions(&$definitions) { parent::alterDefinitions($definitions); foreach ($definitions as & $definition) { - foreach ($definition['props']['properties'] as $prop_id => & $prop) { + foreach ($definition['props']['properties'] as $prop_id => & $prop) { $prop_type = $this->propTypePluginManager->getPropType($prop); $prop['type_definition'] = $prop_type?->label() ?? 'undefined'; } } } + } diff --git a/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php b/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php index 9ba60315..41fcb0a7 100644 --- a/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php +++ b/src/StreamWrapper/UiPatternsPropTypeStreamWrapper.php @@ -2,9 +2,6 @@ namespace Drupal\ui_patterns\StreamWrapper; -use Drupal\Core\Config\ConfigFactory; -use Drupal\ui_patterns\PropTypePluginManager; -use Symfony\Component\HttpFoundation\RequestStack; use Drupal\Core\StreamWrapper\LocalReadOnlyStream; /** @@ -12,30 +9,44 @@ */ class UiPatternsPropTypeStreamWrapper extends LocalReadOnlyStream { - + /** + * + */ public function stream_open($uri, $mode, $options, &$opened_path) { $plugin_id = str_replace('ui-patterns://', '', $uri); $plugin = \Drupal::service('plugin.manager.ui_patterns_prop_type')->getDefinition($plugin_id); - $stream = fopen('php://memory','r+'); + $stream = fopen('php://memory', 'r+'); fwrite($stream, json_encode($plugin['schema'])); rewind($stream); $this->handle = $stream; return $stream; } + /** + * + */ public function getDirectoryPath() { return NULL; } + /** + * + */ public function getName() { return 'ui_patterns'; } + /** + * + */ public function getDescription() { return 'ui_patterns'; } + /** + * + */ public function getExternalUrl() { return NULL; } From 0a23d80d5ec16e4f387a70b0b13799998f9623ca Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 6 Oct 2023 10:48:11 +0200 Subject: [PATCH 54/81] Plugin managers fixes --- src/PropTypePluginManager.php | 13 ++++++++----- src/Sdc/UiPatternsSdcPluginManager.php | 22 +++++++++++++++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index 0b54048e..c3f4b1d6 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -6,6 +6,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\sdc\Component\SchemaCompatibilityChecker; +use Drupal\sdc\Exception\IncompatibleComponentSchema; /** * PropType plugin manager. @@ -38,11 +39,12 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac /** * */ - public function getPropType($prop_schema): ?PropTypeInterface { - $definition = $this->getPropTypeDefinition($prop_schema); + public function getPropType(string $prop_id, array $prop_schema): ?PropTypeInterface { + $definition = $this->getPropTypeDefinition($prop_id, $prop_schema); if ($definition !== NULL) { return $this->createInstance($definition['id'], []); } + return NULL; } /** @@ -59,11 +61,11 @@ public function getSortedDefinitions() { /** * */ - public function getPropTypeDefinition($prop_schema): ?array { + public function getPropTypeDefinition(string $prop_id, array $prop_schema): ?array { $definitions = $this->getSortedDefinitions(); foreach ($definitions as $definition) { - $annotation_schema['properties'][$definition['id']] = $definition['schema']; - $mapped_prop_schema['properties'][$definition['id']] = $prop_schema; + $annotation_schema['properties'][$prop_id] = $definition['schema']; + $mapped_prop_schema['properties'][$prop_id] = $prop_schema; try { $this->compatibilityChecker->isCompatible($mapped_prop_schema, $annotation_schema); return $definition; @@ -72,6 +74,7 @@ public function getPropTypeDefinition($prop_schema): ?array { // Do nothing. } } + return NULL; } } diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index fe0163c1..50a49fa6 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -23,12 +23,28 @@ protected function getCacheKey() { */ protected function alterDefinitions(&$definitions) { parent::alterDefinitions($definitions); - foreach ($definitions as & $definition) { - foreach ($definition['props']['properties'] as $prop_id => & $prop) { - $prop_type = $this->propTypePluginManager->getPropType($prop); + $definitions = $this->addPropTypes($definitions); + } + + /** + * + */ + protected function addPropTypes($definitions) { + foreach ($definitions as $component_id => $definition) { + if (!isset($definition['props'])) { + continue; + } + if (!isset($definition['props']['properties'])) { + continue; + } + foreach ($definition['props']['properties'] as $prop_id => $prop) { + $prop_type = $this->propTypePluginManager->getPropType($prop_id, $prop); $prop['type_definition'] = $prop_type?->label() ?? 'undefined'; + $definition['props']['properties'][$prop_id] = $prop; } + $definitions[$component_id] = $definition; } + return $definitions; } } From e8d0a6e6ff3890f599ac4f1ee2bbdddc1eeb7583 Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 6 Oct 2023 11:03:39 +0200 Subject: [PATCH 55/81] Move TemporaryHelper methods to plugin manager --- .../src/Element/Pattern.php | 3 +- .../src/Element/PatternPreview.php | 7 +- .../src/Controller/LibraryController.php | 3 +- .../src/Element/ComponentStory.php | 6 +- src/Sdc/UiPatternsSdcPluginManager.php | 69 ++++++++++++++++ src/TemporaryHelper.php | 78 ------------------- 6 files changed, 77 insertions(+), 89 deletions(-) delete mode 100644 src/TemporaryHelper.php diff --git a/modules/ui_patterns_legacy/src/Element/Pattern.php b/modules/ui_patterns_legacy/src/Element/Pattern.php index b8246783..5edf9a1c 100644 --- a/modules/ui_patterns_legacy/src/Element/Pattern.php +++ b/modules/ui_patterns_legacy/src/Element/Pattern.php @@ -4,7 +4,6 @@ use Drupal\Core\Render\Element; use Drupal\ui_patterns\Element\ComponentElement; -use Drupal\ui_patterns\TemporaryHelper; /** * Renders a pattern element as a SDC element. @@ -63,7 +62,7 @@ public function convert(array $element): array { $element = self::resolveCompactFormat($element); $element["#type"] = "component"; if (array_key_exists("#id", $element) && is_string($element["#id"])) { - $element["#id"] = TemporaryHelper::getNamespacedId($element["#id"]); + $element["#id"] = \Drupal::service('plugin.manager.sdc')->getNamespacedId($element["#id"]); $element["#component"] = $element["#id"]; unset($element["#id"]); } diff --git a/modules/ui_patterns_legacy/src/Element/PatternPreview.php b/modules/ui_patterns_legacy/src/Element/PatternPreview.php index 719aee14..e54bd290 100644 --- a/modules/ui_patterns_legacy/src/Element/PatternPreview.php +++ b/modules/ui_patterns_legacy/src/Element/PatternPreview.php @@ -2,8 +2,6 @@ namespace Drupal\ui_patterns_legacy\Element; -use Drupal\ui_patterns\TemporaryHelper; - /** * Renders a pattern preview element. * @@ -39,7 +37,8 @@ public function getInfo(): array { * Render array. */ public function loadPreviewStory(array $element): array { - $component = \Drupal::service('plugin.manager.sdc')->getDefinition($element["#component"]); + $manager = \Drupal::service('plugin.manager.sdc'); + $component = $manager->getDefinition($element["#component"]); if (!isset($component["stories"])) { return $element; } @@ -51,7 +50,7 @@ public function loadPreviewStory(array $element): array { $slots = $story["slots"] ?? []; $props = $story["props"] ?? []; $slots = array_merge($element["#slots"], $slots); - $element["#slots"] = TemporaryHelper::processStoriesSlots($slots); + $element["#slots"] = $manager::processStoriesSlots($slots); $element["#props"] = array_merge($element["#props"], $props); return $element; } diff --git a/modules/ui_patterns_library/src/Controller/LibraryController.php b/modules/ui_patterns_library/src/Controller/LibraryController.php index 39bc9beb..5a32066a 100644 --- a/modules/ui_patterns_library/src/Controller/LibraryController.php +++ b/modules/ui_patterns_library/src/Controller/LibraryController.php @@ -4,7 +4,6 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\sdc\ComponentPluginManager; -use Drupal\ui_patterns\TemporaryHelper; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -64,7 +63,7 @@ public function single($name) { * Patterns overview page render array. */ public function overview() { - $groups = TemporaryHelper::getGroupedDefinitions(); + $groups = $this->componentPluginManager->getGroupedDefinitions(); return [ '#theme' => 'ui_patterns_overview_page', '#groups' => $groups, diff --git a/modules/ui_patterns_library/src/Element/ComponentStory.php b/modules/ui_patterns_library/src/Element/ComponentStory.php index 26c790f9..255efddb 100644 --- a/modules/ui_patterns_library/src/Element/ComponentStory.php +++ b/modules/ui_patterns_library/src/Element/ComponentStory.php @@ -3,7 +3,6 @@ namespace Drupal\ui_patterns_library\Element; use Drupal\ui_patterns\Element\ComponentElement; -use Drupal\ui_patterns\TemporaryHelper; /** * Renders a component story. @@ -34,11 +33,12 @@ public function getInfo(): array { * */ public function loadStory(array $element): array { + $manager = \Drupal::service('plugin.manager.sdc'); if (!isset($element["#story"])) { return $element; } $story_id = $element["#story"]; - $component = \Drupal::service('plugin.manager.sdc')->getDefinition($element["#component"]); + $component = $manager->getDefinition($element["#component"]); if (!isset($component["stories"])) { return $element; } @@ -49,7 +49,7 @@ public function loadStory(array $element): array { $slots = $story["slots"] ?? []; $props = $story["props"] ?? []; $slots = array_merge($element["#slots"], $slots); - $element["#slots"] = TemporaryHelper::processStoriesSlots($slots); + $element["#slots"] = $manager::processStoriesSlots($slots); $element["#props"] = array_merge($element["#props"], $props); return $element; } diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index 50a49fa6..478f96f5 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -47,4 +47,73 @@ protected function addPropTypes($definitions) { return $definitions; } + /** + * Do we move this method to ui_patterns_legacy? + */ + public function getNamespacedId(string $component_id): string { + $parts = explode(":", $component_id); + if (count(array_filter($parts)) === 2) { + // Already namespaced. + return $component_id; + } + if (count(array_filter($parts)) > 2) { + // Unexpected situation. + return $component_id; + } + $components = $this->getAllComponents(); + // @todo Search first in current active theme, then parents themes, then modules. + foreach ($components as $component) { + if ($component->getPluginDefinition()["machineName"] === $component_id) { + return $component->getPluginId(); + } + } + return $component_id; + } + + /** + * + */ + public function getGroupedDefinitions(): array { + $definitions = []; + // @todo use category metadata from ui_patterns_library + // Do we move this method to ui_patterns_library? + // Or do we move categories to ui_patterns? + foreach ($this->getAllComponents() as $component) { + $definitions[] = $component->getPluginDefinition(); + } + $groups = [ + "All" => $definitions, + ]; + return $groups; + } + + /** + * Stories slots have no "#" prefix in render arrays. Let's add them. + * A bit like UI Patterns 1.x's PatternPreview::getPreviewMarkup() + * Do we move this method to ui_patterns_library? + * Is it something we want to remove in UI Patterns 2? + */ + public static function processStoriesSlots(array $slots): array { + foreach ($slots as $slot_id => $slot) { + if (!is_array($slot)) { + continue; + } + if (array_is_list($slot)) { + $slots[$slot_id] = self::processStoriesSlots($slot); + } + $slot_keys = array_keys($slot); + $render_keys = ["theme", "type", "markup", "plain_text"]; + if (count(array_intersect($slot_keys, $render_keys)) > 0) { + foreach ($slot as $key => $value) { + if (is_array($value)) { + $value = self::processStoriesSlots($value); + } + $slots[$slot_id]["#" . $key] = $value; + unset($slots[$slot_id][$key]); + } + } + } + return $slots; + } + } diff --git a/src/TemporaryHelper.php b/src/TemporaryHelper.php deleted file mode 100644 index aa7e09c9..00000000 --- a/src/TemporaryHelper.php +++ /dev/null @@ -1,78 +0,0 @@ - 2) { - // Unexpected situation. - return $component_id; - } - $components = \Drupal::service('plugin.manager.sdc')->getAllComponents(); - // @todo Search first in current active theme, then parents themes, then modules. - foreach ($components as $component) { - if ($component->getPluginDefinition()["machineName"] === $component_id) { - return $component->getPluginId(); - } - } - return $component_id; - } - - /** - * - */ - public static function getGroupedDefinitions(): array { - $definitions = []; - // @todo use category metadata from ui_patterns_library - // Do we move this method to ui_patterns_library? - foreach (\Drupal::service('plugin.manager.sdc')->getAllComponents() as $component) { - $definitions[] = $component->getPluginDefinition(); - } - $groups = [ - "All" => $definitions, - ]; - return $groups; - } - - /** - * Stories slots have no "#" prefix in render arrays. Let's add them. - * A bit like UI Patterns 1.x's PatternPreview::getPreviewMarkup() - * Do we move this method to ui_patterns_library? - * Is iy something we want to remove in UI Patterns 2? - */ - public static function processStoriesSlots(array $slots): array { - foreach ($slots as $slot_id => $slot) { - if (!is_array($slot)) { - continue; - } - if (array_is_list($slot)) { - $slots[$slot_id] = self::processStoriesSlots($slot); - } - $slot_keys = array_keys($slot); - $render_keys = ["theme", "type", "markup", "plain_text"]; - if (count(array_intersect($slot_keys, $render_keys)) > 0) { - foreach ($slot as $key => $value) { - if (is_array($value)) { - $value = self::processStoriesSlots($value); - } - $slots[$slot_id]["#" . $key] = $value; - unset($slots[$slot_id][$key]); - } - } - } - return $slots; - } - -} From 15549afe3b574312a3833cec7d72c0ef4257fed7 Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 6 Oct 2023 11:04:31 +0200 Subject: [PATCH 56/81] Disable ui_patterns_legacy.plugin.manager.sdc.decorator because not working yet --- .../ui_patterns_legacy.services.yml | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml index 13bc693d..37c9e11f 100644 --- a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml @@ -6,21 +6,21 @@ services: class: Drupal\ui_patterns_legacy\Template\TwigExtension tags: - { name: twig.extension } - ui_patterns_legacy.plugin.manager.sdc.decorator: - class: Drupal\ui_patterns_legacy\UiPatternsLegacyPluginManager - decorates: plugin.manager.sdc - decoration_priority: 9 - public: false - arguments: - - '@ui_patterns_legacy.plugin.manager.sdc.decorator.inner' - - '@plugin.manager.ui_patterns_prop_type' - - '@module_handler' - - '@theme_handler' - - '@cache.discovery' - - '@config.factory' - - '@theme.manager' - - '@Drupal\sdc\ComponentNegotiator' - - '@file_system' - - '@Drupal\sdc\Component\SchemaCompatibilityChecker' - - '@Drupal\sdc\Component\ComponentValidator' - - '%app.root%' +# ui_patterns_legacy.plugin.manager.sdc.decorator: +# class: Drupal\ui_patterns_legacy\UiPatternsLegacyPluginManager +# decorates: plugin.manager.sdc +# decoration_priority: 9 +# public: false +# arguments: +# - '@ui_patterns_legacy.plugin.manager.sdc.decorator.inner' +# - '@plugin.manager.ui_patterns_prop_type' +# - '@module_handler' +# - '@theme_handler' +# - '@cache.discovery' +# - '@config.factory' +# - '@theme.manager' +# - '@Drupal\sdc\ComponentNegotiator' +# - '@file_system' +# - '@Drupal\sdc\Component\SchemaCompatibilityChecker' +# - '@Drupal\sdc\Component\ComponentValidator' +# - '%app.root%' From 33447a7a5c8be867c3909d2963dde39a95ce2bf1 Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 6 Oct 2023 11:19:36 +0200 Subject: [PATCH 57/81] uii_patterns_library templates improvments --- .../ui-patterns-component-metadata.html.twig | 8 ++++++-- .../ui-patterns-overview-page.html.twig | 17 ++++++++++++----- src/Sdc/UiPatternsSdcPluginManager.php | 15 +++++++-------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig index 1b03345d..bad8f330 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig @@ -28,7 +28,7 @@ - + @@ -45,7 +45,11 @@ - + {% endfor %} diff --git a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig index c348f444..1f1dc86f 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-overview-page.html.twig @@ -8,11 +8,18 @@ {% if groups is not empty %}

    {{ "Available components"|t }}

    - {# List of available patterns with anchor links. #} - {% for group_name, components in groups %} - {% if components|length > 1 %} + {% if groups|length > 1 %} + {% for group_name, components in groups %}

    {{ group_name }}

    - {% endif %} +
    + {% endfor %} + {% else %}
      {% for component in components %}
    • @@ -20,7 +27,7 @@
    • {% endfor %}
    - {% endfor %} + {% endif %}
    diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index 478f96f5..35664f3e 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -71,19 +71,18 @@ public function getNamespacedId(string $component_id): string { } /** - * + * {@inheritdoc} */ - public function getGroupedDefinitions(): array { - $definitions = []; + public function getGroupedDefinitions(?array $definitions = NULL): array { // @todo use category metadata from ui_patterns_library // Do we move this method to ui_patterns_library? // Or do we move categories to ui_patterns? - foreach ($this->getAllComponents() as $component) { - $definitions[] = $component->getPluginDefinition(); + $definitions = $definitions ?: $this->getDefinitions(); + $groups = []; + foreach ($definitions as $id => $definition) { + $category = $definition["category"] ?? "Other"; + $groups[$category][$id] = $definition; } - $groups = [ - "All" => $definitions, - ]; return $groups; } From 532b92b4fc8fa95d55dc53e040e04c8bc9afd256 Mon Sep 17 00:00:00 2001 From: Pierre Date: Fri, 6 Oct 2023 22:58:20 +0200 Subject: [PATCH 58/81] Add SchemaCompatibilityChecker, update tests components --- .../PropType/AttributesPropType.php | 37 ++++ .../UiPatterns/PropType/BooleanPropType.php | 22 +++ .../UiPatterns/PropType/IntegerPropType.php | 22 +++ .../UiPatterns/PropType/UrlPropType.php | 20 +- src/PropTypePluginManager.php | 18 +- src/Sdc/UiPatternsSdcPluginManager.php | 4 +- src/Utils/SchemaCompatibilityChecker.php | 185 ++++++++++++++++++ .../components/card/card.component.yml | 8 - .../components/figure/figure.component.yml | 2 +- .../progress/progress.component.yml | 8 +- .../prop_types_tests.component.yml | 62 ++++++ .../prop_types_tests/prop_types_tests.twig | 1 + .../replaced_figure.component.yml | 2 +- 13 files changed, 363 insertions(+), 28 deletions(-) create mode 100644 src/Plugin/UiPatterns/PropType/AttributesPropType.php create mode 100644 src/Plugin/UiPatterns/PropType/BooleanPropType.php create mode 100644 src/Plugin/UiPatterns/PropType/IntegerPropType.php create mode 100644 src/Utils/SchemaCompatibilityChecker.php create mode 100644 tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml create mode 100644 tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.twig diff --git a/src/Plugin/UiPatterns/PropType/AttributesPropType.php b/src/Plugin/UiPatterns/PropType/AttributesPropType.php new file mode 100644 index 00000000..853dc725 --- /dev/null +++ b/src/Plugin/UiPatterns/PropType/AttributesPropType.php @@ -0,0 +1,37 @@ +getDefinition($prop_type_id); + } $definitions = $this->getSortedDefinitions(); foreach ($definitions as $definition) { - $annotation_schema['properties'][$prop_id] = $definition['schema']; - $mapped_prop_schema['properties'][$prop_id] = $prop_schema; - try { - $this->compatibilityChecker->isCompatible($mapped_prop_schema, $annotation_schema); + $compatibilityChecker = new SchemaCompatibilityChecker(); + if ($compatibilityChecker->isCompatible($definition['schema'], $prop_schema)) { return $definition; } - catch (IncompatibleComponentSchema $exception) { - // Do nothing. - } } return NULL; } diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index 35664f3e..1811d0de 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -89,8 +89,8 @@ public function getGroupedDefinitions(?array $definitions = NULL): array { /** * Stories slots have no "#" prefix in render arrays. Let's add them. * A bit like UI Patterns 1.x's PatternPreview::getPreviewMarkup() - * Do we move this method to ui_patterns_library? - * Is it something we want to remove in UI Patterns 2? + * This method belongs here because sued by both ui_patterns_library and + * ui_patterns_legacy. */ public static function processStoriesSlots(array $slots): array { foreach ($slots as $slot_id => $slot) { diff --git a/src/Utils/SchemaCompatibilityChecker.php b/src/Utils/SchemaCompatibilityChecker.php new file mode 100644 index 00000000..290dae99 --- /dev/null +++ b/src/Utils/SchemaCompatibilityChecker.php @@ -0,0 +1,185 @@ +canonicalize($checked_schema); + $reference_schema = $this->canonicalize($reference_schema); + if (isset($reference_schema["anyOf"])) { + foreach ($reference_schema["anyOf"] as $schema) { + if ($this->isCompatible($checked_schema, $schema)) { + return TRUE; + } + } + return FALSE; + } + if (isset($checked_schema["anyOf"])) { + foreach ($checked_schema["anyOf"] as $schema) { + if ($this->isCompatible($schema, $reference_schema)) { + return TRUE; + } + } + return FALSE; + } + if ($checked_schema["type"] !== $reference_schema["type"]) { + return FALSE; + } + // Now we know $checked_schema and $reference_schema have the same type. + // So, testing $checked_schema type is enough. + $type = $checked_schema["type"]; + if ($type === "boolean") { + return TRUE; + } + if ($type === "null") { + return TRUE; + } + // Complex checking. + if ($this->isSame($checked_schema, $reference_schema)) { + return TRUE; + } + if ($type === "object") { + return $this->isObjectCompatible($checked_schema, $reference_schema); + } + if ($type === "array") { + return $this->isArrayCompatible($checked_schema, $reference_schema); + } + if ($type === "number") { + return $this->isNumberCompatible($checked_schema, $reference_schema); + } + if ($type === "integer") { + return $this->isNumberCompatible($checked_schema, $reference_schema); + } + if ($type === "string") { + return $this->isStringCompatible($checked_schema, $reference_schema); + } + return FALSE; + } + + /** + * + */ + protected function isSame($checked_schema, $reference_schema): bool { + $comparaison = strcmp( + json_encode($checked_schema), + json_encode($reference_schema) + ); + if ($comparaison === 0) { + return TRUE; + } + return FALSE; + } + + /** + * + */ + protected function isObjectCompatible(array $checked_schema, array $reference_schema): bool { + // @todo recursive + return FALSE; + } + + /** + * Check if different arrays are compatible. + */ + protected function isArrayCompatible(array $checked_schema, array $reference_schema): bool { + // @todo https://json-schema.org/understanding-json-schema/reference/array#items + // @todo https://json-schema.org/understanding-json-schema/reference/array#contains + // @todo https://json-schema.org/understanding-json-schema/reference/array#mincontains-maxcontains + // @tood: https://json-schema.org/understanding-json-schema/reference/array#length + // @todo https://json-schema.org/understanding-json-schema/reference/array#uniqueness + // @todo recursive + return FALSE; + } + + /** + * Check if different numbers are compatible. + */ + protected function isNumberCompatible(array $checked_schema, array $reference_schema): bool { + // @todo integer are number + // @todo https://json-schema.org/understanding-json-schema/reference/numeric#multiples + // @todo https://json-schema.org/understanding-json-schema/reference/numeric#range + return FALSE; + } + + /** + * Check if different strings are compatible. + */ + protected function isStringCompatible(array $checked_schema, array $reference_schema): bool { + // @todo https://json-schema.org/understanding-json-schema/reference/string#length + // @todo https://json-schema.org/understanding-json-schema/reference/string#regexp + // @todo https://json-schema.org/understanding-json-schema/reference/string#format + if (isset($checked_schema["format"]) && isset($reference_schema["format"])) { + // @todo uri, uri-reference, iri, iri-reference + // @todo formats & sub-formats + } + if (!isset($checked_schema["format"]) && isset($reference_schema["format"])) { + return FALSE; + } + if (isset($checked_schema["format"]) && !isset($reference_schema["format"])) { + // A string with format is still a string. + return TRUE; + } + return FALSE; + } + + /** + * + */ + protected function canonicalize(array $schema): array { + $schema = $this->removeUselessProperties($schema); + if ($schema["type"] === "object" && isset($schema["properties"])) { + foreach ($schema["properties"] as $property_id => $property) { + $schema["properties"][$property_id] = $this->canonicalize($property); + } + } + if ($schema["type"] === "array" && isset($schema["items"])) { + $schema["items"] = $this->canonicalize($schema["items"]); + } + $schema = array_filter($schema); + ksort($schema); + return $schema; + } + + /** + * + */ + protected function removeUselessProperties(array $schema): array { + $keys = [ + "anyOf", "allOf", "oneOf", "not", "enum", "type", '$ref', "constant", + ]; + $keys_by_type = [ + "string" => ["minLength", "maxLength", "pattern", "format"], + "number" => ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"], + "integer" => ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"], + "boolean" => [], + "null" => [], + "array" => ["minItems", "maxItems", "items", "additionalItems", "uniqueItems"], + "object" => ["properties", "additionalProperties", "required", "minProperties", "maxProperties", "dependencies", "patternProperties"], + ]; + if (isset($schema["type"])) { + $type = $schema["type"]; + $keys = array_merge($keys, $keys_by_type[$type]); + } + return array_intersect_key($schema, array_flip($keys)); + } + +} diff --git a/tests/modules/ui_patterns_test/components/card/card.component.yml b/tests/modules/ui_patterns_test/components/card/card.component.yml index c7e351c7..8a87f528 100644 --- a/tests/modules/ui_patterns_test/components/card/card.component.yml +++ b/tests/modules/ui_patterns_test/components/card/card.component.yml @@ -23,20 +23,12 @@ props: title: "Image column classes" description: "Only for horizontal variant." type: "string" - format: uri-reference default: "col-md-4" content_col_classes: title: "Content column classes" description: "Only for horizontal variant." type: "string" default: "col-md-8" - url_test_1: - title: "Url (explitci typing)" - $ref: "ui-patterns://url" - url_test_2: - title: "Url (implicit typing)" - type: "string" - format: "iri-reference" slots: image: title: "Image" diff --git a/tests/modules/ui_patterns_test/components/figure/figure.component.yml b/tests/modules/ui_patterns_test/components/figure/figure.component.yml index b90d784f..097957e3 100644 --- a/tests/modules/ui_patterns_test/components/figure/figure.component.yml +++ b/tests/modules/ui_patterns_test/components/figure/figure.component.yml @@ -10,7 +10,7 @@ props: figcaption_attributes: title: "Figcaption attributes" description: "The attributes to customize the figcaption tag." - type: "object" + "$ref": "ui-patterns://attributes" slots: image: title: "Image" diff --git a/tests/modules/ui_patterns_test/components/progress/progress.component.yml b/tests/modules/ui_patterns_test/components/progress/progress.component.yml index d1000d09..b565e160 100644 --- a/tests/modules/ui_patterns_test/components/progress/progress.component.yml +++ b/tests/modules/ui_patterns_test/components/progress/progress.component.yml @@ -21,19 +21,19 @@ props: percent: title: "Total progress (%)" description: "Width of the progress element representing total progress (25%, 50%, etc.)." - type: "number" + type: "integer" min: title: "Minimum value" description: "Minimum value of the progress element (default is 0). Used for an aria attribute." - type: "number" + type: "integer" max: title: "Maximum value" description: "Maximum value of the progress element (default is 100). Used for an aria attribute." - type: "number" + type: "integer" bar_height: title: "Height" description: "Height of progress element in pixels (px). Leave empty for default height." - type: "number" + type: "integer" slots: label: title: "Label" diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml new file mode 100644 index 00000000..73744cb9 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml @@ -0,0 +1,62 @@ +$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json +name: "Prop types tests" +description: "A card is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options." +props: + type: object + properties: + string: + title: "String" + type: "string" + string_enum: + title: "String with enum" + type: "string" + enum: + - top + - bottom + string_length: + title: "String with length" + type: "string" + maxLength: 10 + boolean: + title: "Boolean" + type: "boolean" + integer: + title: "Integer" + type: "integer" + number_1: + title: "Number" + type: "number" + number_2: + title: "Number with min max" + type: "number" + minimum: 2 + maximum: 10 + attributes: + title: "Attributes" + type: object + patternProperties: + ".+": + anyOf: + - type: + - string + - number + - type: array + items: + anyOf: + - type: number + - type: string + url_1: + title: "Url (explicit typing)" + $ref: "ui-patterns://url" + url_2: + title: "Url (implicit typing)" + type: "string" + format: "iri-reference" + url_3: + title: "Url (implicit typing, other type)" + type: "string" + format: "uri" + object: + title: "Plain object" + type: "object" +slots: {} diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.twig b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.twig new file mode 100644 index 00000000..8511efcc --- /dev/null +++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.twig @@ -0,0 +1 @@ +

    Empty template

    diff --git a/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml index 18e8f97d..d4ea2091 100644 --- a/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml +++ b/tests/modules/ui_patterns_test/components/replaced_figure/replaced_figure.component.yml @@ -6,7 +6,7 @@ props: figcaption_attributes: title: "Figcaption attributes" description: "The attributes to customize the figcaption tag." - type: "object" + "$ref": "ui-patterns://attributes" slots: image: title: "Image" From b37133a0ebf13511b1da55498054c282cbc211aa Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Sat, 7 Oct 2023 21:27:57 +0200 Subject: [PATCH 59/81] Add initial tests --- .../src/Plugin/Layout/PatternLayout.php | 7 +- .../UiPatternsLayoutsRenderTest.php | 83 ------------------- .../UiPatternsLayoutsSettingsTest.php | 69 --------------- src/Annotation/SourceProvider.php | 33 ++++++++ src/Form/UiPatternsFormBuilderTrait.php | 30 +++++++ src/Plugin/UiPatterns/SourceProvider/Foo.php | 18 ++++ src/PropTypePluginManager.php | 10 +-- src/Sdc/ComponentPluginManagerDecorator.php | 2 + src/Sdc/UiPatternsSdcPluginManager.php | 14 +--- src/SourceProviderInterface.php | 15 ++++ src/SourceProviderPluginBase.php | 20 +++++ src/SourceProviderPluginManager.php | 29 +++++++ src/Utils/SchemaCompatibilityChecker.php | 5 +- .../prop_types_tests.component.yml | 1 - .../src/Kernel/PropTypePluginManagerTest.php | 40 +++++++++ .../SourceProviderPluginManagerTest.php | 41 +++++++++ .../Unit/SchemaCompatibilityCheckerTest.php | 38 +++++++++ ui_patterns.services.yml | 4 + 18 files changed, 287 insertions(+), 172 deletions(-) delete mode 100644 modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php delete mode 100644 modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php create mode 100644 src/Annotation/SourceProvider.php create mode 100644 src/Form/UiPatternsFormBuilderTrait.php create mode 100644 src/Plugin/UiPatterns/SourceProvider/Foo.php create mode 100644 src/SourceProviderInterface.php create mode 100644 src/SourceProviderPluginBase.php create mode 100644 src/SourceProviderPluginManager.php create mode 100644 tests/src/Kernel/PropTypePluginManagerTest.php create mode 100644 tests/src/Kernel/SourceProviderPluginManagerTest.php create mode 100644 tests/src/Unit/SchemaCompatibilityCheckerTest.php diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index a68ed0ba..e8bc9669 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -9,6 +9,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\ui_patterns\Form\UiPatternsFormBuilderTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -18,6 +19,8 @@ */ class PatternLayout extends LayoutDefault implements PluginFormInterface, ContainerFactoryPluginInterface { + use UiPatternsFormBuilderTrait; + /** * Module Handler. * @@ -104,7 +107,7 @@ public function defaultConfiguration() { public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = []; $configuration = $this->getConfiguration(); - $form['pattern'] = [ + $form['component_metadata'] = [ '#group' => 'additional_settings', '#type' => 'container', '#title' => $this->t('Configuration'), @@ -115,6 +118,8 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $plugin_manager = \Drupal::service('plugin.manager.sdc'); /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ $component_metadata = $plugin_manager->find($component_id)?->metadata; + $context = ['form_type' => 'layout', 'form' => $form, 'layout' => $this]; + $form['component_metadata']['form'] = $this->buildComponentForm($form_state, $component_metadata, $context); $this->moduleHandler->alter('ui_patterns_layouts_display_configuration_form', $form['pattern'], $component_metadata, $configuration); return $form; } diff --git a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php deleted file mode 100644 index 6d19dd4a..00000000 --- a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsRenderTest.php +++ /dev/null @@ -1,83 +0,0 @@ -enableTwigDebugMode(); - $this->drupalLogin($this->drupalCreateUser([], NULL, TRUE)); - $node = $this->drupalCreateNode([ - 'title' => 'Test article', - 'body' => 'Test body', - 'type' => 'article', - ]); - - $this->drupalGet($node->toUrl()); - - $assert_session = $this->assertSession(); - - // Assert correct variant suggestions. - $suggestions = [ - 'pattern-one-column--variant-default--layout--node--1.html.twig', - 'pattern-one-column--variant-default--layout--node--article--full.html.twig', - 'pattern-one-column--variant-default--layout--node--full.html.twig', - 'pattern-one-column--variant-default--layout--node--article.html.twig', - 'pattern-one-column--variant-default--layout--node.html.twig', - 'pattern-one-column--variant-default--layout.html.twig', - - 'pattern-one-column--layout--node--1.html.twig', - 'pattern-one-column--layout--node--article--full.html.twig', - 'pattern-one-column--layout--node--full.html.twig', - 'pattern-one-column--layout--node--article.html.twig', - 'pattern-one-column--layout--node.html.twig', - 'pattern-one-column--layout.html.twig', - - 'pattern-one-column--variant-default.html.twig', - 'pattern-one-column.html.twig', - ]; - foreach ($suggestions as $suggestion) { - $assert_session->responseContains($suggestion); - } - - // Test content is rendered in the pattern. - $assert_session->elementContains('css', 'article', 'Test body'); - } - -} diff --git a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php b/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php deleted file mode 100644 index ee8a3633..00000000 --- a/modules/ui_patterns_layouts/tests/src/FunctionalJavascript/UiPatternsLayoutsSettingsTest.php +++ /dev/null @@ -1,69 +0,0 @@ -getSession()->getPage(); - - $user = $this->drupalCreateUser([], NULL, TRUE); - $this->drupalLogin($user); - - // Visit Article's default display settings page. - $this->drupalGet('/admin/structure/types/manage/article/display'); - - // Click on Pattern settings. - $page->pressButton('Layout settings'); - $page->pressButton('Pattern settings'); - - // Select "Highlighted" field template. - $page->selectFieldOption('Variant', 'Highlighted'); - - $page->pressButton('Save'); - - // Get default view mode for Article node bundle. - $display = EntityViewDisplay::load("node.article.default"); - - // Assert existence of third party settings. - $third_party_settings = $display->getThirdPartySettings('field_layout'); - - // Assert settings value. - $this->assertEquals($third_party_settings['settings']['pattern']['variant'], 'highlighted'); - } - -} diff --git a/src/Annotation/SourceProvider.php b/src/Annotation/SourceProvider.php new file mode 100644 index 00000000..c1e73369 --- /dev/null +++ b/src/Annotation/SourceProvider.php @@ -0,0 +1,33 @@ +getSourceProviders($component); + return ['#type' => 'textfield', '#title' => 'Dummy']; + } + + /** + * Build components selector widget. + * + */ + protected function buildComponentsForm():array { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/SourceProvider/Foo.php b/src/Plugin/UiPatterns/SourceProvider/Foo.php new file mode 100644 index 00000000..ee8bff94 --- /dev/null +++ b/src/Plugin/UiPatterns/SourceProvider/Foo.php @@ -0,0 +1,18 @@ +getPropTypeDefinition($prop_id, $prop_schema); + public function getPropType(array $prop_schema): ?PropTypeInterface { + $definition = $this->getPropTypeDefinition($prop_schema); if ($definition !== NULL) { return $this->createInstance($definition['id'], []); } @@ -60,11 +60,7 @@ public function getSortedDefinitions() { /** * */ - public function getPropTypeDefinition(string $prop_id, array $prop_schema): ?array { - if (isset($prop_schema['$ref']) && str_contains($prop_schema['$ref'], "ui-patterns://")) { - $prop_type_id = str_replace("ui-patterns://", "", $prop_schema['$ref']); - return $this->getDefinition($prop_type_id); - } + public function getPropTypeDefinition(array $prop_schema): ?array { $definitions = $this->getSortedDefinitions(); foreach ($definitions as $definition) { $compatibilityChecker = new SchemaCompatibilityChecker(); diff --git a/src/Sdc/ComponentPluginManagerDecorator.php b/src/Sdc/ComponentPluginManagerDecorator.php index 7dc07dde..821ca313 100644 --- a/src/Sdc/ComponentPluginManagerDecorator.php +++ b/src/Sdc/ComponentPluginManagerDecorator.php @@ -14,6 +14,7 @@ use Drupal\sdc\ComponentPluginManager; use Drupal\sdc\Plugin\Component; use Drupal\ui_patterns\PropTypePluginManager; +use Drupal\ui_patterns\SourceProviderPluginManager; /** * Plugin Manager for *.ui_patterns.yml configuration files. @@ -34,6 +35,7 @@ abstract class ComponentPluginManagerDecorator extends ComponentPluginManager { public function __construct( protected ComponentPluginManager $parentSdcPluginManager, protected PropTypePluginManager $propTypePluginManager, + protected SourceProviderPluginManager $sourcePluginManager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $themeHandler, CacheBackendInterface $cacheBackend, diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index 1811d0de..aa1e8f51 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -23,13 +23,6 @@ protected function getCacheKey() { */ protected function alterDefinitions(&$definitions) { parent::alterDefinitions($definitions); - $definitions = $this->addPropTypes($definitions); - } - - /** - * - */ - protected function addPropTypes($definitions) { foreach ($definitions as $component_id => $definition) { if (!isset($definition['props'])) { continue; @@ -38,13 +31,14 @@ protected function addPropTypes($definitions) { continue; } foreach ($definition['props']['properties'] as $prop_id => $prop) { - $prop_type = $this->propTypePluginManager->getPropType($prop_id, $prop); - $prop['type_definition'] = $prop_type?->label() ?? 'undefined'; + $prop_type = $this->propTypePluginManager->getPropType($prop); + $source_providers = $this->sourcePluginManager->getSourceProviders($prop_type); + $prop['ui_patterns']['type_definition'] = $prop_type; + $prop['ui_patterns']['source_providers'] = $source_providers; $definition['props']['properties'][$prop_id] = $prop; } $definitions[$component_id] = $definition; } - return $definitions; } /** diff --git a/src/SourceProviderInterface.php b/src/SourceProviderInterface.php new file mode 100644 index 00000000..1e1c56ac --- /dev/null +++ b/src/SourceProviderInterface.php @@ -0,0 +1,15 @@ +pluginDefinition['label']; + } + +} diff --git a/src/SourceProviderPluginManager.php b/src/SourceProviderPluginManager.php new file mode 100644 index 00000000..b0403638 --- /dev/null +++ b/src/SourceProviderPluginManager.php @@ -0,0 +1,29 @@ +alterInfo('source_provider_info'); + $this->setCacheBackend($cache_backend, 'ui_patterns_source_provider_plugins'); + } + + public function getSourceProviders(PropTypeInterface $prop_type) { + $definitions = $this->getDefinitions(); + } +} diff --git a/src/Utils/SchemaCompatibilityChecker.php b/src/Utils/SchemaCompatibilityChecker.php index 290dae99..191f4f47 100644 --- a/src/Utils/SchemaCompatibilityChecker.php +++ b/src/Utils/SchemaCompatibilityChecker.php @@ -145,7 +145,10 @@ protected function isStringCompatible(array $checked_schema, array $reference_sc * */ protected function canonicalize(array $schema): array { - $schema = $this->removeUselessProperties($schema); + //$schema = $this->removeUselessProperties($schema); + if (!isset($schema["type"])) { + return $schema; + } if ($schema["type"] === "object" && isset($schema["properties"])) { foreach ($schema["properties"] as $property_id => $property) { $schema["properties"][$property_id] = $this->canonicalize($property); diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml index 73744cb9..067015a2 100644 --- a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml +++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml @@ -59,4 +59,3 @@ props: object: title: "Plain object" type: "object" -slots: {} diff --git a/tests/src/Kernel/PropTypePluginManagerTest.php b/tests/src/Kernel/PropTypePluginManagerTest.php new file mode 100644 index 00000000..6856be76 --- /dev/null +++ b/tests/src/Kernel/PropTypePluginManagerTest.php @@ -0,0 +1,40 @@ +find('ui_patterns_test:alert'); + self::assertNotNull($component_metadata); + /** @var \Drupal\ui_patterns\SourceProviderPluginManager $source_provider_plugin_manager */ + $source_provider_plugin_manager = \Drupal::service('plugin.manager.ui_patterns_source_provider'); + $source_providers = $source_provider_plugin_manager->getSourceProviders(''); + self::assertCount(3, count($source_providers)); + } + +} diff --git a/tests/src/Kernel/SourceProviderPluginManagerTest.php b/tests/src/Kernel/SourceProviderPluginManagerTest.php new file mode 100644 index 00000000..dc53179f --- /dev/null +++ b/tests/src/Kernel/SourceProviderPluginManagerTest.php @@ -0,0 +1,41 @@ +find('ui_patterns_test:alert'); + self::assertNotNull($component_metadata); + /** @var \Drupal\ui_patterns\SourceProviderPluginManager $source_provider_plugin_manager */ + $source_provider_plugin_manager = \Drupal::service('plugin.manager.ui_patterns_source_provider'); + $source_providers = $source_provider_plugin_manager->getSourceProviders(''); + self::assertCount(3, count($source_providers)); + } + +} diff --git a/tests/src/Unit/SchemaCompatibilityCheckerTest.php b/tests/src/Unit/SchemaCompatibilityCheckerTest.php new file mode 100644 index 00000000..71f5322e --- /dev/null +++ b/tests/src/Unit/SchemaCompatibilityCheckerTest.php @@ -0,0 +1,38 @@ +isCompatible($checked_schema, $reference_schema)); + } + + public function provideIsCompatibleData() { + return [ + [['schema'], ['schema'], true] + ]; + } + +} diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index c93ae2b4..fbd2668e 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -2,6 +2,9 @@ services: plugin.manager.ui_patterns_prop_type: class: Drupal\ui_patterns\PropTypePluginManager arguments: [ '@container.namespaces', '@cache.discovery', '@module_handler', '@Drupal\sdc\Component\SchemaCompatibilityChecker' ] + plugin.manager.ui_patterns_source_provider: + class: Drupal\ui_patterns\SourceProviderPluginManager + parent: default_plugin_manager ui_patterns.twig.extension: class: Drupal\ui_patterns\Template\TwigExtension tags: @@ -14,6 +17,7 @@ services: arguments: - '@ui_patterns.plugin.manager.sdc.inner' - '@plugin.manager.ui_patterns_prop_type' + - '@plugin.manager.ui_patterns_source_provider' - '@module_handler' - '@theme_handler' - '@cache.discovery' From c9d59f2f1c484401112617c241c3f0ec658c1928 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Sat, 7 Oct 2023 21:32:58 +0200 Subject: [PATCH 60/81] Readd implementation --- src/PropTypePluginManager.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index 70ef674f..b16caf9f 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -61,6 +61,10 @@ public function getSortedDefinitions() { * */ public function getPropTypeDefinition(array $prop_schema): ?array { + if (isset($prop_schema['$ref']) && str_contains($prop_schema['$ref'], "ui-patterns://")) { + $prop_type_id = str_replace("ui-patterns://", "", $prop_schema['$ref']); + return $this->getDefinition($prop_type_id); + } $definitions = $this->getSortedDefinitions(); foreach ($definitions as $definition) { $compatibilityChecker = new SchemaCompatibilityChecker(); From e30349dc52687a7fb6b965c10f55cec43b5d84f6 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Sat, 7 Oct 2023 23:01:59 +0200 Subject: [PATCH 61/81] Add initial source plugin tests --- .../{SourceProvider.php => Source.php} | 10 ++- src/Form/UiPatternsFormBuilderTrait.php | 6 +- .../UiPatterns/Source/SiteNameSource.php | 29 +++++++ .../UiPatterns/Source/TextfieldWidget.php | 40 +++++++++ src/Plugin/UiPatterns/SourceProvider/Foo.php | 18 ---- src/PropTypePluginManager.php | 4 +- src/Sdc/ComponentPluginManagerDecorator.php | 4 +- src/Sdc/UiPatternsSdcPluginManager.php | 6 +- src/SourceInterface.php | 22 +++++ src/SourcePluginBase.php | 82 +++++++++++++++++++ src/SourcePluginManager.php | 66 +++++++++++++++ src/SourceProviderInterface.php | 15 ---- src/SourceProviderPluginBase.php | 20 ----- src/SourceProviderPluginManager.php | 29 ------- .../src/Plugin/UiPatterns/Source/Foo.php | 29 +++++++ .../src/Kernel/PropTypePluginManagerTest.php | 15 ++-- tests/src/Kernel/SourcePluginManagerTest.php | 44 ++++++++++ .../SourceProviderPluginManagerTest.php | 41 ---------- .../Unit/SchemaCompatibilityCheckerTest.php | 2 +- ui_patterns.services.yml | 6 +- 20 files changed, 341 insertions(+), 147 deletions(-) rename src/Annotation/{SourceProvider.php => Source.php} (76%) create mode 100644 src/Plugin/UiPatterns/Source/SiteNameSource.php create mode 100644 src/Plugin/UiPatterns/Source/TextfieldWidget.php delete mode 100644 src/Plugin/UiPatterns/SourceProvider/Foo.php create mode 100644 src/SourceInterface.php create mode 100644 src/SourcePluginBase.php create mode 100644 src/SourcePluginManager.php delete mode 100644 src/SourceProviderInterface.php delete mode 100644 src/SourceProviderPluginBase.php delete mode 100644 src/SourceProviderPluginManager.php create mode 100644 tests/modules/ui_patterns_test/src/Plugin/UiPatterns/Source/Foo.php create mode 100644 tests/src/Kernel/SourcePluginManagerTest.php delete mode 100644 tests/src/Kernel/SourceProviderPluginManagerTest.php diff --git a/src/Annotation/SourceProvider.php b/src/Annotation/Source.php similarity index 76% rename from src/Annotation/SourceProvider.php rename to src/Annotation/Source.php index c1e73369..4c871277 100644 --- a/src/Annotation/SourceProvider.php +++ b/src/Annotation/Source.php @@ -9,7 +9,7 @@ * * @Annotation */ -final class SourceProvider extends Plugin { +final class Source extends Plugin { /** * The plugin ID. @@ -30,4 +30,12 @@ final class SourceProvider extends Plugin { */ public readonly string $description; + /** + * An array of prop types the source provider supports. + * + * @var array + */ + public $prop_types = []; + + } diff --git a/src/Form/UiPatternsFormBuilderTrait.php b/src/Form/UiPatternsFormBuilderTrait.php index 7b160ac0..86606c88 100644 --- a/src/Form/UiPatternsFormBuilderTrait.php +++ b/src/Form/UiPatternsFormBuilderTrait.php @@ -13,9 +13,9 @@ trait UiPatternsFormBuilderTrait { * The pattern definition. */ protected function buildComponentForm(FormStateInterface $form_state, ComponentMetadata $component, array $context):array { - /** @var \Drupal\ui_patterns\SourceProviderPluginManager $source_provider_manager */ - $source_provider_manager = \Drupal::service('plugin.manager.ui_patterns_source_provider'); - $source_providers = $source_provider_manager->getSourceProviders($component); + /** @var \Drupal\ui_patterns\SourcePluginManager $source_provider_manager */ + $source_manager = \Drupal::service('plugin.manager.ui_patterns_source'); + $sources = $source_manager->getSources($component); return ['#type' => 'textfield', '#title' => 'Dummy']; } diff --git a/src/Plugin/UiPatterns/Source/SiteNameSource.php b/src/Plugin/UiPatterns/Source/SiteNameSource.php new file mode 100644 index 00000000..ffe9e39e --- /dev/null +++ b/src/Plugin/UiPatterns/Source/SiteNameSource.php @@ -0,0 +1,29 @@ +propId] = ['#type' => 'textfield', '#title' => $this->propDefinition['title']]; + } + + public function defaultConfiguration() { + return []; + } +} diff --git a/src/Plugin/UiPatterns/SourceProvider/Foo.php b/src/Plugin/UiPatterns/SourceProvider/Foo.php deleted file mode 100644 index ee8bff94..00000000 --- a/src/Plugin/UiPatterns/SourceProvider/Foo.php +++ /dev/null @@ -1,18 +0,0 @@ -getPropTypeDefinition($prop_schema); if ($definition !== NULL) { return $this->createInstance($definition['id'], []); @@ -65,7 +65,7 @@ public function getPropTypeDefinition(array $prop_schema): ?array { $prop_type_id = str_replace("ui-patterns://", "", $prop_schema['$ref']); return $this->getDefinition($prop_type_id); } - $definitions = $this->getSortedDefinitions(); + $definitions = $this->getDefinitions(); foreach ($definitions as $definition) { $compatibilityChecker = new SchemaCompatibilityChecker(); if ($compatibilityChecker->isCompatible($definition['schema'], $prop_schema)) { diff --git a/src/Sdc/ComponentPluginManagerDecorator.php b/src/Sdc/ComponentPluginManagerDecorator.php index 821ca313..4ac91ff8 100644 --- a/src/Sdc/ComponentPluginManagerDecorator.php +++ b/src/Sdc/ComponentPluginManagerDecorator.php @@ -14,7 +14,7 @@ use Drupal\sdc\ComponentPluginManager; use Drupal\sdc\Plugin\Component; use Drupal\ui_patterns\PropTypePluginManager; -use Drupal\ui_patterns\SourceProviderPluginManager; +use Drupal\ui_patterns\SourcePluginManager; /** * Plugin Manager for *.ui_patterns.yml configuration files. @@ -35,7 +35,7 @@ abstract class ComponentPluginManagerDecorator extends ComponentPluginManager { public function __construct( protected ComponentPluginManager $parentSdcPluginManager, protected PropTypePluginManager $propTypePluginManager, - protected SourceProviderPluginManager $sourcePluginManager, + protected SourcePluginManager $sourcePluginManager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $themeHandler, CacheBackendInterface $cacheBackend, diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index aa1e8f51..90e80cfc 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -31,10 +31,10 @@ protected function alterDefinitions(&$definitions) { continue; } foreach ($definition['props']['properties'] as $prop_id => $prop) { - $prop_type = $this->propTypePluginManager->getPropType($prop); - $source_providers = $this->sourcePluginManager->getSourceProviders($prop_type); + $prop_type = $this->propTypePluginManager->getPropTypePlugin($prop); + $sources = $this->sourcePluginManager->getSources($prop_type); $prop['ui_patterns']['type_definition'] = $prop_type; - $prop['ui_patterns']['source_providers'] = $source_providers; + $prop['ui_patterns']['source'] = $sources; $definition['props']['properties'][$prop_id] = $prop; } $definitions[$component_id] = $definition; diff --git a/src/SourceInterface.php b/src/SourceInterface.php new file mode 100644 index 00000000..603fe628 --- /dev/null +++ b/src/SourceInterface.php @@ -0,0 +1,22 @@ +setConfiguration($configuration); + } + + protected array $propDefinition; + + protected $propId; + + /** + * {@inheritdoc} + */ + public function label(): string { + // Cast the label to a string since it is a TranslatableMarkup object. + return (string) $this->pluginDefinition['label']; + } + + public function buildConfigurationForm( + array $form, + FormStateInterface $form_state + ) { + return []; + } + + public function validateConfigurationForm( + array &$form, + FormStateInterface $form_state + ) { + + } + + public function submitConfigurationForm( + array &$form, + FormStateInterface $form_state + ) { + $values = $form_state->getValues(); + foreach ($values as $key => $value) { + $this->configuration[$key] = $value; + } + } + + public function getConfiguration() { + return $this->configuration; + } + + public function setConfiguration(array $configuration) { + if (isset($configuration['prop_definition'])) { + $this->propDefinition = $configuration['prop_definition']; + } + if (isset($configuration['prop_id'])) { + $this->propId = $configuration['prop_id']; + } + $this->configuration = $configuration; + } + + abstract public function defaultConfiguration(); + + public function getPropId(): string { + return $this->propId; + } + + public function getPropDefinition(): array { + return $this->propDefinition; + } + +} diff --git a/src/SourcePluginManager.php b/src/SourcePluginManager.php new file mode 100644 index 00000000..f8c1fa29 --- /dev/null +++ b/src/SourcePluginManager.php @@ -0,0 +1,66 @@ +alterInfo('ui_patterns_source_info'); + $this->setCacheBackend($cache_backend, 'ui_patterns_source_plugins'); + } + + public function getSourceDefinitions($prop_type_id) { + $definitions = $this->getDefinitions(); + $sources = []; + foreach ($definitions as $definition) { + if (isset($definition['prop_types']) && in_array( + $prop_type_id, + $definition['prop_types'] + )) { + $sources[] = $definition; + } + } + return $sources; + } + + public function getSourcePlugins($prop_type_id, $prop_id, $prop_definition):array { + $definitions = $this->getSourceDefinitions($prop_type_id); + $sources = []; + foreach ($definitions as $definition) { + $sources[] = $this->createInstance( + $definition['id'], + [ + 'prop_id' => $prop_id, + 'prop_definition' => $prop_definition, + ] + ); + } + return $sources; + } + +} diff --git a/src/SourceProviderInterface.php b/src/SourceProviderInterface.php deleted file mode 100644 index 1e1c56ac..00000000 --- a/src/SourceProviderInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -pluginDefinition['label']; - } - -} diff --git a/src/SourceProviderPluginManager.php b/src/SourceProviderPluginManager.php deleted file mode 100644 index b0403638..00000000 --- a/src/SourceProviderPluginManager.php +++ /dev/null @@ -1,29 +0,0 @@ -alterInfo('source_provider_info'); - $this->setCacheBackend($cache_backend, 'ui_patterns_source_provider_plugins'); - } - - public function getSourceProviders(PropTypeInterface $prop_type) { - $definitions = $this->getDefinitions(); - } -} diff --git a/tests/modules/ui_patterns_test/src/Plugin/UiPatterns/Source/Foo.php b/tests/modules/ui_patterns_test/src/Plugin/UiPatterns/Source/Foo.php new file mode 100644 index 00000000..61e42bf7 --- /dev/null +++ b/tests/modules/ui_patterns_test/src/Plugin/UiPatterns/Source/Foo.php @@ -0,0 +1,29 @@ +find('ui_patterns_test:alert'); - self::assertNotNull($component_metadata); - /** @var \Drupal\ui_patterns\SourceProviderPluginManager $source_provider_plugin_manager */ - $source_provider_plugin_manager = \Drupal::service('plugin.manager.ui_patterns_source_provider'); - $source_providers = $source_provider_plugin_manager->getSourceProviders(''); - self::assertCount(3, count($source_providers)); + public function testGetPropTypePlugin(): void { + /** @var \Drupal\ui_patterns\PropTypePluginManager $prop_type_plugin_manager */ + $prop_type_plugin_manager = \Drupal::service('plugin.manager.ui_patterns_prop_type'); + $plugin_type = $prop_type_plugin_manager->getPropTypePlugin(['type' => 'string']); + self::assertInstanceOf(StringPropType::class, $plugin_type); } } diff --git a/tests/src/Kernel/SourcePluginManagerTest.php b/tests/src/Kernel/SourcePluginManagerTest.php new file mode 100644 index 00000000..68f94c75 --- /dev/null +++ b/tests/src/Kernel/SourcePluginManagerTest.php @@ -0,0 +1,44 @@ +getSourcePlugins('string', 'test', ['title' => 'test title']); + /** @var SourcePluginBase $source */ + foreach ($sources as $source) { + self::assertNotNull($source); + self::assertInstanceOf(SourcePluginBase::class, $source); + self::assertNotNull($source->getPropId()); + self::assertNotNull($source->getPropDefinition()); + } + self::assertGreaterThan(1, $sources); + } + +} diff --git a/tests/src/Kernel/SourceProviderPluginManagerTest.php b/tests/src/Kernel/SourceProviderPluginManagerTest.php deleted file mode 100644 index dc53179f..00000000 --- a/tests/src/Kernel/SourceProviderPluginManagerTest.php +++ /dev/null @@ -1,41 +0,0 @@ -find('ui_patterns_test:alert'); - self::assertNotNull($component_metadata); - /** @var \Drupal\ui_patterns\SourceProviderPluginManager $source_provider_plugin_manager */ - $source_provider_plugin_manager = \Drupal::service('plugin.manager.ui_patterns_source_provider'); - $source_providers = $source_provider_plugin_manager->getSourceProviders(''); - self::assertCount(3, count($source_providers)); - } - -} diff --git a/tests/src/Unit/SchemaCompatibilityCheckerTest.php b/tests/src/Unit/SchemaCompatibilityCheckerTest.php index 71f5322e..abe05f79 100644 --- a/tests/src/Unit/SchemaCompatibilityCheckerTest.php +++ b/tests/src/Unit/SchemaCompatibilityCheckerTest.php @@ -31,7 +31,7 @@ public function testIsCompatible($checked_schema, $reference_schema, $expected): public function provideIsCompatibleData() { return [ - [['schema'], ['schema'], true] + [['type' => 'string'], ['type' => 'string'], true] ]; } diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index fbd2668e..c3b3d1fc 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -2,8 +2,8 @@ services: plugin.manager.ui_patterns_prop_type: class: Drupal\ui_patterns\PropTypePluginManager arguments: [ '@container.namespaces', '@cache.discovery', '@module_handler', '@Drupal\sdc\Component\SchemaCompatibilityChecker' ] - plugin.manager.ui_patterns_source_provider: - class: Drupal\ui_patterns\SourceProviderPluginManager + plugin.manager.ui_patterns_source: + class: Drupal\ui_patterns\SourcePluginManager parent: default_plugin_manager ui_patterns.twig.extension: class: Drupal\ui_patterns\Template\TwigExtension @@ -17,7 +17,7 @@ services: arguments: - '@ui_patterns.plugin.manager.sdc.inner' - '@plugin.manager.ui_patterns_prop_type' - - '@plugin.manager.ui_patterns_source_provider' + - '@plugin.manager.ui_patterns_source' - '@module_handler' - '@theme_handler' - '@cache.discovery' From 35855221cdb282254a7b1eb93c752fcd7d573f74 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sat, 7 Oct 2023 22:31:53 +0200 Subject: [PATCH 62/81] PropTypes & SchemaCompatibilityChecker: WIP --- .../UiPatterns/PropType/ColorPropType.php | 23 ++ .../UiPatterns/PropType/LinksPropType.php | 30 +++ .../PropType/MachineNamePropType.php | 23 ++ ...IntegerPropType.php => NumberPropType.php} | 10 +- .../UiPatterns/PropType/UrlPropType.php | 20 +- src/Utils/SchemaCompatibilityChecker.php | 219 +++++++++++++----- .../prop_types_tests.component.yml | 46 +++- 7 files changed, 286 insertions(+), 85 deletions(-) create mode 100644 src/Plugin/UiPatterns/PropType/ColorPropType.php create mode 100644 src/Plugin/UiPatterns/PropType/LinksPropType.php create mode 100644 src/Plugin/UiPatterns/PropType/MachineNamePropType.php rename src/Plugin/UiPatterns/PropType/{IntegerPropType.php => NumberPropType.php} (55%) diff --git a/src/Plugin/UiPatterns/PropType/ColorPropType.php b/src/Plugin/UiPatterns/PropType/ColorPropType.php new file mode 100644 index 00000000..33ab4ccd --- /dev/null +++ b/src/Plugin/UiPatterns/PropType/ColorPropType.php @@ -0,0 +1,23 @@ +canonicalize($checked_schema); $reference_schema = $this->canonicalize($reference_schema); + if ($this->isSame($checked_schema, $reference_schema)) { + return TRUE; + } + if (isset($checked_schema["type"]) && isset($reference_schema["type"])) { + return $this->isTypeCompatible($schema, $reference_schema); + } + if (isset($checked_schema["anyOf"]) || isset($reference_schema["anyOf"])) { + return $this->isAnyOfCompatible($schema, $reference_schema); + } + return FALSE; + } + + /** + * + */ + protected function isSame($checked_schema, $reference_schema): bool { + return (serialize($checked_schema) === serialize($reference_schema)); + } + + /** + * + */ + protected function isTypeCompatible($checked_schema, $reference_schema): bool { + if (is_array($checked_schema["type"]) || is_array($checked_schema["type"])) { + // Because of self::resolveMultipleTypes() we are not supposed to meet this + // situation. + return FALSE; + } + if ($checked_schema["type"] !== $reference_schema["type"]) { + return FALSE; + } + // Now we know $checked_schema and $reference_schema have the same type. + // So, testing $checked_schema type is enough. + return match ($checked_schema["type"]) { + 'null' => TRUE, + 'boolean' => TRUE, + 'object' => $this->isObjectCompatible($checked_schema, $reference_schema), + 'array' => $this->isArrayCompatible($checked_schema, $reference_schema), + 'number' => $this->isNumberCompatible($checked_schema, $reference_schema), + 'integer' => $this->isNumberCompatible($checked_schema, $reference_schema), + 'string' => $this->isStringCompatible($checked_schema, $reference_schema), + }; + } + + /** + * + */ + protected function isAnyOfCompatible($checked_schema, $reference_schema): bool { if (isset($reference_schema["anyOf"])) { foreach ($reference_schema["anyOf"] as $schema) { if ($this->isCompatible($checked_schema, $schema)) { @@ -39,52 +87,6 @@ public function isCompatible(array $checked_schema, array $reference_schema): bo return TRUE; } } - return FALSE; - } - if ($checked_schema["type"] !== $reference_schema["type"]) { - return FALSE; - } - // Now we know $checked_schema and $reference_schema have the same type. - // So, testing $checked_schema type is enough. - $type = $checked_schema["type"]; - if ($type === "boolean") { - return TRUE; - } - if ($type === "null") { - return TRUE; - } - // Complex checking. - if ($this->isSame($checked_schema, $reference_schema)) { - return TRUE; - } - if ($type === "object") { - return $this->isObjectCompatible($checked_schema, $reference_schema); - } - if ($type === "array") { - return $this->isArrayCompatible($checked_schema, $reference_schema); - } - if ($type === "number") { - return $this->isNumberCompatible($checked_schema, $reference_schema); - } - if ($type === "integer") { - return $this->isNumberCompatible($checked_schema, $reference_schema); - } - if ($type === "string") { - return $this->isStringCompatible($checked_schema, $reference_schema); - } - return FALSE; - } - - /** - * - */ - protected function isSame($checked_schema, $reference_schema): bool { - $comparaison = strcmp( - json_encode($checked_schema), - json_encode($reference_schema) - ); - if ($comparaison === 0) { - return TRUE; } return FALSE; } @@ -93,7 +95,12 @@ protected function isSame($checked_schema, $reference_schema): bool { * */ protected function isObjectCompatible(array $checked_schema, array $reference_schema): bool { - // @todo recursive + if (isset($checked_schema["properties"]) && isset($reference_schema["properties"])) { + // @todo + } + if (isset($checked_schema["patternProperties"]) && isset($reference_schema["patternProperties"])) { + ksm(array_diff_key($checked_schema["patternProperties"], $reference_schema["patternProperties"])); + } return FALSE; } @@ -101,7 +108,12 @@ protected function isObjectCompatible(array $checked_schema, array $reference_sc * Check if different arrays are compatible. */ protected function isArrayCompatible(array $checked_schema, array $reference_schema): bool { - // @todo https://json-schema.org/understanding-json-schema/reference/array#items + // https://json-schema.org/understanding-json-schema/reference/array#items + if (isset($checked_schema["items"]) && isset($reference_schema["items"])) { + if (!$this->isCompatible($checked_schema, $reference_schema)) { + return FALSE; + } + } // @todo https://json-schema.org/understanding-json-schema/reference/array#contains // @todo https://json-schema.org/understanding-json-schema/reference/array#mincontains-maxcontains // @tood: https://json-schema.org/understanding-json-schema/reference/array#length @@ -125,11 +137,11 @@ protected function isNumberCompatible(array $checked_schema, array $reference_sc */ protected function isStringCompatible(array $checked_schema, array $reference_schema): bool { // @todo https://json-schema.org/understanding-json-schema/reference/string#length - // @todo https://json-schema.org/understanding-json-schema/reference/string#regexp - // @todo https://json-schema.org/understanding-json-schema/reference/string#format + if (isset($checked_schema["pattern"]) && isset($reference_schema["pattern"])) { + return $this->isStringPatternCompatible($checked_schema, $reference_schema); + } if (isset($checked_schema["format"]) && isset($reference_schema["format"])) { - // @todo uri, uri-reference, iri, iri-reference - // @todo formats & sub-formats + return $this->isStringFormatCompatible($checked_schema, $reference_schema); } if (!isset($checked_schema["format"]) && isset($reference_schema["format"])) { return FALSE; @@ -142,12 +154,78 @@ protected function isStringCompatible(array $checked_schema, array $reference_sc } /** - * + * See: https://json-schema.org/understanding-json-schema/reference/string#regexp. + */ + protected function isStringPatternCompatible(array $checked_schema, array $reference_schema): bool { + $checked_pattern = ltrim($checked_schema["pattern"], "^"); + $checked_pattern = rtrim($checked_schema["pattern"], "$"); + // $reference_pattern = str_replace(["(", ")", ", + if (str_contains($reference_schema["pattern"], $checked_pattern)) { + return TRUE; + } + return FALSE; + } + + /** + * See: https://json-schema.org/understanding-json-schema/reference/string#format. + */ + protected function isStringFormatCompatible(array $checked_schema, array $reference_schema): bool { + $checked_format = $checked_schema["format"]; + $reference_format = $reference_schema["format"]; + if ($checked_format == $reference_format) { + return TRUE; + } + // Ex: an uri is also a valid uri-reference + // Ex: an uri-reference is also a valid iri-reference. + $compatibility_map = [ + "uri" => [ + "uri-reference", + "iri-reference", + "iri", + ], + "iri" => [ + "iri-reference", + ], + "uri-reference" => [ + "iri-reference", + ], + "email" => [ + "idn-email", + ], + // @todo add others formats. + ]; + foreach ($compatibility_map as $checked_key => $comaptible_value) { + if ($this->isStringFormatCompatible(["format" => $checked_format], ["format" => $reference_format])) { + return TRUE; + } + } + return FALSE; + } + + /** + * @todo Make it public and unit testable independently? */ protected function canonicalize(array $schema): array { - //$schema = $this->removeUselessProperties($schema); - if (!isset($schema["type"])) { - return $schema; + $schema = $this->removeUselessProperties($schema); + if (isset($schema["type"])) { + $schema = $this->canonicalizeType($schema); + } + if (isset($schema["anyOf"])) { + foreach ($schema["anyOf"] as $index => $sub_schema) { + $schema["anyOf"][$index] = $this->canonicalize($sub_schema); + } + } + $schema = array_filter($schema); + ksort($schema); + return $schema; + } + + /** + * + */ + protected function canonicalizeType(array $schema): array { + if (is_array($schema["type"])) { + $schema = $this->resolveMultipleTypes($schema); } if ($schema["type"] === "object" && isset($schema["properties"])) { foreach ($schema["properties"] as $property_id => $property) { @@ -157,11 +235,30 @@ protected function canonicalize(array $schema): array { if ($schema["type"] === "array" && isset($schema["items"])) { $schema["items"] = $this->canonicalize($schema["items"]); } - $schema = array_filter($schema); - ksort($schema); return $schema; } + /** + * + */ + protected function resolveMultipleTypes(array $schema): array { + if (!is_array($schema["type"])) { + return $schema; + } + $schemas = [ + "anyOf" => [], + ]; + foreach ($schema["type"] as $index => $type) { + $schemas["anyOf"][$index] = array_merge( + $schema, + [ + "type" => $type, + ] + ); + } + return $schemas; + } + /** * */ @@ -178,9 +275,11 @@ protected function removeUselessProperties(array $schema): array { "array" => ["minItems", "maxItems", "items", "additionalItems", "uniqueItems"], "object" => ["properties", "additionalProperties", "required", "minProperties", "maxProperties", "dependencies", "patternProperties"], ]; - if (isset($schema["type"])) { + if (isset($schema["type"]) && is_string($schema["type"])) { $type = $schema["type"]; - $keys = array_merge($keys, $keys_by_type[$type]); + if (array_key_exists($type, $keys_by_type)) { + $keys = array_merge($keys, $keys_by_type[$type]); + } } return array_intersect_key($schema, array_flip($keys)); } diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml index 067015a2..0c6e47f6 100644 --- a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml +++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml @@ -49,13 +49,55 @@ props: title: "Url (explicit typing)" $ref: "ui-patterns://url" url_2: - title: "Url (implicit typing)" + title: "Url (implicit typing, exact type)" type: "string" format: "iri-reference" url_3: title: "Url (implicit typing, other type)" + description: Test format comaptibility type: "string" format: "uri" + color: + title: "Color RGB (6 or 3 hex)" + type: "string" + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + color_6: + title: "Color RGB (6 hex)" + description: Test Regexp comaptibility API + type: "string" + pattern: "^#[A-Fa-f0-9]{6}$" + color_3: + title: "Color RGB (3 hex)" + description: Test Regexp comaptibility API + type: "string" + pattern: "^#[A-Fa-f0-9]{3}$" object: - title: "Plain object" + title: "Undefined (Empty object)" type: "object" + array: + title: "Empty array" + type: "array" + links: + title: "Links" + type: array + items: + type: object + properties: + title: {type: string} + attributes: { $ref: "ui-patterns://attributes" } + below: { $ref: "ui-patterns://links" } + links_2: + title: "Links with extra property" + type: array + items: + type: object + properties: + title: {type: string} + attributes: { $ref: "ui-patterns://attributes" } + below: { $ref: "ui-patterns://links" } + extra: { type: string } + machine_name: + title: "Machine name" + type: string + pattern: '^[A-Za-z]+[\w-]*$' +slots: {} From 9dcf7b80d3ebd7f697f03effdaa41f39ff400023 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sat, 7 Oct 2023 23:46:56 +0200 Subject: [PATCH 63/81] Add SchemaCompatibilityCheckerTest --- src/Utils/SchemaCompatibilityChecker.php | 107 +++++++--- .../Unit/SchemaCompatibilityCheckerTest.php | 24 ++- .../schema_compatibility_checker_data.yml | 195 ++++++++++++++++++ 3 files changed, 295 insertions(+), 31 deletions(-) create mode 100644 tests/src/Unit/schema_compatibility_checker_data.yml diff --git a/src/Utils/SchemaCompatibilityChecker.php b/src/Utils/SchemaCompatibilityChecker.php index 3df733f9..0e3631e3 100644 --- a/src/Utils/SchemaCompatibilityChecker.php +++ b/src/Utils/SchemaCompatibilityChecker.php @@ -29,10 +29,10 @@ public function isCompatible(array $checked_schema, array $reference_schema): bo return TRUE; } if (isset($checked_schema["type"]) && isset($reference_schema["type"])) { - return $this->isTypeCompatible($schema, $reference_schema); + return $this->isTypeCompatible($checked_schema, $reference_schema); } if (isset($checked_schema["anyOf"]) || isset($reference_schema["anyOf"])) { - return $this->isAnyOfCompatible($schema, $reference_schema); + return $this->isAnyOfCompatible($checked_schema, $reference_schema); } return FALSE; } @@ -54,7 +54,10 @@ protected function isTypeCompatible($checked_schema, $reference_schema): bool { return FALSE; } if ($checked_schema["type"] !== $reference_schema["type"]) { - return FALSE; + // Integers are numbers. + if (!($checked_schema["type"] === "integer" && $reference_schema["type"] === "number")) { + return FALSE; + } } // Now we know $checked_schema and $reference_schema have the same type. // So, testing $checked_schema type is enough. @@ -99,9 +102,9 @@ protected function isObjectCompatible(array $checked_schema, array $reference_sc // @todo } if (isset($checked_schema["patternProperties"]) && isset($reference_schema["patternProperties"])) { - ksm(array_diff_key($checked_schema["patternProperties"], $reference_schema["patternProperties"])); + // @todo } - return FALSE; + return TRUE; } /** @@ -114,52 +117,72 @@ protected function isArrayCompatible(array $checked_schema, array $reference_sch return FALSE; } } + // FALSE if at least one of those tests is FALSE. // @todo https://json-schema.org/understanding-json-schema/reference/array#contains // @todo https://json-schema.org/understanding-json-schema/reference/array#mincontains-maxcontains - // @tood: https://json-schema.org/understanding-json-schema/reference/array#length + // @todo https://json-schema.org/understanding-json-schema/reference/array#length // @todo https://json-schema.org/understanding-json-schema/reference/array#uniqueness - // @todo recursive - return FALSE; + return TRUE; } /** * Check if different numbers are compatible. */ protected function isNumberCompatible(array $checked_schema, array $reference_schema): bool { - // @todo integer are number + // FALSE if at least one of those tests is FALSE. + if (isset($reference_schema["enum"])) { + if (!$this->isEnumCompatible($checked_schema, $reference_schema)) { + return FALSE; + } + } // @todo https://json-schema.org/understanding-json-schema/reference/numeric#multiples // @todo https://json-schema.org/understanding-json-schema/reference/numeric#range - return FALSE; + return TRUE; } /** * Check if different strings are compatible. */ protected function isStringCompatible(array $checked_schema, array $reference_schema): bool { - // @todo https://json-schema.org/understanding-json-schema/reference/string#length - if (isset($checked_schema["pattern"]) && isset($reference_schema["pattern"])) { - return $this->isStringPatternCompatible($checked_schema, $reference_schema); + // FALSE if at least one of those tests is FALSE. + if (isset($reference_schema["pattern"])) { + if (!$this->isStringPatternCompatible($checked_schema, $reference_schema)) { + return FALSE; + } + } + if (isset($reference_schema["format"])) { + if (!$this->isStringFormatCompatible($checked_schema, $reference_schema)) { + return FALSE; + } } - if (isset($checked_schema["format"]) && isset($reference_schema["format"])) { - return $this->isStringFormatCompatible($checked_schema, $reference_schema); + if (isset($reference_schema["enum"])) { + if (!$this->isEnumCompatible($checked_schema, $reference_schema)) { + return FALSE; + } } - if (!isset($checked_schema["format"]) && isset($reference_schema["format"])) { - return FALSE; + if (isset($reference_schema["minLength"])) { + if (!$this->isMinLengthCompatible($checked_schema, $reference_schema)) { + return FALSE; + } } - if (isset($checked_schema["format"]) && !isset($reference_schema["format"])) { - // A string with format is still a string. - return TRUE; + if (isset($reference_schema["maxLength"])) { + if (!$this->isMaxLengthCompatible($checked_schema, $reference_schema)) { + return FALSE; + } } - return FALSE; + return TRUE; } /** * See: https://json-schema.org/understanding-json-schema/reference/string#regexp. */ protected function isStringPatternCompatible(array $checked_schema, array $reference_schema): bool { + if (!isset($checked_schema["pattern"])) { + return FALSE; + } $checked_pattern = ltrim($checked_schema["pattern"], "^"); $checked_pattern = rtrim($checked_schema["pattern"], "$"); - // $reference_pattern = str_replace(["(", ")", ", + // @todo $reference_pattern = str_replace(["(", ")", ", if (str_contains($reference_schema["pattern"], $checked_pattern)) { return TRUE; } @@ -170,6 +193,9 @@ protected function isStringPatternCompatible(array $checked_schema, array $refer * See: https://json-schema.org/understanding-json-schema/reference/string#format. */ protected function isStringFormatCompatible(array $checked_schema, array $reference_schema): bool { + if (!isset($checked_schema["format"])) { + return FALSE; + } $checked_format = $checked_schema["format"]; $reference_format = $reference_schema["format"]; if ($checked_format == $reference_format) { @@ -194,11 +220,40 @@ protected function isStringFormatCompatible(array $checked_schema, array $refere ], // @todo add others formats. ]; - foreach ($compatibility_map as $checked_key => $comaptible_value) { - if ($this->isStringFormatCompatible(["format" => $checked_format], ["format" => $reference_format])) { - return TRUE; - } + if (array_key_exists($checked_format, $compatibility_map)) { + return in_array($reference_format, $compatibility_map[$checked_format]); + } + return FALSE; + } + + /** + * + */ + protected function isMinLengthCompatible(array $checked_schema, array $reference_schema): bool { + if (!isset($checked_schema["minLength"])) { + return FALSE; + } + return ($checked_schema["minLength"] >= $reference_schema["minLength"]); + } + + /** + * + */ + protected function isMaxLengthCompatible(array $checked_schema, array $reference_schema): bool { + if (!isset($checked_schema["maxLength"])) { + return FALSE; + } + return ($checked_schema["maxLength"] <= $reference_schema["maxLength"]); + } + + /** + * + */ + protected function isEnumCompatible(array $checked_schema, array $reference_schema): bool { + if (!isset($checked_schema["enum"])) { + return FALSE; } + // @todo return FALSE; } diff --git a/tests/src/Unit/SchemaCompatibilityCheckerTest.php b/tests/src/Unit/SchemaCompatibilityCheckerTest.php index abe05f79..07f89e90 100644 --- a/tests/src/Unit/SchemaCompatibilityCheckerTest.php +++ b/tests/src/Unit/SchemaCompatibilityCheckerTest.php @@ -4,6 +4,7 @@ use Drupal\Tests\UnitTestCase; use Drupal\ui_patterns\Utils\SchemaCompatibilityChecker; +use Drupal\Component\Serialization\Yaml; /** * Test description. @@ -24,15 +25,28 @@ protected function setUp(): void { * * @dataProvider provideIsCompatibleData */ - public function testIsCompatible($checked_schema, $reference_schema, $expected): void { + public function testIsCompatible($test_name, $checked_schema, $reference_schema, $expected_result): void { + ob_end_clean(); + print("\n" . $test_name . ": "); + ob_start(); $validator = new SchemaCompatibilityChecker(); - self::assertEquals($expected, $validator->isCompatible($checked_schema, $reference_schema)); + self::assertEquals($expected_result, $validator->isCompatible($checked_schema, $reference_schema)); } public function provideIsCompatibleData() { - return [ - [['type' => 'string'], ['type' => 'string'], true] - ]; + $data = []; + $sources = Yaml::decode(file_get_contents(__DIR__ . "/schema_compatibility_checker_data.yml")); + foreach ($sources as $source) { + foreach ($source["tests"] as $test) { + $data[] = [ + $source["label"] . ": " . $test["label"] . " is " . ($test["result"] ? "OK" : "KO"), + $test["schema"], + $source["schema"], + (bool) $test["result"], + ]; + } + }; + return $data; } } diff --git a/tests/src/Unit/schema_compatibility_checker_data.yml b/tests/src/Unit/schema_compatibility_checker_data.yml new file mode 100644 index 00000000..21ba9d15 --- /dev/null +++ b/tests/src/Unit/schema_compatibility_checker_data.yml @@ -0,0 +1,195 @@ +# String prop type +- label: "String prop type" + schema: + type: string + tests: + - label: "String" + schema: + type: string + result: true + - label: "String with a format" + schema: + type: string + format: whatever + result: true + - label: "String with max length" + schema: + type: string + maxLength: 10 + result: true + +# Other string tests +- label: "String with max length" + schema: + type: string + maxLength: 10 + tests: + - label: "No length" + schema: + type: string + result: false + - label: "Same max length" + schema: + type: string + maxLength: 10 + result: true + - label: "Smaller max length" + schema: + type: string + maxLength: 5 + result: true + - label: "Larger max length" + schema: + type: string + maxLength: 20 + result: false +- label: "String with min length" + schema: + type: string + minLength: 10 + tests: + - label: "No length" + schema: + type: string + result: false + - label: "Same min length" + schema: + type: string + minLength: 10 + result: true + - label: "Smaller min length" + schema: + type: string + minLength: 5 + result: false + - label: "Larger min length" + schema: + type: string + minLength: 20 + result: true + +# Enum +- label: "Enum" + schema: + type: string + enum: + - foo + - bar + tests: + - label: "Same enum" + schema: + type: string + enum: + - foo + - bar + result: true + - label: "Enum with addition" + schema: + type: string + enum: + - foo + - bar + - baz + result: false + - label: "Enum with removal" + schema: + type: string + enum: + - foo + result: true + +# Number prop type +- label: "Number prop type" + schema: + type: number + tests: + - label: "Number" + schema: + type: number + result: true + - label: "Integer" + schema: + type: integer + result: true + +# Url prop type +- label: "URL prop type" + schema: + type: string + format: iri-reference + tests: + - label: "IRI Reference" + schema: + type: string + format: iri-reference + result: true + - label: "URI reference" + schema: + type: string + format: uri-reference + result: true + - label: "IRI" + schema: + type: string + format: iri + result: true + - label: "URI" + schema: + type: string + format: uri + result: true + +# Color prop type +- label: "Color prop type" + schema: + type: string + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + tests: + - label: "Color (6 or 3 hex)" + schema: + type: string + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + result: true + - label: "Color (6)" + schema: + type: string + pattern: "^#([A-Fa-f0-9]{6})$" + result: true + - label: "Color (3)" + schema: + type: string + pattern: "^#([A-Fa-f0-9]{3})$" + result: true + +# Links prop type +- label: "Links prop type" + schema: + type: array + items: + type: object + properties: + title: { type: string } + attributes: { $ref: "ui-patterns://attributes" } + below: { $ref: "ui-patterns://links" } + tests: + - label: "Links" + schema: + type: array + items: + type: object + properties: + title: { type: string } + attributes: { $ref: "ui-patterns://attributes" } + below: { $ref: "ui-patterns://links" } + result: true + - label: "Links with extra property on" + schema: + type: array + items: + type: object + properties: + title: { type: string } + attributes: { $ref: "ui-patterns://attributes" } + below: { $ref: "ui-patterns://links" } + extra: { type: string } + result: true From 2a4f75283b8937f62aec96214821759329e83e59 Mon Sep 17 00:00:00 2001 From: Christian Wiedemann Date: Sun, 8 Oct 2023 01:25:50 +0200 Subject: [PATCH 64/81] =?UTF-8?q?Add=20initial=20component=20form=C2=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Plugin/Layout/PatternLayout.php | 14 ++++--- src/Form/UiPatternsFormBuilderTrait.php | 39 ++++++++++++++++--- .../UiPatterns/PropType/UnKnowPropType.php | 21 ++++++++++ .../UiPatterns/Source/TextfieldWidget.php | 6 +-- src/PropTypePluginManager.php | 8 +++- src/Sdc/UiPatternsSdcPluginManager.php | 2 +- src/SourcePluginBase.php | 8 ++-- 7 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 src/Plugin/UiPatterns/PropType/UnKnowPropType.php diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index e8bc9669..93a758d7 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -4,6 +4,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\SubformState; use Drupal\Core\Layout\LayoutDefault; use Drupal\Core\Layout\LayoutDefinition; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -107,7 +108,7 @@ public function defaultConfiguration() { public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = []; $configuration = $this->getConfiguration(); - $form['component_metadata'] = [ + $form['ui_patterns'] = [ '#group' => 'additional_settings', '#type' => 'container', '#title' => $this->t('Configuration'), @@ -118,9 +119,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $plugin_manager = \Drupal::service('plugin.manager.sdc'); /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ $component_metadata = $plugin_manager->find($component_id)?->metadata; - $context = ['form_type' => 'layout', 'form' => $form, 'layout' => $this]; - $form['component_metadata']['form'] = $this->buildComponentForm($form_state, $component_metadata, $context); - $this->moduleHandler->alter('ui_patterns_layouts_display_configuration_form', $form['pattern'], $component_metadata, $configuration); + $context = ['form_type' => 'layout', 'form' => $form, 'layout' => $this, 'form_values' => $this->configuration['ui_patterns'] ?? []]; + $form['ui_patterns'] = $this->buildComponentForm($form_state, $component_metadata, $context); + $this->moduleHandler->alter('ui_patterns_layouts_display_configuration_form', $form['ui_patterns'], $component_metadata, $configuration); return $form; } @@ -134,7 +135,8 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { - $this->configuration = $form_state->getValues(); + $sub_form_values = $this->submitComponentForm($form, $form_state, []); + $this->configuration['ui_patterns'] = $sub_form_values; + parent::submitConfigurationForm($form, $form_state); } - } diff --git a/src/Form/UiPatternsFormBuilderTrait.php b/src/Form/UiPatternsFormBuilderTrait.php index 86606c88..51711711 100644 --- a/src/Form/UiPatternsFormBuilderTrait.php +++ b/src/Form/UiPatternsFormBuilderTrait.php @@ -2,6 +2,7 @@ namespace Drupal\ui_patterns\Form; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\SubformState; use Drupal\sdc\Component\ComponentMetadata; trait UiPatternsFormBuilderTrait { @@ -12,11 +13,39 @@ trait UiPatternsFormBuilderTrait { * @param Drupal\sdc\Component\ComponentMetadata $component * The pattern definition. */ - protected function buildComponentForm(FormStateInterface $form_state, ComponentMetadata $component, array $context):array { - /** @var \Drupal\ui_patterns\SourcePluginManager $source_provider_manager */ - $source_manager = \Drupal::service('plugin.manager.ui_patterns_source'); - $sources = $source_manager->getSources($component); - return ['#type' => 'textfield', '#title' => 'Dummy']; + protected function buildComponentForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context):array { + $sub_sources_form_value = $context['form_values']; + $form = []; + $sub_sources = []; + foreach ($component_metadata->schema['properties'] as $prop_id => $prop) { + $sources = $prop['ui_patterns']['source']; + if (count($sources) > 0 ) { + /** @var \Drupal\ui_patterns\SourcePluginBase $default_source */ + $default_source = current($sources); + $configuration = $default_source->getConfiguration(); + if (isset($sub_sources_form_value[$prop_id])) { + $configuration['form_value'] = $sub_sources_form_value[$prop_id]; + $default_source->setConfiguration($configuration); + } + $form[$prop_id] = $default_source->buildConfigurationForm($form, $form_state); + $sub_sources[$prop_id] = $default_source; + } + } + if (!$form_state->has('sub_sources')) { + $form_state->set('sub_sources', $sub_sources); + } + return $form; + } + + protected function submitComponentForm($form, FormStateInterface $form_state, array $context):array { + + $sub_sources = $form_state->get('sub_sources'); + $sub_values = []; + foreach ($sub_sources as $prop_id => $sub_source) { + $sub_source->submitConfigurationForm($form['ui_patterns'][$prop_id], $form_state); + $sub_values[$prop_id] = $sub_source->getConfiguration()['form_value']; + } + return $sub_values; } /** diff --git a/src/Plugin/UiPatterns/PropType/UnKnowPropType.php b/src/Plugin/UiPatterns/PropType/UnKnowPropType.php new file mode 100644 index 00000000..149c00d6 --- /dev/null +++ b/src/Plugin/UiPatterns/PropType/UnKnowPropType.php @@ -0,0 +1,21 @@ +propId] = ['#type' => 'textfield', '#title' => $this->propDefinition['title']]; + return ['#type' => 'textfield', '#title' => $this->propDefinition['title'], '#default_value' => $this->configuration['form_value']]; } public function defaultConfiguration() { diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index 4833dcda..be1d679b 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -2,6 +2,7 @@ namespace Drupal\ui_patterns; +use Drupal\Component\Plugin\FallbackPluginManagerInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; @@ -10,7 +11,7 @@ /** * PropType plugin manager. */ -class PropTypePluginManager extends DefaultPluginManager { +class PropTypePluginManager extends DefaultPluginManager implements FallbackPluginManagerInterface { /** * Constructs PropTypePluginManager object. @@ -43,7 +44,7 @@ public function getPropTypePlugin(array $prop_schema): ?PropTypeInterface { if ($definition !== NULL) { return $this->createInstance($definition['id'], []); } - return NULL; + return $this->createInstance('unknown_prop_type', []); } /** @@ -75,4 +76,7 @@ public function getPropTypeDefinition(array $prop_schema): ?array { return NULL; } + public function getFallbackPluginId($plugin_id, array $configuration = []) { + return 'unknown_prop_type'; + } } diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index 90e80cfc..4ed1f5bd 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -32,7 +32,7 @@ protected function alterDefinitions(&$definitions) { } foreach ($definition['props']['properties'] as $prop_id => $prop) { $prop_type = $this->propTypePluginManager->getPropTypePlugin($prop); - $sources = $this->sourcePluginManager->getSources($prop_type); + $sources = $this->sourcePluginManager->getSourcePlugins($prop_type->getPluginId(), $prop_id, $prop); $prop['ui_patterns']['type_definition'] = $prop_type; $prop['ui_patterns']['source'] = $sources; $definition['props']['properties'][$prop_id] = $prop; diff --git a/src/SourcePluginBase.php b/src/SourcePluginBase.php index 9c7d91ed..6848f2d9 100644 --- a/src/SourcePluginBase.php +++ b/src/SourcePluginBase.php @@ -49,10 +49,10 @@ public function submitConfigurationForm( array &$form, FormStateInterface $form_state ) { - $values = $form_state->getValues(); - foreach ($values as $key => $value) { - $this->configuration[$key] = $value; - } + $parents = $form['#parents']; + unset($parents[0]); + $value = $form_state->getValue($parents); + $this->configuration['form_value'] = $value; } public function getConfiguration() { From 723740bbbc2ffdd98df2b42d3c4aaf75258d5bc5 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 12:34:57 +0200 Subject: [PATCH 65/81] Add EnumPropType --- .../ui-patterns-component-metadata.html.twig | 4 +- .../UiPatterns/PropType/EnumPropType.php | 23 ++++ src/PropTypePluginManager.php | 4 +- src/Utils/SchemaCompatibilityChecker.php | 107 ++++++++++++------ .../prop_types_tests.component.yml | 7 +- .../Unit/SchemaCompatibilityCheckerTest.php | 3 - .../schema_compatibility_checker_data.yml | 60 +++++++--- 7 files changed, 147 insertions(+), 61 deletions(-) create mode 100644 src/Plugin/UiPatterns/PropType/EnumPropType.php diff --git a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig index bad8f330..96777e15 100644 --- a/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig +++ b/modules/ui_patterns_library/templates/ui-patterns-component-metadata.html.twig @@ -44,10 +44,10 @@
    - + diff --git a/src/Plugin/UiPatterns/PropType/EnumPropType.php b/src/Plugin/UiPatterns/PropType/EnumPropType.php new file mode 100644 index 00000000..348a8c03 --- /dev/null +++ b/src/Plugin/UiPatterns/PropType/EnumPropType.php @@ -0,0 +1,23 @@ +getDefinition($prop_type_id); } - $definitions = $this->getDefinitions(); + $definitions = $this->getSortedDefinitions(); foreach ($definitions as $definition) { $compatibilityChecker = new SchemaCompatibilityChecker(); - if ($compatibilityChecker->isCompatible($definition['schema'], $prop_schema)) { + if ($compatibilityChecker->isCompatible($prop_schema, $definition['schema'])) { return $definition; } } diff --git a/src/Utils/SchemaCompatibilityChecker.php b/src/Utils/SchemaCompatibilityChecker.php index 0e3631e3..89304e37 100644 --- a/src/Utils/SchemaCompatibilityChecker.php +++ b/src/Utils/SchemaCompatibilityChecker.php @@ -16,9 +16,9 @@ class SchemaCompatibilityChecker { * Checks if the second schema is compatible with the first one. * * @param array $checked_schema - * The schema to check compatibility against. + * The schema that should be compatible with the other one. * @param array $reference_schema - * The schema that should be compatible with the first one. + * The schema to check compatibility against. * * @return bool */ @@ -54,7 +54,7 @@ protected function isTypeCompatible($checked_schema, $reference_schema): bool { return FALSE; } if ($checked_schema["type"] !== $reference_schema["type"]) { - // Integers are numbers. + // Integers are numbers, but numbers are not always integer. if (!($checked_schema["type"] === "integer" && $reference_schema["type"] === "number")) { return FALSE; } @@ -67,7 +67,7 @@ protected function isTypeCompatible($checked_schema, $reference_schema): bool { 'object' => $this->isObjectCompatible($checked_schema, $reference_schema), 'array' => $this->isArrayCompatible($checked_schema, $reference_schema), 'number' => $this->isNumberCompatible($checked_schema, $reference_schema), - 'integer' => $this->isNumberCompatible($checked_schema, $reference_schema), + 'integer' => $this->isIntegerCompatible($checked_schema, $reference_schema), 'string' => $this->isStringCompatible($checked_schema, $reference_schema), }; } @@ -82,7 +82,6 @@ protected function isAnyOfCompatible($checked_schema, $reference_schema): bool { return TRUE; } } - return FALSE; } if (isset($checked_schema["anyOf"])) { foreach ($checked_schema["anyOf"] as $schema) { @@ -98,6 +97,13 @@ protected function isAnyOfCompatible($checked_schema, $reference_schema): bool { * */ protected function isObjectCompatible(array $checked_schema, array $reference_schema): bool { + // FALSE if at least one of those tests is FALSE. + if (!isset($checked_schema["properties"]) && isset($reference_schema["properties"])) { + return FALSE; + } + if (!isset($checked_schema["patternProperties"]) && isset($reference_schema["patternProperties"])) { + return FALSE; + } if (isset($checked_schema["properties"]) && isset($reference_schema["properties"])) { // @todo } @@ -111,13 +117,16 @@ protected function isObjectCompatible(array $checked_schema, array $reference_sc * Check if different arrays are compatible. */ protected function isArrayCompatible(array $checked_schema, array $reference_schema): bool { + // FALSE if at least one of those tests is FALSE. + if (!isset($checked_schema["items"]) && isset($reference_schema["items"])) { + return FALSE; + } // https://json-schema.org/understanding-json-schema/reference/array#items if (isset($checked_schema["items"]) && isset($reference_schema["items"])) { - if (!$this->isCompatible($checked_schema, $reference_schema)) { + if (!$this->isCompatible($checked_schema["items"], $reference_schema["items"])) { return FALSE; } } - // FALSE if at least one of those tests is FALSE. // @todo https://json-schema.org/understanding-json-schema/reference/array#contains // @todo https://json-schema.org/understanding-json-schema/reference/array#mincontains-maxcontains // @todo https://json-schema.org/understanding-json-schema/reference/array#length @@ -129,8 +138,26 @@ protected function isArrayCompatible(array $checked_schema, array $reference_sch * Check if different numbers are compatible. */ protected function isNumberCompatible(array $checked_schema, array $reference_schema): bool { + if ($reference_schema["type"] === "integer") { + // Integers are always numbers, but numbers are not always integer. + return FALSE; + } + return $this->isNumericCompatible($checked_schema, $reference_schema); + } + + /** + * Check if different integers are compatible. + */ + protected function isIntegerCompatible(array $checked_schema, array $reference_schema): bool { + return $this->isNumericCompatible($checked_schema, $reference_schema); + } + + /** + * Rules shared by numbers and integers. + */ + protected function isNumericCompatible(array $checked_schema, array $reference_schema): bool { // FALSE if at least one of those tests is FALSE. - if (isset($reference_schema["enum"])) { + if (array_key_exists("enum", $reference_schema)) { if (!$this->isEnumCompatible($checked_schema, $reference_schema)) { return FALSE; } @@ -145,27 +172,27 @@ protected function isNumberCompatible(array $checked_schema, array $reference_sc */ protected function isStringCompatible(array $checked_schema, array $reference_schema): bool { // FALSE if at least one of those tests is FALSE. - if (isset($reference_schema["pattern"])) { + if (array_key_exists("pattern", $reference_schema)) { if (!$this->isStringPatternCompatible($checked_schema, $reference_schema)) { return FALSE; } } - if (isset($reference_schema["format"])) { + if (array_key_exists("format", $reference_schema)) { if (!$this->isStringFormatCompatible($checked_schema, $reference_schema)) { return FALSE; } } - if (isset($reference_schema["enum"])) { + if (array_key_exists("enum", $reference_schema)) { if (!$this->isEnumCompatible($checked_schema, $reference_schema)) { return FALSE; } } - if (isset($reference_schema["minLength"])) { + if (array_key_exists("minLength", $reference_schema)) { if (!$this->isMinLengthCompatible($checked_schema, $reference_schema)) { return FALSE; } } - if (isset($reference_schema["maxLength"])) { + if (array_key_exists("maxLength", $reference_schema)) { if (!$this->isMaxLengthCompatible($checked_schema, $reference_schema)) { return FALSE; } @@ -177,15 +204,10 @@ protected function isStringCompatible(array $checked_schema, array $reference_sc * See: https://json-schema.org/understanding-json-schema/reference/string#regexp. */ protected function isStringPatternCompatible(array $checked_schema, array $reference_schema): bool { - if (!isset($checked_schema["pattern"])) { + if (!array_key_exists("pattern", $checked_schema)) { return FALSE; } - $checked_pattern = ltrim($checked_schema["pattern"], "^"); - $checked_pattern = rtrim($checked_schema["pattern"], "$"); - // @todo $reference_pattern = str_replace(["(", ")", ", - if (str_contains($reference_schema["pattern"], $checked_pattern)) { - return TRUE; - } + // @todo Is checked schema pattern and sub pattern of reference schema? return FALSE; } @@ -193,7 +215,7 @@ protected function isStringPatternCompatible(array $checked_schema, array $refer * See: https://json-schema.org/understanding-json-schema/reference/string#format. */ protected function isStringFormatCompatible(array $checked_schema, array $reference_schema): bool { - if (!isset($checked_schema["format"])) { + if (!array_key_exists("format", $checked_schema)) { return FALSE; } $checked_format = $checked_schema["format"]; @@ -230,7 +252,7 @@ protected function isStringFormatCompatible(array $checked_schema, array $refere * */ protected function isMinLengthCompatible(array $checked_schema, array $reference_schema): bool { - if (!isset($checked_schema["minLength"])) { + if (!array_key_exists("minLength", $checked_schema)) { return FALSE; } return ($checked_schema["minLength"] >= $reference_schema["minLength"]); @@ -240,7 +262,7 @@ protected function isMinLengthCompatible(array $checked_schema, array $reference * */ protected function isMaxLengthCompatible(array $checked_schema, array $reference_schema): bool { - if (!isset($checked_schema["maxLength"])) { + if (!array_key_exists("maxLength", $checked_schema)) { return FALSE; } return ($checked_schema["maxLength"] <= $reference_schema["maxLength"]); @@ -250,10 +272,23 @@ protected function isMaxLengthCompatible(array $checked_schema, array $reference * */ protected function isEnumCompatible(array $checked_schema, array $reference_schema): bool { - if (!isset($checked_schema["enum"])) { + if (!array_key_exists("enum", $checked_schema)) { return FALSE; } - // @todo + if (empty($reference_schema["enum"])) { + return TRUE; + } + if (count($checked_schema["enum"]) === count($reference_schema["enum"])) { + $diff = array_diff($checked_schema["enum"], $reference_schema["enum"]); + return ($diff === []); + } + if (count($checked_schema["enum"]) > count($reference_schema["enum"])) { + return FALSE; + } + if (count($checked_schema["enum"]) < count($reference_schema["enum"])) { + $diff = array_diff($reference_schema["enum"], $checked_schema["enum"]); + return (count($diff) >= 0); + } return FALSE; } @@ -261,16 +296,15 @@ protected function isEnumCompatible(array $checked_schema, array $reference_sche * @todo Make it public and unit testable independently? */ protected function canonicalize(array $schema): array { - $schema = $this->removeUselessProperties($schema); - if (isset($schema["type"])) { + $schema = $this->keepOnlyUsefulProperties($schema); + if (array_key_exists("type", $schema)) { $schema = $this->canonicalizeType($schema); } - if (isset($schema["anyOf"])) { + if (array_key_exists("anyOf", $schema)) { foreach ($schema["anyOf"] as $index => $sub_schema) { $schema["anyOf"][$index] = $this->canonicalize($sub_schema); } } - $schema = array_filter($schema); ksort($schema); return $schema; } @@ -279,8 +313,12 @@ protected function canonicalize(array $schema): array { * */ protected function canonicalizeType(array $schema): array { + if (!isset($schema["type"])) { + return $schema; + } if (is_array($schema["type"])) { $schema = $this->resolveMultipleTypes($schema); + return $this->canonicalize($schema); } if ($schema["type"] === "object" && isset($schema["properties"])) { foreach ($schema["properties"] as $property_id => $property) { @@ -304,12 +342,9 @@ protected function resolveMultipleTypes(array $schema): array { "anyOf" => [], ]; foreach ($schema["type"] as $index => $type) { - $schemas["anyOf"][$index] = array_merge( - $schema, - [ - "type" => $type, - ] - ); + $sub_schema = $schema; + $sub_schema["type"] = $type; + $schemas["anyOf"][$index] = $sub_schema; } return $schemas; } @@ -317,7 +352,7 @@ protected function resolveMultipleTypes(array $schema): array { /** * */ - protected function removeUselessProperties(array $schema): array { + protected function keepOnlyUsefulProperties(array $schema): array { $keys = [ "anyOf", "allOf", "oneOf", "not", "enum", "type", '$ref', "constant", ]; diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml index 0c6e47f6..df979b30 100644 --- a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml +++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml @@ -83,7 +83,7 @@ props: items: type: object properties: - title: {type: string} + title: { type: string } attributes: { $ref: "ui-patterns://attributes" } below: { $ref: "ui-patterns://links" } links_2: @@ -92,12 +92,11 @@ props: items: type: object properties: - title: {type: string} + title: { type: string } attributes: { $ref: "ui-patterns://attributes" } below: { $ref: "ui-patterns://links" } - extra: { type: string } + extra: { type: string } machine_name: title: "Machine name" type: string pattern: '^[A-Za-z]+[\w-]*$' -slots: {} diff --git a/tests/src/Unit/SchemaCompatibilityCheckerTest.php b/tests/src/Unit/SchemaCompatibilityCheckerTest.php index 07f89e90..19e07c2f 100644 --- a/tests/src/Unit/SchemaCompatibilityCheckerTest.php +++ b/tests/src/Unit/SchemaCompatibilityCheckerTest.php @@ -26,9 +26,6 @@ protected function setUp(): void { * @dataProvider provideIsCompatibleData */ public function testIsCompatible($test_name, $checked_schema, $reference_schema, $expected_result): void { - ob_end_clean(); - print("\n" . $test_name . ": "); - ob_start(); $validator = new SchemaCompatibilityChecker(); self::assertEquals($expected_result, $validator->isCompatible($checked_schema, $reference_schema)); } diff --git a/tests/src/Unit/schema_compatibility_checker_data.yml b/tests/src/Unit/schema_compatibility_checker_data.yml index 21ba9d15..837ee0f1 100644 --- a/tests/src/Unit/schema_compatibility_checker_data.yml +++ b/tests/src/Unit/schema_compatibility_checker_data.yml @@ -68,8 +68,36 @@ minLength: 20 result: true -# Enum -- label: "Enum" +# Enum prop type +- label: "Enum prop type" + schema: + type: [string, number, integer] + enum: [] + tests: + - label: "Empty enum" + schema: + type: string + enum: [] + result: true + - label: "With strings" + schema: + type: string + enum: + - foo + - bar + - baz + result: true + - label: "With numbers" + schema: + type: number + enum: + - 4 + - 5 + - 8 + result: true + +# Other enum tests +- label: "Enum with 2 strings" schema: type: string enum: @@ -83,7 +111,7 @@ - foo - bar result: true - - label: "Enum with addition" + - label: "Enum with an addition" schema: type: string enum: @@ -91,7 +119,7 @@ - bar - baz result: false - - label: "Enum with removal" + - label: "Enum with a removal" schema: type: string enum: @@ -150,16 +178,16 @@ type: string pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" result: true - - label: "Color (6)" - schema: - type: string - pattern: "^#([A-Fa-f0-9]{6})$" - result: true - - label: "Color (3)" - schema: - type: string - pattern: "^#([A-Fa-f0-9]{3})$" - result: true +# - label: "Color (6)" +# schema: +# type: string +# pattern: "^#([A-Fa-f0-9]{6})$" +# result: true +# - label: "Color (3)" +# schema: +# type: string +# pattern: "^#([A-Fa-f0-9]{3})$" +# result: true # Links prop type - label: "Links prop type" @@ -193,3 +221,7 @@ below: { $ref: "ui-patterns://links" } extra: { type: string } result: true + - label: "Simple array" + schema: + type: array + result: false From 4e297ba1106add32f63092a38a4a3b5e367f1914 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 13:30:35 +0200 Subject: [PATCH 66/81] Init ui_patterns_views --- .../UiPatterns/Source/ViewRowSource.php | 41 +++++++++++++++++++ .../UiPatterns/Source/ViewRowsSource.php | 37 +++++++++++++++++ .../UiPatterns/Source/ViewTitleSource.php | 37 +++++++++++++++++ .../src/Plugin/views/row/Component.php | 25 +++++++++++ .../src/Plugin/views/style/Component.php | 26 ++++++++++++ .../ui_patterns_views.info.yml | 13 ++++++ 6 files changed, 179 insertions(+) create mode 100644 modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowSource.php create mode 100644 modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php create mode 100644 modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewTitleSource.php create mode 100644 modules/ui_patterns_views/src/Plugin/views/row/Component.php create mode 100755 modules/ui_patterns_views/src/Plugin/views/style/Component.php create mode 100644 modules/ui_patterns_views/ui_patterns_views.info.yml diff --git a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowSource.php b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowSource.php new file mode 100644 index 00000000..4892d5bb --- /dev/null +++ b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowSource.php @@ -0,0 +1,41 @@ +getContextProperty('view'); + // foreach ($view->display_handler->getFieldLabels() as $name => $label) { + // $sources[] = $this->getSourceField($name, $label); + // } + return []; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php new file mode 100644 index 00000000..aa535466 --- /dev/null +++ b/modules/ui_patterns_views/src/Plugin/UiPatterns/Source/ViewRowsSource.php @@ -0,0 +1,37 @@ + Date: Sun, 8 Oct 2023 14:17:42 +0200 Subject: [PATCH 67/81] Run PHPCS --- src/Annotation/Source.php | 5 ++-- src/Form/UiPatternsFormBuilderTrait.php | 11 ++++++-- .../UiPatterns/Source/SiteNameSource.php | 10 ++++++- .../UiPatterns/Source/TextfieldWidget.php | 25 +++++++++++++---- src/PropTypePluginManager.php | 4 +++ src/SourceInterface.php | 15 +++++++++- src/SourcePluginBase.php | 28 ++++++++++++++++++- src/SourcePluginManager.php | 9 ++++-- 8 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/Annotation/Source.php b/src/Annotation/Source.php index 4c871277..e8a319f2 100644 --- a/src/Annotation/Source.php +++ b/src/Annotation/Source.php @@ -1,4 +1,6 @@ -schema['properties'] as $prop_id => $prop) { $sources = $prop['ui_patterns']['source']; - if (count($sources) > 0 ) { + if (count($sources) > 0) { /** @var \Drupal\ui_patterns\SourcePluginBase $default_source */ $default_source = current($sources); $configuration = $default_source->getConfiguration(); @@ -37,6 +40,9 @@ protected function buildComponentForm(FormStateInterface $form_state, ComponentM return $form; } + /** + * + */ protected function submitComponentForm($form, FormStateInterface $form_state, array $context):array { $sub_sources = $form_state->get('sub_sources'); @@ -50,7 +56,6 @@ protected function submitComponentForm($form, FormStateInterface $form_state, ar /** * Build components selector widget. - * */ protected function buildComponentsForm():array { return []; diff --git a/src/Plugin/UiPatterns/Source/SiteNameSource.php b/src/Plugin/UiPatterns/Source/SiteNameSource.php index ffe9e39e..34e05a77 100644 --- a/src/Plugin/UiPatterns/Source/SiteNameSource.php +++ b/src/Plugin/UiPatterns/Source/SiteNameSource.php @@ -1,4 +1,6 @@ - 'textfield', '#title' => $this->propDefinition['title'], '#default_value' => $this->configuration['form_value']]; + /** + * + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + return [ + '#type' => 'textfield', + '#title' => $this->propDefinition['title'], + '#default_value' => $this->configuration['form_value'] + ]; } + /** + * + */ public function defaultConfiguration() { return []; } + } diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index d1abe9dd..221c2880 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -76,7 +76,11 @@ public function getPropTypeDefinition(array $prop_schema): ?array { return NULL; } + /** + * + */ public function getFallbackPluginId($plugin_id, array $configuration = []) { return 'unknown_prop_type'; } + } diff --git a/src/SourceInterface.php b/src/SourceInterface.php index 603fe628..5a7113b0 100644 --- a/src/SourceInterface.php +++ b/src/SourceInterface.php @@ -1,4 +1,6 @@ -pluginDefinition['label']; } + /** + * + */ public function buildConfigurationForm( array $form, FormStateInterface $form_state @@ -38,6 +43,9 @@ public function buildConfigurationForm( return []; } + /** + * + */ public function validateConfigurationForm( array &$form, FormStateInterface $form_state @@ -45,6 +53,9 @@ public function validateConfigurationForm( } + /** + * + */ public function submitConfigurationForm( array &$form, FormStateInterface $form_state @@ -55,10 +66,16 @@ public function submitConfigurationForm( $this->configuration['form_value'] = $value; } + /** + * + */ public function getConfiguration() { return $this->configuration; } + /** + * + */ public function setConfiguration(array $configuration) { if (isset($configuration['prop_definition'])) { $this->propDefinition = $configuration['prop_definition']; @@ -69,12 +86,21 @@ public function setConfiguration(array $configuration) { $this->configuration = $configuration; } + /** + * + */ abstract public function defaultConfiguration(); + /** + * + */ public function getPropId(): string { return $this->propId; } + /** + * + */ public function getPropDefinition(): array { return $this->propDefinition; } diff --git a/src/SourcePluginManager.php b/src/SourcePluginManager.php index f8c1fa29..2bfe443c 100644 --- a/src/SourcePluginManager.php +++ b/src/SourcePluginManager.php @@ -1,4 +1,5 @@ setCacheBackend($cache_backend, 'ui_patterns_source_plugins'); } + /** + * + */ public function getSourceDefinitions($prop_type_id) { $definitions = $this->getDefinitions(); $sources = []; @@ -48,6 +50,9 @@ public function getSourceDefinitions($prop_type_id) { return $sources; } + /** + * + */ public function getSourcePlugins($prop_type_id, $prop_id, $prop_definition):array { $definitions = $this->getSourceDefinitions($prop_type_id); $sources = []; From fcc986e78128107ab82d853100220cfad13cf21d Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 14:25:43 +0200 Subject: [PATCH 68/81] Add administrative label to layouts --- modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index 93a758d7..e2c21e39 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -106,7 +106,7 @@ public function defaultConfiguration() { * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $form = []; + $form = parent::buildConfigurationForm($form, $form_state); $configuration = $this->getConfiguration(); $form['ui_patterns'] = [ '#group' => 'additional_settings', From 5780266c244e5b8eb07d3b6fe924eb2303b45ddf Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 16:20:05 +0200 Subject: [PATCH 69/81] Add reverse regexp geenrator for SchemaCompatibilityChecker --- composer.json | 3 ++- src/PropTypePluginManager.php | 2 +- src/Utils/SchemaCompatibilityChecker.php | 26 ++++++++++++++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 6a355514..9ca03f4a 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "license": "GPL-2.0-or-later", "type": "drupal-module", "require": { - "drupal/token": "^1.0" + "drupal/token": "^1.0", + "ilario-pierbattista/reverse-regex": "0.3.*" } } diff --git a/src/PropTypePluginManager.php b/src/PropTypePluginManager.php index 221c2880..4c29e339 100644 --- a/src/PropTypePluginManager.php +++ b/src/PropTypePluginManager.php @@ -67,8 +67,8 @@ public function getPropTypeDefinition(array $prop_schema): ?array { return $this->getDefinition($prop_type_id); } $definitions = $this->getSortedDefinitions(); + $compatibilityChecker = new SchemaCompatibilityChecker(); foreach ($definitions as $definition) { - $compatibilityChecker = new SchemaCompatibilityChecker(); if ($compatibilityChecker->isCompatible($prop_schema, $definition['schema'])) { return $definition; } diff --git a/src/Utils/SchemaCompatibilityChecker.php b/src/Utils/SchemaCompatibilityChecker.php index 89304e37..679b3fbf 100644 --- a/src/Utils/SchemaCompatibilityChecker.php +++ b/src/Utils/SchemaCompatibilityChecker.php @@ -2,6 +2,11 @@ namespace Drupal\ui_patterns\Utils; +use ReverseRegex\Generator\Scope; +use ReverseRegex\Lexer; +use ReverseRegex\Parser; +use ReverseRegex\Random\SimpleRandom; + /** * Checks whether two schemas are compatible. * @@ -207,8 +212,25 @@ protected function isStringPatternCompatible(array $checked_schema, array $refer if (!array_key_exists("pattern", $checked_schema)) { return FALSE; } - // @todo Is checked schema pattern and sub pattern of reference schema? - return FALSE; + // Is checked schema pattern and sub pattern of reference schema? + $example = $this->generateExampleFromPattern($checked_schema["pattern"]); + $result = preg_match("/" . $reference_schema["pattern"] . "/", $example); + if ($result !== 1) { + return FALSE; + } + return TRUE; + } + + /** + * + */ + protected function generateExampleFromPattern(string $pattern): string { + $lexer = new Lexer($pattern); + $gen = new SimpleRandom(10007); + $result = ''; + $parser = new Parser($lexer, new Scope(), new Scope()); + $parser->parse()->getResult()->generate($result, $gen); + return $result; } /** From fb9e32b05bb3ef64e6b3d5aa29f8b7ab2597aeaa Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 16:29:56 +0200 Subject: [PATCH 70/81] Tiny tweaks on sources --- src/Plugin/UiPatterns/Source/SiteNameSource.php | 2 +- src/Plugin/UiPatterns/Source/TextfieldWidget.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin/UiPatterns/Source/SiteNameSource.php b/src/Plugin/UiPatterns/Source/SiteNameSource.php index 34e05a77..9afb4f72 100644 --- a/src/Plugin/UiPatterns/Source/SiteNameSource.php +++ b/src/Plugin/UiPatterns/Source/SiteNameSource.php @@ -24,7 +24,7 @@ final class SiteNameSource extends SourcePluginBase { * */ public function getData(): mixed { - return 'Nice Site name'; + return \Drupal::config('system.site')>get('name'); } /** diff --git a/src/Plugin/UiPatterns/Source/TextfieldWidget.php b/src/Plugin/UiPatterns/Source/TextfieldWidget.php index eef81c69..5d71979a 100644 --- a/src/Plugin/UiPatterns/Source/TextfieldWidget.php +++ b/src/Plugin/UiPatterns/Source/TextfieldWidget.php @@ -13,7 +13,7 @@ * @Source( * id = "textfield", * label = @Translation("Textfield"), - * description = @Translation("Foo description."), + * description = @Translation("One-line text field."), * prop_types = { * "string" * } From cef87d193a610701ca6ace037827c5577fc6cca8 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 17:04:46 +0200 Subject: [PATCH 71/81] Add source & prop types plugins --- .../PropType/MachineNamePropType.php | 4 +- .../UiPatterns/Source/BreadcrumbSource.php | 85 +++++++++++++++++++ src/Plugin/UiPatterns/Source/ColorWidget.php | 49 +++++++++++ .../UiPatterns/Source/MachineNameWidget.php | 49 +++++++++++ src/Plugin/UiPatterns/Source/MenuSource.php | 72 ++++++++++++++++ src/Plugin/UiPatterns/Source/NumberWidget.php | 49 +++++++++++ src/Plugin/UiPatterns/Source/SelectWidget.php | 54 ++++++++++++ .../UiPatterns/Source/SiteNameSource.php | 2 +- .../UiPatterns/Source/TextfieldWidget.php | 2 +- src/Plugin/UiPatterns/Source/UrlWidget.php | 49 +++++++++++ 10 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 src/Plugin/UiPatterns/Source/BreadcrumbSource.php create mode 100644 src/Plugin/UiPatterns/Source/ColorWidget.php create mode 100644 src/Plugin/UiPatterns/Source/MachineNameWidget.php create mode 100644 src/Plugin/UiPatterns/Source/MenuSource.php create mode 100644 src/Plugin/UiPatterns/Source/NumberWidget.php create mode 100644 src/Plugin/UiPatterns/Source/SelectWidget.php create mode 100644 src/Plugin/UiPatterns/Source/UrlWidget.php diff --git a/src/Plugin/UiPatterns/PropType/MachineNamePropType.php b/src/Plugin/UiPatterns/PropType/MachineNamePropType.php index c25326ce..b9f16b0b 100644 --- a/src/Plugin/UiPatterns/PropType/MachineNamePropType.php +++ b/src/Plugin/UiPatterns/PropType/MachineNamePropType.php @@ -11,10 +11,10 @@ * id = "machine_name", * label = @Translation("Machine name"), * description = @Translation("TBD"), - * priority = 10, + * priority = 100, * schema = { * "type": "string", - * "pattern": "^[A-Za-z]+[\w-]*$" + * "pattern": "^[A-Za-z]+\w*$" * } * ) */ diff --git a/src/Plugin/UiPatterns/Source/BreadcrumbSource.php b/src/Plugin/UiPatterns/Source/BreadcrumbSource.php new file mode 100644 index 00000000..c2942a3a --- /dev/null +++ b/src/Plugin/UiPatterns/Source/BreadcrumbSource.php @@ -0,0 +1,85 @@ +get('module_handler'), + $container->get('entity_type.manager'), + ); + /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ + $translation = $container->get('string_translation'); + $plugin->setStringTranslation($translation); + $plugin->breadcrumbManager = $container->get('breadcrumb'); + $plugin->routeMatch = $container->get('current_route_match'); + return $plugin; + } + + /** + * + */ + public function getData(): mixed { + $breadcrumb = $this->breadcrumbManager->build($this->routeMatch); + $renderable = $breadcrumb->toRenderable(); + if (isset($renderable["#cache"])) { + $this->cacheArray = $renderable["#cache"]; + } + $links = []; + foreach ($breadcrumb->getLinks() as $link) { + $links[] = [ + "title" => $link->getText(), + "url" => $link->getUrl()->toString(), + ]; + } + return $links; + + return []; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/Source/ColorWidget.php b/src/Plugin/UiPatterns/Source/ColorWidget.php new file mode 100644 index 00000000..3c2cadd3 --- /dev/null +++ b/src/Plugin/UiPatterns/Source/ColorWidget.php @@ -0,0 +1,49 @@ + 'color', + '#title' => $this->propDefinition['title'], + '#default_value' => $this->configuration['form_value'], + ]; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/Source/MachineNameWidget.php b/src/Plugin/UiPatterns/Source/MachineNameWidget.php new file mode 100644 index 00000000..94e84865 --- /dev/null +++ b/src/Plugin/UiPatterns/Source/MachineNameWidget.php @@ -0,0 +1,49 @@ + 'machine_name', + '#title' => $this->propDefinition['title'], + '#default_value' => $this->configuration['form_value'], + ]; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/Source/MenuSource.php b/src/Plugin/UiPatterns/Source/MenuSource.php new file mode 100644 index 00000000..d58a0263 --- /dev/null +++ b/src/Plugin/UiPatterns/Source/MenuSource.php @@ -0,0 +1,72 @@ + 'select', + '#title' => 'Menu', + '#options' => $this->getMenuList(), + '#default_value' => \array_key_exists("menu", $value) ? $value["menu"] : "", + ]; + $options = range(0, $this->menuLinkTree->maxDepth()); + unset($options[0]); + $form['level'] = [ + '#type' => 'select', + '#title' => $this->t('Initial visibility level'), + '#default_value' => \array_key_exists("level", $value) ? $value["level"] : 1, + '#options' => $options, + '#required' => TRUE, + ]; + $options[0] = $this->t('Unlimited'); + $form['depth'] = [ + '#type' => 'select', + '#title' => $this->t('Number of levels to display'), + '#default_value' => \array_key_exists("depth", $value) ? $value["depth"] : 0, + '#options' => $options, + '#description' => $this->t('This maximum number includes the initial level and the final display is dependant of the pattern template.'), + '#required' => TRUE, + ]; + + return $form; + + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/Source/NumberWidget.php b/src/Plugin/UiPatterns/Source/NumberWidget.php new file mode 100644 index 00000000..a5a4252f --- /dev/null +++ b/src/Plugin/UiPatterns/Source/NumberWidget.php @@ -0,0 +1,49 @@ + 'number', + '#title' => $this->propDefinition['title'], + '#default_value' => $this->configuration['form_value'], + ]; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/Source/SelectWidget.php b/src/Plugin/UiPatterns/Source/SelectWidget.php new file mode 100644 index 00000000..c36c9efe --- /dev/null +++ b/src/Plugin/UiPatterns/Source/SelectWidget.php @@ -0,0 +1,54 @@ + 'select', + '#title' => $this->propDefinition['title'], + '#default_value' => $this->configuration['form_value'], + "#options" => $options, + ]; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} diff --git a/src/Plugin/UiPatterns/Source/SiteNameSource.php b/src/Plugin/UiPatterns/Source/SiteNameSource.php index 9afb4f72..682fc70f 100644 --- a/src/Plugin/UiPatterns/Source/SiteNameSource.php +++ b/src/Plugin/UiPatterns/Source/SiteNameSource.php @@ -24,7 +24,7 @@ final class SiteNameSource extends SourcePluginBase { * */ public function getData(): mixed { - return \Drupal::config('system.site')>get('name'); + return \Drupal::config('system.site') > get('name'); } /** diff --git a/src/Plugin/UiPatterns/Source/TextfieldWidget.php b/src/Plugin/UiPatterns/Source/TextfieldWidget.php index 5d71979a..110fe0e3 100644 --- a/src/Plugin/UiPatterns/Source/TextfieldWidget.php +++ b/src/Plugin/UiPatterns/Source/TextfieldWidget.php @@ -35,7 +35,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta return [ '#type' => 'textfield', '#title' => $this->propDefinition['title'], - '#default_value' => $this->configuration['form_value'] + '#default_value' => $this->configuration['form_value'], ]; } diff --git a/src/Plugin/UiPatterns/Source/UrlWidget.php b/src/Plugin/UiPatterns/Source/UrlWidget.php new file mode 100644 index 00000000..d140506f --- /dev/null +++ b/src/Plugin/UiPatterns/Source/UrlWidget.php @@ -0,0 +1,49 @@ + 'url', + '#title' => $this->propDefinition['title'], + '#default_value' => $this->configuration['form_value'], + ]; + } + + /** + * + */ + public function defaultConfiguration() { + return []; + } + +} From 18eb4978f802739559b8b413c093c2f7786e6ab7 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 17:09:55 +0200 Subject: [PATCH 72/81] Change modules descriptions, and run prettier on *.info.yml --- .../ui_patterns_layouts/ui_patterns_layouts.info.yml | 6 +++--- modules/ui_patterns_legacy/ui_patterns_legacy.info.yml | 6 +++--- .../ui_patterns_library/ui_patterns_library.info.yml | 6 +++--- modules/ui_patterns_views/ui_patterns_views.info.yml | 10 +++++----- ui_patterns.info.yml | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml index 0afbc04f..198c9b49 100644 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml +++ b/modules/ui_patterns_layouts/ui_patterns_layouts.info.yml @@ -1,8 +1,8 @@ -name: 'UI Patterns Layouts' +name: "UI Patterns Layouts" type: module -description: 'Use patterns as layouts via the Layout Discovery module.' +description: "Use UI components as layouts plugins." core_version_requirement: ^10 -package: 'User interface' +package: "User interface" dependencies: - drupal:layout_discovery - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml index ee986ee0..6221865c 100644 --- a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml +++ b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml @@ -1,7 +1,7 @@ -name: 'UI Patterns Legacy' +name: "UI Patterns Legacy" type: module -description: 'Discovery of UI Patterns 1.x components.' +description: "Compatibility layer for UI Patterns 1.x components." core_version_requirement: ^10 -package: 'User interface' +package: "User interface" dependencies: - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_library/ui_patterns_library.info.yml b/modules/ui_patterns_library/ui_patterns_library.info.yml index e5e4bae5..151e5c28 100644 --- a/modules/ui_patterns_library/ui_patterns_library.info.yml +++ b/modules/ui_patterns_library/ui_patterns_library.info.yml @@ -1,7 +1,7 @@ -name: 'UI Patterns Library' +name: "UI Patterns Library" type: module -description: 'Browse components in library pages.' +description: "Browse UI components in library pages." core_version_requirement: ^10 -package: 'User interface' +package: "User interface" dependencies: - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_views/ui_patterns_views.info.yml b/modules/ui_patterns_views/ui_patterns_views.info.yml index 62d29ee7..e0fa0dd1 100644 --- a/modules/ui_patterns_views/ui_patterns_views.info.yml +++ b/modules/ui_patterns_views/ui_patterns_views.info.yml @@ -1,13 +1,13 @@ -name: 'UI Patterns Views' +name: "UI Patterns Views" type: module -description: 'Use patterns as Views templates.' +description: "Use UI components ins Views rows and styles plugins." core_version_requirement: ^9 || ^10 -package: 'User interface' +package: "User interface" dependencies: - drupal:views - ui_patterns:ui_patterns # Information added by Drupal.org packaging script on 2023-06-29 -version: '8.x-1.7' -project: 'ui_patterns' +version: "8.x-1.7" +project: "ui_patterns" datestamp: 1688044508 diff --git a/ui_patterns.info.yml b/ui_patterns.info.yml index be62e50f..00543fbf 100644 --- a/ui_patterns.info.yml +++ b/ui_patterns.info.yml @@ -1,7 +1,7 @@ -name: 'UI Patterns' +name: "UI Patterns" type: module -description: 'UI patterns.' +description: "Define and expose self-contained UI Components as Drupal plugins and use them seamlessly in Drupal development and site-building." core_version_requirement: ^10 -package: 'User interface' +package: "User interface" dependencies: - drupal:sdc From 27add87a1e5f7f4086ccf98951fff541581aecbc Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 17:11:24 +0200 Subject: [PATCH 73/81] update *.info.yml --- modules/ui_patterns_views/ui_patterns_views.info.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/modules/ui_patterns_views/ui_patterns_views.info.yml b/modules/ui_patterns_views/ui_patterns_views.info.yml index e0fa0dd1..be4033ab 100644 --- a/modules/ui_patterns_views/ui_patterns_views.info.yml +++ b/modules/ui_patterns_views/ui_patterns_views.info.yml @@ -1,13 +1,8 @@ name: "UI Patterns Views" type: module -description: "Use UI components ins Views rows and styles plugins." -core_version_requirement: ^9 || ^10 +description: "Use UI components with Views rows and styles plugins." +core_version_requirement: ^10 package: "User interface" dependencies: - drupal:views - ui_patterns:ui_patterns - -# Information added by Drupal.org packaging script on 2023-06-29 -version: "8.x-1.7" -project: "ui_patterns" -datestamp: 1688044508 From 4893a53a482e2e79c60309181cb53ce272b59968 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 17:23:57 +0200 Subject: [PATCH 74/81] Reorganize UiPatternsFormBuilderTrait --- src/Form/UiPatternsFormBuilderTrait.php | 29 +++++++++++++++++-- src/Plugin/UiPatterns/Source/SelectWidget.php | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Form/UiPatternsFormBuilderTrait.php b/src/Form/UiPatternsFormBuilderTrait.php index 456b0b8f..48c6b95b 100644 --- a/src/Form/UiPatternsFormBuilderTrait.php +++ b/src/Form/UiPatternsFormBuilderTrait.php @@ -16,7 +16,32 @@ trait UiPatternsFormBuilderTrait { * @param Drupal\sdc\Component\ComponentMetadata $component * The pattern definition. */ - protected function buildComponentForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context):array { + protected function buildComponentForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context): array { + return [ + $this->buildVariantSelectorForm($form_state, $component_metadata), + $this->buildSlotsForm($form_state, $component_metadata, $context), + $this->buildPropsForm($form_state, $component_metadata, $context), + ]; + } + + /** + * + */ + protected function buildVariantSelectorForm(FormStateInterface $form_state, ComponentMetadata $component_metadata): array { + return []; + } + + /** + * + */ + protected function buildSlotsForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context): array { + return []; + } + + /** + * + */ + protected function buildPropsForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context): array { $sub_sources_form_value = $context['form_values']; $form = []; $sub_sources = []; @@ -57,7 +82,7 @@ protected function submitComponentForm($form, FormStateInterface $form_state, ar /** * Build components selector widget. */ - protected function buildComponentsForm():array { + protected function buildComponentsForm(): array { return []; } diff --git a/src/Plugin/UiPatterns/Source/SelectWidget.php b/src/Plugin/UiPatterns/Source/SelectWidget.php index c36c9efe..e3e84d7e 100644 --- a/src/Plugin/UiPatterns/Source/SelectWidget.php +++ b/src/Plugin/UiPatterns/Source/SelectWidget.php @@ -32,7 +32,7 @@ public function getData(): mixed { * */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $otpions = [ + $options = [ "foo", "poo", ]; From d1b79fc2e48530159d6d0f76959a793d66e498e4 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 18:05:26 +0200 Subject: [PATCH 75/81] From category to SDC group --- src/Sdc/UiPatternsSdcPluginManager.php | 9 +++------ .../components/blockquote/blockquote.component.yml | 2 +- .../components/button/button.component.yml | 2 +- .../ui_patterns_test/components/card/card.component.yml | 2 +- .../components/close_button/close_button.component.yml | 2 +- .../prop_types_tests/prop_types_tests.component.yml | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Sdc/UiPatternsSdcPluginManager.php b/src/Sdc/UiPatternsSdcPluginManager.php index 4ed1f5bd..b5b123ac 100644 --- a/src/Sdc/UiPatternsSdcPluginManager.php +++ b/src/Sdc/UiPatternsSdcPluginManager.php @@ -68,14 +68,11 @@ public function getNamespacedId(string $component_id): string { * {@inheritdoc} */ public function getGroupedDefinitions(?array $definitions = NULL): array { - // @todo use category metadata from ui_patterns_library - // Do we move this method to ui_patterns_library? - // Or do we move categories to ui_patterns? $definitions = $definitions ?: $this->getDefinitions(); $groups = []; foreach ($definitions as $id => $definition) { - $category = $definition["category"] ?? "Other"; - $groups[$category][$id] = $definition; + $group = $definition["group"] ?? "Other"; + $groups[$group][$id] = $definition; } return $groups; } @@ -83,7 +80,7 @@ public function getGroupedDefinitions(?array $definitions = NULL): array { /** * Stories slots have no "#" prefix in render arrays. Let's add them. * A bit like UI Patterns 1.x's PatternPreview::getPreviewMarkup() - * This method belongs here because sued by both ui_patterns_library and + * This method belongs here because used by both ui_patterns_library and * ui_patterns_legacy. */ public static function processStoriesSlots(array $slots): array { diff --git a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml index f696d09f..94089024 100644 --- a/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml +++ b/tests/modules/ui_patterns_test/components/blockquote/blockquote.component.yml @@ -3,7 +3,7 @@ name: Blockquote description: "For quoting blocks of content from another source within your document." links: - "https://getbootstrap.com/docs/5.3/content/typography/#blockquotes" -category: "Typography" +group: "Typography" props: type: object properties: diff --git a/tests/modules/ui_patterns_test/components/button/button.component.yml b/tests/modules/ui_patterns_test/components/button/button.component.yml index 219f8af1..9bc44c32 100644 --- a/tests/modules/ui_patterns_test/components/button/button.component.yml +++ b/tests/modules/ui_patterns_test/components/button/button.component.yml @@ -3,7 +3,7 @@ name: "Button" description: "For actions in forms, dialogs, and more with support for multiple sizes, states, and more." links: - "https://getbootstrap.com/docs/5.3/components/buttons/" -category: "Button" +group: "Button" template: pattern-button.html.twig variants: default: diff --git a/tests/modules/ui_patterns_test/components/card/card.component.yml b/tests/modules/ui_patterns_test/components/card/card.component.yml index 8a87f528..4783fdfe 100644 --- a/tests/modules/ui_patterns_test/components/card/card.component.yml +++ b/tests/modules/ui_patterns_test/components/card/card.component.yml @@ -3,7 +3,7 @@ name: "Card" description: "A card is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options." links: - "https://getbootstrap.com/docs/5.3/components/card/" -category: "Card" +group: "Card" variants: default: title: "Default" diff --git a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml index b2bb1187..a2f5abe6 100644 --- a/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml +++ b/tests/modules/ui_patterns_test/components/close_button/close_button.component.yml @@ -3,7 +3,7 @@ name: "Close button" description: "A generic close button for dismissing content like modals and alerts." links: - "https://getbootstrap.com/docs/5.3/components/close-button" -category: "Button" +group: "Button" variants: default: title: "Default" diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml index df979b30..33b1b241 100644 --- a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml +++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml @@ -99,4 +99,4 @@ props: machine_name: title: "Machine name" type: string - pattern: '^[A-Za-z]+[\w-]*$' + pattern: '^[A-Za-z]+\w*$' From 8db5a73a887c0fa13b612de5e9041e8f1c8adeb9 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 19:59:11 +0200 Subject: [PATCH 76/81] Add variants to Form/UiPatternsFormBuilderTrait --- .../src/Plugin/Layout/PatternLayout.php | 6 +-- src/Form/UiPatternsFormBuilderTrait.php | 52 ++++++++++++++----- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php index e2c21e39..9d1d16e3 100644 --- a/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php +++ b/modules/ui_patterns_layouts/src/Plugin/Layout/PatternLayout.php @@ -118,10 +118,10 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta /** @var \Drupal\sdc\ComponentPluginManager $plugin_manager */ $plugin_manager = \Drupal::service('plugin.manager.sdc'); /** @var \Drupal\sdc\Component\ComponentMetadata[] $components */ - $component_metadata = $plugin_manager->find($component_id)?->metadata; + $component = $plugin_manager->find($component_id); $context = ['form_type' => 'layout', 'form' => $form, 'layout' => $this, 'form_values' => $this->configuration['ui_patterns'] ?? []]; - $form['ui_patterns'] = $this->buildComponentForm($form_state, $component_metadata, $context); - $this->moduleHandler->alter('ui_patterns_layouts_display_configuration_form', $form['ui_patterns'], $component_metadata, $configuration); + $form['ui_patterns'] = $this->buildComponentForm($form_state, $component, $context); + $this->moduleHandler->alter('ui_patterns_layouts_display_configuration_form', $form['ui_patterns'], $component, $configuration); return $form; } diff --git a/src/Form/UiPatternsFormBuilderTrait.php b/src/Form/UiPatternsFormBuilderTrait.php index 48c6b95b..1d800801 100644 --- a/src/Form/UiPatternsFormBuilderTrait.php +++ b/src/Form/UiPatternsFormBuilderTrait.php @@ -3,7 +3,7 @@ namespace Drupal\ui_patterns\Form; use Drupal\Core\Form\FormStateInterface; -use Drupal\sdc\Component\ComponentMetadata; +use Drupal\sdc\Plugin\Component; /** * @@ -13,41 +13,53 @@ trait UiPatternsFormBuilderTrait { /** * Build component fieldset. * - * @param Drupal\sdc\Component\ComponentMetadata $component - * The pattern definition. + * @param Drupal\sdc\Plugin\Component $component + * The comopnent plugin. */ - protected function buildComponentForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context): array { + protected function buildComponentForm(FormStateInterface $form_state, Component $component, array $context): array { return [ - $this->buildVariantSelectorForm($form_state, $component_metadata), - $this->buildSlotsForm($form_state, $component_metadata, $context), - $this->buildPropsForm($form_state, $component_metadata, $context), + $this->buildVariantSelectorForm($form_state, $component), + $this->buildSlotsForm($form_state, $component, $context), + $this->buildPropsForm($form_state, $component, $context), ]; } /** * */ - protected function buildVariantSelectorForm(FormStateInterface $form_state, ComponentMetadata $component_metadata): array { - return []; + protected function buildVariantSelectorForm(FormStateInterface $form_state, Component $component): array { + $definition = $component->getPluginDefinition(); + if (!isset($definition["variants"])) { + return []; + } + $options = []; + foreach ($definition["variants"] as $variant_id => $variant) { + $options[$variant_id] = $variant["title"]; + } + return [ + "#type" => "select", + "#title" => t("Variants"), + "#options" => $options, + ]; } /** * */ - protected function buildSlotsForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context): array { + protected function buildSlotsForm(FormStateInterface $form_state, Component $component, array $context): array { return []; } /** * */ - protected function buildPropsForm(FormStateInterface $form_state, ComponentMetadata $component_metadata, array $context): array { + protected function buildPropsForm(FormStateInterface $form_state, Component $component, array $context): array { $sub_sources_form_value = $context['form_values']; $form = []; $sub_sources = []; - foreach ($component_metadata->schema['properties'] as $prop_id => $prop) { + foreach ($component->metadata->schema['properties'] as $prop_id => $prop) { $sources = $prop['ui_patterns']['source']; - if (count($sources) > 0) { + if (count($sources) == 1) { /** @var \Drupal\ui_patterns\SourcePluginBase $default_source */ $default_source = current($sources); $configuration = $default_source->getConfiguration(); @@ -58,6 +70,19 @@ protected function buildPropsForm(FormStateInterface $form_state, ComponentMetad $form[$prop_id] = $default_source->buildConfigurationForm($form, $form_state); $sub_sources[$prop_id] = $default_source; } + elseif (count($sources) > 1) { + $options = []; + foreach ($sources as $source) { + $options[$source->getPluginId()] = $source->label(); + } + $form[$prop_id] = [ + "#type" => "select", + "#title" => $prop["title"], + "#options" => $options, + ]; + // @todo dynamically load source form on select. + // @todo $sub_sources? + } } if (!$form_state->has('sub_sources')) { $form_state->set('sub_sources', $sub_sources); @@ -69,7 +94,6 @@ protected function buildPropsForm(FormStateInterface $form_state, ComponentMetad * */ protected function submitComponentForm($form, FormStateInterface $form_state, array $context):array { - $sub_sources = $form_state->get('sub_sources'); $sub_values = []; foreach ($sub_sources as $prop_id => $sub_source) { From 0dce47279d0a5b361f8d730476998a41b9d3f52e Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 20:08:05 +0200 Subject: [PATCH 77/81] Init ui_patterns_field_formatters --- .../ComponentOneForAllFormatter.php | 34 +++++++++++++++++ .../ComponentOneForEachFormatter.php | 26 +++++++++++++ .../Source/FieldMetaPropertiesSource.php | 30 +++++++++++++++ .../Source/FieldRawPropertiesSource.php | 38 +++++++++++++++++++ .../ui_patterns_field_formatters.info.yml | 7 ++++ .../ui_patterns_field_formatters.module | 19 ++++++++++ 6 files changed, 154 insertions(+) create mode 100644 modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php create mode 100644 modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForEachFormatter.php create mode 100644 modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldMetaPropertiesSource.php create mode 100644 modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldRawPropertiesSource.php create mode 100644 modules/ui_patterns_field_formatters/ui_patterns_field_formatters.info.yml create mode 100644 modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php new file mode 100644 index 00000000..fc1b57df --- /dev/null +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php @@ -0,0 +1,34 @@ +getSourceField('_label', 'Label'); + $sources[] = $this->getSourceField('_formatted', 'Formatted values'); + return $sources; + } + +} diff --git a/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldRawPropertiesSource.php b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldRawPropertiesSource.php new file mode 100644 index 00000000..b58ddd03 --- /dev/null +++ b/modules/ui_patterns_field_formatters/src/Plugin/UiPatterns/Source/FieldRawPropertiesSource.php @@ -0,0 +1,38 @@ +getContextProperty('storageDefinition'); + $fields = $storageDefinition->getPropertyNames(); + foreach ($fields as $field) { + if (!$this->getContextProperty('limit')) { + $sources[] = $this->getSourceField($field, $storageDefinition->getPropertyDefinition($field)->getLabel()); + } + elseif (in_array($field, $this->getContextProperty('limit'))) { + $sources[] = $this->getSourceField($field, $storageDefinition->getPropertyDefinition($field)->getLabel()); + } + } + return $sources; + } + +} diff --git a/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.info.yml b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.info.yml new file mode 100644 index 00000000..b30ab67d --- /dev/null +++ b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.info.yml @@ -0,0 +1,7 @@ +name: UI Patterns Field Formatters +type: module +description: Use UI components with field formatters. +core_version_requirement: ^10 +package: User interface +dependencies: + - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module new file mode 100644 index 00000000..7759b5f7 --- /dev/null +++ b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module @@ -0,0 +1,19 @@ +getDefinitions()); + $info['pattern_all_formatter']['field_types'] = $field_types; + $info['pattern_each_formatter']['field_types'] = $field_types; +} From 4e61c5faa348c85f36039236720d6f81d845d601 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 20:14:57 +0200 Subject: [PATCH 78/81] PHPCS on ui_patterns_field_formatters --- .../FieldFormatter/ComponentOneForAllFormatter.php | 10 ---------- .../FieldFormatter/ComponentOneForEachFormatter.php | 5 ----- 2 files changed, 15 deletions(-) diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php index fc1b57df..1023d0e2 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php @@ -2,18 +2,8 @@ namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; -use Drupal\Core\Entity\Plugin\DataType\EntityReference; -use Drupal\Core\Field\FieldItemInterface; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\Core\Render\Element; use Drupal\field_formatter\Plugin\Field\FieldFormatter\FieldWrapperBase; -use Drupal\text\TextProcessed; -use Drupal\ui_patterns\Form\PatternDisplayFormTrait; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\TypedData\Plugin\DataType\Uri; -use Drupal\Core\Url; /** * Plugin implementation of the 'component_all' formatter. diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForEachFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForEachFormatter.php index abcdba5b..1c5be4f4 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForEachFormatter.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForEachFormatter.php @@ -2,11 +2,6 @@ namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; -use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Field\FieldStorageDefinitionInterface; -use Drupal\Core\Form\FormStateInterface; - /** * Plugin implementation of the 'component_each' formatter. * From 55d8ff11bb02714569a8d34c86772b200d88beab Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 22:22:39 +0200 Subject: [PATCH 79/81] Some work on ComponentOneForAllFormatter --- .../ComponentOneForAllFormatter.php | 64 ++++++++++++++++++- .../ui_patterns_field_formatters.module | 4 +- src/Form/UiPatternsFormBuilderTrait.php | 55 ++++++++++++---- 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php index 1023d0e2..f0e16eb3 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentOneForAllFormatter.php @@ -2,8 +2,11 @@ namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\FormatterBase; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\field_formatter\Plugin\Field\FieldFormatter\FieldWrapperBase; +use Drupal\ui_patterns\Form\UiPatternsFormBuilderTrait; /** * Plugin implementation of the 'component_all' formatter. @@ -19,6 +22,63 @@ * }, * ) */ -class ComponentOneForAllFormatter extends FieldWrapperBase implements ContainerFactoryPluginInterface { +class ComponentOneForAllFormatter extends FormatterBase implements ContainerFactoryPluginInterface { + + use UiPatternsFormBuilderTrait; + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + $settings = array_merge( + parent::defaultSettings(), + self::getComponentFormDefault(), + ); + // Temp. + return $settings; + + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + $plugin_manager = \Drupal::service('plugin.manager.sdc'); + $component_id = "ui_patterns_test:card"; + $component = $plugin_manager->find($component_id); + $context = []; + $form['ui_patterns'] = $this->buildComponentForm($form_state, $component, $context); + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + return $summary; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $component_id = "ui_patterns_test:card"; + $variant_id = ""; + $slots = $this->getSetting('slots'); + $props = array_merge( + $this->getSetting('props'), + [ + "variant" => $variant_id, + ] + ); + return [ + '#type' => 'component', + '#component' => $component_id, + '#slots' => $slots, + '#props' => $props, + ]; + } } diff --git a/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module index 7759b5f7..6eac378f 100644 --- a/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module +++ b/modules/ui_patterns_field_formatters/ui_patterns_field_formatters.module @@ -14,6 +14,6 @@ function ui_patterns_field_formatters_field_formatter_info_alter(array &$info) { /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */ $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); $field_types = array_keys($field_type_manager->getDefinitions()); - $info['pattern_all_formatter']['field_types'] = $field_types; - $info['pattern_each_formatter']['field_types'] = $field_types; + $info['component_all']['field_types'] = $field_types; + $info['component_each']['field_types'] = $field_types; } diff --git a/src/Form/UiPatternsFormBuilderTrait.php b/src/Form/UiPatternsFormBuilderTrait.php index 1d800801..9551a0d5 100644 --- a/src/Form/UiPatternsFormBuilderTrait.php +++ b/src/Form/UiPatternsFormBuilderTrait.php @@ -11,16 +11,52 @@ trait UiPatternsFormBuilderTrait { /** - * Build component fieldset. + * ... + * + * To use with: + * - PluginSettingsInterface::defaultSettings + * - ConfigurableInterface::defaultConfiguration + * - views/PluginBase::setOptionDefaults + * - ... + */ + public static function getComponentFormDefault() { + return [ + "component_id" => "", + "variant_id" => "", + "slots" => [], + "props" => [], + ]; + } + + /** + * Build the complete form. * * @param Drupal\sdc\Plugin\Component $component - * The comopnent plugin. + * The component plugin. */ protected function buildComponentForm(FormStateInterface $form_state, Component $component, array $context): array { return [ - $this->buildVariantSelectorForm($form_state, $component), - $this->buildSlotsForm($form_state, $component, $context), - $this->buildPropsForm($form_state, $component, $context), + "component_id" => $this->buildComponentSelectorForm(), + "variant_id" => $this->buildVariantSelectorForm($form_state, $component), + "slots" => $this->buildSlotsForm($form_state, $component, $context), + "forms" => $this->buildPropsForm($form_state, $component, $context), + ]; + } + + /** + * Build components selector widget. + */ + protected function buildComponentSelectorForm(): array { + $components = \Drupal::service("plugin.manager.sdc")->getDefinitions(); + // @todo getGroupedDefinitions? + $options = []; + foreach ($components as $component_id => $component) { + $options[$component_id] = $component["name"]; + } + return [ + "#type" => "select", + "#title" => t("Component"), + "#options" => $options, ]; } @@ -38,7 +74,7 @@ protected function buildVariantSelectorForm(FormStateInterface $form_state, Comp } return [ "#type" => "select", - "#title" => t("Variants"), + "#title" => t("Variant"), "#options" => $options, ]; } @@ -103,11 +139,4 @@ protected function submitComponentForm($form, FormStateInterface $form_state, ar return $sub_values; } - /** - * Build components selector widget. - */ - protected function buildComponentsForm(): array { - return []; - } - } From 70589956f762b2f07e33827b14d5a3e410fdf1c3 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 8 Oct 2023 22:24:06 +0200 Subject: [PATCH 80/81] Clean ui_patterns_layouts --- modules/ui_patterns_layouts/README.md | 7 ------ .../ui_patterns_layouts.api.php | 22 ------------------- .../ui_patterns_layouts.install | 6 ----- 3 files changed, 35 deletions(-) delete mode 100644 modules/ui_patterns_layouts/README.md delete mode 100644 modules/ui_patterns_layouts/ui_patterns_layouts.api.php delete mode 100644 modules/ui_patterns_layouts/ui_patterns_layouts.install diff --git a/modules/ui_patterns_layouts/README.md b/modules/ui_patterns_layouts/README.md deleted file mode 100644 index a6cc0ed4..00000000 --- a/modules/ui_patterns_layouts/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# UI Patterns Layouts - -Integrates UI Patterns with the **Layout Discovery** core module. - -- To use pattern layouts on view modes install the [Display Suite](https://www.drupal.org/project/ds) module. -- To use pattern layouts with [Page Manager](https://www.drupal.org/project/page_manager) - install the [Panels](https://www.drupal.org/project/panels) module. diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.api.php b/modules/ui_patterns_layouts/ui_patterns_layouts.api.php deleted file mode 100644 index 455b718d..00000000 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.api.php +++ /dev/null @@ -1,22 +0,0 @@ - 'input']; -} diff --git a/modules/ui_patterns_layouts/ui_patterns_layouts.install b/modules/ui_patterns_layouts/ui_patterns_layouts.install deleted file mode 100644 index 1def9606..00000000 --- a/modules/ui_patterns_layouts/ui_patterns_layouts.install +++ /dev/null @@ -1,6 +0,0 @@ - Date: Sun, 8 Oct 2023 22:29:34 +0200 Subject: [PATCH 81/81] Remove ilario-pierbattista/reverse-regex --- composer.json | 3 +-- src/Utils/SchemaCompatibilityChecker.php | 28 +++++++++--------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index 9ca03f4a..6a355514 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,6 @@ "license": "GPL-2.0-or-later", "type": "drupal-module", "require": { - "drupal/token": "^1.0", - "ilario-pierbattista/reverse-regex": "0.3.*" + "drupal/token": "^1.0" } } diff --git a/src/Utils/SchemaCompatibilityChecker.php b/src/Utils/SchemaCompatibilityChecker.php index 679b3fbf..0bb5937d 100644 --- a/src/Utils/SchemaCompatibilityChecker.php +++ b/src/Utils/SchemaCompatibilityChecker.php @@ -2,11 +2,6 @@ namespace Drupal\ui_patterns\Utils; -use ReverseRegex\Generator\Scope; -use ReverseRegex\Lexer; -use ReverseRegex\Parser; -use ReverseRegex\Random\SimpleRandom; - /** * Checks whether two schemas are compatible. * @@ -177,11 +172,6 @@ protected function isNumericCompatible(array $checked_schema, array $reference_s */ protected function isStringCompatible(array $checked_schema, array $reference_schema): bool { // FALSE if at least one of those tests is FALSE. - if (array_key_exists("pattern", $reference_schema)) { - if (!$this->isStringPatternCompatible($checked_schema, $reference_schema)) { - return FALSE; - } - } if (array_key_exists("format", $reference_schema)) { if (!$this->isStringFormatCompatible($checked_schema, $reference_schema)) { return FALSE; @@ -202,6 +192,11 @@ protected function isStringCompatible(array $checked_schema, array $reference_sc return FALSE; } } + if (array_key_exists("pattern", $reference_schema)) { + if (!$this->isStringPatternCompatible($checked_schema, $reference_schema)) { + return FALSE; + } + } return TRUE; } @@ -212,24 +207,21 @@ protected function isStringPatternCompatible(array $checked_schema, array $refer if (!array_key_exists("pattern", $checked_schema)) { return FALSE; } + return FALSE; // Is checked schema pattern and sub pattern of reference schema? $example = $this->generateExampleFromPattern($checked_schema["pattern"]); $result = preg_match("/" . $reference_schema["pattern"] . "/", $example); - if ($result !== 1) { - return FALSE; + if ($result === 1) { + return TRUE; } - return TRUE; + return FALSE; } /** * */ protected function generateExampleFromPattern(string $pattern): string { - $lexer = new Lexer($pattern); - $gen = new SimpleRandom(10007); - $result = ''; - $parser = new Parser($lexer, new Scope(), new Scope()); - $parser->parse()->getResult()->generate($result, $gen); + // ilario-pierbattista/reverse-regex. return $result; }
    {{ "Type"|t }} {{ "Name"|t }} {{ "Label"|t }} {{ "Type"|t }}
    {{ "Slot"|t }} {{ slot_id }} {{ slot.title }}slot {{ slot.description }}
    {{ "Prop"|t }} {{ prop_id }} {{ prop.title }} {{ prop.type }}
    {{ prop_id }} {{ prop.title }}{{ prop.type }}{{ prop.type_definition }} {{ prop.description }}
    {{ "Name"|t }} {{ "Label"|t }} {{ "Type"|t }}{{ "Description"|t }} / {{ "Options"|t }}{{ "Description"|t }}
    {{ prop_id }} {{ prop.title }} {{ prop.type_definition }}{{ prop.description }}{{ prop.description }} +
    {{ prop|filter((v, k) => k != 'title')|filter((v, k) => k != 'description')
    +          |filter((v, k) => k != 'examples')|filter((v, k) => k != 'default')|filter((v, k) => k != 'type_definition')
    +          |json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) }}
    +
    {{ prop_id }} {{ prop.title }}{{ prop.type_definition }}{{ prop.ui_patterns.type_definition.pluginId }} {{ prop.description }}
    {{ prop|filter((v, k) => k != 'title')|filter((v, k) => k != 'description')
    -          |filter((v, k) => k != 'examples')|filter((v, k) => k != 'default')|filter((v, k) => k != 'type_definition')
    +          |filter((v, k) => k != 'examples')|filter((v, k) => k != 'default')|filter((v, k) => k != 'ui_patterns')
               |json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) }}