diff --git a/core/modules/layout_builder/css/layout-builder.css b/core/modules/layout_builder/css/layout-builder.css index 4c547e9..11d8c55 100644 --- a/core/modules/layout_builder/css/layout-builder.css +++ b/core/modules/layout_builder/css/layout-builder.css @@ -40,20 +40,12 @@ background-image: url(../../../misc/icons/787878/ex.svg); } -#drupal-off-canvas details.layout-selection { - background-color: transparent; -} - -#drupal-off-canvas details.layout-selection summary { - margin-bottom: 1em; -} - -#drupal-off-canvas details.layout-selection li { +#drupal-off-canvas .layout-selection li { display: block; padding-bottom: 1em; } -#drupal-off-canvas details.layout-selection li a { +#drupal-off-canvas .layout-selection li a { display: block; padding-top: 0.55em; } diff --git a/core/modules/layout_builder/js/layout-builder.es6.js b/core/modules/layout_builder/js/layout-builder.es6.js index 150f9a6..391e462 100644 --- a/core/modules/layout_builder/js/layout-builder.es6.js +++ b/core/modules/layout_builder/js/layout-builder.es6.js @@ -14,21 +14,20 @@ * An object containing information about the item being sorted. */ update(event, ui) { - const data = { - region_to: $(this).data('region'), - block_uuid: ui.item.data('layout-block-uuid'), - delta_to: ui.item.closest('[data-layout-delta]').data('layout-delta'), - preceding_block_uuid: ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid'), - }; - // Only process if the item was moved from one region to another. if (ui.sender) { - data.region_from = ui.sender.data('region'); - data.delta_from = ui.sender.closest('[data-layout-delta]').data('layout-delta'); - ajax({ - url: ui.item.closest('[data-layout-update-url]').data('layout-update-url'), - submit: data, + url: [ + ui.item.closest('[data-layout-update-url]').data('layout-update-url'), + ui.sender.closest('[data-layout-delta]').data('layout-delta'), + ui.item.closest('[data-layout-delta]').data('layout-delta'), + ui.sender.data('region'), + $(this).data('region'), + ui.item.data('layout-block-uuid'), + ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid'), + ] + .filter(element => element !== undefined) + .join('/'), }).execute(); } }, diff --git a/core/modules/layout_builder/js/layout-builder.js b/core/modules/layout_builder/js/layout-builder.js index e9f6558..d4dadc8 100644 --- a/core/modules/layout_builder/js/layout-builder.js +++ b/core/modules/layout_builder/js/layout-builder.js @@ -16,20 +16,11 @@ connectWith: '.layout-builder--layout__region', update: function update(event, ui) { - var data = { - region_to: $(this).data('region'), - block_uuid: ui.item.data('layout-block-uuid'), - delta_to: ui.item.closest('[data-layout-delta]').data('layout-delta'), - preceding_block_uuid: ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid') - }; - if (ui.sender) { - data.region_from = ui.sender.data('region'); - data.delta_from = ui.sender.closest('[data-layout-delta]').data('layout-delta'); - ajax({ - url: ui.item.closest('[data-layout-update-url]').data('layout-update-url'), - submit: data + url: [ui.item.closest('[data-layout-update-url]').data('layout-update-url'), ui.sender.closest('[data-layout-delta]').data('layout-delta'), ui.item.closest('[data-layout-delta]').data('layout-delta'), ui.sender.data('region'), $(this).data('region'), ui.item.data('layout-block-uuid'), ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid')].filter(function (element) { + return element !== undefined; + }).join('/') }).execute(); } } diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index 12545bc..e8d8a11 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -44,7 +44,7 @@ function layout_builder_form_entity_view_display_edit_form_alter(&$form, FormSta '#tree' => TRUE, ]; // @todo Unchecking this box is a destructive action, this should be made - // clear to the user. + // clear to the user in https://www.drupal.org/node/2914484. $form['layout']['allow_custom'] = [ '#type' => 'checkbox', '#title' => t('Allow each @entity to have its layout customized.', [ diff --git a/core/modules/layout_builder/layout_builder.permissions.yml b/core/modules/layout_builder/layout_builder.permissions.yml index 1fca7af..00aac63 100644 --- a/core/modules/layout_builder/layout_builder.permissions.yml +++ b/core/modules/layout_builder/layout_builder.permissions.yml @@ -1,4 +1,5 @@ -# @todo Expand permissions to be more granular. +# @todo Expand permissions to be more granular in +# https://www.drupal.org/node/2914486. configure any layout: title: 'Configure any layout' restrict access: true diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml index 78e7410..27a234a 100644 --- a/core/modules/layout_builder/layout_builder.routing.yml +++ b/core/modules/layout_builder/layout_builder.routing.yml @@ -107,10 +107,15 @@ layout_builder.remove_block: layout_builder_tempstore: TRUE layout_builder.move_block: - path: '/layout_builder/move/block/{entity_type_id}/{entity}' + path: '/layout_builder/move/block/{entity_type_id}/{entity}/{delta_from}/{delta_to}/{region_from}/{region_to}/{block_uuid}/{preceding_block_uuid}' defaults: _controller: '\Drupal\layout_builder\Controller\MoveBlockController::build' - methods: [POST] + delta_from: null + delta_to: null + region_from: null + region_to: null + block_uuid: null + preceding_block_uuid: null requirements: _permission: 'configure any layout' options: diff --git a/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php b/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php index d756ca3..e3833cc 100644 --- a/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php +++ b/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php @@ -43,7 +43,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager) { * The access result. */ public function access(RouteMatchInterface $route_match, AccountInterface $account) { - // Attempt to retrive the generic 'entity' parameter, otherwise look up the + // Attempt to retrieve the generic 'entity' parameter, otherwise look up the // specific entity via the entity type ID. $entity = $route_match->getParameter('entity') ?: $route_match->getParameter($route_match->getParameter('entity_type_id')); diff --git a/core/modules/layout_builder/src/Controller/AddSectionController.php b/core/modules/layout_builder/src/Controller/AddSectionController.php index 5f6b9ce..117bb7f6 100644 --- a/core/modules/layout_builder/src/Controller/AddSectionController.php +++ b/core/modules/layout_builder/src/Controller/AddSectionController.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\RequestStack; /** - * @todo. + * Defines a controller to add a new section. */ class AddSectionController implements ContainerInjectionInterface { diff --git a/core/modules/layout_builder/src/Controller/ChooseBlockController.php b/core/modules/layout_builder/src/Controller/ChooseBlockController.php index 3d2ff8b..5ff7d4e 100644 --- a/core/modules/layout_builder/src/Controller/ChooseBlockController.php +++ b/core/modules/layout_builder/src/Controller/ChooseBlockController.php @@ -10,7 +10,7 @@ use Symfony\Component\HttpFoundation\RequestStack; /** - * @todo. + * Defines a controller to choose a new block. */ class ChooseBlockController implements ContainerInjectionInterface { diff --git a/core/modules/layout_builder/src/Controller/ChooseSectionController.php b/core/modules/layout_builder/src/Controller/ChooseSectionController.php index 7055f6e..0ecd28b 100644 --- a/core/modules/layout_builder/src/Controller/ChooseSectionController.php +++ b/core/modules/layout_builder/src/Controller/ChooseSectionController.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\RequestStack; /** - * @todo. + * Defines a controller to choose a new section. */ class ChooseSectionController implements ContainerInjectionInterface { @@ -61,7 +61,8 @@ public static function create(ContainerInterface $container) { * The render array. */ public function build(EntityInterface $entity, $delta) { - $output = []; + $output['#title'] = $this->t('Choose a layout'); + $items = []; foreach ($this->layoutManager->getDefinitions() as $plugin_id => $definition) { $layout = $this->layoutManager->createInstance($plugin_id); @@ -91,25 +92,14 @@ public function build(EntityInterface $entity, $delta) { } $items[] = $item; } - // @todo Look into removing this details wrapper, or rewording the title. $output['layouts'] = [ - '#type' => 'details', - '#title' => $this->t('Basic Layouts'), - '#open' => TRUE, + '#theme' => 'item_list', + '#items' => $items, '#attributes' => [ 'class' => [ 'layout-selection', ], ], - 'list' => [ - '#theme' => 'item_list', - '#items' => $items, - '#attributes' => [ - 'class' => [ - 'layout-list', - ], - ], - ], ]; return $output; diff --git a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php index 0195599..abf35e1 100644 --- a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php +++ b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php @@ -16,7 +16,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; /** - * @todo. + * Defines a controller to provide the Layout Builder admin UI. */ class LayoutBuilderController implements ContainerInjectionInterface { @@ -225,6 +225,7 @@ protected function buildAdministrativeSection(LayoutSectionItemInterface $item, ]; $build[$region]['layout_builder_add_block']['#type'] = 'container'; $build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['add-block']]; + $build[$region]['layout_builder_add_block']['#weight'] = -1000; $build[$region]['#attributes']['data-region'] = $region; $build[$region]['#attributes']['class'][] = 'layout-builder--layout__region'; } diff --git a/core/modules/layout_builder/src/Controller/MoveBlockController.php b/core/modules/layout_builder/src/Controller/MoveBlockController.php index b1b771d..33e7570 100644 --- a/core/modules/layout_builder/src/Controller/MoveBlockController.php +++ b/core/modules/layout_builder/src/Controller/MoveBlockController.php @@ -11,7 +11,7 @@ use Symfony\Component\HttpFoundation\RequestStack; /** - * @todo. + * Defines a controller to move a block. */ class MoveBlockController implements ContainerInjectionInterface { @@ -63,27 +63,29 @@ public static function create(ContainerInterface $container) { * @return \Drupal\Core\Ajax\AjaxResponse * An AJAX response. */ - public function build(EntityInterface $entity, Request $request) { - // @todo Either enforce the presence of each part of $data, or convert this - // to use URL parameters. - $data = $request->request->all(); - + public function build(EntityInterface $entity, Request $request, $delta_from, $delta_to, $region_from, $region_to, $block_uuid, $preceding_block_uuid = NULL) { /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */ - $field = $entity->layout_builder__layout->get($data['delta_from']); + $field = $entity->layout_builder__layout->get($delta_from); $values = $field->section; - $region_from = $data['region_from']; - $region_to = $data['region_to']; - $block_uuid = $data['block_uuid']; $configuration = $values[$region_from][$block_uuid]; unset($values[$region_from][$block_uuid]); $field->section = array_filter($values); /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */ - $field = $entity->layout_builder__layout->get($data['delta_to']); + $field = $entity->layout_builder__layout->get($delta_to); $values = $field->section; - if (isset($data['preceding_block_uuid'])) { - $slice_id = array_search($data['preceding_block_uuid'], array_keys($values[$region_to])); + + if (isset($preceding_block_uuid)) { + if (!isset($values[$region_to])) { + throw new \InvalidArgumentException('Invalid region'); + } + + $slice_id = array_search($preceding_block_uuid, array_keys($values[$region_to])); + if ($slice_id === FALSE) { + throw new \InvalidArgumentException('Invalid preceeding block UUID'); + } + $before = array_slice($values[$region_to], 0, $slice_id + 1); $after = array_slice($values[$region_to], $slice_id + 1); $values[$region_to] = array_merge($before, [$block_uuid => $configuration], $after); diff --git a/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php index ab7aae7..136073b 100644 --- a/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php +++ b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php @@ -78,13 +78,13 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) 'layout_settings' => [ 'type' => 'blob', 'size' => 'normal', - // @todo Is this okay? + // @todo Address in https://www.drupal.org/node/2914503. 'serialize' => TRUE, ], 'section' => [ 'type' => 'blob', 'size' => 'normal', - // @todo Is this okay? + // @todo Address in https://www.drupal.org/node/2914503. 'serialize' => TRUE, ], ], diff --git a/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php b/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php index d2feff1..cddbdde 100644 --- a/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php +++ b/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php @@ -16,9 +16,7 @@ class LayoutBuilderLocalTask extends LocalTaskDefault { public function getRouteParameters(RouteMatchInterface $route_match) { $parameters = parent::getRouteParameters($route_match); - // @todo This assumes that the route match contains a valid entity, - // investigate whether that assumption is safe, or if this code is even - // needed. + // @todo Revisit this code once https://www.drupal.org/node/2912363 is in. $parameters['entity'] = $route_match->getParameter('entity'); return $parameters; } diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php index 0faca8f..840c670 100644 --- a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php +++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php @@ -16,16 +16,15 @@ class LayoutBuilderRouteEnhancer implements RouteEnhancerInterface { * {@inheritdoc} */ public function applies(Route $route) { - // Find layout builder routes that override existing paths. - return $route->hasOption('_layout_builder'); + return $route->getOption('_layout_builder') && $route->getDefault('entity_type_id'); } /** * {@inheritdoc} */ public function enhance(array $defaults, Request $request) { - if (!isset($defaults['entity_type_id'])) { - throw new \RuntimeException(sprintf('Failed to find the entity type ID in route named %s', $defaults[RouteObjectInterface::ROUTE_NAME])); + if (!isset($defaults[$defaults['entity_type_id']])) { + throw new \RuntimeException(sprintf('Failed to find the "%s" entity in route named %s', $defaults['entity_type_id'], $defaults[RouteObjectInterface::ROUTE_NAME])); } // Copy the entity by reference so that any changes are reflected. diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRouteEnhancerTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRouteEnhancerTest.php new file mode 100644 index 0000000..6e449c6 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRouteEnhancerTest.php @@ -0,0 +1,133 @@ +applies($route); + $this->assertSame($expected, $result); + } + + /** + * Provides test data for ::testApplies(). + */ + public function providerTestApplies() { + $data = []; + $data['layout_builder_true'] = [ + ['entity_type_id' => 'the_entity_type'], + ['_layout_builder' => TRUE], + TRUE, + ]; + $data['layout_builder_false'] = [ + ['entity_type_id' => 'the_entity_type'], + ['_layout_builder' => FALSE], + FALSE, + ]; + $data['layout_builder_null'] = [ + ['entity_type_id' => 'the_entity_type'], + ['_layout_builder' => NULL], + FALSE, + ]; + $data['entity_type_id_empty'] = [ + ['entity_type_id' => ''], + ['_layout_builder' => TRUE], + FALSE, + ]; + $data['no_entity_type_id'] = [ + [], + ['_layout_builder' => TRUE], + FALSE, + ]; + $data['no_layout_builder'] = [ + ['entity_type_id' => 'the_entity_type'], + [], + FALSE, + ]; + $data['empty'] = [ + [], + [], + FALSE, + ]; + return $data; + } + + /** + * @covers ::enhance + */ + public function testEnhanceValidDefaults() { + $route_enhancer = new LayoutBuilderRouteEnhancer(); + $object = new \stdClass(); + $defaults = [ + 'entity_type_id' => 'the_entity_type', + 'the_entity_type' => $object, + ]; + // Ensure that the 'entity' key now contains the value stored for a given + // entity type. + $expected = [ + 'entity_type_id' => 'the_entity_type', + 'the_entity_type' => $object, + 'entity' => $object, + ]; + + $result = $route_enhancer->enhance($defaults, new Request()); + $this->assertEquals($expected, $result); + $this->assertSame($object, $result['entity']); + + // Modifying the original value updates the 'entity' copy. + $result['the_entity_type'] = 'something else'; + $this->assertSame('something else', $result['entity']); + } + + /** + * @covers ::enhance + */ + public function testEnhanceMissingEntity() { + $route_enhancer = new LayoutBuilderRouteEnhancer(); + $defaults = [ + RouteObjectInterface::ROUTE_NAME => 'the_route', + 'entity_type_id' => 'the_entity_type', + ]; + $this->setExpectedException(\RuntimeException::class, 'Failed to find the "the_entity_type" entity in route named the_route'); + $route_enhancer->enhance($defaults, new Request()); + } + + /** + * Provides test data for ::testEnhanceException(). + */ + public function providerTestEnhanceException() { + $data = []; + $data['missing_entity'] = [ + [ + RouteObjectInterface::ROUTE_NAME => 'the_route', + 'entity_type_id' => 'the_entity_type', + ], + 'Failed to find the "the_entity_type" entity in route named the_route', + ]; + $data['missing_entity_type_id'] = [ + [ + RouteObjectInterface::ROUTE_NAME => 'the_route', + ], + 'Failed to find an entity type ID in route named the_route', + ]; + return $data; + } + +} diff --git a/core/modules/settings_tray/css/off-canvas.reset.css b/core/modules/settings_tray/css/off-canvas.reset.css index 796da64..ee12792 100644 --- a/core/modules/settings_tray/css/off-canvas.reset.css +++ b/core/modules/settings_tray/css/off-canvas.reset.css @@ -13,8 +13,7 @@ #drupal-off-canvas *:not(svg *), #drupal-off-canvas *:after, #drupal-off-canvas *:before { - /* @todo This breaks SVGs, fix in https://www.drupal.org/node/2907420. */ - /*all: initial;*/ + all: initial; box-sizing: border-box; text-shadow: none; -webkit-font-smoothing: antialiased;