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

Avoid overriding custom settings on font library save #60438

Merged
merged 4 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ import {
checkFontFaceInstalled,
} from './utils';
import { toggleFont } from './utils/toggleFont';
import setNestedValue from '../../../utils/set-nested-value';

export const FontLibraryContext = createContext( {} );

function FontLibraryProvider( { children } ) {
const { __experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits } =
useDispatch( coreStore );
const { saveEntityRecord } = useDispatch( coreStore );
const { globalStylesId } = useSelect( ( select ) => {
const { __experimentalGetCurrentGlobalStylesId } = select( coreStore );
return { globalStylesId: __experimentalGetCurrentGlobalStylesId() };
Expand Down Expand Up @@ -94,11 +94,30 @@ function FontLibraryProvider( { children } ) {
'base'
);

// Save font families to the global styles post in the database.
const saveFontFamilies = () => {
saveSpecifiedEntityEdits( 'root', 'globalStyles', globalStylesId, [
'settings.typography.fontFamilies',
] );
/*
* Save the font families to the database.

* This function is called when the user activates or deactivates a font family.
* It updates the global styles post content in the database just new font families.
* This avoids saving other styles/settings done by the user using other parts of the editor.
*
* It uses the font families from the param to avoid using the font families from a outdated state.
*
* @param {Array} fonts - The font families that will be saved to the database.
*/
const saveFontFamilies = async ( fonts ) => {
// Gets the global styles databsse post content.
const updatedGlobalStyles = globalStyles.record;

// Updates the database version of global styles with the edited font families in the client.
setNestedValue(
updatedGlobalStyles,
[ 'settings', 'typography', 'fontFamilies' ],
fonts
);

// Saves a new version of the global styles in the database.
await saveEntityRecord( 'root', 'globalStyles', updatedGlobalStyles );
};

// Library Fonts
Expand Down Expand Up @@ -322,15 +341,11 @@ function FontLibraryProvider( { children } ) {

if ( fontFamiliesToActivate.length > 0 ) {
// Activate the font family (add the font family to the global styles).
activateCustomFontFamilies( fontFamiliesToActivate );

// Save the global styles to the database.
await saveSpecifiedEntityEdits(
'root',
'globalStyles',
globalStylesId,
[ 'settings.typography.fontFamilies' ]
const activeFonts = activateCustomFontFamilies(
fontFamiliesToActivate
);
// Save the global styles to the database.
await saveFontFamilies( activeFonts );

refreshLibrary();
}
Expand Down Expand Up @@ -360,14 +375,11 @@ function FontLibraryProvider( { children } ) {
// Deactivate the font family if delete request is successful
// (Removes the font family from the global styles).
if ( uninstalledFontFamily.deleted ) {
deactivateFontFamily( fontFamilyToUninstall );
// Save the global styles to the database.
await saveSpecifiedEntityEdits(
'root',
'globalStyles',
globalStylesId,
[ 'settings.typography.fontFamilies' ]
const activeFonts = deactivateFontFamily(
fontFamilyToUninstall
);
// Save the global styles to the database.
await saveFontFamilies( activeFonts );
}

// Refresh the library (the library font families from database).
Expand All @@ -391,42 +403,54 @@ function FontLibraryProvider( { children } ) {
const newCustomFonts = initialCustomFonts.filter(
( f ) => f.slug !== font.slug
);
setFontFamilies( {
const activeFonts = {
...fontFamilies,
[ font.source ]: newCustomFonts,
} );
};
setFontFamilies( activeFonts );

if ( font.fontFace ) {
font.fontFace.forEach( ( face ) => {
unloadFontFaceInBrowser( face, 'all' );
} );
}
return activeFonts;
};

const activateCustomFontFamilies = ( fontsToAdd ) => {
// Removes the id from the families and faces to avoid saving that to global styles post content.
const fontsToActivate = fontsToAdd.map(
( { id: _familyDbId, fontFace, ...font } ) => ( {
...font,
...( fontFace && fontFace.length > 0
? {
fontFace: fontFace.map(
( { id: _faceDbId, ...face } ) => face
),
}
: {} ),
} )
);
const fontsToActivate = cleanFontsForSave( fontsToAdd );

// Activate the fonts by set the new custom fonts array.
setFontFamilies( {
const activeFonts = {
...fontFamilies,
// Merge the existing custom fonts with the new fonts.
custom: mergeFontFamilies( fontFamilies?.custom, fontsToActivate ),
} );
};

// Activate the fonts by set the new custom fonts array.
setFontFamilies( activeFonts );

loadFontsInBrowser( fontsToActivate );

return activeFonts;
};

// Removes the id from the families and faces to avoid saving that to global styles post content.
const cleanFontsForSave = ( fonts ) => {
return fonts.map( ( { id: _familyDbId, fontFace, ...font } ) => ( {
...font,
...( fontFace && fontFace.length > 0
? {
fontFace: fontFace.map(
( { id: _faceDbId, ...face } ) => face
),
}
: {} ),
} ) );
};

const loadFontsInBrowser = ( fonts ) => {
// Add custom fonts to the browser.
fontsToActivate.forEach( ( font ) => {
fonts.forEach( ( font ) => {
if ( font.fontFace ) {
font.fontFace.forEach( ( face ) => {
// Load font faces just in the iframe because they already are in the document.
Expand Down Expand Up @@ -518,6 +542,7 @@ function FontLibraryProvider( { children } ) {
value={ {
libraryFontSelected,
handleSetLibraryFontSelected,
fontFamilies,
themeFonts,
baseThemeFonts,
customFonts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function InstalledFonts() {
fontFamiliesHasChanges,
notice,
setNotice,
fontFamilies,
} = useContext( FontLibraryContext );
const [ isConfirmDeleteOpen, setIsConfirmDeleteOpen ] = useState( false );
const customFontFamilyId =
Expand Down Expand Up @@ -262,7 +263,9 @@ function InstalledFonts() {
) }
<Button
variant="primary"
onClick={ saveFontFamilies }
onClick={ () => {
saveFontFamilies( fontFamilies );
} }
disabled={ ! fontFamiliesHasChanges }
__experimentalIsFocusable
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { store as coreStore } from '@wordpress/core-data';
import { useSupportedStyles } from '../../components/global-styles/hooks';
import { unlock } from '../../lock-unlock';
import cloneDeep from '../../utils/clone-deep';
import setNestedValue from '../../utils/set-nested-value';

const { cleanEmptyObject, GlobalStylesContext } = unlock(
blockEditorPrivateApis
Expand Down Expand Up @@ -236,46 +237,6 @@ function useChangesToPush( name, attributes, userConfig ) {
}, [ supports, attributes, blockUserConfig ] );
}

/**
* Sets the value at path of object.
* If a portion of path doesn’t exist, it’s created.
* Arrays are created for missing index properties while objects are created
* for all other missing properties.
*
* This function intentionally mutates the input object.
*
* Inspired by _.set().
*
* @see https://lodash.com/docs/4.17.15#set
*
* @todo Needs to be deduplicated with its copy in `@wordpress/core-data`.
*
* @param {Object} object Object to modify
* @param {Array} path Path of the property to set.
* @param {*} value Value to set.
*/
function setNestedValue( object, path, value ) {
if ( ! object || typeof object !== 'object' ) {
return object;
}

path.reduce( ( acc, key, idx ) => {
if ( acc[ key ] === undefined ) {
if ( Number.isInteger( path[ idx + 1 ] ) ) {
acc[ key ] = [];
} else {
acc[ key ] = {};
}
}
if ( idx === path.length - 1 ) {
acc[ key ] = value;
}
return acc[ key ];
}, object );

return object;
}

function PushChangesToGlobalStylesControl( {
name,
attributes,
Expand Down
39 changes: 39 additions & 0 deletions packages/edit-site/src/utils/set-nested-value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Sets the value at path of object.
* If a portion of path doesn’t exist, it’s created.
* Arrays are created for missing index properties while objects are created
* for all other missing properties.
*
* This function intentionally mutates the input object.
*
* Inspired by _.set().
*
* @see https://lodash.com/docs/4.17.15#set
*
* @todo Needs to be deduplicated with its copy in `@wordpress/core-data`.
*
* @param {Object} object Object to modify
* @param {Array} path Path of the property to set.
* @param {*} value Value to set.
*/
export default function setNestedValue( object, path, value ) {
if ( ! object || typeof object !== 'object' ) {
return object;
}

path.reduce( ( acc, key, idx ) => {
if ( acc[ key ] === undefined ) {
if ( Number.isInteger( path[ idx + 1 ] ) ) {
acc[ key ] = [];
} else {
acc[ key ] = {};
}
}
if ( idx === path.length - 1 ) {
acc[ key ] = value;
}
return acc[ key ];
}, object );

return object;
}
Loading