|
1 |
| -/** |
2 |
| - * External dependencies |
3 |
| - */ |
4 |
| -import { noop, partial } from 'lodash'; |
5 |
| - |
6 | 1 | /**
|
7 | 2 | * WordPress dependencies
|
8 | 3 | */
|
9 | 4 | import { Component } from '@wordpress/element';
|
10 |
| -import { Placeholder, Spinner, Disabled } from '@wordpress/components'; |
11 |
| -import { withSelect, withDispatch } from '@wordpress/data'; |
| 5 | +import { Placeholder, Spinner, Disabled, SlotFillProvider } from '@wordpress/components'; |
| 6 | +import { |
| 7 | + withSelect, |
| 8 | + withDispatch, |
| 9 | + withRegistry, |
| 10 | +} from '@wordpress/data'; |
12 | 11 | import { __ } from '@wordpress/i18n';
|
13 |
| -import { BlockEdit } from '@wordpress/block-editor'; |
| 12 | +import { |
| 13 | + BlockList, |
| 14 | + BlockControls, |
| 15 | + BlockFormatControls, |
| 16 | + FormatToolbar, |
| 17 | + __experimentalBlockSettingsMenuFirstItem, |
| 18 | + __experimentalBlockSettingsMenuPluginsExtension, |
| 19 | +} from '@wordpress/block-editor'; |
| 20 | +import { EditorProvider } from '@wordpress/editor'; |
14 | 21 | import { compose } from '@wordpress/compose';
|
15 | 22 |
|
16 | 23 | /**
|
17 | 24 | * Internal dependencies
|
18 | 25 | */
|
19 | 26 | import ReusableBlockEditPanel from './edit-panel';
|
20 | 27 | import ReusableBlockIndicator from './indicator';
|
| 28 | +import SelectionObserver from './selection-observer'; |
21 | 29 |
|
22 | 30 | class ReusableBlockEdit extends Component {
|
23 |
| - constructor( { reusableBlock } ) { |
| 31 | + constructor() { |
24 | 32 | super( ...arguments );
|
25 | 33 |
|
26 |
| - this.startEditing = this.startEditing.bind( this ); |
27 |
| - this.stopEditing = this.stopEditing.bind( this ); |
28 |
| - this.setAttributes = this.setAttributes.bind( this ); |
29 |
| - this.setTitle = this.setTitle.bind( this ); |
30 |
| - this.save = this.save.bind( this ); |
31 |
| - |
32 |
| - if ( reusableBlock && reusableBlock.isTemporary ) { |
33 |
| - // Start in edit mode when we're working with a newly created reusable block |
34 |
| - this.state = { |
35 |
| - isEditing: true, |
36 |
| - title: reusableBlock.title, |
37 |
| - changedAttributes: {}, |
38 |
| - }; |
39 |
| - } else { |
40 |
| - // Start in preview mode when we're working with an existing reusable block |
41 |
| - this.state = { |
42 |
| - isEditing: false, |
43 |
| - title: null, |
44 |
| - changedAttributes: null, |
45 |
| - }; |
46 |
| - } |
47 |
| - } |
48 |
| - |
49 |
| - componentDidMount() { |
50 |
| - if ( ! this.props.reusableBlock ) { |
51 |
| - this.props.fetchReusableBlock(); |
52 |
| - } |
53 |
| - } |
54 |
| - |
55 |
| - startEditing() { |
56 |
| - const { reusableBlock } = this.props; |
57 |
| - |
58 |
| - this.setState( { |
59 |
| - isEditing: true, |
60 |
| - title: reusableBlock.title, |
61 |
| - changedAttributes: {}, |
62 |
| - } ); |
63 |
| - } |
| 34 | + this.startEditing = () => this.toggleIsEditing( true ); |
| 35 | + this.stopEditing = () => this.toggleIsEditing( false ); |
| 36 | + this.cancelEditing = this.cancelEditing.bind( this ); |
64 | 37 |
|
65 |
| - stopEditing() { |
66 |
| - this.setState( { |
| 38 | + this.state = { |
| 39 | + cancelIncrementKey: 0, |
| 40 | + // TODO: Check if this needs to consider reusable block being temporary (this was in original PR) |
67 | 41 | isEditing: false,
|
68 |
| - title: null, |
69 |
| - changedAttributes: null, |
70 |
| - } ); |
71 |
| - } |
72 |
| - |
73 |
| - setAttributes( attributes ) { |
74 |
| - this.setState( ( prevState ) => { |
75 |
| - if ( prevState.changedAttributes !== null ) { |
76 |
| - return { changedAttributes: { ...prevState.changedAttributes, ...attributes } }; |
77 |
| - } |
78 |
| - } ); |
| 42 | + }; |
79 | 43 | }
|
80 | 44 |
|
81 |
| - setTitle( title ) { |
82 |
| - this.setState( { title } ); |
| 45 | + /** |
| 46 | + * Starts or stops editing, corresponding to the given boolean value. |
| 47 | + * |
| 48 | + * @param {boolean} isEditing Whether editing mode should be made active. |
| 49 | + */ |
| 50 | + toggleIsEditing( isEditing ) { |
| 51 | + this.setState( { isEditing } ); |
83 | 52 | }
|
84 | 53 |
|
85 |
| - save() { |
86 |
| - const { reusableBlock, onUpdateTitle, updateAttributes, block, onSave } = this.props; |
87 |
| - const { title, changedAttributes } = this.state; |
88 |
| - |
89 |
| - if ( title !== reusableBlock.title ) { |
90 |
| - onUpdateTitle( title ); |
91 |
| - } |
92 |
| - |
93 |
| - updateAttributes( block.clientId, changedAttributes ); |
94 |
| - onSave(); |
95 |
| - |
| 54 | + /** |
| 55 | + * Stops editing and restores the reusable block to its original saved |
| 56 | + * state. |
| 57 | + */ |
| 58 | + cancelEditing() { |
96 | 59 | this.stopEditing();
|
| 60 | + |
| 61 | + // Cancelling takes effect by assigning a new key for the rendered |
| 62 | + // EditorProvider which forces a re-mount to reset editing state. |
| 63 | + let { cancelIncrementKey } = this.state; |
| 64 | + cancelIncrementKey++; |
| 65 | + this.setState( { cancelIncrementKey } ); |
97 | 66 | }
|
98 | 67 |
|
99 | 68 | render() {
|
100 |
| - const { isSelected, reusableBlock, block, isFetching, isSaving, canUpdateBlock } = this.props; |
101 |
| - const { isEditing, title, changedAttributes } = this.state; |
102 |
| - |
103 |
| - if ( ! reusableBlock && isFetching ) { |
104 |
| - return <Placeholder><Spinner /></Placeholder>; |
105 |
| - } |
106 |
| - |
107 |
| - if ( ! reusableBlock || ! block ) { |
108 |
| - return <Placeholder>{ __( 'Block has been deleted or is unavailable.' ) }</Placeholder>; |
| 69 | + const { |
| 70 | + isSelected, |
| 71 | + reusableBlock, |
| 72 | + isFetching, |
| 73 | + canUpdateBlock, |
| 74 | + settings, |
| 75 | + } = this.props; |
| 76 | + const { cancelIncrementKey, isEditing } = this.state; |
| 77 | + |
| 78 | + if ( ! reusableBlock ) { |
| 79 | + return ( |
| 80 | + <Placeholder> |
| 81 | + { |
| 82 | + isFetching ? |
| 83 | + <Spinner /> : |
| 84 | + __( 'Block has been deleted or is unavailable.' ) |
| 85 | + } |
| 86 | + </Placeholder> |
| 87 | + ); |
109 | 88 | }
|
110 | 89 |
|
111 |
| - let element = ( |
112 |
| - <BlockEdit |
113 |
| - { ...this.props } |
114 |
| - isSelected={ isEditing && isSelected } |
115 |
| - clientId={ block.clientId } |
116 |
| - name={ block.name } |
117 |
| - attributes={ { ...block.attributes, ...changedAttributes } } |
118 |
| - setAttributes={ isEditing ? this.setAttributes : noop } |
119 |
| - /> |
120 |
| - ); |
121 |
| - |
| 90 | + let list = <BlockList />; |
122 | 91 | if ( ! isEditing ) {
|
123 |
| - element = <Disabled>{ element }</Disabled>; |
| 92 | + list = <Disabled>{ list }</Disabled>; |
124 | 93 | }
|
125 | 94 |
|
126 | 95 | return (
|
127 |
| - <> |
128 |
| - { ( isSelected || isEditing ) && ( |
129 |
| - <ReusableBlockEditPanel |
130 |
| - isEditing={ isEditing } |
131 |
| - title={ title !== null ? title : reusableBlock.title } |
132 |
| - isSaving={ isSaving && ! reusableBlock.isTemporary } |
133 |
| - isEditDisabled={ ! canUpdateBlock } |
134 |
| - onEdit={ this.startEditing } |
135 |
| - onChangeTitle={ this.setTitle } |
136 |
| - onSave={ this.save } |
137 |
| - onCancel={ this.stopEditing } |
| 96 | + <SlotFillProvider |
| 97 | + slots={ [ |
| 98 | + FormatToolbar.Slot, |
| 99 | + BlockControls.Slot, |
| 100 | + BlockFormatControls.Slot, |
| 101 | + __experimentalBlockSettingsMenuFirstItem.Slot, |
| 102 | + __experimentalBlockSettingsMenuPluginsExtension.Slot, |
| 103 | + ] } |
| 104 | + > |
| 105 | + <EditorProvider |
| 106 | + key={ cancelIncrementKey } |
| 107 | + post={ reusableBlock } |
| 108 | + settings={ { ...settings, templateLock: ! isEditing } } |
| 109 | + > |
| 110 | + <SelectionObserver |
| 111 | + isParentSelected={ isSelected } |
| 112 | + onBlockSelected={ this.props.selectBlock } |
138 | 113 | />
|
139 |
| - ) } |
140 |
| - { ! isSelected && ! isEditing && <ReusableBlockIndicator title={ reusableBlock.title } /> } |
141 |
| - { element } |
142 |
| - </> |
| 114 | + { ( isSelected || isEditing ) && ( |
| 115 | + <ReusableBlockEditPanel |
| 116 | + isEditing={ isEditing } |
| 117 | + isEditDisabled={ ! canUpdateBlock } |
| 118 | + onEdit={ this.startEditing } |
| 119 | + onSave={ this.stopEditing } |
| 120 | + onCancel={ this.cancelEditing } |
| 121 | + /> |
| 122 | + ) } |
| 123 | + { ! isSelected && ! isEditing && ( |
| 124 | + <ReusableBlockIndicator title={ reusableBlock.title } /> |
| 125 | + ) } |
| 126 | + { list } |
| 127 | + </EditorProvider> |
| 128 | + </SlotFillProvider> |
143 | 129 | );
|
144 | 130 | }
|
145 | 131 | }
|
146 | 132 |
|
147 | 133 | export default compose( [
|
| 134 | + withRegistry, |
148 | 135 | withSelect( ( select, ownProps ) => {
|
149 |
| - const { |
150 |
| - __experimentalGetReusableBlock: getReusableBlock, |
151 |
| - __experimentalIsFetchingReusableBlock: isFetchingReusableBlock, |
152 |
| - __experimentalIsSavingReusableBlock: isSavingReusableBlock, |
153 |
| - } = select( 'core/editor' ); |
154 |
| - const { canUser } = select( 'core' ); |
155 |
| - const { |
156 |
| - getBlock, |
157 |
| - } = select( 'core/block-editor' ); |
158 |
| - const { ref } = ownProps.attributes; |
159 |
| - const reusableBlock = getReusableBlock( ref ); |
| 136 | + const { clientId, attributes } = ownProps; |
| 137 | + const { ref } = attributes; |
| 138 | + const { canUser, getEntityRecord } = select( 'core' ); |
| 139 | + const { isResolving } = select( 'core/data' ); |
| 140 | + const { getEditorSettings } = select( 'core/editor' ); |
| 141 | + const { isBlockSelected } = select( 'core/block-editor' ); |
| 142 | + |
| 143 | + const isTemporaryReusableBlock = ! Number.isFinite( ref ); |
| 144 | + |
| 145 | + let reusableBlock; |
| 146 | + if ( ! isTemporaryReusableBlock ) { |
| 147 | + reusableBlock = getEntityRecord( 'postType', 'wp_block', ref ); |
| 148 | + } |
160 | 149 |
|
161 | 150 | return {
|
162 | 151 | reusableBlock,
|
163 |
| - isFetching: isFetchingReusableBlock( ref ), |
164 |
| - isSaving: isSavingReusableBlock( ref ), |
165 |
| - block: reusableBlock ? getBlock( reusableBlock.clientId ) : null, |
166 |
| - canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ), |
| 152 | + isSelected: isBlockSelected( clientId ), |
| 153 | + isFetching: isResolving( |
| 154 | + 'core', |
| 155 | + 'getEntityRecord', |
| 156 | + [ 'postType', 'wp_block', ref ] |
| 157 | + ), |
| 158 | + canUpdateBlock: ( |
| 159 | + !! reusableBlock && |
| 160 | + ! isTemporaryReusableBlock && |
| 161 | + !! canUser( 'update', 'blocks', ref ) |
| 162 | + ), |
| 163 | + settings: getEditorSettings(), |
167 | 164 | };
|
168 | 165 | } ),
|
169 | 166 | withDispatch( ( dispatch, ownProps ) => {
|
170 |
| - const { |
171 |
| - __experimentalFetchReusableBlocks: fetchReusableBlocks, |
172 |
| - __experimentalUpdateReusableBlockTitle: updateReusableBlockTitle, |
173 |
| - __experimentalSaveReusableBlock: saveReusableBlock, |
174 |
| - } = dispatch( 'core/editor' ); |
175 |
| - const { |
176 |
| - updateBlockAttributes, |
177 |
| - } = dispatch( 'core/block-editor' ); |
178 |
| - const { ref } = ownProps.attributes; |
| 167 | + const { selectBlock } = dispatch( 'core/block-editor' ); |
179 | 168 |
|
180 | 169 | return {
|
181 |
| - fetchReusableBlock: partial( fetchReusableBlocks, ref ), |
182 |
| - updateAttributes: updateBlockAttributes, |
183 |
| - onUpdateTitle: partial( updateReusableBlockTitle, ref ), |
184 |
| - onSave: partial( saveReusableBlock, ref ), |
| 170 | + selectBlock() { |
| 171 | + selectBlock( ownProps.clientId ); |
| 172 | + }, |
185 | 173 | };
|
186 | 174 | } ),
|
187 | 175 | ] )( ReusableBlockEdit );
|
0 commit comments