diff --git a/package-lock.json b/package-lock.json index 272eaa3ac..03168ade6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1107,6 +1107,11 @@ "caniuse-lite": "^1.0.30000844", "electron-to-chromium": "^1.3.47" } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" } } }, @@ -1929,6 +1934,13 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } } } @@ -2400,6 +2412,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -4223,9 +4240,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.noop": { "version": "3.0.1", @@ -4237,6 +4254,11 @@ "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" }, + "lodash.times": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.times/-/lodash.times-4.3.2.tgz", + "integrity": "sha1-Ph8lZcQxdU1Uq1fy7RdBk5KFyh0=" + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -4661,6 +4683,13 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "normalize-path": { @@ -4901,6 +4930,13 @@ "registry-auth-token": "^3.0.1", "registry-url": "^3.0.3", "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "pako": { @@ -5648,9 +5684,9 @@ } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "semver-diff": { "version": "2.1.0", @@ -5658,6 +5694,13 @@ "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "set-blocking": { diff --git a/package.json b/package.json index d48f33d4c..81a4a9d97 100755 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ }, "dependencies": { "cgb-scripts": "^1.18.1", - "lodash.noop": "^3.0.1" + "lodash.noop": "^3.0.1", + "lodash.times": "^4.3.2", + "semver": "^6.3.0" } } diff --git a/src/class-wp-bootstrap-blocks.php b/src/class-wp-bootstrap-blocks.php index f2d6858a3..b9be267a1 100755 --- a/src/class-wp-bootstrap-blocks.php +++ b/src/class-wp-bootstrap-blocks.php @@ -141,7 +141,7 @@ public function enqueue_block_assets() { */ public function enqueue_block_editor_assets() { // Scripts. - wp_enqueue_script( + wp_register_script( $this->token . '-js', // Handle. esc_url( $this->assets_url ) . 'blocks.build.js', // block.build.js: We register the block here. Built with Webpack. array( @@ -157,6 +157,18 @@ public function enqueue_block_editor_assets() { true // Enqueue the script in the footer. ); + global $wp_version; + wp_localize_script( + $this->token . '-js', + 'wpBootstrapBlocks', + array( + 'gutenbergVersion' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : false, + 'wpVersion' => $wp_version, + ) + ); + + wp_enqueue_script( $this->token . '-js' ); + // Styles. wp_enqueue_style( $this->token . '-editor-styles', // Handle. diff --git a/src/row/block.js b/src/row/block.js index 39f8e9cc2..39f1e6af5 100755 --- a/src/row/block.js +++ b/src/row/block.js @@ -2,120 +2,15 @@ * BLOCK: wp-bootstrap-blocks/row */ +import edit from './edit'; + // Import CSS. import './style.scss'; import './editor.scss'; -import { alignBottom, alignCenter, alignTop } from './icons'; - const { __ } = wp.i18n; // Import __() from wp.i18n const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks -const { InnerBlocks, InspectorControls, BlockControls, AlignmentToolbar } = wp.editor; -const { SelectControl, CheckboxControl, PanelBody } = wp.components; -const { Fragment } = wp.element; -const { dispatch, select } = wp.data; -const { applyFilters } = wp.hooks; - -const ALLOWED_BLOCKS = [ 'wp-bootstrap-blocks/column' ]; -let templates = { - '1-1': { - label: __( '2 Columns (1:1)', 'wp-bootstrap-blocks' ), - templateLock: 'all', - blocks: [ - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 6, - }, - ], - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 6, - }, - ], - ], - }, - '1-2': { - label: __( '2 Columns (1:2)', 'wp-bootstrap-blocks' ), - templateLock: 'all', - blocks: [ - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 4, - }, - ], - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 8, - }, - ], - ], - }, - '2-1': { - label: __( '2 Columns (2:1)', 'wp-bootstrap-blocks' ), - templateLock: 'all', - blocks: [ - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 8, - }, - ], - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 4, - }, - ], - ], - }, - '1-1-1': { - label: __( '3 Columns (1:1:1)', 'wp-bootstrap-blocks' ), - templateLock: 'all', - blocks: [ - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 4, - }, - ], - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 4, - }, - ], - [ - 'wp-bootstrap-blocks/column', - { - sizeMd: 4, - }, - ], - ], - }, -}; -templates = applyFilters( 'wpBootstrapBlocks.row.templates', templates ); - -const enableCustomTemplate = applyFilters( 'wpBootstrapBlocks.row.enableCustomTemplate', true ); -if ( enableCustomTemplate ) { - templates.custom = { - label: __( 'Custom', 'wp-bootstrap-blocks' ), - templateLock: false, - blocks: [ - [ 'wp-bootstrap-blocks/column' ], - ], - }; -} - -const getColumnsTemplate = ( template ) => { - return templates[ template ] ? templates[ template ].blocks : []; -}; -const getColumnsTemplateLock = ( template ) => { - return templates[ template ] ? templates[ template ].templateLock : false; -}; +const { InnerBlocks } = wp.editor; registerBlockType( 'wp-bootstrap-blocks/row', { // Block name. Block names must be string that contains a namespace prefix. Example: my-plugin/my-custom-block. @@ -141,109 +36,7 @@ registerBlockType( 'wp-bootstrap-blocks/row', { }; }, - edit( { className, attributes, setAttributes, clientId } ) { - const { template, noGutters, alignment, verticalAlignment } = attributes; - const templateOptions = []; - Object.keys( templates ).forEach( ( templateName ) => { - templateOptions.push( { - label: templates[ templateName ].label, - value: templateName, - } ); - } ); - const onTemplateChange = ( selectedTemplate ) => { - // Grab columns of existing block - const cols = select( 'core/editor' ).getBlocksByClientId( clientId )[ 0 ].innerBlocks; - - // Update sizes to fit with selected template - cols.forEach( ( col, index ) => { - if ( templates[ selectedTemplate ] && templates[ selectedTemplate ].blocks.length > index ) { - const newAttributes = templates[ selectedTemplate ].blocks[ index ][ 1 ]; - dispatch( 'core/editor' ).updateBlockAttributes( col.clientId, newAttributes ); - } - } ); - - setAttributes( { - template: selectedTemplate, - } ); - }; - - const alignmentControls = [ - { - icon: 'editor-alignleft', - title: __( 'Align columns left', 'wp-bootstrap-blocks' ), - align: 'left', - }, - { - icon: 'editor-aligncenter', - title: __( 'Align columns center', 'wp-bootstrap-blocks' ), - align: 'center', - }, - { - icon: 'editor-alignright', - title: __( 'Align columns right', 'wp-bootstrap-blocks' ), - align: 'right', - }, - ]; - - const verticalAlignmentControls = [ - { - icon: alignTop, - title: __( 'Align columns top', 'wp-bootstrap-blocks' ), - align: 'top', - }, - { - icon: alignCenter, - title: __( 'Align columns center', 'wp-bootstrap-blocks' ), - align: 'center', - }, - { - icon: alignBottom, - title: __( 'Align columns bottom', 'wp-bootstrap-blocks' ), - align: 'bottom', - }, - ]; - - return ( - - - - { - onTemplateChange( selectedTemplate ); - } } - /> - setAttributes( { noGutters: isChecked } ) } - /> - - - - setAttributes( { alignment: newAlignment } ) } - alignmentControls={ alignmentControls } - /> - setAttributes( { verticalAlignment: newVerticalAlignment } ) } - alignmentControls={ verticalAlignmentControls } - /> - -
- -
-
- ); - }, + edit, save() { return ( diff --git a/src/row/class-row-block-type.php b/src/row/class-row-block-type.php index da9a96b9f..72382694b 100755 --- a/src/row/class-row-block-type.php +++ b/src/row/class-row-block-type.php @@ -44,6 +44,9 @@ class Row_Block_Type extends Block_Type { 'verticalAlignment' => array( 'type' => 'string', ), + 'isCustomTemplate' => array( + 'type' => 'boolean', + ), ); /** diff --git a/src/row/edit.js b/src/row/edit.js new file mode 100644 index 000000000..2a83eaf32 --- /dev/null +++ b/src/row/edit.js @@ -0,0 +1,324 @@ +/* global wpBootstrapBlocks */ +import times from 'lodash.times'; +import { alignBottom, alignCenter, alignTop, templateIconMissing } from './icons'; +const semver = require( 'semver' ); + +const { __ } = wp.i18n; +const { InnerBlocks, InspectorControls, BlockControls, AlignmentToolbar } = wp.editor; +const { IconButton, Button, CheckboxControl, PanelBody, SVG, Path } = wp.components; +const { Component, Fragment } = wp.element; +const { withSelect, withDispatch } = wp.data; +const { applyFilters } = wp.hooks; +const { compose } = wp.compose; + +let templatePickerAvailable = false; +if ( wpBootstrapBlocks.gutenbergVersion ) { + templatePickerAvailable = semver.gte( wpBootstrapBlocks.gutenbergVersion, semver.coerce( '6.0' ) ); +} else { + templatePickerAvailable = semver.gte( wpBootstrapBlocks.wpVersion, semver.coerce( '6' ) ); // Since this feature is not yet in core we don't know the exact WordPress version. +} + +const ALLOWED_BLOCKS = [ 'wp-bootstrap-blocks/column' ]; + +const perpareTemplates = templates => { + // If templates are already in new structure do nothing + if ( Array.isArray( templates ) ) { + return templates; + } + return Object.keys( templates ).map( templateName => { + return { + title: templates[ templateName ].title || templates[ templateName ].label, + icon: templates[ templateName ].icon || templateIconMissing, + template: templates[ templateName ].template || templates[ templateName ].blocks, + name: templateName, + }; + } ); +}; + +let templates = { + '1-1': { + title: __( '2 Columns (1:1)', 'wp-bootstrap-blocks' ), + icon: , + template: [ + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 6, + }, + ], + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 6, + }, + ], + ], + }, + '1-2': { + title: __( '2 Columns (1:2)', 'wp-bootstrap-blocks' ), + icon: , + template: [ + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 4, + }, + ], + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 8, + }, + ], + ], + }, + '2-1': { + title: __( '2 Columns (2:1)', 'wp-bootstrap-blocks' ), + icon: , + template: [ + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 8, + }, + ], + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 4, + }, + ], + ], + }, + '1-1-1': { + title: __( '3 Columns (1:1:1)', 'wp-bootstrap-blocks' ), + icon: , + template: [ + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 4, + }, + ], + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 4, + }, + ], + [ + 'wp-bootstrap-blocks/column', + { + sizeMd: 4, + }, + ], + ], + }, +}; +templates = applyFilters( 'wpBootstrapBlocks.row.templates', templates ); +templates = perpareTemplates( templates ); // Ensure backwards compatibility to older templates structure + +const enableCustomTemplate = applyFilters( 'wpBootstrapBlocks.row.enableCustomTemplate', true ); +const customTemplateColumnCount = applyFilters( 'wpBootstrapBlocks.row.customTemplateColumnCount', 2 ); + +const getColumnsTemplate = ( columnCount ) => { + if ( columnCount === undefined ) { + return null; + } + return times( columnCount, () => [ + 'wp-bootstrap-blocks/column', + { + sizeMd: columnCount ? 12 / columnCount : 12, + }, + ] ); +}; +const getDefaultTemplate = () => templates.length > 0 ? templates[ 0 ].template : null; +const getColumnsTemplateLock = isCustomTemplate => isCustomTemplate ? false : 'all'; + +class BootstrapRowEdit extends Component { + constructor( props ) { + super( ...props ); + let template = null; + if ( props.columnCount !== 0 ) { + template = getColumnsTemplate( props.columnCount ); + } else if ( templatePickerAvailable ) { + template = null; + } else { + template = getDefaultTemplate(); + } + this.state = { + template, + }; + } + + render() { + const { className, attributes, setAttributes, columns, updateBlockAttributes } = this.props; + const { template } = this.state; + const { isCustomTemplate, noGutters, alignment, verticalAlignment } = attributes; + + const showTemplateSelector = templatePickerAvailable && ! template; + + const onTemplateChange = ( templateIndex ) => { + if ( templates[ templateIndex ] ) { + // Update sizes to fit with selected template + columns.forEach( ( column, index ) => { + if ( templates[ templateIndex ].template.length > index ) { + const newAttributes = templates[ templateIndex ].template[ index ][ 1 ]; + updateBlockAttributes( column.clientId, newAttributes ); + } + } ); + + this.setState( { + template: templates[ templateIndex ].template, + } ); + } + }; + + const alignmentControls = [ + { + icon: 'editor-alignleft', + title: __( 'Align columns left', 'wp-bootstrap-blocks' ), + align: 'left', + }, + { + icon: 'editor-aligncenter', + title: __( 'Align columns center', 'wp-bootstrap-blocks' ), + align: 'center', + }, + { + icon: 'editor-alignright', + title: __( 'Align columns right', 'wp-bootstrap-blocks' ), + align: 'right', + }, + ]; + + const verticalAlignmentControls = [ + { + icon: alignTop, + title: __( 'Align columns top', 'wp-bootstrap-blocks' ), + align: 'top', + }, + { + icon: alignCenter, + title: __( 'Align columns center', 'wp-bootstrap-blocks' ), + align: 'center', + }, + { + icon: alignBottom, + title: __( 'Align columns bottom', 'wp-bootstrap-blocks' ), + align: 'bottom', + }, + ]; + + return ( + + { ! showTemplateSelector && ( + + + +
    + { templates.map( ( template, index ) => ( // eslint-disable-line no-shadow +
  • + { + setAttributes( { + isCustomTemplate: false, + } ); + onTemplateChange( index ); + } } + > +
    { template.title }
    +
    +
  • + ) ) } +
+ { enableCustomTemplate && ( + + ) } +
+ + setAttributes( { noGutters: isChecked } ) } + /> + +
+ + setAttributes( { alignment: newAlignment } ) } + alignmentControls={ alignmentControls } + /> + setAttributes( { verticalAlignment: newVerticalAlignment } ) } + alignmentControls={ verticalAlignmentControls } + /> + +
+ ) } +
+ { + if ( nextTemplate === undefined ) { + nextTemplate = getColumnsTemplate( customTemplateColumnCount ); + setAttributes( { + isCustomTemplate: true, + } ); + } + + this.setState( { + template: nextTemplate, + } ); + } } + __experimentalAllowTemplateOptionSkip={ enableCustomTemplate } + /> +
+
+ ); + } +} + +const applyWithSelect = withSelect( ( select, { clientId } ) => { + const { getBlocksByClientId } = select( 'core/editor' ); + + const columns = getBlocksByClientId( clientId )[ 0 ] ? getBlocksByClientId( clientId )[ 0 ].innerBlocks : []; + + return { + columnCount: columns.length, + columns, + }; +} ); + +const applyWithDispatch = withDispatch( ( dispatch ) => { + const { updateBlockAttributes } = dispatch( 'core/editor' ); + + return { + updateBlockAttributes, + }; +} ); + +export default compose( + applyWithSelect, + applyWithDispatch, +)( BootstrapRowEdit ); diff --git a/src/row/editor.scss b/src/row/editor.scss index 3636885cc..5c10024e2 100755 --- a/src/row/editor.scss +++ b/src/row/editor.scss @@ -95,6 +95,23 @@ } } +.wp-bootstrap-blocks-template-selector-list { + display: flex; + flex-wrap: wrap; +} + +.wp-bootstrap-blocks-template-selector-button { + flex: 0 0 50%; + + > .components-icon-button { + flex-direction: column; + } +} + +.wp-bootstrap-blocks-template-selector-button-label { + font-size: 12px; +} + // Column block [data-type="wp-bootstrap-blocks/column"] { background-color: rgba(255, 255, 255, .7); diff --git a/src/row/icons.js b/src/row/icons.js index 76fc3e5bc..0f08e3568 100644 --- a/src/row/icons.js +++ b/src/row/icons.js @@ -25,3 +25,9 @@ export const alignTop = ( ); + +export const templateIconMissing = ( + + + +);