-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Explore Multi-Entity Saving #18029
Explore Multi-Entity Saving #18029
Changes from all commits
472767c
a352839
d519153
7dbcae6
4a7faf4
98c930d
4265794
35312aa
2b88ed5
cf9e4fe
944d112
68a687d
fcd479a
acbf0a3
3e0b339
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { startCase } from 'lodash'; | ||
import EquivalentKeyMap from 'equivalent-key-map'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { CheckboxControl, Modal, Button } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { useSelect, useDispatch } from '@wordpress/data'; | ||
import { useState } from '@wordpress/element'; | ||
|
||
const EntitiesSavedStatesCheckbox = ( { | ||
id, | ||
name, | ||
changes: { rawRecord }, | ||
checked, | ||
setCheckedById, | ||
} ) => ( | ||
<CheckboxControl | ||
label={ `${ startCase( name ) }: "${ rawRecord.title || | ||
rawRecord.name || | ||
__( 'Untitled' ) }"` } | ||
checked={ checked } | ||
onChange={ ( nextChecked ) => setCheckedById( id, nextChecked ) } | ||
/> | ||
); | ||
|
||
export default function EntitiesSavedStates( { | ||
isOpen, | ||
onRequestClose, | ||
ignoredForSave = new EquivalentKeyMap(), | ||
} ) { | ||
const entityRecordChangesByRecord = useSelect( ( select ) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand properly, we will be receiving changes from all entities here even entities that are not edited in the editor itself (think plugin, left over from other pages if it's an SPA...). Do you think the "editor" should register the entities it's editing somehow instead of just getting all edits from the core data store? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I thought that wouldn't be a blocker since we only have one editor for now.
Yes, I think the Should I do that in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure it's the responsibility of I'm ok for this being in a separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, but it is meant to be rendered in an editing context and it's the only way to link the two dynamically without walking the block tree. I.e. The only way for the editor to answer: Which entities am I rendering/editing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was imagining something like the EditorProvider registering its entities and maybe the Post block, Site block registering their own. I can see how it's not great either as extra work for block authors. We can try and iterate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, and we can do it in |
||
select( 'core' ).getEntityRecordChangesByRecord() | ||
); | ||
const { saveEditedEntityRecord } = useDispatch( 'core' ); | ||
|
||
const [ checkedById, _setCheckedById ] = useState( () => new EquivalentKeyMap() ); | ||
const setCheckedById = ( id, checked ) => | ||
_setCheckedById( ( prevCheckedById ) => { | ||
const nextCheckedById = new EquivalentKeyMap( prevCheckedById ); | ||
if ( checked ) { | ||
nextCheckedById.set( id, true ); | ||
} else { | ||
nextCheckedById.delete( id ); | ||
} | ||
return nextCheckedById; | ||
} ); | ||
const saveCheckedEntities = () => { | ||
checkedById.forEach( ( _checked, id ) => { | ||
if ( ! ignoredForSave.has( id ) ) { | ||
saveEditedEntityRecord( | ||
...id.filter( ( s, i ) => i !== id.length - 1 || s !== 'undefined' ) | ||
); | ||
} | ||
} ); | ||
onRequestClose( checkedById ); | ||
}; | ||
return ( | ||
isOpen && ( | ||
epiqueras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Modal | ||
title={ __( 'What do you want to save?' ) } | ||
onRequestClose={ () => onRequestClose() } | ||
contentLabel={ __( 'Select items to save.' ) } | ||
> | ||
{ Object.keys( entityRecordChangesByRecord ).map( ( changedKind ) => | ||
Object.keys( entityRecordChangesByRecord[ changedKind ] ).map( | ||
( changedName ) => | ||
Object.keys( | ||
entityRecordChangesByRecord[ changedKind ][ changedName ] | ||
).map( ( changedKey ) => { | ||
epiqueras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const id = [ changedKind, changedName, changedKey ]; | ||
return ( | ||
<EntitiesSavedStatesCheckbox | ||
key={ id.join( ' | ' ) } | ||
id={ id } | ||
name={ changedName } | ||
changes={ | ||
entityRecordChangesByRecord[ changedKind ][ changedName ][ | ||
changedKey | ||
] | ||
} | ||
checked={ checkedById.get( id ) } | ||
setCheckedById={ setCheckedById } | ||
/> | ||
); | ||
} ) | ||
) | ||
) } | ||
<Button | ||
isPrimary | ||
disabled={ checkedById.size === 0 } | ||
onClick={ saveCheckedEntities } | ||
className="editor-entities-saved-states__save-button" | ||
> | ||
{ __( 'Save' ) } | ||
</Button> | ||
</Modal> | ||
) | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.editor-entities-saved-states__save-button { | ||
display: block; | ||
margin-left: auto; | ||
margin-right: 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we'll need such a selector. Each time the returned value from a selector is that complex, I feel something is wrong. Can we just replace it with a selector that checks if an entity is Dirty?
I guess right now we'd also need a selector to retrieve all dirty records (hopefully we could remove that later).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we would need that anyways.