/**
* WordPress dependencies
*/
import {
useContext,
useEffect,
useState,
useMemo,
createInterpolateElement,
} from '@wordpress/element';
import {
__experimentalSpacer as Spacer,
__experimentalText as WCText,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
Navigator,
__experimentalHeading as Heading,
Notice,
SelectControl,
Flex,
Button,
DropdownMenu,
SearchControl,
ProgressBar,
CheckboxControl,
} from '@wordpress/components';
import { debounce } from '@wordpress/compose';
import { sprintf, __, _x, isRTL } from '@wordpress/i18n';
import {
moreVertical,
next,
previous,
chevronLeft,
chevronRight,
} from '@wordpress/icons';
import { useEntityRecord } from '@wordpress/core-data';
import type {
FontCollection as FontCollectionType,
FontFace,
FontFamily,
CollectionFontFamily,
} from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { FontLibraryContext } from './context';
import FontCard from './font-card';
import filterFonts from './utils/filter-fonts';
import { toggleFont } from './utils/toggleFont';
import {
getFontsOutline,
isFontFontFaceInOutline,
} from './utils/fonts-outline';
import GoogleFontsConfirmDialog from './google-fonts-confirm-dialog';
import { downloadFontFaceAssets } from './utils';
import { sortFontFaces } from './utils/sort-font-faces';
import CollectionFontVariant from './collection-font-variant';
import type { FontFamilyToUpload } from './types';
const DEFAULT_CATEGORY = {
slug: 'all',
name: _x( 'All', 'font categories' ),
};
const LOCAL_STORAGE_ITEM = 'wp-font-library-google-fonts-permission';
const MIN_WINDOW_HEIGHT = 500;
function FontCollection( { slug }: { slug: string } ) {
const requiresPermission = slug === 'google-fonts';
const getGoogleFontsPermissionFromStorage = () => {
return window.localStorage.getItem( LOCAL_STORAGE_ITEM ) === 'true';
};
const [ selectedFont, setSelectedFont ] = useState< FontFamily | null >(
null
);
const [ lastSelectedFontSlug, setLastSelectedFontSlug ] = useState<
string | undefined
>( undefined );
const [ notice, setNotice ] = useState< {
type: 'success' | 'error' | 'info';
message: string;
} | null >( null );
const [ fontsToInstall, setFontsToInstall ] = useState< FontFamily[] >(
[]
);
const [ page, setPage ] = useState( 1 );
const [ filters, setFilters ] = useState< {
category?: string;
search?: string;
} >( {} );
const [ renderConfirmDialog, setRenderConfirmDialog ] = useState(
requiresPermission && ! getGoogleFontsPermissionFromStorage()
);
const { installFonts, isInstalling } = useContext( FontLibraryContext );
const { record: selectedCollection, isResolving: isLoading } =
useEntityRecord< FontCollectionType >( 'root', 'fontCollection', slug );
useEffect( () => {
const handleStorage = () => {
setRenderConfirmDialog(
requiresPermission && ! getGoogleFontsPermissionFromStorage()
);
};
handleStorage();
window.addEventListener( 'storage', handleStorage );
return () => window.removeEventListener( 'storage', handleStorage );
}, [ slug, requiresPermission ] );
const revokeAccess = () => {
window.localStorage.setItem( LOCAL_STORAGE_ITEM, 'false' );
window.dispatchEvent( new Event( 'storage' ) );
};
useEffect( () => {
setSelectedFont( null );
}, [ slug ] );
useEffect( () => {
// If the selected fonts change, reset the selected fonts to install
setFontsToInstall( [] );
}, [ selectedFont ] );
const collectionFonts = useMemo(
() =>
( selectedCollection?.font_families as
| CollectionFontFamily[]
| undefined ) ?? [],
[ selectedCollection ]
);
const collectionCategories = selectedCollection?.categories ?? [];
const categories = [ DEFAULT_CATEGORY, ...collectionCategories ];
const fonts = useMemo(
() => filterFonts( collectionFonts, filters ),
[ collectionFonts, filters ]
);
// NOTE: The height of the font library modal unavailable to use for rendering font family items is roughly 417px
// The height of each font family item is 61px.
const windowHeight = Math.max( window.innerHeight, MIN_WINDOW_HEIGHT );
const pageSize = Math.floor( ( windowHeight - 417 ) / 61 );
const totalPages = Math.ceil( fonts.length / pageSize );
const itemsStart = ( page - 1 ) * pageSize;
const itemsLimit = page * pageSize;
const items = fonts.slice( itemsStart, itemsLimit );
const handleCategoryFilter = ( category: string ) => {
setFilters( { ...filters, category } );
setPage( 1 );
};
const handleUpdateSearchInput = ( value: string ) => {
setFilters( { ...filters, search: value } );
setPage( 1 );
};
// @ts-expect-error
const debouncedUpdateSearchInput = debounce( handleUpdateSearchInput, 300 );
const handleToggleVariant = ( font: FontFamily, face?: FontFace ) => {
const newFontsToInstall = toggleFont( font, face, fontsToInstall );
setFontsToInstall( newFontsToInstall );
};
const fontToInstallOutline = getFontsOutline( fontsToInstall );
const resetFontsToInstall = () => {
setFontsToInstall( [] );
};
const selectFontCount =
fontsToInstall.length > 0
? fontsToInstall[ 0 ]?.fontFace?.length ?? 0
: 0;
// Check if any fonts are selected.
const isIndeterminate =
selectFontCount > 0 &&
selectFontCount !== selectedFont?.fontFace?.length;
// Check if all fonts are selected.
const isSelectAllChecked =
selectFontCount === selectedFont?.fontFace?.length;
// Toggle select all fonts.
const toggleSelectAll = () => {
const newFonts: FontFamily[] = [];
if ( ! isSelectAllChecked && selectedFont ) {
newFonts.push( selectedFont );
}
setFontsToInstall( newFonts );
};
const handleInstall = async () => {
setNotice( null );
const fontFamily: FontFamilyToUpload = fontsToInstall[ 0 ];
try {
if ( fontFamily?.fontFace ) {
await Promise.all(
fontFamily.fontFace.map( async ( fontFace ) => {
if ( fontFace.src ) {
fontFace.file = await downloadFontFaceAssets(
fontFace.src
);
}
} )
);
}
} catch {
// If any of the fonts fail to download,
// show an error notice and stop the request from being sent.
setNotice( {
type: 'error',
message: __(
'Error installing the fonts, could not be downloaded.'
),
} );
return;
}
try {
await installFonts( [ fontFamily ] );
setNotice( {
type: 'success',
message: __( 'Fonts were installed successfully.' ),
} );
} catch ( error ) {
setNotice( {
type: 'error',
message: ( error as Error ).message,
} );
}
resetFontsToInstall();
};
const getSortedFontFaces = ( fontFamily: FontFamily ) => {
if ( ! fontFamily ) {
return [];
}
if ( ! fontFamily.fontFace || ! fontFamily.fontFace.length ) {
return [
{
fontFamily: fontFamily.fontFamily,
fontStyle: 'normal',
fontWeight: '400',
},
];
}
return sortFontFaces( fontFamily.fontFace );
};
if ( renderConfirmDialog ) {
return