Skip to content

Commit

Permalink
Global styles revisions: highlight currently-loaded revision (#50725)
Browse files Browse the repository at this point in the history
* Rename current to selected to reflect the usage
Ensure the latest revision is highlighted even when there is no selected id
Add useEffect dependencies
Updating tests
Align text to left for long strings

* use a stable reference of an empty array
  • Loading branch information
ramonjd committed May 18, 2023
1 parent 671d727 commit b0d20ed
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,11 @@ function ScreenRevisions() {
blocks: select( blockEditorStore ).getBlocks(),
};
}, [] );

const { revisions, isLoading, hasUnsavedChanges } =
useGlobalStylesRevisions();
const [ selectedRevisionId, setSelectedRevisionId ] = useState();
const [ globalStylesRevision, setGlobalStylesRevision ] =
useState( userConfig );

const [ currentRevisionId, setCurrentRevisionId ] = useState(
/*
* We need this for the first render,
* otherwise the unsaved changes haven't been merged into the revisions array yet.
*/
hasUnsavedChanges ? 'unsaved' : revisions?.[ 0 ]?.id
);
const [
isLoadingRevisionWithUnsavedChanges,
setIsLoadingRevisionWithUnsavedChanges,
Expand Down Expand Up @@ -89,7 +81,7 @@ function ScreenRevisions() {
settings: revision?.settings,
id: revision?.id,
} );
setCurrentRevisionId( revision?.id );
setSelectedRevisionId( revision?.id );
};

const isLoadButtonEnabled =
Expand Down Expand Up @@ -117,7 +109,7 @@ function ScreenRevisions() {
<div className="edit-site-global-styles-screen-revisions">
<RevisionsButtons
onChange={ selectRevision }
currentRevisionId={ currentRevisionId }
selectedRevisionId={ selectedRevisionId }
userRevisions={ revisions }
/>
{ isLoadButtonEnabled && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date';
*/
function getRevisionLabel( revision ) {
const authorDisplayName = revision?.author?.name || __( 'User' );
const isUnsaved = 'unsaved' === revision?.id;

if ( isUnsaved ) {
if ( 'unsaved' === revision?.id ) {
return sprintf(
/* translators: %(name)s author display name */
__( 'Unsaved changes by %(name)s' ),
Expand Down Expand Up @@ -57,45 +56,42 @@ function getRevisionLabel( revision ) {
* Returns a rendered list of revisions buttons.
*
* @typedef {Object} props
* @property {Array<Object>} userRevisions A collection of user revisions.
* @property {number} currentRevisionId Callback fired when the modal is closed or action cancelled.
* @property {Function} onChange Callback fired when a revision is selected.
* @property {Array<Object>} userRevisions A collection of user revisions.
* @property {number} selectedRevisionId The id of the currently-selected revision.
* @property {Function} onChange Callback fired when a revision is selected.
*
* @param {props} Component props.
* @param {props} Component props.
* @return {JSX.Element} The modal component.
*/
function RevisionsButtons( { userRevisions, currentRevisionId, onChange } ) {
function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) {
return (
<ol
className="edit-site-global-styles-screen-revisions__revisions-list"
aria-label={ __( 'Global styles revisions' ) }
role="group"
>
{ userRevisions.map( ( revision ) => {
const { id, author, isLatest, modified } = revision;
{ userRevisions.map( ( revision, index ) => {
const { id, author, modified } = revision;
const authorDisplayName = author?.name || __( 'User' );
const authorAvatar = author?.avatar_urls?.[ '48' ];
/*
* If the currentId hasn't been selected yet, the first revision is
* the current one so long as the API returns revisions in descending order.
*/
const isActive = !! currentRevisionId
? id === currentRevisionId
: isLatest;
const isUnsaved = 'unsaved' === revision?.id;
const isSelected = selectedRevisionId
? selectedRevisionId === revision?.id
: index === 0;

return (
<li
className={ classnames(
'edit-site-global-styles-screen-revisions__revision-item',
{
'is-current': isActive,
'is-selected': isSelected,
}
) }
key={ id }
>
<Button
className="edit-site-global-styles-screen-revisions__revision-button"
disabled={ isActive }
disabled={ isSelected }
onClick={ () => {
onChange( revision );
} }
Expand All @@ -106,13 +102,25 @@ function RevisionsButtons( { userRevisions, currentRevisionId, onChange } ) {
{ humanTimeDiff( modified ) }
</time>
<span className="edit-site-global-styles-screen-revisions__meta">
{ sprintf(
/* translators: %(name)s author display name */
__( 'Changes saved by %(name)s' ),
{
name: authorDisplayName,
}
) }
{ isUnsaved
? sprintf(
/* translators: %(name)s author display name */
__(
'Unsaved changes by %(name)s'
),
{
name: authorDisplayName,
}
)
: sprintf(
/* translators: %(name)s author display name */
__(
'Changes saved by %(name)s'
),
{
name: authorDisplayName,
}
) }

<img
alt={ author?.name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
left: 0;
transform: translate(-50%, -50%);
}
&.is-current::before {
&.is-selected::before {
background: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
}
}
Expand All @@ -56,7 +56,7 @@
}
}

.is-current {
.is-selected {
.edit-site-global-styles-screen-revisions__revision-button {
color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
opacity: 1;
Expand Down Expand Up @@ -86,6 +86,7 @@
justify-content: space-between;
width: 100%;
align-items: center;
text-align: left;

img {
width: $grid-unit-20;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ describe( 'useGlobalStylesRevisions', () => {
styles: {},
},
],
isLoading: false,
};

it( 'returns loaded revisions with no unsaved changes', () => {
Expand Down Expand Up @@ -109,10 +108,24 @@ describe( 'useGlobalStylesRevisions', () => {
] );
} );

it( 'returns empty revisions when still loading', () => {
it( 'returns empty revisions', () => {
useSelect.mockImplementation( () => ( {
...selectValue,
isLoading: true,
revisions: [],
} ) );

const { result } = renderHook( () => useGlobalStylesRevisions() );
const { revisions, isLoading, hasUnsavedChanges } = result.current;

expect( isLoading ).toBe( true );
expect( hasUnsavedChanges ).toBe( false );
expect( revisions ).toEqual( [] );
} );

it( 'returns empty revisions when authors are not yet available', () => {
useSelect.mockImplementation( () => ( {
...selectValue,
authors: [],
} ) );

const { result } = renderHook( () => useGlobalStylesRevisions() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,53 +20,46 @@ const SITE_EDITOR_AUTHORS_QUERY = {
context: 'view',
capabilities: [ 'edit_theme_options' ],
};

const EMPTY_ARRAY = [];
const { GlobalStylesContext } = unlock( blockEditorPrivateApis );
export default function useGlobalStylesRevisions() {
const { user: userConfig } = useContext( GlobalStylesContext );
const { authors, currentUser, isDirty, revisions, isLoading } = useSelect(
const { authors, currentUser, isDirty, revisions } = useSelect(
( select ) => {
const {
__experimentalGetDirtyEntityRecords,
getCurrentUser,
getUsers,
getCurrentThemeGlobalStylesRevisions,
isResolving,
} = select( coreStore );
const dirtyEntityRecords = __experimentalGetDirtyEntityRecords();
const _currentUser = getCurrentUser();
const _isDirty = dirtyEntityRecords.length > 0;
const globalStylesRevisions =
getCurrentThemeGlobalStylesRevisions() || [];
const _authors = getUsers( SITE_EDITOR_AUTHORS_QUERY );
getCurrentThemeGlobalStylesRevisions() || EMPTY_ARRAY;
const _authors =
getUsers( SITE_EDITOR_AUTHORS_QUERY ) || EMPTY_ARRAY;

return {
authors: _authors,
currentUser: _currentUser,
isDirty: _isDirty,
revisions: globalStylesRevisions,
isLoading:
! globalStylesRevisions.length ||
isResolving( 'getUsers', [ SITE_EDITOR_AUTHORS_QUERY ] ),
};
},
[]
);
return useMemo( () => {
let _modifiedRevisions = [];
if ( isLoading || ! revisions.length ) {
if ( ! authors.length || ! revisions.length ) {
return {
revisions: _modifiedRevisions,
hasUnsavedChanges: isDirty,
isLoading,
isLoading: true,
};
}
/*
* Adds a flag to the first revision, which is the latest.
* Also adds author information to the revision.
* Then, if there are unsaved changes in the editor, create a
* new "revision" item that represents the unsaved changes.
*/

// Adds author details to each revision.
_modifiedRevisions = revisions.map( ( revision ) => {
return {
...revision,
Expand All @@ -76,10 +69,12 @@ export default function useGlobalStylesRevisions() {
};
} );

if ( _modifiedRevisions[ 0 ]?.id !== 'unsaved' ) {
// Flags the most current saved revision.
if ( _modifiedRevisions[ 0 ].id !== 'unsaved' ) {
_modifiedRevisions[ 0 ].isLatest = true;
}

// Adds an item for unsaved changes.
if ( isDirty && ! isEmpty( userConfig ) && currentUser ) {
const unsavedRevision = {
id: 'unsaved',
Expand All @@ -94,10 +89,11 @@ export default function useGlobalStylesRevisions() {

_modifiedRevisions.unshift( unsavedRevision );
}

return {
revisions: _modifiedRevisions,
hasUnsavedChanges: isDirty,
isLoading,
isLoading: false,
};
}, [ revisions.length, isDirty, isLoading ] );
}, [ isDirty, revisions, currentUser, authors, userConfig ] );
}

0 comments on commit b0d20ed

Please sign in to comment.