Skip to content

Commit

Permalink
Save panel - decouple Site Entity items for individual saving. (#30816)
Browse files Browse the repository at this point in the history
* decouple edited site props in checkbox list

* move to original useSelect to update dirtyEntityRecords

* modularize site entity saving

* make new action not site entity specific

* add basic e2e test

* try waitForXPath before typing
  • Loading branch information
Addison-Stavlo authored Apr 23, 2021
1 parent e5b0a86 commit f4b3c72
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 14 deletions.
43 changes: 43 additions & 0 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,49 @@ export function* saveEditedEntityRecord( kind, name, recordId, options ) {
return yield* saveEntityRecord( kind, name, record, options );
}

/**
* Action triggered to save only specified properties for the entity.
*
* @param {string} kind Kind of the entity.
* @param {string} name Name of the entity.
* @param {Object} recordId ID of the record.
* @param {Array} itemsToSave List of entity properties to save.
* @param {Object} options Saving options.
*/
export function* __experimentalSaveSpecifiedEntityEdits(
kind,
name,
recordId,
itemsToSave,
options
) {
if (
! ( yield controls.select(
'core',
'hasEditsForEntityRecord',
kind,
name,
recordId
) )
) {
return;
}
const edits = yield controls.select(
'core',
'getEntityRecordNonTransientEdits',
kind,
name,
recordId
);
const editsToSave = {};
for ( const edit in edits ) {
if ( itemsToSave.some( ( item ) => item === edit ) ) {
editsToSave[ edit ] = edits[ edit ];
}
}
return yield* saveEntityRecord( kind, name, editsToSave, options );
}

/**
* Returns an action object used in signalling that Upload permissions have been received.
*
Expand Down
28 changes: 28 additions & 0 deletions packages/e2e-tests/specs/experiments/multi-entity-saving.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import {
createNewPost,
disablePrePublishChecks,
insertBlock,
publishPost,
trashAllPosts,
Expand Down Expand Up @@ -168,6 +169,33 @@ describe( 'Multi-entity save flow', () => {
await assertMultiSaveEnabled();
await assertExistance( saveA11ySelector, true );
} );

it( 'Site blocks should save individually', async () => {
await createNewPost();
await disablePrePublishChecks();

await insertBlock( 'Site Title' );
await page.waitForXPath( '//a[contains(text(), "gutenberg")]' ); // Ensure title is retrieved before typing.
await page.keyboard.type( '...' );
await insertBlock( 'Site Tagline' );
await page.waitForXPath(
'//p[contains(text(), "Just another WordPress site")]'
); // Esnure tagline is retrieved before typing.
await page.keyboard.type( '...' );

await page.click( savePostSelector );
await page.waitForSelector( savePanelSelector );
let checkboxInputs = await page.$$( checkboxInputSelector );
expect( checkboxInputs ).toHaveLength( 3 );

await checkboxInputs[ 1 ].click();
await page.click( entitiesSaveSelector );

await page.click( savePostSelector );
await page.waitForSelector( savePanelSelector );
checkboxInputs = await page.$$( checkboxInputSelector );
expect( checkboxInputs ).toHaveLength( 1 );
} );
} );

describe( 'Site Editor', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { some } from 'lodash';
import { useSelect } from '@wordpress/data';
import { PanelBody } from '@wordpress/components';
import { page, layout } from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';

/**
* Internal dependencies
Expand All @@ -29,7 +30,7 @@ export default function EntityTypeList( {
const firstRecord = list[ 0 ];
const entity = useSelect(
( select ) =>
select( 'core' ).getEntity( firstRecord.kind, firstRecord.name ),
select( coreStore ).getEntity( firstRecord.kind, firstRecord.name ),
[ firstRecord.kind, firstRecord.name ]
);

Expand All @@ -42,15 +43,16 @@ export default function EntityTypeList( {
{ list.map( ( record ) => {
return (
<EntityRecordItem
key={ record.key || 'site' }
key={ record.key || record.property }
record={ record }
checked={
! some(
unselectedEntities,
( elt ) =>
elt.kind === record.kind &&
elt.name === record.name &&
elt.key === record.key
elt.key === record.key &&
elt.property === record.property
)
}
onChange={ ( value ) =>
Expand Down
70 changes: 59 additions & 11 deletions packages/editor/src/components/entities-saved-states/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ import { store as coreStore } from '@wordpress/core-data';
*/
import EntityTypeList from './entity-type-list';

const TRANSLATED_SITE_PROTPERTIES = {
title: __( 'Title' ),
description: __( 'Tagline' ),
sitelogo: __( 'Logo' ),
show_on_front: __( 'Show on front' ),
page_on_front: __( 'Page on front' ),
};

function EntitiesSavedStates( { isOpen, close } ) {
const saveButtonRef = useRef();
useEffect( () => {
Expand All @@ -27,13 +35,42 @@ function EntitiesSavedStates( { isOpen, close } ) {
}
}, [ isOpen ] );
const { dirtyEntityRecords } = useSelect( ( select ) => {
const dirtyRecords = select(
coreStore
).__experimentalGetDirtyEntityRecords();

// Remove site object and decouple into its edited pieces.
const dirtyRecordsWithoutSite = dirtyRecords.filter(
( record ) => ! ( record.kind === 'root' && record.name === 'site' )
);

const siteEdits = select( coreStore ).getEntityRecordEdits(
'root',
'site'
);

const siteEditsAsEntities = [];
for ( const property in siteEdits ) {
siteEditsAsEntities.push( {
kind: 'root',
name: 'site',
title: TRANSLATED_SITE_PROTPERTIES[ property ] || property,
property,
} );
}
const dirtyRecordsWithSiteItems = [
...dirtyRecordsWithoutSite,
...siteEditsAsEntities,
];

return {
dirtyEntityRecords: select(
coreStore
).__experimentalGetDirtyEntityRecords(),
dirtyEntityRecords: dirtyRecordsWithSiteItems,
};
}, [] );
const { saveEditedEntityRecord } = useDispatch( coreStore );
const {
saveEditedEntityRecord,
__experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits,
} = useDispatch( coreStore );

// To group entities by type.
const partitionedSavables = Object.values(
Expand All @@ -43,42 +80,53 @@ function EntitiesSavedStates( { isOpen, close } ) {
// Unchecked entities to be ignored by save function.
const [ unselectedEntities, _setUnselectedEntities ] = useState( [] );

const setUnselectedEntities = ( { kind, name, key }, checked ) => {
const setUnselectedEntities = (
{ kind, name, key, property },
checked
) => {
if ( checked ) {
_setUnselectedEntities(
unselectedEntities.filter(
( elt ) =>
elt.kind !== kind ||
elt.name !== name ||
elt.key !== key
elt.key !== key ||
elt.property !== property
)
);
} else {
_setUnselectedEntities( [
...unselectedEntities,
{ kind, name, key },
{ kind, name, key, property },
] );
}
};

const saveCheckedEntities = () => {
const entitiesToSave = dirtyEntityRecords.filter(
( { kind, name, key } ) => {
( { kind, name, key, property } ) => {
return ! some(
unselectedEntities,
( elt ) =>
elt.kind === kind &&
elt.name === name &&
elt.key === key
elt.key === key &&
elt.property === property
);
}
);

close( entitiesToSave );

entitiesToSave.forEach( ( { kind, name, key } ) => {
saveEditedEntityRecord( kind, name, key );
const siteItemsToSave = [];
entitiesToSave.forEach( ( { kind, name, key, property } ) => {
if ( 'root' === kind && 'site' === name ) {
siteItemsToSave.push( property );
} else {
saveEditedEntityRecord( kind, name, key );
}
} );
saveSpecifiedEntityEdits( 'root', 'site', undefined, siteItemsToSave );
};

// Explicitly define this with no argument passed. Using `close` on
Expand Down

0 comments on commit f4b3c72

Please sign in to comment.