Skip to content

Commit

Permalink
Mockup for improved card management controls for Supernova and Supern…
Browse files Browse the repository at this point in the history
…ova Items blocks - see #404
  • Loading branch information
razwan committed Jun 29, 2022
1 parent a549128 commit a4dfa7e
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.novablocks-navigable-menu {

.components-button {
height: auto;
white-space: normal;
text-align: left;

p {
margin: 0;
}
}

.novablocks-navigable-menu__item-wrap {
white-space: normal;
}

.novablocks-navigable-menu__item-label + .novablocks-navigable-menu__item-help {
margin-top: 0.25em;
}

.novablocks-navigable-menu__item-help {
font-size: 12px;
opacity: 0.5;
}
}
16 changes: 16 additions & 0 deletions packages/block-editor/src/components/custom-menu-item/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MenuItem } from '@wordpress/components';

const CustomMenuItem = ( props ) => {
const { help, children, ...passedProps } = props;

return (
<MenuItem { ...passedProps }>
<div className="novablocks-navigable-menu__item-wrap">
<p className="novablocks-navigable-menu__item-label">{ children }</p>
{ help && <p className="novablocks-navigable-menu__item-help">{ help }</p> }
</div>
</MenuItem>
)
}

export default CustomMenuItem;
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as AutocompleteTokenField } from "./autocomplete-tokenfield";
export { default as BlockVerticalAlignmentToolbar } from "./block-vertical-alignment-toolbar";
export { default as CustomMenuItem } from './custom-menu-item';
export { default as CardFieldsPreview } from './card-fields-preview';
export { default as ControlsGroup } from './controls-group';
export { default as ColorPicker } from './color-picker';
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/editor-styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import "components/autocomplete-tokenfield/editor-styles";
@import 'components/controls-sections/editor-styles';
@import 'components/custom-menu-item/editor-styles';
@import 'components/controls-group/editor-styles';
@import 'components/drawer/editor-styles';
@import 'components/image-select-control/editor-styles';
Expand Down
37 changes: 28 additions & 9 deletions packages/block-editor/src/hooks/use-inner-blocks-count/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import { createBlock } from '@wordpress/blocks';
import { useDispatch, useSelect } from "@wordpress/data";
import { useEffect } from "@wordpress/element";
import { useEffect, useRef } from "@wordpress/element";

import { useInnerBlocks } from "../../hooks";

const useInnerBlocksCount = ( clientId, postsToShow, innerBlockName, innerBlockAttributes ) => {
const itemsCount = useSelect( select => select( 'core/block-editor' ).getBlockCount( clientId ), [ clientId ] );
const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );
const { replaceInnerBlocks, updateBlockAttributes } = useDispatch( 'core/block-editor' );
const innerBlocks = useInnerBlocks( clientId );

return useEffect( () => {
const newInnerBlocks = innerBlocks.slice( 0, postsToShow );
const innerBlocksCount = useRef( innerBlocks.length );
const postsToShowValue = useRef( postsToShow );

if ( postsToShow > itemsCount ) {
for ( let i = 0; i < postsToShow - itemsCount; i++ ) {
newInnerBlocks.push( createBlock( innerBlockName, innerBlockAttributes ) );
useEffect( () => {

if ( innerBlocks.length !== postsToShow ) {

if ( innerBlocks.length !== innerBlocksCount.current ) {
// inner blocks changed
innerBlocksCount.current = innerBlocks.length;
postsToShowValue.current = innerBlocks.length;
updateBlockAttributes( clientId, { postsToShow: innerBlocks.length } );
} else {
// postsToShow changed
innerBlocksCount.current = postsToShow;
postsToShowValue.current = postsToShow;

const newInnerBlocks = innerBlocks.slice( 0, postsToShow );

if ( postsToShow > itemsCount ) {
for ( let i = 0; i < postsToShow - itemsCount; i++ ) {
newInnerBlocks.push( createBlock( innerBlockName, innerBlockAttributes ) );
}
}

replaceInnerBlocks( clientId, newInnerBlocks );
}
}

replaceInnerBlocks( clientId, newInnerBlocks );
}, [ postsToShow ] );
}, [ postsToShow, innerBlocks ] );
};

export default useInnerBlocksCount;
45 changes: 45 additions & 0 deletions packages/block-library/src/blocks/supernova-item/block-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { __ } from "@wordpress/i18n";
import { createBlock } from "@wordpress/blocks";
import { useDispatch, useSelect } from "@wordpress/data";
import { useCallback } from "@wordpress/element";
import { BlockControls } from "@wordpress/block-editor";
import { ToolbarButton, ToolbarGroup } from "@wordpress/components";

import { useInnerBlocks } from "@novablocks/block-editor";

const SupernovaItemBlockControls = ( props ) => {
const { clientId, attributes } = props;
const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );

const parentClientId = useSelect( select => {
const parents = select( 'core/block-editor' ).getBlockParents( clientId ).slice();

if ( parents.length ) {
return parents[ parents.length - 1 ];
}

return null;
}, [ clientId ] );

const parentInnerBlocks = useInnerBlocks( parentClientId );

const addNewCard = useCallback( () => {
const newInnerBlocks = parentInnerBlocks.slice();
const index = newInnerBlocks.findIndex( block => block.clientId === clientId );
const newBlock = createBlock( 'novablocks/supernova-item', attributes );
newInnerBlocks.splice( index + 1, 0, newBlock );
replaceInnerBlocks( parentClientId, newInnerBlocks );
}, [ clientId, parentClientId, attributes ] );

return (
<BlockControls>
<ToolbarGroup label={ __( 'Cards', '__plugin_txtd' ) }>
<ToolbarButton onClick={ addNewCard }>
{ __( 'Duplicate Card', '__plugin_txtd' ) }
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
)
}

export default SupernovaItemBlockControls;
26 changes: 15 additions & 11 deletions packages/block-library/src/blocks/supernova-item/edit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useEffect, useMemo, useState } from '@wordpress/element';
import { Fragment, useEffect, useMemo, useState } from '@wordpress/element';
import { Popover } from '@wordpress/components';
import { useSelect } from '@wordpress/data';

Expand Down Expand Up @@ -33,6 +33,7 @@ import {
import { withShapeModelingDecoration } from "@novablocks/shape-modeling";

import { getNewDefaults } from "./utils";
import BlockControls from './block-controls';

const SupernovaItemEdit = props => {
const { attributes, setControlsVisibility, clientId } = props;
Expand Down Expand Up @@ -68,16 +69,19 @@ const SupernovaItemEdit = props => {
});

return (
<div { ...blockProps }>
<Card { ...props }>
{ showMedia &&
<CardMediaWrapper { ...props }>
<CardMedia { ...props } />
</CardMediaWrapper>
}
<SupernovaItemContent { ...props } />
</Card>
</div>
<Fragment>
<div { ...blockProps }>
<Card { ...props }>
{ showMedia &&
<CardMediaWrapper { ...props }>
<CardMedia { ...props } />
</CardMediaWrapper>
}
<SupernovaItemContent { ...props } />
</Card>
</div>
<BlockControls { ...props } />
</Fragment>
)
};

Expand Down
132 changes: 120 additions & 12 deletions packages/block-library/src/blocks/supernova/block-controls.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,133 @@
import { __ } from '@wordpress/i18n';
import { BlockAlignmentControl, BlockControls } from "@wordpress/block-editor";
import { Button, Toolbar } from '@wordpress/components';
import { createBlock } from '@wordpress/blocks';
import { useCallback, useMemo } from '@wordpress/element';
import { addCard, gallery, plus } from '@wordpress/icons';

import { getIconSvg } from "@novablocks/block-editor";
import { needsPreview } from "@novablocks/utils";
import {
BlockAlignmentControl,
BlockControls,
MediaUpload,
} from "@wordpress/block-editor";

import {
Dropdown,
NavigableMenu,
MenuGroup,
ToolbarGroup,
ToolbarButton,
} from '@wordpress/components';

import { useDispatch } from '@wordpress/data';

import {
CustomMenuItem,
normalizeImages,
useInnerBlocks
} from "@novablocks/block-editor";

import FlipMediaControls from './controls/flip-media-controls';
import { compileSupernovaItemAttributes } from './utils';

const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ];

const Controls = ( props ) => {

const { attributes, setAttributes, inQuery } = props;
const { align } = attributes;
const { attributes, setAttributes, clientId } = props;
const { align, postsToShow } = attributes;
const innerBlocks = useInnerBlocks( clientId );
const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );

const innerBlockAttributes = useMemo( () => {
return compileSupernovaItemAttributes( attributes );
}, [ attributes ] );

return (
const onSelectImages = useCallback( images => {
const newInnerBlocks = innerBlocks.slice();

<BlockControls group={ 'block' }>
<BlockAlignmentControl value={ align } onChange={ nextAlign => {
setAttributes( { align: nextAlign ?? 'none' } );
} } />
<FlipMediaControls { ...props } />
normalizeImages( images ).then( newImages => {
newImages.forEach( image => {
newInnerBlocks.push( createBlock( 'novablocks/supernova-item', {
...innerBlockAttributes,
defaultsGenerated: true,
images: [ image ]
} ) );
} );

replaceInnerBlocks( clientId, newInnerBlocks );
} );

}, [ clientId, innerBlocks ] );

const addNewCard = useCallback( () => {
const newInnerBlocks = innerBlocks.slice();
const newBlockAttributes = innerBlocks[ innerBlocks.length - 1 ].attributes;
const newBlock = createBlock( 'novablocks/supernova-item', newBlockAttributes );
newInnerBlocks.push( newBlock );
replaceInnerBlocks( clientId, newInnerBlocks );
}, [ innerBlocks, postsToShow ] );

return (
<BlockControls>
<ToolbarGroup label={ __( 'Layout', '__plugin_txtd' ) }>
<BlockAlignmentControl value={ align } onChange={ nextAlign => {
setAttributes( { align: nextAlign ?? 'none' } );
} } />
<FlipMediaControls { ...props } />
</ToolbarGroup>
<ToolbarGroup label={ __( 'Cards', '__plugin_txtd' ) }>
<Dropdown
position="bottom right"
contentClassName="block-editor-media-replace-flow__options"
renderToggle={ ( { isOpen, onToggle } ) => (
<ToolbarButton onClick={ onToggle } aria-expanded={ isOpen }>
{ __( 'Add Cards', '__plugin_txtd' ) }
</ToolbarButton>
) }
renderContent={ ( { onClose } ) => (
<NavigableMenu className={ 'novablocks-navigable-menu' }>
<MenuGroup>
<CustomMenuItem
onClick={ () => {
addNewCard();
onClose();
} }
help={ __( 'Insert a card that matches the same design and details as the last.', '__plugin_txtd' ) }
icon={ addCard }>
{ __( 'Add New Card', '__plugin_txtd' ) }
</CustomMenuItem>
<CustomMenuItem
onClick={ () => {
setAttributes( { postsToShow: postsToShow + 1 } );
onClose();
} }
icon={ plus }
>
{ __( 'Add Blank Card', '__plugin_txtd' ) }
</CustomMenuItem>
</MenuGroup>
<MenuGroup>
<MediaUpload
allowedTypes={ ALLOWED_MEDIA_TYPES }
multiple
value={ [] }
onSelect={ onSelectImages }
onClose={ onClose }
render={ ( { open } ) => (
<CustomMenuItem
help={ __( 'Open the Media Library and insert new cards for each selected media item.', '__plugin_txtd' ) }
icon={ gallery }
onClick={ open }
>
{ __( 'Add Cards Gallery', '__plugin_txtd' ) }
</CustomMenuItem>
) }
/>
</MenuGroup>
</NavigableMenu>
) }
>
</Dropdown>
</ToolbarGroup>
</BlockControls>
)
};
Expand Down
23 changes: 5 additions & 18 deletions packages/block-library/src/blocks/supernova/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ import { useMeta, useInnerBlocksCount, useInnerBlocks, useInnerBlocksLock, norma
import { Collection, CollectionHeader } from '@novablocks/collection';
import { BlockControls as MediaCompositionBlockControls } from '@novablocks/media-composition';

import { getAlignFromMatrix } from '@novablocks/utils';

import BlockControls from './block-controls';
import InspectorControls from './inspector-controls';

import { getAlignFromMatrix } from '@novablocks/utils';

import {
PostsCollectionLayout,
CardsCollectionLayout,
withControlsVisibility,
} from './components';

import { compileSupernovaItemAttributes } from './utils';

const ChangeMediaBlockControls = ( props ) => {
const { clientId } = props;
const innerBlocks = useInnerBlocks( clientId );
Expand Down Expand Up @@ -136,22 +138,7 @@ const SupernovaEdit = props => {
}

const innerBlocksAttributes = useMemo( () => {

const {
title,
subtitle,
contentColorSignal,
contentPaletteVariation,
contentType,
...innerBlocksAttributes
} = attributes;

return Object.assign( {}, innerBlocksAttributes, {
colorSignal: contentColorSignal,
paletteVariation: contentPaletteVariation,
useSourceColorAsReference: false,
} );

return compileSupernovaItemAttributes( attributes );
}, [ attributes ] );

// Make sure that we keep the number of inner Supernova Items in sync with the number of items.
Expand Down
Loading

0 comments on commit a4dfa7e

Please sign in to comment.