Skip to content

Commit

Permalink
Font Library: refactor client side install functions to work with rev…
Browse files Browse the repository at this point in the history
…ised API (#57844)

* Add batchInstallFontFaces function and related plumbing.

* Fix resolver name.

* Add embedding and rebuild theme.json settings for fontFamily.

* Handle responses directly, add to collection before activating. Remove unused test.

* Remove getIntersectingFontFaces.

* Check for existing font family before installing.

* Reference src, not uploadedFile key.

Co-authored-by: Matias Benedetto <[email protected]>

* Check for existing font family using GET /font-families?slug=.

* Filter already installed font faces (determined by matching fontWeight AND fontStyle)

---------

Co-authored-by: Matias Benedetto <[email protected]>
Co-authored-by: Jason Crist <[email protected]>
  • Loading branch information
3 people authored Jan 17, 2024
1 parent f84cc6d commit 492a3ee
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
* Internal dependencies
*/
import {
fetchInstallFont,
fetchGetFontFamilyBySlug,
fetchInstallFontFamily,
fetchUninstallFonts,
fetchFontCollections,
fetchFontCollection,
Expand All @@ -26,10 +27,11 @@ import {
mergeFontFamilies,
loadFontFaceInBrowser,
getDisplaySrcFromFontFace,
makeFormDataFromFontFamily,
makeFontFacesFormData,
makeFontFamilyFormData,
batchInstallFontFaces,
} from './utils';
import { toggleFont } from './utils/toggleFont';
import getIntersectingFontFaces from './utils/get-intersecting-font-faces';

export const FontLibraryContext = createContext( {} );

Expand Down Expand Up @@ -60,12 +62,19 @@ function FontLibraryProvider( { children } ) {
records: libraryPosts = [],
isResolving: isResolvingLibrary,
hasResolved: hasResolvedLibrary,
} = useEntityRecords( 'postType', 'wp_font_family', { refreshKey } );
} = useEntityRecords( 'postType', 'wp_font_family', {
refreshKey,
_embed: true,
} );

const libraryFonts =
( libraryPosts || [] ).map( ( post ) =>
JSON.parse( post.content.raw )
) || [];
( libraryPosts || [] ).map( ( post ) => {
post.font_family_settings.fontFace =
post?._embedded?.font_faces.map(
( face ) => face.font_face_settings
) || [];
return post.font_family_settings;
} ) || [];

// Global Styles (settings) font families
const [ fontFamilies, setFontFamilies ] = useGlobalSetting(
Expand Down Expand Up @@ -195,32 +204,108 @@ function FontLibraryProvider( { children } ) {
async function installFont( font ) {
setIsInstalling( true );
try {
// Prepare formData to install.
const formData = makeFormDataFromFontFamily( font );
// Get the ID of the font family post, if it is already installed.
let installedFontFamily = await fetchGetFontFamilyBySlug(
font.slug
)
.then( ( response ) => {
if ( ! response || response.length === 0 ) {
return null;
}
const fontFamilyPost = response[ 0 ];
return {
id: fontFamilyPost.id,
...fontFamilyPost.font_family_settings,
fontFace:
fontFamilyPost?._embedded?.font_faces.map(
( face ) => face.font_face_settings
) || [],
};
} )
.catch( ( e ) => {
// eslint-disable-next-line no-console
console.error( e );
return null;
} );

// Otherwise, install it.
if ( ! installedFontFamily ) {
const fontFamilyFormData = makeFontFamilyFormData( font );
// Prepare font family form data to install.
installedFontFamily = await fetchInstallFontFamily(
fontFamilyFormData
)
.then( ( response ) => {
return {
id: response.id,
...response.font_face_settings,
fontFace: [],
};
} )
.catch( ( e ) => {
throw Error( e.message );
} );
}

// Filter Font Faces that have already been installed
// We determine that by comparing the fontWeight and fontStyle
font.fontFace = font.fontFace.filter( ( fontFaceToInstall ) => {
return (
-1 ===
installedFontFamily.fontFace.findIndex(
( installedFontFace ) => {
return (
installedFontFace.fontWeight ===
fontFaceToInstall.fontWeight &&
installedFontFace.fontStyle ===
fontFaceToInstall.fontStyle
);
}
)
);
} );

if ( font.fontFace.length === 0 ) {
// Looks like we're only trying to install fonts that are already installed.
// Let's not do that.
// TODO: Exit with an error message?
return {
errors: [ 'All font faces are already installed' ],
};
}

// Prepare font faces form data to install.
const fontFacesFormData = makeFontFacesFormData( font );

// Install the fonts (upload the font files to the server and create the post in the database).
const response = await fetchInstallFont( formData );
const fontsInstalled = response?.successes || [];
// Get intersecting font faces between the fonts we tried to installed and the fonts that were installed
// (to avoid activating a non installed font).
const fontToBeActivated = getIntersectingFontFaces(
fontsInstalled,
[ font ]
const response = await batchInstallFontFaces(
installedFontFamily.id,
fontFacesFormData
);
// Activate the font families (add the font families to the global styles).
activateCustomFontFamilies( fontToBeActivated );

const fontFacesInstalled = response?.successes || [];

// Rebuild fontFace settings
font.fontFace =
fontFacesInstalled.map( ( face ) => {
return face.font_face_settings;
} ) || [];

// Activate the font family (add the font family to the global styles).
activateCustomFontFamilies( [ font ] );
// Save the global styles to the database.
saveSpecifiedEntityEdits( 'root', 'globalStyles', globalStylesId, [
'settings.typography.fontFamilies',
] );
refreshLibrary();
setIsInstalling( false );

return response;
} catch ( error ) {
setIsInstalling( false );
return {
errors: [ error ],
};
} finally {
setIsInstalling( false );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import apiFetch from '@wordpress/api-fetch';

export async function fetchInstallFont( data ) {
export async function fetchInstallFontFamily( data ) {
const config = {
path: '/wp/v2/font-families',
method: 'POST',
Expand All @@ -16,6 +16,23 @@ export async function fetchInstallFont( data ) {
return apiFetch( config );
}

export async function fetchInstallFontFace( fontFamilyId, data ) {
const config = {
path: `/wp/v2/font-families/${ fontFamilyId }/font-faces`,
method: 'POST',
body: data,
};
return apiFetch( config );
}

export async function fetchGetFontFamilyBySlug( slug ) {
const config = {
path: `/wp/v2/font-families?slug=${ slug }&_embed=true`,
method: 'GET',
};
return apiFetch( config );
}

export async function fetchUninstallFonts( fonts ) {
const data = {
font_families: fonts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components';
*/
import { FONT_WEIGHTS, FONT_STYLES } from './constants';
import { unlock } from '../../../../lock-unlock';
import { fetchInstallFontFace } from '../resolvers';

/**
* Browser dependencies
Expand Down Expand Up @@ -135,39 +136,84 @@ export function getDisplaySrcFromFontFace( input, urlPrefix ) {
return src;
}

export function makeFormDataFromFontFamily( fontFamily ) {
export function makeFontFamilyFormData( fontFamily ) {
const formData = new FormData();
const { kebabCase } = unlock( componentsPrivateApis );

const newFontFamily = {
...fontFamily,
const { fontFace, category, ...familyWithValidParameters } = fontFamily;
const fontFamilySettings = {
...familyWithValidParameters,
slug: kebabCase( fontFamily.slug ),
};

if ( newFontFamily?.fontFace ) {
const newFontFaces = newFontFamily.fontFace.map(
( face, faceIndex ) => {
if ( face.file ) {
// Slugified file name because the it might contain spaces or characters treated differently on the server.
const fileId = `file-${ faceIndex }`;
// Add the files to the formData
formData.append( fileId, face.file, face.file.name );
// remove the file object from the face object the file is referenced by the uploadedFile key
const { file, ...faceWithoutFileProperty } = face;
const newFace = {
...faceWithoutFileProperty,
uploadedFile: fileId,
};
return newFace;
}
return face;
formData.append(
'font_family_settings',
JSON.stringify( fontFamilySettings )
);
return formData;
}

export function makeFontFacesFormData( font ) {
if ( font?.fontFace ) {
const fontFacesFormData = font.fontFace.map( ( face, faceIndex ) => {
const formData = new FormData();
if ( face.file ) {
// Slugified file name because the it might contain spaces or characters treated differently on the server.
const fileId = `file-${ faceIndex }`;
// Add the files to the formData
formData.append( fileId, face.file, face.file.name );
// remove the file object from the face object the file is referenced in src
const { file, ...faceWithoutFileProperty } = face;
const fontFaceSettings = {
...faceWithoutFileProperty,
src: fileId,
};
formData.append(
'font_face_settings',
JSON.stringify( fontFaceSettings )
);
} else {
formData.append( 'font_face_settings', JSON.stringify( face ) );
}
);
newFontFamily.fontFace = newFontFaces;
return formData;
} );

return fontFacesFormData;
}
}

formData.append( 'font_family_settings', JSON.stringify( newFontFamily ) );
return formData;
export async function batchInstallFontFaces( fontFamilyId, fontFacesData ) {
const promises = fontFacesData.map( ( faceData ) =>
fetchInstallFontFace( fontFamilyId, faceData )
);
const responses = await Promise.allSettled( promises );

const results = {
errors: [],
successes: [],
};

responses.forEach( ( result, index ) => {
if ( result.status === 'fulfilled' ) {
const response = result.value;
if ( response.id ) {
results.successes.push( response );
} else {
results.errors.push( {
data: fontFacesData[ index ],
message: `Error: ${ response.message }`,
} );
}
} else {
// Handle network errors or other fetch-related errors
results.errors.push( {
data: fontFacesData[ index ],
error: `Fetch error: ${ result.reason }`,
} );
}
} );

return results;
}

/*
Expand Down

This file was deleted.

0 comments on commit 492a3ee

Please sign in to comment.