Skip to content
Snippets Groups Projects
Verified Commit 54c4637e authored by Théodore Biadala's avatar Théodore Biadala
Browse files

Issue #3484600 by anjali rathod, plopesc, finnsky, lauriii, m4olivei, ckrina,...

Issue #3484600 by anjali rathod, plopesc, finnsky, lauriii, m4olivei, ckrina, berdir, smustgrave, catch: Show entity information on the Top Bar
parent 873f84fc
No related branches found
No related tags found
4 merge requests!11197Issue #3506427 by eduardo morales alberti: Remove responsive_image.ajax from hook,!11131[10.4.x-only-DO-NOT-MERGE]: Issue ##2842525 Ajax attached to Views exposed filter form does not trigger callbacks,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!617Issue #3043725: Provide a Entity Handler for user cancelation
Pipeline #403376 passed with warnings
Pipeline: drupal

#403401

    Pipeline: drupal

    #403398

      Pipeline: drupal

      #403396

        +4
        Showing
        with 695 additions and 98 deletions
        # This is so your IDE knows about the syntax for fixes and autocomplete.
        $schema: https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json
        # The human readable name.
        name: Badge
        # Status can be: "experimental", "stable", "deprecated", "obsolete".
        status: experimental
        # Schema for the props. We support www.json-schema.org. Learn more about the
        # syntax there.
        props:
        # Props are always an object with keys. Each key is a variable in your
        # component template.
        type: object
        properties:
        status:
        type: string
        default: info
        enum:
        - info
        - success
        slots:
        label:
        type: string
        description: Label text
        /*
        * DO NOT EDIT THIS FILE.
        * See the following change record for more information,
        * https://www.drupal.org/node/3084859
        * @preserve
        */
        /* cspell:ignore csvg cpath wght */
        /**
        * @file
        * Toolbar badge styles.
        */
        .toolbar-badge {
        margin-top: 0.1875rem;
        padding: var(--admin-toolbar-space-4) var(--admin-toolbar-space-8);
        border-radius: 1rem;
        background-color: var(--admin-toolbar-color-gray-100);
        font-size: var(--admin-toolbar-font-size-label-sm);
        line-height: var(--admin-toolbar-line-height-label-sm);
        font-variation-settings: "wght" 700;
        }
        .toolbar-badge--success {
        color: var(--admin-toolbar-color-green-600);
        background-color: var(--admin-toolbar-color-green-050);
        }
        /* cspell:ignore csvg cpath wght */
        /**
        * @file
        * Toolbar badge styles.
        */
        .toolbar-badge {
        margin-top: 3px;
        padding: var(--admin-toolbar-space-4) var(--admin-toolbar-space-8);
        border-radius: 1rem;
        background-color: var(--admin-toolbar-color-gray-100);
        font-size: var(--admin-toolbar-font-size-label-sm);
        line-height: var(--admin-toolbar-line-height-label-sm);
        font-variation-settings: "wght" 700;
        }
        .toolbar-badge--success {
        color: var(--admin-toolbar-color-green-600);
        background-color: var(--admin-toolbar-color-green-050);
        }
        <div{{ attributes.addClass('toolbar-badge', 'toolbar-badge--' ~ status|default('info')) }}>
        {% block label %}
        {{ label }}
        {% endblock %}
        </div>
        <svg fill="none" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m2 2.5c0 .00062.00009.00292.00153.00765.00169.00557.00588.01658.01601.03307.02122.03457.0645.08654.14558.15044.16509.13011.43496.26914.81301.39516.75088.25029 1.82002.41368 3.02387.41368 1.20386 0 2.27299-.16339 3.02387-.41368.37805-.12602.64792-.26505.81301-.39516.08108-.0639.12436-.11587.14558-.15044.01013-.01649.01431-.0275.01601-.03307.00146-.00479.00153-.00695.00153-.00754 0-.00003 0 .00002 0 0 0-.00036 0-.00258-.00153-.00776-.0017-.00557-.00588-.01658-.01601-.03307-.02122-.03457-.0645-.08654-.14558-.15044-.16509-.13011-.43496-.26914-.81301-.39516-.75088-.25029-1.82001-.41368-3.02387-.41368-1.20385 0-2.27299.16339-3.02387.41368-.37805.12602-.64792.26505-.81301.39516-.08108.0639-.12436.11587-.14558.15044-.01013.01649-.01432.0275-.01601.03307-.00154.00504-.00153.00721-.00153.00765zm8 1.2633c-.20061.10259-.42333.19284-.65991.2717-.8778.2926-2.05866.465-3.34009.465s-2.46229-.1724-3.34009-.465c-.23658-.07886-.4593-.16911-.65991-.2717v2.2367l.00008.00078c.0001.00065.00037.00219.00113.00474.0015.00503.00535.01547.01486.03134.01989.03316.06113.08421.13994.14763.16054.12919.42904.27147.82012.40183.77887.25962 1.86698.41368 3.02387.41368s2.245-.15406 3.02387-.41368c.39108-.13036.65958-.27264.82012-.40183.07881-.06342.12005-.11447.13994-.14763.00951-.01587.01336-.02631.01486-.03134.00047-.00159.00076-.00279.00093-.00362.0001-.00051.00016-.00088.0002-.00112l.00007-.00065zm1-1.2633c0-.42597-.2594-.75221-.5441-.97657-.2904-.22881-.67952-.413-1.11581-.558432-.8778-.2926-2.05866-.464998-3.34009-.464998s-2.46229.172398-3.34009.464998c-.43629.145432-.82543.329622-1.11576.558432-.2847.22436-.54415.5506-.54415.97657v7c0 .41262.2462.7359.52907.9636.28749.2313.678.4205 1.13084.5714.90895.303 2.11003.465 3.34009.465s2.43114-.162 3.34009-.465c.45283-.1509.84331-.3401 1.13081-.5714.2829-.2277.5291-.55098.5291-.9636zm-1 4.76299c-.19854.10174-.42052.19222-.65991.27201-.90895.30299-2.11003.465-3.34009.465s-2.43114-.16201-3.34009-.465c-.23939-.07979-.46137-.17027-.65991-.27201v2.23701l.00008.00078c.0001.00065.00037.00219.00113.00474.0015.00503.00535.01547.01486.03134.01989.03316.06113.08421.13994.14763.16054.12919.42904.27147.82012.40181.77887.2596 1.86698.4137 3.02387.4137s2.245-.1541 3.02387-.4137c.39108-.13034.65958-.27262.82012-.40181.07881-.06342.12005-.11447.13994-.14763.00951-.01587.01336-.02631.01486-.03134.00076-.00255.00103-.00409.00113-.00474l.00007-.00065z" fill="#000" fill-rule="evenodd"/></svg>
        \ No newline at end of file
        <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z"></path></svg>
        # This is so your IDE knows about the syntax for fixes and autocomplete.
        $schema: https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json
        # The human readable name.
        name: Title
        # Status can be: "experimental", "stable", "deprecated", "obsolete".
        status: experimental
        # Schema for the props. We support www.json-schema.org. Learn more about the
        # syntax there.
        props:
        type: object
        properties:
        modifiers:
        type: array
        title: Modifier classes.
        description:
        Title modifiers.
        https://en.bem.info/methodology/css/#modifiers
        items:
        type: string
        enum:
        - ellipsis
        - xs
        extra_classes:
        type: array
        title: Extra classes.
        description:
        External modifiers added from the placement context.
        https://en.bem.info/methodology/css/#mixes
        items:
        type: string
        html_tag:
        type: string
        title: HTML tag for title
        # Limit the available options by using enums.
        enum:
        - h1
        - h2
        - h3
        - h4
        - h5
        - h6
        - span
        # Provide a default value
        default: h2
        icon:
        title: Icon
        type: string
        slots:
        content:
        title: Content
        description: Content of title.
        /*
        * DO NOT EDIT THIS FILE.
        * See the following change record for more information,
        * https://www.drupal.org/node/3084859
        * @preserve
        */
        /* cspell:ignore csvg cpath wght */
        /**
        * @file
        * Toolbar title styles.
        */
        .toolbar-title {
        font-variation-settings: "wght" 500;
        }
        /* Sizes aligned with variables from css/base/variables.pcss.css */
        .toolbar-title--xs {
        font-size: var(--admin-toolbar-font-size-heading-xs);
        }
        .toolbar-title--ellipsis .toolbar-title__label {
        overflow: hidden;
        max-width: var(--toolbar--title-max-width);
        white-space: nowrap;
        text-overflow: ellipsis;
        }
        /* Class starts with `toolbar-title--icon` */
        [class*="toolbar-title--icon"] {
        display: flex;
        align-items: center;
        gap: var(--admin-toolbar-space-8);
        }
        [class*="toolbar-title--icon"]::before {
        display: flex;
        flex-shrink: 0;
        align-items: center;
        justify-content: center;
        margin-top: 1px;
        content: "";
        color: currentColor;
        background-color: currentColor;
        inline-size: var(--admin-toolbar-space-16);
        block-size: var(--admin-toolbar-space-16);
        mask-repeat: no-repeat;
        mask-position: center center;
        mask-size: 100% auto;
        mask-image: var(--icon);
        }
        .toolbar-title--icon--file {
        --icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3e%3cpath d='M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z'%3e%3c/path%3e%3c/svg%3e");
        }
        .toolbar-title--icon--database {
        --icon: url("data:image/svg+xml,%3csvg fill='none' height='12' viewBox='0 0 12 12' width='12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath clip-rule='evenodd' d='m2 2.5c0 .00062.00009.00292.00153.00765.00169.00557.00588.01658.01601.03307.02122.03457.0645.08654.14558.15044.16509.13011.43496.26914.81301.39516.75088.25029 1.82002.41368 3.02387.41368 1.20386 0 2.27299-.16339 3.02387-.41368.37805-.12602.64792-.26505.81301-.39516.08108-.0639.12436-.11587.14558-.15044.01013-.01649.01431-.0275.01601-.03307.00146-.00479.00153-.00695.00153-.00754 0-.00003 0 .00002 0 0 0-.00036 0-.00258-.00153-.00776-.0017-.00557-.00588-.01658-.01601-.03307-.02122-.03457-.0645-.08654-.14558-.15044-.16509-.13011-.43496-.26914-.81301-.39516-.75088-.25029-1.82001-.41368-3.02387-.41368-1.20385 0-2.27299.16339-3.02387.41368-.37805.12602-.64792.26505-.81301.39516-.08108.0639-.12436.11587-.14558.15044-.01013.01649-.01432.0275-.01601.03307-.00154.00504-.00153.00721-.00153.00765zm8 1.2633c-.20061.10259-.42333.19284-.65991.2717-.8778.2926-2.05866.465-3.34009.465s-2.46229-.1724-3.34009-.465c-.23658-.07886-.4593-.16911-.65991-.2717v2.2367l.00008.00078c.0001.00065.00037.00219.00113.00474.0015.00503.00535.01547.01486.03134.01989.03316.06113.08421.13994.14763.16054.12919.42904.27147.82012.40183.77887.25962 1.86698.41368 3.02387.41368s2.245-.15406 3.02387-.41368c.39108-.13036.65958-.27264.82012-.40183.07881-.06342.12005-.11447.13994-.14763.00951-.01587.01336-.02631.01486-.03134.00047-.00159.00076-.00279.00093-.00362.0001-.00051.00016-.00088.0002-.00112l.00007-.00065zm1-1.2633c0-.42597-.2594-.75221-.5441-.97657-.2904-.22881-.67952-.413-1.11581-.558432-.8778-.2926-2.05866-.464998-3.34009-.464998s-2.46229.172398-3.34009.464998c-.43629.145432-.82543.329622-1.11576.558432-.2847.22436-.54415.5506-.54415.97657v7c0 .41262.2462.7359.52907.9636.28749.2313.678.4205 1.13084.5714.90895.303 2.11003.465 3.34009.465s2.43114-.162 3.34009-.465c.45283-.1509.84331-.3401 1.13081-.5714.2829-.2277.5291-.55098.5291-.9636zm-1 4.76299c-.19854.10174-.42052.19222-.65991.27201-.90895.30299-2.11003.465-3.34009.465s-2.43114-.16201-3.34009-.465c-.23939-.07979-.46137-.17027-.65991-.27201v2.23701l.00008.00078c.0001.00065.00037.00219.00113.00474.0015.00503.00535.01547.01486.03134.01989.03316.06113.08421.13994.14763.16054.12919.42904.27147.82012.40181.77887.2596 1.86698.4137 3.02387.4137s2.245-.1541 3.02387-.4137c.39108-.13034.65958-.27262.82012-.40181.07881-.06342.12005-.11447.13994-.14763.00951-.01587.01336-.02631.01486-.03134.00076-.00255.00103-.00409.00113-.00474l.00007-.00065z' fill='%23000' fill-rule='evenodd'/%3e%3c/svg%3e");
        }
        /* cspell:ignore csvg cpath wght */
        /**
        * @file
        * Toolbar title styles.
        */
        .toolbar-title {
        font-variation-settings: "wght" 500;
        }
        /* Sizes aligned with variables from css/base/variables.pcss.css */
        .toolbar-title--xs {
        font-size: var(--admin-toolbar-font-size-heading-xs);
        }
        .toolbar-title--ellipsis {
        .toolbar-title__label {
        overflow: hidden;
        max-width: var(--toolbar--title-max-width);
        white-space: nowrap;
        text-overflow: ellipsis;
        }
        }
        /* Class starts with `toolbar-title--icon` */
        [class*="toolbar-title--icon"] {
        display: flex;
        align-items: center;
        gap: var(--admin-toolbar-space-8);
        &::before {
        display: flex;
        flex-shrink: 0;
        align-items: center;
        justify-content: center;
        margin-top: 1px;
        content: "";
        color: currentColor;
        background-color: currentColor;
        inline-size: var(--admin-toolbar-space-16);
        block-size: var(--admin-toolbar-space-16);
        mask-repeat: no-repeat;
        mask-position: center center;
        mask-size: 100% auto;
        mask-image: var(--icon);
        }
        }
        .toolbar-title--icon--file {
        --icon: url(./assets/file.svg);
        }
        .toolbar-title--icon--database {
        --icon: url(./assets/database.svg);
        }
        {%
        set classes = [
        'toolbar-title',
        icon ? 'toolbar-title--icon--' ~ icon : '',
        ]
        %}
        {% if modifiers is iterable %}
        {% set classes = classes|merge(modifiers|map(modifier => "toolbar-title--#{modifier}")) %}
        {% endif %}
        {% if extra_classes is iterable %}
        {% set classes = classes|merge(extra_classes) %}
        {% endif %}
        <{{html_tag|default('h2')}}{{attributes.addClass(classes)}}>
        <span class="toolbar-title__label">
        {% block content %}
        {{ content }}
        {% endblock %}
        </span>
        </{{html_tag|default('h2')}}>
        ......@@ -27,9 +27,8 @@
        }
        .toolbar-button {
        z-index: 1;
        flex-grow: 0;
        align-items: center;
        padding-inline: var(--admin-toolbar-space-16);
        padding-block: var(--admin-toolbar-space-10);
        min-height: var(--admin-toolbar-space-40);
        cursor: pointer;
        text-align: start;
        ......@@ -41,8 +40,10 @@
        border-radius: var(--admin-toolbar-space-8);
        background-color: var(--toolbar-button-bg);
        font-size: var(--admin-toolbar-font-size-info-sm);
        font-variation-settings: "wght" 700;
        line-height: var(--admin-toolbar-line-height-info-sm);
        padding-inline: var(--admin-toolbar-space-16);
        padding-block: var(--admin-toolbar-space-10);
        font-variation-settings: "wght" 700;
        gap: calc(0.5 * var(--admin-toolbar-rem));
        }
        .toolbar-button:has(+ .toolbar-popover__wrapper .is-active) {
        ......
        ......@@ -25,9 +25,8 @@
        .toolbar-button {
        z-index: 1;
        flex-grow: 0;
        align-items: center;
        padding-inline: var(--admin-toolbar-space-16);
        padding-block: var(--admin-toolbar-space-10);
        min-height: var(--admin-toolbar-space-40);
        cursor: pointer;
        text-align: start;
        ......@@ -38,8 +37,10 @@
        border-radius: var(--admin-toolbar-space-8);
        background-color: var(--toolbar-button-bg);
        font-size: var(--admin-toolbar-font-size-info-sm);
        font-variation-settings: "wght" 700;
        line-height: var(--admin-toolbar-line-height-info-sm);
        padding-inline: var(--admin-toolbar-space-16);
        padding-block: var(--admin-toolbar-space-10);
        font-variation-settings: "wght" 700;
        gap: calc(0.5 * var(--admin-toolbar-rem));
        &:has(+ .toolbar-popover__wrapper .is-active) {
        ......
        ......@@ -46,6 +46,7 @@
        }
        .top-bar__actions {
        display: flex;
        justify-content: end;
        gap: 0.5rem;
        }
        @media (min-width: 64rem) {
        ......@@ -56,9 +57,9 @@
        }
        .top-bar__content {
        display: grid;
        grid-auto-flow: column;
        grid-template-columns: 1fr auto 1fr;
        align-items: center;
        justify-content: space-between;
        justify-content: center;
        gap: var(--admin-toolbar-space-16);
        width: 100%;
        }
        ......@@ -73,9 +74,13 @@
        }
        .top-bar__context {
        display: flex;
        gap: 0.5rem;
        flex-wrap: wrap;
        align-items: center;
        justify-content: start;
        justify-content: center;
        gap: var(--admin-toolbar-space-20);
        }
        .top-bar__title {
        --toolbar--title-max-width: 40ch;
        }
        .top-bar__tools {
        display: flex;
        ......
        ......@@ -45,6 +45,7 @@
        .top-bar__actions {
        display: flex;
        justify-content: end;
        gap: 0.5rem;
        @media (--admin-toolbar-desktop) {
        ......@@ -55,9 +56,9 @@
        .top-bar__content {
        display: grid;
        grid-auto-flow: column;
        grid-template-columns: 1fr auto 1fr;
        align-items: center;
        justify-content: space-between;
        justify-content: center;
        gap: var(--admin-toolbar-space-16);
        width: 100%;
        }
        ......@@ -73,9 +74,14 @@
        .top-bar__context {
        display: flex;
        gap: 0.5rem;
        flex-wrap: wrap;
        align-items: center;
        justify-content: start;
        justify-content: center;
        gap: var(--admin-toolbar-space-20);
        }
        .top-bar__title {
        --toolbar--title-max-width: 40ch;
        }
        .top-bar__tools {
        ......
        ......@@ -9,7 +9,6 @@ services:
        '@module_handler',
        '@current_route_match',
        '@plugin.manager.menu.local_task',
        '@entity_type.manager',
        '@image.factory',
        '@file_url_generator',
        '@plugin.manager.layout_builder.section_storage',
        ......@@ -17,6 +16,7 @@ services:
        '@extension.list.module',
        '@current_user',
        '%renderer.config%',
        '@navigation.entity_route_helper',
        ]
        Drupal\navigation\NavigationRenderer: '@navigation.renderer'
        ......@@ -31,6 +31,16 @@ services:
        '@callable_resolver',
        ]
        navigation.entity_route_helper:
        class: Drupal\navigation\EntityRouteHelper
        arguments:
        [
        '@current_route_match',
        '@entity_type.manager',
        '@cache.discovery',
        ]
        Drupal\navigation\EntityRouteHelper: '@navigation.entity_route_helper'
        navigation.user_lazy_builder:
        class: Drupal\navigation\UserLazyBuilder
        arguments: ['@current_user']
        ......
        <?php
        declare(strict_types=1);
        namespace Drupal\navigation;
        use Drupal\Core\Cache\CacheBackendInterface;
        use Drupal\Core\Entity\ContentEntityInterface;
        use Drupal\Core\Entity\EntityTypeInterface;
        use Drupal\Core\Entity\EntityTypeManagerInterface;
        use Drupal\Core\Entity\FieldableEntityInterface;
        use Drupal\Core\Routing\CurrentRouteMatch;
        /**
        * Helper service to detect entity routes.
        *
        * @internal
        */
        class EntityRouteHelper {
        const ENTITY_ROUTE_CID = 'navigation_content_entity_paths';
        /**
        * A list of all the link paths of enabled content entities.
        *
        * @var array
        */
        protected array $contentEntityPaths;
        /**
        * EntityRouteHelper constructor.
        *
        * @param \Drupal\Core\Routing\CurrentRouteMatch $routeMatch
        * The route match.
        * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
        * The entity type manager.
        * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
        * The cache backend.
        */
        public function __construct(
        protected CurrentRouteMatch $routeMatch,
        protected EntityTypeManagerInterface $entityTypeManager,
        protected CacheBackendInterface $cacheBackend,
        ) {
        }
        /**
        * Determines if content entity route condition is met.
        *
        * @return bool
        * TRUE if the content entity route condition is met, FALSE otherwise.
        */
        public function isContentEntityRoute(): bool {
        return array_key_exists($this->routeMatch->getRouteObject()->getPath(), $this->getContentEntityPaths());
        }
        public function getContentEntityFromRoute(): ?ContentEntityInterface {
        $path = $this->routeMatch->getRouteObject()->getPath();
        if (!$entity_type = $this->getContentEntityPaths()[$path] ?? NULL) {
        return NULL;
        }
        $entity = $this->routeMatch->getParameter($entity_type);
        if ($entity instanceof ContentEntityInterface && $entity->getEntityTypeId() === $entity_type) {
        return $entity;
        }
        return NULL;
        }
        /**
        * Returns the paths for the link templates of all content entities.
        *
        * @return array
        * An array of all content entity type IDs, keyed by the corresponding link
        * template paths.
        */
        protected function getContentEntityPaths(): array {
        if (isset($this->contentEntityPaths)) {
        return $this->contentEntityPaths;
        }
        $content_entity_paths = $this->cacheBackend->get(static::ENTITY_ROUTE_CID);
        if (isset($content_entity_paths->data)) {
        $this->contentEntityPaths = $content_entity_paths->data;
        return $this->contentEntityPaths;
        }
        $this->contentEntityPaths = $this->doGetContentEntityPaths();
        $this->cacheBackend->set(static::ENTITY_ROUTE_CID, $this->contentEntityPaths, CacheBackendInterface::CACHE_PERMANENT, ['entity_types', 'routes']);
        return $this->contentEntityPaths;
        }
        protected function doGetContentEntityPaths(): array {
        $content_entity_paths = [];
        $entity_types = $this->entityTypeManager->getDefinitions();
        foreach ($entity_types as $entity_type) {
        if ($entity_type->entityClassImplements(ContentEntityInterface::class)) {
        $entity_paths = $this->getContentEntityTypePaths($entity_type);
        $content_entity_paths = array_merge($content_entity_paths, $entity_paths);
        }
        }
        return $content_entity_paths;
        }
        /**
        * Returns the path for the link template for a given content entity type.
        *
        * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
        * The entity type definition.
        *
        * @return array
        * Array containing the paths for the given content entity type.
        */
        protected function getContentEntityTypePaths(EntityTypeInterface $entity_type): array {
        $paths = array_filter($entity_type->getLinkTemplates(), fn ($template) => $template !== 'collection', ARRAY_FILTER_USE_KEY);
        if ($this->isLayoutBuilderEntityType($entity_type)) {
        $paths[] = $entity_type->getLinkTemplate('canonical') . '/layout';
        }
        return array_fill_keys($paths, $entity_type->id());
        }
        /**
        * Determines if a given entity type is layout builder relevant or not.
        *
        * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
        * The entity type.
        *
        * @return bool
        * Whether this entity type is a Layout builder candidate or not
        *
        * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getEntityTypes()
        */
        protected function isLayoutBuilderEntityType(EntityTypeInterface $entity_type): bool {
        return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasHandlerClass('form', 'layout_builder') && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
        }
        }
        ......@@ -5,13 +5,9 @@
        use Drupal\Component\Utility\NestedArray;
        use Drupal\Component\Utility\SortArray;
        use Drupal\Core\Block\BlockPluginInterface;
        use Drupal\Core\Cache\CacheBackendInterface;
        use Drupal\Core\Cache\CacheableMetadata;
        use Drupal\Core\Cache\CacheBackendInterface;
        use Drupal\Core\Config\ConfigFactoryInterface;
        use Drupal\Core\Entity\ContentEntityInterface;
        use Drupal\Core\Entity\EntityTypeInterface;
        use Drupal\Core\Entity\EntityTypeManagerInterface;
        use Drupal\Core\Entity\FieldableEntityInterface;
        use Drupal\Core\Extension\ModuleExtensionList;
        use Drupal\Core\Extension\ModuleHandlerInterface;
        use Drupal\Core\File\FileUrlGeneratorInterface;
        ......@@ -48,13 +44,6 @@ final class NavigationRenderer {
        */
        const LOGO_PROVIDER_CUSTOM = 'custom';
        /**
        * A list of all the link paths of enabled content entities.
        *
        * @var array
        */
        protected array $contentEntityPaths;
        /**
        * The navigation local tasks render array.
        *
        ......@@ -70,7 +59,6 @@ public function __construct(
        private ModuleHandlerInterface $moduleHandler,
        private RouteMatchInterface $routeMatch,
        private LocalTaskManagerInterface $localTaskManager,
        private EntityTypeManagerInterface $entityTypeManager,
        private ImageFactory $imageFactory,
        private FileUrlGeneratorInterface $fileUrlGenerator,
        private SectionStorageManagerInterface $sectionStorageManager,
        ......@@ -78,6 +66,7 @@ public function __construct(
        private ModuleExtensionList $moduleExtensionList,
        private AccountInterface $currentUser,
        private array $rendererConfig,
        private EntityRouteHelper $entityRouteHelper,
        ) {}
        /**
        ......@@ -271,7 +260,7 @@ public function getLocalTasks(): array {
        ];
        // For now, we're only interested in local tasks corresponding to a content
        // entity.
        if (!$this->meetsContentEntityRoutesCondition()) {
        if (!$this->entityRouteHelper->isContentEntityRoute()) {
        return $this->localTasks;
        }
        $entity_local_tasks = $this->localTaskManager->getLocalTasks($this->routeMatch->getRouteName());
        ......@@ -315,70 +304,4 @@ public function hasLocalTasks(): bool {
        return !empty($local_tasks['tasks']);
        }
        /**
        * Determines if content entity route condition is met.
        *
        * @return bool
        * TRUE if the content entity route condition is met, FALSE otherwise.
        */
        protected function meetsContentEntityRoutesCondition(): bool {
        return array_key_exists($this->routeMatch->getRouteObject()->getPath(), $this->getContentEntityPaths());
        }
        /**
        * Returns the paths for the link templates of all content entities.
        *
        * @return array
        * An array of all content entity type IDs, keyed by the corresponding link
        * template paths.
        */
        protected function getContentEntityPaths(): array {
        if (isset($this->contentEntityPaths)) {
        return $this->contentEntityPaths;
        }
        $this->contentEntityPaths = [];
        $entity_types = $this->entityTypeManager->getDefinitions();
        foreach ($entity_types as $entity_type) {
        if ($entity_type->entityClassImplements(ContentEntityInterface::class)) {
        $entity_paths = $this->getContentEntityTypePaths($entity_type);
        $this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths);
        }
        }
        return $this->contentEntityPaths;
        }
        /**
        * Returns the path for the link template for a given content entity type.
        *
        * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
        * The entity type definition.
        *
        * @return array
        * Array containing the paths for the given content entity type.
        */
        protected function getContentEntityTypePaths(EntityTypeInterface $entity_type): array {
        $paths = array_filter($entity_type->getLinkTemplates(), fn ($template) => $template !== 'collection', ARRAY_FILTER_USE_KEY);
        if ($this->isLayoutBuilderEntityType($entity_type)) {
        $paths[] = $entity_type->getLinkTemplate('canonical') . '/layout';
        }
        return array_fill_keys($paths, $entity_type->id());
        }
        /**
        * Determines if a given entity type is layout builder relevant or not.
        *
        * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
        * The entity type.
        *
        * @return bool
        * Whether this entity type is a Layout builder candidate or not
        *
        * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getEntityTypes()
        */
        protected function isLayoutBuilderEntityType(EntityTypeInterface $entity_type): bool {
        return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasHandlerClass('form', 'layout_builder') && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
        }
        }
        <?php
        declare(strict_types=1);
        namespace Drupal\navigation\Plugin\TopBarItem;
        use Drupal\Core\Entity\EntityInterface;
        use Drupal\Core\Entity\EntityPublishedInterface;
        use Drupal\Core\Entity\EntityTypeManagerInterface;
        use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
        use Drupal\Core\StringTranslation\StringTranslationTrait;
        use Drupal\Core\StringTranslation\TranslatableMarkup;
        use Drupal\navigation\Attribute\TopBarItem;
        use Drupal\navigation\EntityRouteHelper;
        use Drupal\navigation\TopBarItemBase;
        use Drupal\navigation\TopBarRegion;
        use Symfony\Component\DependencyInjection\ContainerInterface;
        /**
        * Provides the Page Context top bar item.
        */
        #[TopBarItem(
        id: 'page_context',
        region: TopBarRegion::Context,
        label: new TranslatableMarkup('Page Context'),
        )]
        class PageContext extends TopBarItemBase implements ContainerFactoryPluginInterface {
        use StringTranslationTrait;
        /**
        * Constructs a new PageContext instance.
        *
        * @param array $configuration
        * A configuration array containing information about the plugin instance.
        * @param string $plugin_id
        * The plugin ID for the plugin instance.
        * @param mixed $plugin_definition
        * The plugin implementation definition.
        * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
        * The entity type manager service.
        * @param \Drupal\navigation\EntityRouteHelper $entityRouteHelper
        * The entity route helper service.
        */
        public function __construct(
        array $configuration,
        $plugin_id,
        $plugin_definition,
        private EntityTypeManagerInterface $entityTypeManager,
        private EntityRouteHelper $entityRouteHelper,
        ) {
        parent::__construct($configuration, $plugin_id, $plugin_definition);
        }
        /**
        * {@inheritdoc}
        */
        public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
        return new static(
        $configuration,
        $plugin_id,
        $plugin_definition,
        $container->get(EntityTypeManagerInterface::class),
        $container->get(EntityRouteHelper::class)
        );
        }
        /**
        * {@inheritdoc}
        */
        public function build(): array {
        $build = [
        '#cache' => [
        'contexts' => ['route'],
        ],
        ];
        if (!$entity = $this->entityRouteHelper->getContentEntityFromRoute()) {
        return $build;
        }
        $build += [
        [
        '#type' => 'component',
        '#component' => 'navigation:title',
        '#props' => [
        'icon' => 'database',
        'html_tag' => 'span',
        'modifiers' => ['ellipsis', 'xs'],
        'extra_classes' => ['top-bar__title'],
        ],
        '#slots' => [
        'content' => $entity->label(),
        ],
        ],
        ];
        if ($label = $this->getBadgeLabel($entity)) {
        $build += [
        '#type' => 'component',
        '#component' => 'navigation:badge',
        '#props' => [
        'status' => $this->getBadgeStatus($entity) ?? 'info',
        ],
        '#slots' => [
        'label' => $label,
        ],
        ];
        }
        return $build;
        }
        /**
        * Retrieves the badge label for the given entity.
        *
        * @param \Drupal\Core\Entity\EntityInterface $entity
        * The entity for which the label is being retrieved.
        *
        * @return string|null
        * The translated status if available. NULL otherwise.
        * The status if available. NULL otherwise.
        */
        protected function getBadgeLabel(EntityInterface $entity): ?string {
        if (!$entity instanceof EntityPublishedInterface) {
        return NULL;
        }
        return (string) ($entity->isPublished() ? $this->t('Published') : $this->t('Unpublished'));
        }
        /**
        * Retrieves the badge status for the given entity.
        *
        * @param \Drupal\Core\Entity\EntityInterface $entity
        * The entity for which the status is being retrieved.
        *
        * @return string|null
        * The badge status if available. NULL otherwise.
        */
        protected function getBadgeStatus(EntityInterface $entity): ?string {
        if (!$entity instanceof EntityPublishedInterface) {
        return NULL;
        }
        return $entity->isPublished() ? 'success' : 'info';
        }
        }
        <?php
        declare(strict_types=1);
        namespace Drupal\Tests\navigation\Functional;
        use Drupal\Core\Url;
        use Drupal\Tests\BrowserTestBase;
        use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
        use Drupal\Tests\node\Traits\NodeCreationTrait;
        use Drupal\user\UserInterface;
        /**
        * Tests the PageContext top bar item functionality.
        *
        * @group navigation
        */
        class NavigationTopBarPageContextTest extends BrowserTestBase {
        use ContentTypeCreationTrait;
        use NodeCreationTrait;
        /**
        * {@inheritdoc}
        */
        protected static $modules = [
        'node',
        'navigation',
        'navigation_top_bar',
        'test_page_test',
        ];
        /**
        * {@inheritdoc}
        */
        protected $defaultTheme = 'stark';
        /**
        * An admin user to configure the test environment.
        *
        * @var \Drupal\user\UserInterface
        */
        protected UserInterface $adminUser;
        /**
        * {@inheritdoc}
        */
        protected function setUp(): void {
        parent::setUp();
        // Create and log in an administrative user.
        $this->adminUser = $this->drupalCreateUser([
        'access navigation',
        'bypass node access',
        ]);
        $this->drupalLogin($this->adminUser);
        // Ensure the 'article' content type exists.
        $this->createContentType(['type' => 'article', 'name' => 'Article']);
        }
        /**
        * Tests the PageContext top bar item output for a published node.
        */
        public function testPageContextTopBarItemNode(): void {
        // Create a published node entity.
        $node = $this->createNode([
        'type' => 'article',
        'title' => 'No easy twist on the bow',
        'status' => 1,
        'uid' => $this->adminUser->id(),
        ]);
        $test_page_url = Url::fromRoute('test_page_test.test_page');
        $this->drupalGet($test_page_url);
        // Ensure the top bar item is not present.
        $this->assertSession()->elementNotExists('css', '.top-bar .top-bar__context .toolbar-title');
        // Test the PageContext output for the published node.
        $this->drupalGet($node->toUrl());
        // Ensure the top bar exists and is valid.
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Published');
        $this->drupalGet($node->toUrl('edit-form'));
        // Ensure the top bar exists and is valid.
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Published');
        // Unpublish the node.
        $node->setUnpublished();
        $node->save();
        // Test the PageContext output for the unpublished node.
        $this->drupalGet($node->toUrl());
        // Ensure the top bar exists and is valid.
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Unpublished');
        $this->drupalGet($node->toUrl('edit-form'));
        // Ensure the top bar exists and is valid.
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
        $this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Unpublished');
        }
        }
        ......@@ -73,11 +73,11 @@ public function testLogin(): void {
        $expected = [
        'QueryCount' => 4,
        'CacheGetCount' => 60,
        'CacheGetCount' => 61,
        'CacheSetCount' => 2,
        'CacheDeleteCount' => 0,
        'CacheTagChecksumCount' => 2,
        'CacheTagIsValidCount' => 29,
        'CacheTagChecksumCount' => 3,
        'CacheTagIsValidCount' => 31,
        'CacheTagInvalidationCount' => 0,
        'ScriptCount' => 2,
        'ScriptBytes' => 215500,
        ......
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Finish editing this message first!
        Please register or to comment