Skip to content

Commit

Permalink
Global styles: add revisions UI (#50089)
Browse files Browse the repository at this point in the history
* initial commit.

* Adding state for revisions
Adding revisions global styles sidebar UI
Adding revisions fill

Adding revisions components and styles.

Added e2e tests

* Making selectors more consistent in e2e tests

* This commit:
- redesigns the revisions list to show a timeline view
- changes the revisions endpoint response (and tests) to return a human friendly diff only. The frontend can format dates on the fly
- Adds a slot to the edit side sidebar that can position a component at the bottom of the interface sidebar
- updates e2e tests

* This commit:
- adds wordpress/date to package-lock.jdon
- ensures that we correctly modify the user revisions to add unsaved and islatest flags

* This commit:
- extends the e2e test a little bit and adds a TODO to do it better
- Adds ally labels to the color panel

* This commit:
- uses a clientside human-readable time diff method instead of the server side one. This is to make the response as close to the post revisions response as possible (for now)
- makes the current timeline dot blue

This commit:

* This commit:
- removes the specific author/user properties from the rest controller
- grabs revision author information in the JS
- shows a loading wheel while we grab revisions and user data

* Label titles

* Style adjustments

Adding a reset to theme default item to the revisions list.

Adding a dependencies to the `useMemo` so that it knows to update when the number of revisions change.

This is so we can ensure we add the right metadata in the right order, e.g., `isLatest`

Extracted hooks and components from screen revisions monster file

* Adding tests for the useGetGlobalStylesRevisions hook

* Extracting SCSS to external file
Renaming date > meta in classname of DIV that wraps revision date and author meta
Extra checks so that we don't render the meta block at all if there's no data

* Reverting the reset theme default button and ensuring that revisions panel only display when there are more than two revisions.
Updated tests accordingly.

bumping gravatar size to 48

Updating other E2E tests after having added aria labels to the color controls

Update e2e button spec after changes to button labels

* This commit:
- refactors useGlobalOutput by splitting out the build logic into a separate hook so that we can pass a custom config
- uses wp.date.getSettings().formats.datetimeAbbreviated for formatted date in the revisions buttons
- removes unnecessary role on the ordered list
- updated copy
- replacing modal component with the confirm dialog component
- minor code optimizations
- making the revisions store methods stable
- updating tests
- removing shouldShowClose button prop on the editor-canvas-container

update e2e

* Tweaked the revisions buttons copy, shifting the date and meta around.
Updated tests

---------

Co-authored-by: James Koster <james@jameskoster.co.uk>
  • Loading branch information
ramonjd and jameskoster authored May 8, 2023
1 parent f4f5e37 commit 81cd233
Show file tree
Hide file tree
Showing 36 changed files with 1,375 additions and 48 deletions.
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(
/* translators: %s is the type of color property, e.g., "background" */
__( 'Color %s styles' ),
label
),
};

return (
Expand Down
6 changes: 5 additions & 1 deletion packages/block-editor/src/components/global-styles/index.js
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';
58 changes: 57 additions & 1 deletion packages/block-editor/src/components/global-styles/test/utils.js
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 )
);
}
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

1 comment on commit 81cd233

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 81cd233.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4910773476
📝 Reported issues:

Please sign in to comment.