| 1 |
// This was mostly copied 1:1 over from legacy launch |
| 2 |
import { deepMerge, sleep } from '@shared/lib/utils'; |
| 3 |
import apiFetch from '@wordpress/api-fetch'; |
| 4 |
import { addQueryArgs } from '@wordpress/url'; |
| 5 |
|
| 6 |
export const registerFontFamily = async (fontFamily) => { |
| 7 |
try { |
| 8 |
const existingFontFamily = ( |
| 9 |
await apiFetch({ |
| 10 |
path: addQueryArgs('/wp/v2/font-families', { |
| 11 |
slug: fontFamily.slug, |
| 12 |
_embed: true, |
| 13 |
}), |
| 14 |
}) |
| 15 |
)?.[0]; |
| 16 |
|
| 17 |
if (existingFontFamily) { |
| 18 |
return { |
| 19 |
id: existingFontFamily.id, |
| 20 |
...existingFontFamily.font_family_settings, |
| 21 |
fontFace: existingFontFamily._embedded.font_faces.map( |
| 22 |
({ id, font_face_settings }) => ({ |
| 23 |
id, |
| 24 |
...font_face_settings, |
| 25 |
}), |
| 26 |
), |
| 27 |
}; |
| 28 |
} |
| 29 |
|
| 30 |
const newFontFamily = await apiFetch({ |
| 31 |
path: '/wp/v2/font-families', |
| 32 |
method: 'POST', |
| 33 |
body: makeFontFamilyFormData(fontFamily), |
| 34 |
}); |
| 35 |
|
| 36 |
return { |
| 37 |
id: newFontFamily.id, |
| 38 |
...newFontFamily.font_family_settings, |
| 39 |
fontFace: newFontFamily.fontFaces, |
| 40 |
}; |
| 41 |
} catch (error) { |
| 42 |
console.error('Failed to register font family:', error.message); |
| 43 |
return; |
| 44 |
} |
| 45 |
}; |
| 46 |
|
| 47 |
export const registerFontFace = async ({ fontFamilyId, ...fontFace }) => { |
| 48 |
const max_retries = 2; |
| 49 |
|
| 50 |
const fontFaceSlug = `${fontFace.fontFamilySlug}-${fontFace.fontWeight}`; |
| 51 |
|
| 52 |
for (let attempt = 0; attempt <= max_retries; attempt++) { |
| 53 |
try { |
| 54 |
// Add delay of 1 second if this is not the first attempt |
| 55 |
if (attempt > 0) await sleep(1000); |
| 56 |
|
| 57 |
const response = await apiFetch({ |
| 58 |
path: `/wp/v2/font-families/${fontFamilyId}/font-faces`, |
| 59 |
method: 'POST', |
| 60 |
body: makeFontFaceFormData(fontFace), |
| 61 |
}); |
| 62 |
|
| 63 |
return { |
| 64 |
id: response.id, |
| 65 |
...response.font_face_settings, |
| 66 |
}; |
| 67 |
} catch (error) { |
| 68 |
if (attempt <= max_retries) { |
| 69 |
console.error( |
| 70 |
`Failed attempt to upload font file ${fontFaceSlug}:`, |
| 71 |
error.message, |
| 72 |
); |
| 73 |
continue; |
| 74 |
} |
| 75 |
|
| 76 |
console.error( |
| 77 |
`Failed to upload font file ${fontFaceSlug} after ${max_retries + 1} attempts.`, |
| 78 |
); |
| 79 |
|
| 80 |
return; |
| 81 |
} |
| 82 |
} |
| 83 |
}; |
| 84 |
|
| 85 |
export const installFontFamily = async (fontFamily) => { |
| 86 |
const fontFaceDownloadRequests = fontFamily.fontFace.map(async (fontFace) => { |
| 87 |
const file = await fetchFontFaceFile(fontFace.src); |
| 88 |
if (!file) return; |
| 89 |
return { ...fontFace, file }; |
| 90 |
}); |
| 91 |
|
| 92 |
const fontFacesWithFile = ( |
| 93 |
await Promise.all(fontFaceDownloadRequests) |
| 94 |
).filter(Boolean); |
| 95 |
|
| 96 |
// If we don't have any font file to install, we don't register the font family. |
| 97 |
if (!fontFacesWithFile.length) return; |
| 98 |
|
| 99 |
const registeredFontFamily = await registerFontFamily(fontFamily); |
| 100 |
|
| 101 |
// If we couldn't register the font family, we don't register the font faces. |
| 102 |
if (!registeredFontFamily) return; |
| 103 |
|
| 104 |
// If font family has font faces, it means it was already registered |
| 105 |
// and doesn't need to be installed. |
| 106 |
if (registeredFontFamily?.fontFace?.length) { |
| 107 |
return registeredFontFamily; |
| 108 |
} |
| 109 |
|
| 110 |
const fontFaces = fontFacesWithFile.map((fontFace) => ({ |
| 111 |
fontFamilyId: registeredFontFamily.id, |
| 112 |
fontFamilySlug: registeredFontFamily.slug, |
| 113 |
...fontFace, |
| 114 |
})); |
| 115 |
|
| 116 |
const registeredFontFaces = []; |
| 117 |
|
| 118 |
for (const fontFace of fontFaces) { |
| 119 |
registeredFontFaces.push(await registerFontFace(fontFace)); |
| 120 |
} |
| 121 |
|
| 122 |
return { |
| 123 |
...registeredFontFamily, |
| 124 |
fontFace: registeredFontFaces.filter(Boolean), |
| 125 |
}; |
| 126 |
}; |
| 127 |
|
| 128 |
export const installFontFamilies = async (fontFamilies) => { |
| 129 |
const installedFontFamilies = []; |
| 130 |
|
| 131 |
for (const fontFamily of fontFamilies) { |
| 132 |
installedFontFamilies.push(await installFontFamily(fontFamily)); |
| 133 |
} |
| 134 |
|
| 135 |
return installedFontFamilies.filter(Boolean); |
| 136 |
}; |
| 137 |
|
| 138 |
export const fetchFontFaceFile = async (url) => { |
| 139 |
for (let attempt = 0; attempt <= 2; attempt++) { |
| 140 |
try { |
| 141 |
// Add delay if this is not the first attempt |
| 142 |
if (attempt > 0) await sleep(1000); |
| 143 |
|
| 144 |
const response = await fetch(url); |
| 145 |
|
| 146 |
if (!response.ok) { |
| 147 |
throw new Error('Failed to fetch font file.'); |
| 148 |
} |
| 149 |
|
| 150 |
const blob = await response.blob(); |
| 151 |
const filename = url.split('/').pop(); |
| 152 |
|
| 153 |
return new File([blob], filename, { |
| 154 |
type: blob.type, |
| 155 |
}); |
| 156 |
} catch (_) { |
| 157 |
if (attempt <= 2) continue; |
| 158 |
return; |
| 159 |
} |
| 160 |
} |
| 161 |
}; |
| 162 |
|
| 163 |
export const makeFontFamilyFormData = ({ name, slug, fontFamily }) => { |
| 164 |
const formData = new FormData(); |
| 165 |
const fontFamilySettings = { name, slug, fontFamily }; |
| 166 |
formData.append('font_family_settings', JSON.stringify(fontFamilySettings)); |
| 167 |
|
| 168 |
return formData; |
| 169 |
}; |
| 170 |
|
| 171 |
export const makeFontFaceFormData = ({ |
| 172 |
fontFamilySlug, |
| 173 |
fontFamily, |
| 174 |
fontStyle, |
| 175 |
fontWeight, |
| 176 |
fontDisplay, |
| 177 |
unicodeRange, |
| 178 |
src = [], |
| 179 |
file = [], |
| 180 |
}) => { |
| 181 |
const formData = new FormData(); |
| 182 |
const fontFaceSettings = { |
| 183 |
fontFamily, |
| 184 |
fontStyle, |
| 185 |
fontWeight, |
| 186 |
fontDisplay, |
| 187 |
unicodeRange: |
| 188 |
unicodeRange === undefined || unicodeRange === null ? '' : unicodeRange, |
| 189 |
src: Array.isArray(src) ? src : [src], |
| 190 |
}; |
| 191 |
const files = Array.isArray(file) ? file : [file]; |
| 192 |
|
| 193 |
// Add each font file to the form data. |
| 194 |
files.forEach((file) => { |
| 195 |
const fileId = `${fontFamilySlug}-${fontWeight}-${fontStyle}`; |
| 196 |
formData.append(fileId, file, file.name); |
| 197 |
|
| 198 |
// Use the file ids as src for WP to match and upload the files. |
| 199 |
if (!src?.length) { |
| 200 |
fontFaceSettings.src.push(fileId); |
| 201 |
} else { |
| 202 |
fontFaceSettings.src = [fileId]; |
| 203 |
} |
| 204 |
}); |
| 205 |
|
| 206 |
formData.append('font_face_settings', JSON.stringify(fontFaceSettings)); |
| 207 |
|
| 208 |
return formData; |
| 209 |
}; |
| 210 |
|
| 211 |
export const mergeFontsIntoVariation = (variation, fonts) => |
| 212 |
deepMerge( |
| 213 |
variation, |
| 214 |
// We set to null first to reset the field. |
| 215 |
{ settings: { typography: { fontFamilies: { custom: null } } } }, |
| 216 |
// We add the installed font families here to activate them. |
| 217 |
{ |
| 218 |
settings: { |
| 219 |
typography: { |
| 220 |
fontFamilies: { |
| 221 |
custom: fonts.filter(Boolean), |
| 222 |
}, |
| 223 |
}, |
| 224 |
}, |
| 225 |
}, |
| 226 |
); |
| 227 |
|