Unverified Commit 6cb23978 authored by alexpott's avatar alexpott

Issue #2938132 by tedbow, tim.plunkett, xjm, EclipseGc, AaronMcHale, alexpott,...

Issue #2938132 by tedbow, tim.plunkett, xjm, EclipseGc, AaronMcHale, alexpott, DyanneNova: Ship layouts that make sense with Layout Builder's concept of sections
parent a013f053
layout_twocol_section:
label: 'Two column'
path: layouts/twocol_section
template: layout--twocol-section
library: layout_builder/twocol_section
class: '\Drupal\layout_builder\Plugin\Layout\TwoColumnLayout'
category: 'Columns: 2'
default_region: first
icon_map:
- [first, second]
regions:
first:
label: First
second:
label: Second
layout_threecol_section:
label: 'Three column'
path: layouts/threecol_section
template: layout--threecol-section
library: layout_builder/threecol_section
class: '\Drupal\layout_builder\Plugin\Layout\ThreeColumnLayout'
category: 'Columns: 3'
default_region: second
icon_map:
- [first, second, third]
regions:
first:
label: First
second:
label: Second
third:
label: Third
layout_fourcol_section:
label: 'Four column'
path: layouts/fourcol_section
template: layout--fourcol-section
library: layout_builder/fourcol_section
category: 'Columns: 4'
default_region: first
icon_map:
- [first, second, third, fourth]
regions:
first:
label: First
second:
label: Second
third:
label: Third
fourth:
label: Fourth
......@@ -8,3 +8,20 @@ drupal.layout_builder:
dependencies:
- core/jquery.ui.sortable
- core/drupal.dialog.off_canvas
# CSS libraries for Layout plugins.
twocol_section:
version: VERSION
css:
theme:
layouts/twocol_section/twocol_section.css: {}
threecol_section:
version: VERSION
css:
theme:
layouts/threecol_section/threecol_section.css: {}
fourcol_section:
version: VERSION
css:
theme:
layouts/fourcol_section/fourcol_section.css: {}
......@@ -234,3 +234,35 @@ function layout_builder_layout_builder_section_storage_alter(array &$definitions
$definitions['overrides']->getContextDefinition('entity')
->addConstraint('EntityHasField', OverridesSectionStorage::FIELD_NAME);
}
/**
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
*/
function layout_builder_plugin_filter_layout__layout_builder_alter(array &$definitions, array $extra) {
// Remove layouts provide by layout discovery that are not needed because of
// layouts provided by this module.
$duplicate_layouts = [
'layout_twocol',
'layout_twocol_bricks',
'layout_threecol_25_50_25',
'layout_threecol_33_34_33',
];
foreach ($duplicate_layouts as $duplicate_layout) {
/** @var \Drupal\Core\Layout\LayoutDefinition[] $definitions */
if (isset($definitions[$duplicate_layout])) {
if ($definitions[$duplicate_layout]->getProvider() === 'layout_discovery') {
unset($definitions[$duplicate_layout]);
}
}
}
// Move the one column layout to the top.
if (isset($definitions['layout_onecol']) && $definitions['layout_onecol']->getProvider() === 'layout_discovery') {
$one_col = $definitions['layout_onecol'];
unset($definitions['layout_onecol']);
$definitions = [
'layout_onecol' => $one_col,
] + $definitions;
}
}
/*
* @file
* Provides the layout styles for four-column layout section.
*/
.layout--fourcol-section {
display: flex;
flex-wrap: wrap;
}
.layout--fourcol-section > .layout__region {
flex: 0 1 100%;
}
@media screen and (min-width: 40em) {
.layout--fourcol-section > .layout__region {
flex: 0 1 25%;
}
}
{#
/**
* @file
* Default theme implementation for a four-column 25%-25%-25%-25% layout.
*
* Available variables:
* - content: The content for this layout.
* - attributes: HTML attributes for the layout <div>.
*
* @ingroup themeable
*/
#}
{%
set classes = [
'layout',
'layout--fourcol-section',
]
%}
{% if content %}
<div{{ attributes.addClass(classes) }}>
{% if content.first %}
<div {{ region_attributes.first.addClass('layout__region', 'layout__region--first') }}>
{{ content.first }}
</div>
{% endif %}
{% if content.second %}
<div {{ region_attributes.second.addClass('layout__region', 'layout__region--second') }}>
{{ content.second }}
</div>
{% endif %}
{% if content.third %}
<div {{ region_attributes.third.addClass('layout__region', 'layout__region--third') }}>
{{ content.third }}
</div>
{% endif %}
{% if content.fourth %}
<div {{ region_attributes.fourth.addClass('layout__region', 'layout__region--fourth') }}>
{{ content.fourth }}
</div>
{% endif %}
</div>
{% endif %}
{#
/**
* @file
* Default theme implementation for a three-column layout.
*
* Available variables:
* - content: The content for this layout.
* - attributes: HTML attributes for the layout <div>.
*
* @ingroup themeable
*/
#}
{% if content %}
<div{{ attributes.addClass(classes) }}>
{% if content.first %}
<div {{ region_attributes.first.addClass('layout__region', 'layout__region--first') }}>
{{ content.first }}
</div>
{% endif %}
{% if content.second %}
<div {{ region_attributes.second.addClass('layout__region', 'layout__region--second') }}>
{{ content.second }}
</div>
{% endif %}
{% if content.third %}
<div {{ region_attributes.third.addClass('layout__region', 'layout__region--third') }}>
{{ content.third }}
</div>
{% endif %}
</div>
{% endif %}
/*
* @file
* Provides the layout styles for three-column layout section.
*/
.layout--threecol-section {
display: flex;
flex-wrap: wrap;
}
.layout--threecol-section > .layout__region {
flex: 0 1 100%;
}
@media screen and (min-width: 40em) {
.layout--threecol-section--25-50-25 > .layout__region--first,
.layout--threecol-section--25-50-25 > .layout__region--third,
.layout--threecol-section--25-25-50 > .layout__region--first,
.layout--threecol-section--25-25-50 > .layout__region--second,
.layout--threecol-section--50-25-25 > .layout__region--second,
.layout--threecol-section--50-25-25 > .layout__region--third {
flex: 0 1 25%;
}
.layout--threecol-section--25-50-25 > .layout__region--second,
.layout--threecol-section--25-25-50 > .layout__region--third,
.layout--threecol-section--50-25-25 > .layout__region--first {
flex: 0 1 50%;
}
.layout--threecol-section--33-34-33 > .layout__region--first,
.layout--threecol-section--33-34-33 > .layout__region--third {
flex: 0 1 33%;
}
.layout--threecol-section--33-34-33 > .layout__region--second {
flex: 0 1 34%;
}
}
{#
/**
* @file
* Default theme implementation to display a two-column layout.
*
* Available variables:
* - content: The content for this layout.
* - attributes: HTML attributes for the layout <div>.
*
* @ingroup themeable
*/
#}
{% if content %}
<div{{ attributes }}>
{% if content.first %}
<div {{ region_attributes.first.addClass('layout__region', 'layout__region--first') }}>
{{ content.first }}
</div>
{% endif %}
{% if content.second %}
<div {{ region_attributes.second.addClass('layout__region', 'layout__region--second') }}>
{{ content.second }}
</div>
{% endif %}
</div>
{% endif %}
/*
* @file
* Provides the layout styles for two-column layout section.
*/
.layout--twocol-section {
display: flex;
flex-wrap: wrap;
}
.layout--twocol-section > .layout__region {
flex: 0 1 100%;
}
@media screen and (min-width: 40em) {
.layout--twocol-section.layout--twocol-section--50-50 > .layout__region--first,
.layout--twocol-section.layout--twocol-section--50-50 > .layout__region--second {
flex: 0 1 50%;
}
.layout--twocol-section.layout--twocol-section--33-67 > .layout__region--first,
.layout--twocol-section.layout--twocol-section--67-33 > .layout__region--second {
flex: 0 1 33%;
}
.layout--twocol-section.layout--twocol-section--33-67 > .layout__region--second,
.layout--twocol-section.layout--twocol-section--67-33 > .layout__region--first {
flex: 0 1 67%;
}
.layout--twocol-section.layout--twocol-section--25-75 > .layout__region--first,
.layout--twocol-section.layout--twocol-section--75-25 > .layout__region--second {
flex: 0 1 25%;
}
.layout--twocol-section.layout--twocol-section--25-75 > .layout__region--second,
.layout--twocol-section.layout--twocol-section--75-25 > .layout__region--first {
flex: 0 1 75%;
}
}
<?php
namespace Drupal\layout_builder\Plugin\Layout;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Layout\LayoutDefault;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Base class of layouts with configurable widths.
*
* @internal
* Layout Builder is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*/
abstract class MultiWidthLayoutBase extends LayoutDefault implements PluginFormInterface {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$width_classes = array_keys($this->getWidthOptions());
return [
'column_widths' => array_shift($width_classes),
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['column_widths'] = [
'#type' => 'select',
'#title' => $this->t('Column widths'),
'#default_value' => $this->configuration['column_widths'],
'#options' => $this->getWidthOptions(),
'#description' => $this->t('Choose the column widths for this layout.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['column_widths'] = $form_state->getValue('column_widths');
}
/**
* {@inheritdoc}
*/
public function build(array $regions) {
$build = parent::build($regions);
$build['#attributes']['class'] = [
'layout',
$this->getPluginDefinition()->getTemplate(),
$this->getPluginDefinition()->getTemplate() . '--' . $this->configuration['column_widths'],
];
return $build;
}
/**
* Gets the width options for the configuration form.
*
* The first option will be used as the default 'column_widths' configuration
* value.
*
* @return string[]
* The width options array where the keys are strings that will be added to
* the CSS classes and the values are the human readable labels.
*/
abstract protected function getWidthOptions();
}
<?php
namespace Drupal\layout_builder\Plugin\Layout;
/**
* Configurable three column layout plugin class.
*
* @internal
* Plugin classes are internal.
*/
class ThreeColumnLayout extends MultiWidthLayoutBase {
/**
* {@inheritdoc}
*/
protected function getWidthOptions() {
return [
'25-50-25' => '25%/50%/25%',
'33-34-33' => '33%/34%/33%',
'25-25-50' => '25%/25%/50%',
'50-25-25' => '50%/25%/25%',
];
}
}
<?php
namespace Drupal\layout_builder\Plugin\Layout;
/**
* Configurable two column layout plugin class.
*
* @internal
* Plugin classes are internal.
*/
class TwoColumnLayout extends MultiWidthLayoutBase {
/**
* {@inheritdoc}
*/
protected function getWidthOptions() {
return [
'50-50' => '50%/50%',
'33-67' => '33%/67%',
'67-33' => '67%/33%',
'25-75' => '25%/75%',
'75-25' => '75%/25%',
];
}
}
......@@ -192,8 +192,11 @@ public function testLayoutBuilderUi() {
// Add a new section.
$this->clickLink('Add Section');
$this->assertCorrectLayouts();
$assert_session->linkExists('Two column');
$this->clickLink('Two column');
$assert_session->buttonExists('Add section');
$page->pressButton('Add section');
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$assert_session->pageTextNotContains('The first node body');
......@@ -518,7 +521,8 @@ public function testLayoutBuilderChooseBlocksAlter() {
$this->clickLink('Add Section', 1);
$assert_session->linkExists('Two column');
$this->clickLink('Two column');
$assert_session->buttonExists('Add section');
$this->getSession()->getPage()->pressButton('Add section');
// Add a new block to second section.
$this->clickLink('Add Block', 1);
......@@ -680,4 +684,31 @@ public function testSimpleConfigBasedLayout() {
$assert_session->elementsCount('css', '.layout--onecol', 1);
}
/**
* Asserts that the correct layouts are available.
*/
protected function assertCorrectLayouts() {
$assert_session = $this->assertSession();
// Ensure the layouts provided by layout_builder are available.
$expected_layouts_hrefs = [
'layout_builder/add/section/overrides/node.1/0/layout_onecol',
'layout_builder/configure/section/overrides/node.1/0/layout_twocol_section',
'layout_builder/configure/section/overrides/node.1/0/layout_threecol_section',
'layout_builder/add/section/overrides/node.1/0/layout_fourcol_section',
];
foreach ($expected_layouts_hrefs as $expected_layouts_href) {
$assert_session->linkByHrefExists($expected_layouts_href);
}
// Ensure the layout_discovery module's layouts were removed.
$unexpected_layouts = [
'twocol',
'twocol_bricks',
'threecol_25_50_25',
'threecol_33_34_33',
];
foreach ($unexpected_layouts as $unexpected_layout) {
$assert_session->linkByHrefNotExists("layout_builder/add/section/overrides/node.1/0/$unexpected_layout");
}
}
}
<?php
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Test the multi-width layout plugins.
*
* @group layout_builder
*/
class TestMultiWidthLayoutsTest extends WebDriverTestBase {
/**
* Path prefix for the field UI for the test bundle.
*
* @var string
*/
const FIELD_UI_PREFIX = 'admin/structure/types/manage/bundle_with_section_field';
public static $modules = [
'layout_builder',
'block',
'node',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// @todo The Layout Builder UI relies on local tasks; fix in
// https://www.drupal.org/project/drupal/issues/2917777.
$this->drupalPlaceBlock('local_tasks_block');
$this->createContentType(['type' => 'bundle_with_section_field']);
$this->drupalLogin($this->drupalCreateUser([
'configure any layout',
'administer node display',
'administer node fields',
]));
}
/**
* Test changing the columns widths of a multi-width section.
*/
public function testWidthChange() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
// Enable layout builder.
$this->drupalPostForm(
static::FIELD_UI_PREFIX . '/display/default',
['layout[enabled]' => TRUE],
'Save'
);
$this->clickLink('Manage layout');
$assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
$width_options = [
[
'label' => 'Two column',
'widths' => [
'50-50',
'33-67',
'67-33',
'25-75',
'75-25',
],
'class' => 'layout--twocol-section--',
],
[
'label' => 'Three column',
'widths' => [
'25-50-25',
'33-34-33',
'25-25-50',
'50-25-25',
],
'class' => 'layout--threecol-section--',
],
];
foreach ($width_options as $width_option) {
$width = array_shift($width_option['widths']);
$assert_session->linkExists('Add Section');
$page->clickLink('Add Section');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', "#drupal-off-canvas a:contains(\"{$width_option['label']}\")"));
$page->clickLink($width_option['label']);
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[type="submit"][value="Add section"]'));
$page->pressButton("Add section");
$this->assertWidthClassApplied($width_option['class'] . $width);
foreach ($width_option['widths'] as $width) {
$width_class = $width_option['class'] . $width;
$assert_session->linkExists('Configure section');
$page->clickLink('Configure section');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[type="submit"][value="Update"]'));
$page->findField('layout_settings[column_widths]')->setValue($width);
$page->pressButton("Update");
$this->assertWidthClassApplied($width_class);
}
$assert_session->linkExists('Remove section');
$this->clickLink('Remove section');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[type="submit"][value="Remove"]'));
$page->pressButton('Remove');
$this->waitForNoElement(".$width_class");
}
}
/**
* Waits for an element to be removed from the page.
*
* @param string $selector
* CSS selector.
* @param int $timeout
* (optional) Timeout in milliseconds, defaults to 10000.
*
* @todo Remove in https://www.drupal.org/node/2892440.
*/
protected function waitForNoElement($selector, $timeout = 10000) {
$condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)";
$this->assertJsCondition($condition, $timeout);
}
/**
* Asserts the width class is applied to the first section.
*
* @param string $width_class
* The width class.
*/
protected function assertWidthClassApplied($width_class) {
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', ".{$width_class}[data-layout-delta=\"0\"]"));
}
}
/*
* @file
* Provides the layout styles for layout_threecol_25_50_25.
*
* @todo Using display: flex requires https://www.drupal.org/node/2842298 to be
* in before this can be marked as stable.
*/
.layout--threecol-25-50-25 {
display: flex;
......
/*
* @file
* Provides the layout styles for layout_threecol_33_34_33.
*
* @todo Using display: flex requires https://www.drupal.org/node/2842298 to be
* in before this can be marked as stable.
*/
.layout--threecol-33-34-33 {
......
/*
* @file
* Provides the layout styles for layout_twocol.
*
* @todo Using display: flex requires https://www.drupal.org/node/2842298 to be
* in before this can be marked as stable.
*/
.layout--twocol {
......
/*
* @file
* Provides the layout styles for layout_twocol_bricks.
*