diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/context.js b/packages/edit-site/src/components/global-styles/font-library-modal/context.js index c6e37d45c689ec..078d11e235c043 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/context.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/context.js @@ -9,6 +9,7 @@ import { useEntityRecords, store as coreStore, } from '@wordpress/core-data'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -30,6 +31,7 @@ import { makeFontFacesFormData, makeFontFamilyFormData, batchInstallFontFaces, + checkFontFaceInstalled, } from './utils'; import { toggleFont } from './utils/toggleFont'; @@ -201,109 +203,94 @@ function FontLibraryProvider( { children } ) { return getActivatedFontsOutline( source )[ slug ] || []; }; - async function installFont( font ) { + async function installFont( fontFamilyToInstall ) { setIsInstalling( true ); try { - // Get the ID of the font family post, if it is already installed. + // Get the font family if it already exists. 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; - } ); + fontFamilyToInstall.slug + ); - // Otherwise, install it. + // Otherwise create 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 ); - } ); + makeFontFamilyFormData( fontFamilyToInstall ) + ); } - // 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 - ); - } + // Collect font faces that have already been installed (to be activated later) + const alreadyInstalledFontFaces = + installedFontFamily.fontFace.filter( ( fontFaceToInstall ) => + checkFontFaceInstalled( + fontFaceToInstall, + fontFamilyToInstall.fontFace ) ); - } ); - - 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 ); + // Filter out Font Faces that have already been installed (so that they are not re-installed) + fontFamilyToInstall.fontFace = fontFamilyToInstall.fontFace.filter( + ( fontFaceToInstall ) => + ! checkFontFaceInstalled( + fontFaceToInstall, + installedFontFamily.fontFace + ) + ); // Install the fonts (upload the font files to the server and create the post in the database). - const response = await batchInstallFontFaces( - installedFontFamily.id, - fontFacesFormData + let sucessfullyInstalledFontFaces = []; + let unsucessfullyInstalledFontFaces = []; + if ( fontFamilyToInstall.fontFace.length > 0 ) { + const response = await batchInstallFontFaces( + installedFontFamily.id, + makeFontFacesFormData( fontFamilyToInstall ) + ); + sucessfullyInstalledFontFaces = response?.successes; + unsucessfullyInstalledFontFaces = response?.errors; + } + + const detailedErrorMessage = unsucessfullyInstalledFontFaces.reduce( + ( errorMessageCollection, error ) => { + return `${ errorMessageCollection } ${ error.message }`; + }, + '' ); - const fontFacesInstalled = response?.successes || []; + // If there were no successes and nothing already installed then we don't need to activate anything and can bounce now. + if ( + sucessfullyInstalledFontFaces.length === 0 && + alreadyInstalledFontFaces.length === 0 + ) { + throw new Error( + __( 'No font faces were installed. ' ) + + detailedErrorMessage + ); + } - // Rebuild fontFace settings - font.fontFace = - fontFacesInstalled.map( ( face ) => { - return face.font_face_settings; - } ) || []; + // Use the sucessfully installed font faces + // As well as any font faces that were already installed (those will be activated) + fontFamilyToInstall.fontFace = [ + ...sucessfullyInstalledFontFaces, + ...alreadyInstalledFontFaces, + ]; // Activate the font family (add the font family to the global styles). - activateCustomFontFamilies( [ font ] ); + activateCustomFontFamilies( [ fontFamilyToInstall ] ); + // Save the global styles to the database. saveSpecifiedEntityEdits( 'root', 'globalStyles', globalStylesId, [ 'settings.typography.fontFamilies', ] ); + refreshLibrary(); - return response; - } catch ( error ) { - return { - errors: [ error ], - }; + if ( unsucessfullyInstalledFontFaces.length > 0 ) { + throw new Error( + __( + 'Some font faces were installed. There were some errors. ' + ) + detailedErrorMessage + ); + } } finally { setIsInstalling( false ); } @@ -324,7 +311,7 @@ function FontLibraryProvider( { children } ) { [ 'settings.typography.fontFamilies' ] ); } - // Refresh the library (the the library font families from database). + // Refresh the library (the library font families from database). refreshLibrary(); return response; } catch ( error ) { diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index f7f33032f1e3f5..5b6eeb2481e7a4 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -30,7 +30,6 @@ import CollectionFontDetails from './collection-font-details'; import { toggleFont } from './utils/toggleFont'; import { getFontsOutline } from './utils/fonts-outline'; import GoogleFontsConfirmDialog from './google-fonts-confirm-dialog'; -import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; import { downloadFontFaceAsset } from './utils'; const DEFAULT_CATEGORY = { @@ -182,9 +181,18 @@ function FontCollection( { id } ) { return; } - const response = await installFont( fontFamily ); - const installNotice = getNoticeFromInstallResponse( response ); - setNotice( installNotice ); + try { + await installFont( fontFamily ); + setNotice( { + type: 'success', + message: __( 'Fonts were installed successfully.' ), + } ); + } catch ( error ) { + setNotice( { + type: 'error', + message: error.message, + } ); + } resetFontsToInstall(); }; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js index d493a2a297b18b..0c481f6e4d8ca8 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js @@ -22,7 +22,6 @@ import FontsGrid from './fonts-grid'; import LibraryFontDetails from './library-font-details'; import LibraryFontCard from './library-font-card'; import ConfirmDeleteDialog from './confirm-delete-dialog'; -import { getNoticeFromUninstallResponse } from './utils/get-notice-from-response'; import { unlock } from '../../../lock-unlock'; const { ProgressBar } = unlock( componentsPrivateApis ); @@ -50,8 +49,9 @@ function InstalledFonts() { const handleConfirmUninstall = async () => { const response = await uninstallFont( libraryFontSelected ); - const uninstallNotice = getNoticeFromUninstallResponse( response ); - setNotice( uninstallNotice ); + // TODO: Refactor uninstall notices + // const uninstallNotice = getNoticeFromUninstallResponse( response ); + // setNotice( uninstallNotice ); // If the font was succesfully uninstalled it is unselected if ( ! response?.errors?.length ) { handleUnselectFont(); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js index d4221b420cb613..a77b524dddbed0 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js @@ -23,7 +23,6 @@ import { FontLibraryContext } from './context'; import { Font } from '../../../../lib/lib-font.browser'; import makeFamiliesFromFaces from './utils/make-families-from-faces'; import { loadFontFaceInBrowser } from './utils'; -import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; import { unlock } from '../../../lock-unlock'; const { ProgressBar } = unlock( componentsPrivateApis ); @@ -161,12 +160,23 @@ function LocalFonts() { 'Variants from only one font family can be uploaded at a time.' ), } ); + setIsUploading( false ); return; } - const response = await installFont( fontFamilies[ 0 ] ); - const installNotice = getNoticeFromInstallResponse( response ); - setNotice( installNotice ); + try { + await installFont( fontFamilies[ 0 ] ); + setNotice( { + type: 'success', + message: __( 'Fonts were installed successfully.' ), + } ); + } catch ( error ) { + setNotice( { + type: 'error', + message: error, + } ); + } + setIsUploading( false ); }; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js index 08e37dc7ee95fb..df10904b75026f 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js @@ -13,7 +13,13 @@ export async function fetchInstallFontFamily( data ) { method: 'POST', body: data, }; - return apiFetch( config ); + return apiFetch( config ).then( ( response ) => { + return { + id: response.id, + ...response.font_face_settings, + fontFace: [], + }; + } ); } export async function fetchInstallFontFace( fontFamilyId, data ) { @@ -22,7 +28,12 @@ export async function fetchInstallFontFace( fontFamilyId, data ) { method: 'POST', body: data, }; - return apiFetch( config ); + return apiFetch( config ).then( ( response ) => { + return { + id: response.id, + ...response.font_face_settings, + }; + } ); } export async function fetchGetFontFamilyBySlug( slug ) { @@ -30,7 +41,20 @@ export async function fetchGetFontFamilyBySlug( slug ) { path: `/wp/v2/font-families?slug=${ slug }&_embed=true`, method: 'GET', }; - return apiFetch( config ); + return apiFetch( config ).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 + ) || [], + }; + } ); } export async function fetchUninstallFonts( fonts ) { diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-intersecting-font-faces.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-intersecting-font-faces.js deleted file mode 100644 index e21e72c58ed533..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-intersecting-font-faces.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Retrieves intersecting font faces between two sets of fonts. - * - * For each font in the `incoming` list, the function checks for a corresponding match - * in the `existing` list based on the `slug` property. If a match is found and both - * have `fontFace` properties, it further narrows down to matching font faces based on - * the `fontWeight` and `fontStyle`. The result includes the properties of the matched - * existing font but only with intersecting font faces. - * - * @param {Array.<{ slug: string, fontFace?: Array.<{ fontWeight: string, fontStyle: string }> }>} incoming - The list of fonts to compare. - * @param {Array.<{ slug: string, fontFace?: Array.<{ fontWeight: string, fontStyle: string }> }>} existing - The reference list of fonts. - * - * @return {Array.<{ slug: string, fontFace?: Array.<{ fontWeight: string, fontStyle: string }> }>} An array of fonts from the `existing` list with intersecting font faces. - * - * @example - * const incomingFonts = [ - * { slug: 'arial', fontFace: [{ fontWeight: '400', fontStyle: 'normal' }] }, - * { slug: 'times-new', fontFace: [{ fontWeight: '700', fontStyle: 'italic' }] } - * ]; - * - * const existingFonts = [ - * { slug: 'arial', fontFace: [{ fontWeight: '400', fontStyle: 'normal' }, { fontWeight: '700', fontStyle: 'italic' }] }, - * { slug: 'helvetica', fontFace: [{ fontWeight: '400', fontStyle: 'normal' }] } - * ]; - * - * getIntersectingFontFaces(incomingFonts, existingFonts); - * // Returns: - * // [{ slug: 'arial', fontFace: [{ fontWeight: '400', fontStyle: 'normal' }] }] - */ -export default function getIntersectingFontFaces( incoming, existing ) { - const matches = []; - - for ( const incomingFont of incoming ) { - const existingFont = existing.find( - ( f ) => f.slug === incomingFont.slug - ); - - if ( existingFont ) { - if ( incomingFont?.fontFace ) { - const matchingFaces = incomingFont.fontFace.filter( - ( face ) => { - return ( existingFont?.fontFace || [] ).find( ( f ) => { - return ( - f.fontWeight === face.fontWeight && - f.fontStyle === face.fontStyle - ); - } ); - } - ); - matches.push( { ...incomingFont, fontFace: matchingFaces } ); - } else { - matches.push( incomingFont ); - } - } - } - - return matches; -} diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js deleted file mode 100644 index b22bd0afe23248..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - -export function getNoticeFromInstallResponse( response ) { - const { errors = [], successes = [] } = response; - // Everything failed. - if ( errors.length && ! successes.length ) { - return { - type: 'error', - message: __( 'Error installing the fonts.' ), - }; - } - - // Eveerything succeeded. - if ( ! errors.length && successes.length ) { - return { - type: 'success', - message: __( 'Fonts were installed successfully.' ), - }; - } - - // Some succeeded, some failed. - if ( errors.length && successes.length ) { - return { - type: 'warning', - message: __( - 'Some fonts were installed successfully and some failed.' - ), - }; - } -} - -export function getNoticeFromUninstallResponse( response ) { - const { errors = [], successes = [] } = response; - // Everything failed. - if ( errors.length && ! successes.length ) { - return { - type: 'error', - message: __( 'Error uninstalling the fonts.' ), - }; - } - - // Everything succeeded. - if ( ! errors.length && successes.length ) { - return { - type: 'success', - message: __( 'Fonts were uninstalled successfully.' ), - }; - } - - // Some succeeded, some failed. - if ( errors.length && successes.length ) { - return { - type: 'warning', - message: __( - 'Some fonts were uninstalled successfully and some failed.' - ), - }; - } -} diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js index 98b6375740e5b4..1adc8847f15171 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js @@ -208,7 +208,7 @@ export async function batchInstallFontFaces( fontFamilyId, fontFacesData ) { // Handle network errors or other fetch-related errors results.errors.push( { data: fontFacesData[ index ], - error: `Fetch error: ${ result.reason }`, + message: `Fetch error: ${ result.reason.message }`, } ); } } ); @@ -245,3 +245,23 @@ export async function downloadFontFaceAsset( url ) { throw error; } ); } + +/* + * Determine if a given Font Face is present in a given collection. + * We determine that a font face has been installed by comparing the fontWeight and fontStyle + * + * @param {Object} fontFace The Font Face to seek + * @param {Array} collection The Collection to seek in + * @returns True if the font face is found in the collection. Otherwise False. + */ +export function checkFontFaceInstalled( fontFace, collection ) { + return ( + -1 !== + collection.findIndex( ( collectionFontFace ) => { + return ( + collectionFontFace.fontWeight === fontFace.fontWeight && + collectionFontFace.fontStyle === fontFace.fontStyle + ); + } ) + ); +} diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/getIntersectingFontFaces.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/getIntersectingFontFaces.spec.js deleted file mode 100644 index 9899005ad65b89..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/getIntersectingFontFaces.spec.js +++ /dev/null @@ -1,271 +0,0 @@ -/** - * Internal dependencies - */ -import getIntersectingFontFaces from '../get-intersecting-font-faces'; - -describe( 'getIntersectingFontFaces', () => { - it( 'returns matching font faces for matching font family', () => { - const incomingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const existingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - - expect( result ).toEqual( incomingFontFamilies ); - } ); - - it( 'returns empty array when there is no match', () => { - const incomingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const existingFontFamilies = [ - { - slug: 'montserrat', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - - expect( result ).toEqual( [] ); - } ); - - it( 'returns matching font faces', () => { - const incomingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - { - fontWeight: '700', - fontStyle: 'italic', - }, - ], - }, - { - slug: 'times', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const existingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - { - fontWeight: '800', - fontStyle: 'italic', - }, - { - fontWeight: '900', - fontStyle: 'italic', - }, - ], - }, - ]; - - const expectedOutput = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - - expect( result ).toEqual( expectedOutput ); - } ); - - it( 'returns empty array when the first list is empty', () => { - const incomingFontFamilies = []; - - const existingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - - expect( result ).toEqual( [] ); - } ); - - it( 'returns empty array when the second list is empty', () => { - const incomingFontFamilies = [ - { - slug: 'lobster', - fontFace: [ - { - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const existingFontFamilies = []; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - - expect( result ).toEqual( [] ); - } ); - - it( 'returns intersecting font family when there are no fonfaces', () => { - const incomingFontFamilies = [ - { - slug: 'piazzolla', - fontFace: [ { fontStyle: 'normal', fontWeight: '400' } ], - }, - { - slug: 'lobster', - }, - ]; - - const existingFontFamilies = [ - { - slug: 'lobster', - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - - expect( result ).toEqual( existingFontFamilies ); - } ); - - it( 'returns intersecting if there is an intended font face and is not present in the returning it should not be returned', () => { - const incomingFontFamilies = [ - { - slug: 'piazzolla', - fontFace: [ { fontStyle: 'normal', fontWeight: '400' } ], - }, - { - slug: 'lobster', - fontFace: [ { fontStyle: 'normal', fontWeight: '400' } ], - }, - ]; - - const existingFontFamilies = [ - { - slug: 'lobster', - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - const expected = [ - { - slug: 'lobster', - fontFace: [], - }, - ]; - expect( result ).toEqual( expected ); - } ); - - it( 'updates font family definition using the incoming data', () => { - const incomingFontFamilies = [ - { - slug: 'gothic-a1', - fontFace: [ { fontStyle: 'normal', fontWeight: '400' } ], - fontFamily: "'Gothic A1', serif", - }, - ]; - - const existingFontFamilies = [ - { - slug: 'gothic-a1', - fontFace: [ { fontStyle: 'normal', fontWeight: '400' } ], - fontFamily: 'Gothic A1, serif', - }, - ]; - - const result = getIntersectingFontFaces( - incomingFontFamilies, - existingFontFamilies - ); - const expected = [ - { - slug: 'gothic-a1', - fontFace: [ { fontStyle: 'normal', fontWeight: '400' } ], - fontFamily: "'Gothic A1', serif", - }, - ]; - expect( result ).toEqual( expected ); - } ); -} );