-
Notifications
You must be signed in to change notification settings - Fork 4k
/
context.js
376 lines (338 loc) · 10.8 KB
/
context.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/**
* WordPress dependencies
*/
import { createContext, useState, useEffect } from '@wordpress/element';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import {
useEntityRecord,
useEntityRecords,
store as coreStore,
} from '@wordpress/core-data';
/**
* Internal dependencies
*/
import {
fetchInstallFonts,
fetchUninstallFonts,
fetchFontCollections,
fetchFontCollection,
} from './resolvers';
import { unlock } from '../../../lock-unlock';
const { useGlobalSetting } = unlock( blockEditorPrivateApis );
import {
setUIValuesNeeded,
mergeFontFamilies,
loadFontFaceInBrowser,
getDisplaySrcFromFontFace,
makeFormDataFromFontFamilies,
} from './utils';
import { toggleFont } from './utils/toggleFont';
import getIntersectingFontFaces from './utils/get-intersecting-font-faces';
export const FontLibraryContext = createContext( {} );
function FontLibraryProvider( { children } ) {
const { __experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits } =
useDispatch( coreStore );
const { globalStylesId } = useSelect( ( select ) => {
const { __experimentalGetCurrentGlobalStylesId } = select( coreStore );
return { globalStylesId: __experimentalGetCurrentGlobalStylesId() };
} );
const globalStyles = useEntityRecord(
'root',
'globalStyles',
globalStylesId
);
const fontFamiliesHasChanges =
!! globalStyles?.edits?.settings?.typography?.fontFamilies;
const [ isInstalling, setIsInstalling ] = useState( false );
const [ refreshKey, setRefreshKey ] = useState( 0 );
const refreshLibrary = () => {
setRefreshKey( Date.now() );
};
const {
records: libraryPosts = [],
isResolving: isResolvingLibrary,
hasResolved: hasResolvedLibrary,
} = useEntityRecords( 'postType', 'wp_font_family', { refreshKey } );
const libraryFonts =
( libraryPosts || [] ).map( ( post ) =>
JSON.parse( post.content.raw )
) || [];
// Global Styles (settings) font families
const [ fontFamilies, setFontFamilies ] = useGlobalSetting(
'typography.fontFamilies'
);
// theme.json file font families
const [ baseFontFamilies ] = useGlobalSetting(
'typography.fontFamilies',
undefined,
'base'
);
// Save font families to the global styles post in the database.
const saveFontFamilies = () => {
saveSpecifiedEntityEdits( 'root', 'globalStyles', globalStylesId, [
'settings.typography.fontFamilies',
] );
};
// Library Fonts
const [ modalTabOpen, setModalTabOpen ] = useState( false );
const [ libraryFontSelected, setLibraryFontSelected ] = useState( null );
const baseThemeFonts = baseFontFamilies?.theme
? baseFontFamilies.theme
.map( ( f ) => setUIValuesNeeded( f, { source: 'theme' } ) )
.sort( ( a, b ) => a.name.localeCompare( b.name ) )
: [];
const themeFonts = fontFamilies?.theme
? fontFamilies.theme
.map( ( f ) => setUIValuesNeeded( f, { source: 'theme' } ) )
.sort( ( a, b ) => a.name.localeCompare( b.name ) )
: [];
const customFonts = fontFamilies?.custom
? fontFamilies.custom
.map( ( f ) => setUIValuesNeeded( f, { source: 'custom' } ) )
.sort( ( a, b ) => a.name.localeCompare( b.name ) )
: [];
const baseCustomFonts = libraryFonts
? libraryFonts
.map( ( f ) => setUIValuesNeeded( f, { source: 'custom' } ) )
.sort( ( a, b ) => a.name.localeCompare( b.name ) )
: [];
useEffect( () => {
if ( ! modalTabOpen ) {
setLibraryFontSelected( null );
}
}, [ modalTabOpen ] );
const handleSetLibraryFontSelected = ( font ) => {
// If font is null, reset the selected font
if ( ! font ) {
setLibraryFontSelected( null );
return;
}
const fonts =
font.source === 'theme' ? baseThemeFonts : baseCustomFonts;
// Tries to find the font in the installed fonts
const fontSelected = fonts.find( ( f ) => f.slug === font.slug );
// If the font is not found (it is only defined in custom styles), use the font from custom styles
setLibraryFontSelected( {
...( fontSelected || font ),
source: font.source,
} );
};
const toggleModal = ( tabName ) => {
setModalTabOpen( tabName || null );
};
// Demo
const [ loadedFontUrls ] = useState( new Set() );
// Theme data
const { site, currentTheme } = useSelect( ( select ) => {
return {
site: select( coreStore ).getSite(),
currentTheme: select( coreStore ).getCurrentTheme(),
};
} );
const themeUrl =
site?.url + '/wp-content/themes/' + currentTheme?.stylesheet;
const getAvailableFontsOutline = ( availableFontFamilies ) => {
const outline = availableFontFamilies.reduce( ( acc, font ) => {
const availableFontFaces = Array.isArray( font?.fontFace )
? font?.fontFace.map(
( face ) => `${ face.fontStyle + face.fontWeight }`
)
: [ 'normal400' ]; // If the font doesn't have fontFace, we assume it is a system font and we add the defaults: normal 400
acc[ font.slug ] = availableFontFaces;
return acc;
}, {} );
return outline;
};
const getActivatedFontsOutline = ( source ) => {
switch ( source ) {
case 'theme':
return getAvailableFontsOutline( themeFonts );
case 'custom':
default:
return getAvailableFontsOutline( customFonts );
}
};
const isFontActivated = ( slug, style, weight, source ) => {
if ( ! style && ! weight ) {
return !! getActivatedFontsOutline( source )[ slug ];
}
return !! getActivatedFontsOutline( source )[ slug ]?.includes(
style + weight
);
};
const getFontFacesActivated = ( slug, source ) => {
return getActivatedFontsOutline( source )[ slug ] || [];
};
async function installFonts( fonts ) {
setIsInstalling( true );
try {
// Prepare formData to install.
const formData = makeFormDataFromFontFamilies( fonts );
// Install the fonts (upload the font files to the server and create the post in the database).
const response = await fetchInstallFonts( 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,
fonts
);
// Activate the font families (add the font families to the global styles).
activateCustomFontFamilies( fontToBeActivated );
// 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 ],
};
}
}
async function uninstallFont( font ) {
try {
// Uninstall the font (remove the font files from the server and the post from the database).
const response = await fetchUninstallFonts( [ font ] );
// Deactivate the font family (remove the font family from the global styles).
if ( ! response.errors ) {
deactivateFontFamily( font );
// Save the global styles to the database.
await saveSpecifiedEntityEdits(
'root',
'globalStyles',
globalStylesId,
[ 'settings.typography.fontFamilies' ]
);
}
// Refresh the library (the the library font families from database).
refreshLibrary();
return response;
} catch ( error ) {
// eslint-disable-next-line no-console
console.error( error );
return {
errors: [ error ],
};
}
}
const deactivateFontFamily = ( font ) => {
// If the user doesn't have custom fonts defined, include as custom fonts all the theme fonts
// We want to save as active all the theme fonts at the beginning
const initialCustomFonts = fontFamilies?.[ font.source ] ?? [];
const newCustomFonts = initialCustomFonts.filter(
( f ) => f.slug !== font.slug
);
setFontFamilies( {
...fontFamilies,
[ font.source ]: newCustomFonts,
} );
};
const activateCustomFontFamilies = ( fontsToAdd ) => {
// Merge the existing custom fonts with the new fonts.
const newCustomFonts = mergeFontFamilies(
fontFamilies?.custom,
fontsToAdd
);
// Activate the fonts by set the new custom fonts array.
setFontFamilies( {
...fontFamilies,
custom: newCustomFonts,
} );
// Add custom fonts to the browser.
fontsToAdd.forEach( ( font ) => {
if ( font.fontFace ) {
font.fontFace.forEach( ( face ) => {
// Load font faces just in the iframe because they already are in the document.
loadFontFaceInBrowser(
face,
getDisplaySrcFromFontFace( face.src ),
'iframe'
);
} );
}
} );
};
const toggleActivateFont = ( font, face ) => {
// If the user doesn't have custom fonts defined, include as custom fonts all the theme fonts
// We want to save as active all the theme fonts at the beginning
const initialFonts = fontFamilies?.[ font.source ] ?? [];
// Toggles the received font family or font face
const newFonts = toggleFont( font, face, initialFonts );
// Updates the font families activated in global settings:
setFontFamilies( {
...fontFamilies,
[ font.source ]: newFonts,
} );
};
const loadFontFaceAsset = async ( fontFace ) => {
// If the font doesn't have a src, don't load it.
if ( ! fontFace.src ) return;
// Get the src of the font.
const src = getDisplaySrcFromFontFace( fontFace.src, themeUrl );
// If the font is already loaded, don't load it again.
if ( ! src || loadedFontUrls.has( src ) ) return;
// Load the font in the browser.
loadFontFaceInBrowser( fontFace, src, 'document' );
// Add the font to the loaded fonts list.
loadedFontUrls.add( src );
};
// Font Collections
const [ collections, setFontCollections ] = useState( [] );
const getFontCollections = async () => {
const response = await fetchFontCollections();
setFontCollections( response );
};
const getFontCollection = async ( id ) => {
const hasData = !! collections.find(
( collection ) => collection.id === id
)?.data;
if ( hasData ) return;
const response = await fetchFontCollection( id );
const updatedCollections = collections.map( ( collection ) =>
collection.id === id
? { ...collection, data: { ...response?.data } }
: collection
);
setFontCollections( updatedCollections );
};
useEffect( () => {
getFontCollections();
}, [] );
return (
<FontLibraryContext.Provider
value={ {
libraryFontSelected,
handleSetLibraryFontSelected,
themeFonts,
baseThemeFonts,
customFonts,
baseCustomFonts,
isFontActivated,
getFontFacesActivated,
loadFontFaceAsset,
installFonts,
uninstallFont,
toggleActivateFont,
getAvailableFontsOutline,
modalTabOpen,
toggleModal,
refreshLibrary,
saveFontFamilies,
fontFamiliesHasChanges,
isResolvingLibrary,
hasResolvedLibrary,
isInstalling,
collections,
getFontCollection,
} }
>
{ children }
</FontLibraryContext.Provider>
);
}
export default FontLibraryProvider;