-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Block Bindings: Output granular post meta changes in a list inside save panel #62982
Changes from all commits
60a64ee
47faed5
a39409b
909a5c6
fd6caf7
62c0329
659ade1
af502b6
673b292
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,3 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import fastDeepEqual from 'fast-deep-equal'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
|
@@ -34,6 +29,49 @@ const EMPTY_INSERTION_POINT = { | |
insertionIndex: undefined, | ||
filterValue: undefined, | ||
}; | ||
const isObject = ( obj ) => obj !== null && typeof obj === 'object'; | ||
|
||
/** | ||
* A deep comparison of two objects, optimized for comparing metadata changes. | ||
* @param {Object} changedObject The changed object to compare. | ||
* @param {Object} originalObject The original object to compare against. | ||
* @param {string} parentPath A key/value pair object of block names and their rendered titles. | ||
* @return {string[]} An array of paths whose values have changed. | ||
*/ | ||
function deepCompare( changedObject, originalObject, parentPath = '' ) { | ||
// We have two non-object values to compare. | ||
if ( ! isObject( changedObject ) && ! isObject( originalObject ) ) { | ||
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. This doesn't seem to be the place to have such a util. It has nothing to do with the store. |
||
/* | ||
* Only return a path if the value has changed. | ||
*/ | ||
return changedObject !== originalObject | ||
? parentPath.split( '.' ).join( ' > ' ) | ||
: undefined; | ||
} | ||
|
||
// Enable comparison when an object doesn't have a corresponding property to compare. | ||
changedObject = isObject( changedObject ) ? changedObject : {}; | ||
originalObject = isObject( originalObject ) ? originalObject : {}; | ||
|
||
const allKeys = new Set( [ | ||
...Object.keys( changedObject ), | ||
...Object.keys( originalObject ), | ||
] ); | ||
|
||
let diffs = []; | ||
for ( const key of allKeys ) { | ||
const path = parentPath ? parentPath + '.' + key : key; | ||
const changedPath = deepCompare( | ||
changedObject[ key ], | ||
originalObject[ key ], | ||
path | ||
); | ||
if ( changedPath ) { | ||
diffs = diffs.concat( changedPath ); | ||
} | ||
} | ||
return diffs; | ||
} | ||
|
||
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. How does this deep equality comparison look? The one mentioned in this comment works well, so I essentially copied it, just changing the appearance of the diff slightly. I'm not sure in what other scenarios metadata might change or how, and if this comparison would suit those scenarios. 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. Also, since we're only using the deep comparison in this file, I decided to just keep it local rather than creating a utils file for now. How does that sound? 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. The only thing that changes is the way we show nested properties, right? (This line) For me, both cases are really similar so maybe it makes sense to keep consistency between them? Additionally, I think it's fine to keep it local for now, but I would open a follow-up pull request to move |
||
/** | ||
* Get the insertion point for the inserter. | ||
|
@@ -153,31 +191,28 @@ export const getCurrentTemplateTemplateParts = createRegistrySelector( | |
* | ||
* @return {boolean} Whether there are edits or not in the meta fields of the relevant post. | ||
*/ | ||
export const hasPostMetaChanges = createRegistrySelector( | ||
export const getPostMetaChanges = createRegistrySelector( | ||
( select ) => ( state, postType, postId ) => { | ||
const { type: currentPostType, id: currentPostId } = | ||
getCurrentPost( state ); | ||
// If no postType or postId is passed, use the current post. | ||
const edits = select( coreStore ).getEntityRecordNonTransientEdits( | ||
const original = select( coreStore ).getEntityRecord( | ||
'postType', | ||
postType || currentPostType, | ||
postId || currentPostId | ||
); | ||
|
||
if ( ! edits?.meta ) { | ||
return false; | ||
} | ||
|
||
// Compare if anything apart from `footnotes` has changed. | ||
const originalPostMeta = select( coreStore ).getEntityRecord( | ||
const edits = select( coreStore ).getEntityRecordNonTransientEdits( | ||
'postType', | ||
postType || currentPostType, | ||
postId || currentPostId | ||
)?.meta; | ||
); | ||
|
||
if ( ! original?.meta || ! edits?.meta ) { | ||
return []; | ||
} | ||
|
||
return ! fastDeepEqual( | ||
{ ...originalPostMeta, footnotes: undefined }, | ||
{ ...edits.meta, footnotes: undefined } | ||
return deepCompare( edits.meta, original.meta ).filter( | ||
( item ) => item !== 'footnotes' | ||
); | ||
} | ||
); | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,3 +1,29 @@ | ||||||||||||||
/** | ||||||||||||||
* Internal dependencies | ||||||||||||||
*/ | ||||||||||||||
import isShallowEqualArrays from './arrays'; | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Returns true if the two values are equal, or false otherwise. | ||||||||||||||
* Includes a check for shallow array equality. | ||||||||||||||
* | ||||||||||||||
* @param {any} a | ||||||||||||||
* @param {any} b | ||||||||||||||
* | ||||||||||||||
* @return {boolean} Whether the two values are equal. | ||||||||||||||
*/ | ||||||||||||||
function isEqual( a, b ) { | ||||||||||||||
if ( Array.isArray( a ) && Array.isArray( b ) ) { | ||||||||||||||
return isShallowEqualArrays( a, b ); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if ( a === b ) { | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Returns true if the two objects are shallow equal, or false otherwise. | ||||||||||||||
* | ||||||||||||||
|
@@ -31,7 +57,7 @@ export default function isShallowEqualObjects( a, b ) { | |||||||||||||
// | ||||||||||||||
// Example: isShallowEqualObjects( { a: undefined }, { b: 5 } ) | ||||||||||||||
( aValue === undefined && ! b.hasOwnProperty( key ) ) || | ||||||||||||||
aValue !== b[ key ] | ||||||||||||||
! isEqual( aValue, b[ key ] ) | ||||||||||||||
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. In using Here's a screenshot of the values being compared along with the warning: This check is called in the following lines: gutenberg/packages/data/src/components/use-select/index.js Lines 161 to 166 in 7dda8fd
Essentially, a new array is returned each time from I'm not sure the equality check was written with this scenario in mind, so I revised it to remove the warning and aid with discussion. Would appreciate any pointers or thoughts 🙏 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 don't feel fully comfortable changing the conditional of the warning. I would like to understand better why the existing implementation is triggering that warning. I've been doing some checks with global styles, which is a really similar use case to what we are trying to achieve and this is what I found: In In the Site Editor:
In this branch without the changes in
That makes me think that something might be wrong with this implementation. That's why I would like to understand better the root cause. Why is it working in 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. This shouldn't change and the warning tries to protect us from unnecessary rerenders. Probably what we need here is have a selector that gets the data(returns stable reference) and do the comparison in the consumer components. We could keep the |
||||||||||||||
) { | ||||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
|
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.
Is not enough to use
postMetaChanges?.length
here?