Skip to content
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

Global styles: add revisions UI #50089

Merged
merged 15 commits into from
May 8, 2023
12 changes: 12 additions & 0 deletions docs/reference-guides/data/data-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ _Returns_

- `any`: The current theme.

### getCurrentThemeGlobalStylesRevisions

Returns the revisions of the current global styles theme.

_Parameters_

- _state_ `State`: Data state.

_Returns_

- `Object | null`: The current global styles.

### getCurrentUser

Returns the current user.
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

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
Expand Up @@ -20,7 +20,7 @@ import {
Button,
} from '@wordpress/components';
import { useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
Expand Down Expand Up @@ -230,6 +230,11 @@ function ColorPanelDropdown( {
{ 'is-open': isOpen }
),
'aria-expanded': isOpen,
'aria-label': sprintf(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracting out this change plus necessary updates to E2E tests over in #50307 to reduce a bit of clutter

/* translators: %s is the type of color property, e.g., "background" */
__( 'Color %s styles' ),
label
),
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export {
useSettingsForBlockElement,
} from './hooks';
export { getBlockCSSSelector } from './get-block-css-selector';
export { useGlobalStylesOutput } from './use-global-styles-output';
export {
useGlobalStylesOutput,
useGlobalStylesOutputWithConfig,
} from './use-global-styles-output';
export { GlobalStylesContext } from './context';
export {
default as TypographyPanel,
Expand All @@ -20,3 +23,4 @@ export { default as ColorPanel, useHasColorPanel } from './color-panel';
export { default as EffectsPanel, useHasEffectsPanel } from './effects-panel';
export { default as FiltersPanel, useHasFiltersPanel } from './filters-panel';
export { default as AdvancedPanel } from './advanced-panel';
export { areGlobalStyleConfigsEqual } from './utils';
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
/**
* Internal dependencies
*/
import { getPresetVariableFromValue, getValueFromVariable } from '../utils';
import {
areGlobalStyleConfigsEqual,
getPresetVariableFromValue,
getValueFromVariable,
} from '../utils';

describe( 'editor utils', () => {
const themeJson = {
Expand Down Expand Up @@ -203,4 +207,56 @@ describe( 'editor utils', () => {
} );
} );
} );

describe( 'areGlobalStyleConfigsEqual', () => {
test.each( [
{ original: null, variation: null, expected: true },
{ original: {}, variation: {}, expected: true },
{ original: {}, variation: undefined, expected: false },
{
original: {
styles: {
color: { text: 'var(--wp--preset--color--red)' },
},
},
variation: {
styles: {
color: { text: 'var(--wp--preset--color--blue)' },
},
},
expected: false,
},
{ original: {}, variation: undefined, expected: false },
{
original: {
styles: {
color: { text: 'var(--wp--preset--color--red)' },
},
settings: {
typography: {
fontSize: true,
},
},
},
variation: {
styles: {
color: { text: 'var(--wp--preset--color--red)' },
},
settings: {
typography: {
fontSize: true,
},
},
},
expected: true,
},
] )(
'.areGlobalStyleConfigsEqual( $original, $variation )',
( { original, variation, expected } ) => {
expect(
areGlobalStyleConfigsEqual( original, variation )
).toBe( expected );
}
);
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -1106,9 +1106,17 @@ const processCSSNesting = ( css, blockSelector ) => {
return processedCSS;
};

export function useGlobalStylesOutput() {
let { merged: mergedConfig } = useContext( GlobalStylesContext );

/**
* Returns the global styles output using a global styles configuration.
* If wishing to generate global styles and settings based on the
* global styles config loaded in the editor context, use `useGlobalStylesOutput()`.
* The use case for a custom config is to generate bespoke styles
* and settings for previews, or other out-of-editor experiences.
*
* @param {Object} mergedConfig Global styles configuration.
* @return {Array} Array of stylesheets and settings.
*/
export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) {
const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' );
const hasBlockGapSupport = blockGap !== null;
const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support.
Expand Down Expand Up @@ -1190,3 +1198,13 @@ export function useGlobalStylesOutput() {
disableLayoutStyles,
] );
}

/**
* Returns the global styles output based on the current state of global styles config loaded in the editor context.
*
* @return {Array} Array of stylesheets and settings.
*/
export function useGlobalStylesOutput() {
const { merged: mergedConfig } = useContext( GlobalStylesContext );
return useGlobalStylesOutputWithConfig( mergedConfig );
}
27 changes: 27 additions & 0 deletions packages/block-editor/src/components/global-styles/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { get } from 'lodash';
import fastDeepEqual from 'fast-deep-equal/es6';

/**
* Internal dependencies
Expand Down Expand Up @@ -376,3 +377,29 @@ export function scopeSelector( scope, selector ) {

return selectorsScoped.join( ', ' );
}

/**
* Compares global style variations according to their styles and settings properties.
*
* @example
* ```js
* const globalStyles = { styles: { typography: { fontSize: '10px' } }, settings: {} };
* const variation = { styles: { typography: { fontSize: '10000px' } }, settings: {} };
* const isEqual = areGlobalStyleConfigsEqual( globalStyles, variation );
* // false
* ```
*
* @param {Object} original A global styles object.
* @param {Object} variation A global styles object.
*
* @return {boolean} Whether `original` and `variation` match.
*/
export function areGlobalStyleConfigsEqual( original, variation ) {
if ( typeof original !== 'object' || typeof variation !== 'object' ) {
return original === variation;
}
return (
fastDeepEqual( original?.styles, variation?.styles ) &&
fastDeepEqual( original?.settings, variation?.settings )
);
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,18 @@ _Returns_

- `any`: The current theme.

### getCurrentThemeGlobalStylesRevisions

Returns the revisions of the current global styles theme.

_Parameters_

- _state_ `State`: Data state.

_Returns_

- `Object | null`: The current global styles.

### getCurrentUser

Returns the current user.
Expand Down
19 changes: 19 additions & 0 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,25 @@ export function receiveThemeSupports() {
};
}

/**
* Returns an action object used in signalling that the theme global styles CPT post revisions have been received.
* Ignored from documentation as it's internal to the data store.
*
* @ignore
*
* @param {number} currentId The post id.
* @param {Array} revisions The global styles revisions.
*
* @return {Object} Action object.
*/
export function receiveThemeGlobalStyleRevisions( currentId, revisions ) {
return {
type: 'RECEIVE_THEME_GLOBAL_STYLE_REVISIONS',
currentId,
revisions,
};
}

/**
* Returns an action object used in signalling that the preview data for
* a given URl has been received.
Expand Down
21 changes: 21 additions & 0 deletions packages/core-data/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,26 @@ export function navigationFallbackId( state = null, action ) {
return state;
}

/**
* Reducer managing the theme global styles revisions.
*
* @param {Record<string, object>} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Record<string, object>} Updated state.
*/
export function themeGlobalStyleRevisions( state = {}, action ) {
switch ( action.type ) {
case 'RECEIVE_THEME_GLOBAL_STYLE_REVISIONS':
return {
...state,
[ action.currentId ]: action.revisions,
};
}

return state;
}

export default combineReducers( {
terms,
users,
Expand All @@ -659,6 +679,7 @@ export default combineReducers( {
currentUser,
themeGlobalStyleVariations,
themeBaseGlobalStyles,
themeGlobalStyleRevisions,
taxonomies,
entities,
undo,
Expand Down
47 changes: 46 additions & 1 deletion packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export const getEntityRecords =

let records = Object.values( await apiFetch( { path } ) );
// If we request fields but the result doesn't contain the fields,
// explicitely set these fields as "undefined"
// explicitly set these fields as "undefined"
// that way we consider the query "fullfilled".
if ( query._fields ) {
records = records.map( ( record ) => {
Expand Down Expand Up @@ -500,6 +500,51 @@ export const __experimentalGetCurrentThemeGlobalStylesVariations =
);
};

/**
* Fetches and returns the revisions of the current global styles theme.
*/
export const getCurrentThemeGlobalStylesRevisions =
() =>
async ( { resolveSelect, dispatch } ) => {
const globalStylesId =
await resolveSelect.__experimentalGetCurrentGlobalStylesId();
const record = globalStylesId
? await resolveSelect.getEntityRecord(
'root',
'globalStyles',
globalStylesId
)
: undefined;
const revisionsURL = record?._links?.[ 'version-history' ]?.[ 0 ]?.href;

if ( revisionsURL ) {
const resetRevisions = await apiFetch( {
url: revisionsURL,
} );
const revisions = resetRevisions?.map( ( revision ) =>
Object.fromEntries(
Object.entries( revision ).map( ( [ key, value ] ) => [
camelCase( key ),
value,
] )
)
);
dispatch.receiveThemeGlobalStyleRevisions(
globalStylesId,
revisions
);
}
};

getCurrentThemeGlobalStylesRevisions.shouldInvalidate = ( action ) => {
return (
action.type === 'SAVE_ENTITY_RECORD_FINISH' &&
action.kind === 'root' &&
! action.error &&
action.name === 'globalStyles'
);
};

export const getBlockPatterns =
() =>
async ( { dispatch } ) => {
Expand Down
21 changes: 21 additions & 0 deletions packages/core-data/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface State {
entities: EntitiesState;
themeBaseGlobalStyles: Record< string, Object >;
themeGlobalStyleVariations: Record< string, string >;
themeGlobalStyleRevisions: Record< number, Object >;
undo: UndoState;
userPermissions: Record< string, boolean >;
users: UserState;
Expand Down Expand Up @@ -1247,3 +1248,23 @@ export function getNavigationFallbackId(
): EntityRecordKey | undefined {
return state.navigationFallbackId;
}

/**
* Returns the revisions of the current global styles theme.
*
* @param state Data state.
*
* @return The current global styles.
*/
export function getCurrentThemeGlobalStylesRevisions(
state: State
): Object | null {
const currentGlobalStylesId =
__experimentalGetCurrentGlobalStylesId( state );

if ( ! currentGlobalStylesId ) {
return null;
}

return state.themeGlobalStyleRevisions[ currentGlobalStylesId ];
}
Loading